From a9ccdcc7327fea32f795d622a8dbc936e634f901 Mon Sep 17 00:00:00 2001 From: Tadhg Boyle Date: Sat, 8 Jun 2024 11:28:37 -0700 Subject: [PATCH 1/2] Allow per user permissions + rewrite permissions handling --- core/classes/Core/NamelessContainer.php | 31 +++++ core/classes/Core/PermissionHandler.php | 39 ------- core/classes/Core/User.php | 16 +-- core/classes/DTO/Group.php | 1 - core/classes/Database/DatabaseInitialiser.php | 16 ++- core/classes/Misc/Report.php | 1 + core/classes/Permissions/PermissionCache.php | 97 +++++++++++++++ .../Permissions/PermissionCalculator.php | 83 +++++++++++++ .../Permissions/PermissionRegistry.php | 50 ++++++++ .../Permissions/PermissionTristate.php | 15 +++ core/classes/Queue/Queue.php | 4 +- core/classes/Queue/Task.php | 4 +- core/init.php | 12 +- ...0231222011251_create_permissions_table.php | 28 +++++ core/migrations/phinx.php | 2 +- .../Default/core/groups_permissions.tpl | 110 +++++++++++------- modules/Cookie Consent/module.php | 2 +- modules/Core/hooks/CloneGroupHook.php | 17 ++- modules/Core/language/en_UK.json | 1 + modules/Core/module.php | 8 +- modules/Core/pages/panel/groups.php | 21 ++-- .../Core/pages/panel/users_punishments.php | 92 +++++++-------- modules/Core/queries/debug_link.php | 1 + modules/Core/queries/queue.php | 2 +- modules/Discord Integration/module.php | 2 +- modules/Forum/module.php | 2 +- modules/Members/module.php | 2 +- 27 files changed, 473 insertions(+), 186 deletions(-) create mode 100644 core/classes/Core/NamelessContainer.php delete mode 100644 core/classes/Core/PermissionHandler.php create mode 100644 core/classes/Permissions/PermissionCache.php create mode 100644 core/classes/Permissions/PermissionCalculator.php create mode 100644 core/classes/Permissions/PermissionRegistry.php create mode 100644 core/classes/Permissions/PermissionTristate.php create mode 100644 core/migrations/20231222011251_create_permissions_table.php diff --git a/core/classes/Core/NamelessContainer.php b/core/classes/Core/NamelessContainer.php new file mode 100644 index 0000000000..273671b951 --- /dev/null +++ b/core/classes/Core/NamelessContainer.php @@ -0,0 +1,31 @@ +set(Cache::class, function () { + return new Cache([ + 'name' => 'nameless', + 'extension' => '.cache', + 'path' => ROOT_PATH . '/cache/' + ]); + }); + + self::$_instance->set(DB::class, DB::getInstance()); + } +} \ No newline at end of file diff --git a/core/classes/Core/PermissionHandler.php b/core/classes/Core/PermissionHandler.php deleted file mode 100644 index a7c88de19c..0000000000 --- a/core/classes/Core/PermissionHandler.php +++ /dev/null @@ -1,39 +0,0 @@ -> All registered permissions. - */ - private static array $_permissions; - - /** - * Register a permission for display in the StaffCP. - * - * @param string $section Permission section to add permission to. - * @param array $permissions List of unique permissions to register. - */ - public static function registerPermissions(string $section, array $permissions): void { - foreach ($permissions as $permission => $title) { - if (!isset(self::$_permissions[$section][$permission])) { - self::$_permissions[$section][$permission] = $title; - } - } - } - - /** - * Get all registered permissions. - * - * @return array> Permission array. - */ - public static function getPermissions(): array { - return self::$_permissions; - } -} diff --git a/core/classes/Core/User.php b/core/classes/Core/User.php index 02b10a4e1d..7ef5ef2d7e 100644 --- a/core/classes/Core/User.php +++ b/core/classes/Core/User.php @@ -467,19 +467,9 @@ public function hasPermission(string $permission): bool { return false; } - foreach ($this->getGroups() as $group) { - $permissions = json_decode($group->permissions, true) ?? []; - - if (isset($permissions['administrator']) && $permissions['administrator'] == 1) { - return true; - } - - if (isset($permissions[$permission]) && $permissions[$permission] == 1) { - return true; - } - } - - return false; + return NamelessContainer::getInstance() + ->get(PermissionCalculator::class) + ->userHasPermission($this, $permission); } /** diff --git a/core/classes/DTO/Group.php b/core/classes/DTO/Group.php index bdadfac8ab..f83f7a908d 100644 --- a/core/classes/DTO/Group.php +++ b/core/classes/DTO/Group.php @@ -30,7 +30,6 @@ public function __construct(object $row) { $this->group_username_css = $row->group_username_css; $this->admin_cp = $row->admin_cp; $this->staff = $row->staff; - $this->permissions = $row->permissions; $this->default_group = $row->default_group; $this->order = $row->order; $this->force_tfa = $row->force_tfa; diff --git a/core/classes/Database/DatabaseInitialiser.php b/core/classes/Database/DatabaseInitialiser.php index 22fa054841..0e315883a2 100644 --- a/core/classes/Database/DatabaseInitialiser.php +++ b/core/classes/Database/DatabaseInitialiser.php @@ -28,11 +28,11 @@ public static function runPostUser() { $instance->initialiseForum(); } + // todo private function initialiseGroups(): void { $this->_db->insert('groups', [ 'name' => 'Member', 'group_html' => 'Member', - 'permissions' => '{"usercp.messaging":1,"usercp.signature":1,"usercp.nickname":1,"usercp.private_profile":1,"usercp.profile_banner":1}', 'order' => 3 ]); @@ -42,7 +42,6 @@ private function initialiseGroups(): void { 'group_username_color' => '#ff0000', 'group_username_css' => '', 'admin_cp' => true, - 'permissions' => '{"administrator":1,"admincp.core":1,"admincp.core.api":1,"admincp.core.seo":1,"admincp.core.general":1,"admincp.core.avatars":1,"admincp.core.fields":1,"admincp.core.debugging":1,"admincp.core.emails":1,"admincp.core.queue":1,"admincp.core.navigation":1,"admincp.core.announcements":1,"admincp.core.reactions":1,"admincp.core.registration":1,"admincp.core.social_media":1,"admincp.core.terms":1,"admincp.errors":1,"admincp.core.placeholders":1,"admincp.members":1,"admincp.integrations":1,"admincp.integrations.edit":1,"admincp.discord":1,"admincp.minecraft":1,"admincp.minecraft.authme":1,"admincp.minecraft.servers":1,"admincp.minecraft.query_errors":1,"admincp.minecraft.banners":1,"admincp.modules":1,"admincp.pages":1,"admincp.security":1,"admincp.security.acp_logins":1,"admincp.security.template":1,"admincp.styles":1,"admincp.styles.panel_templates":1,"admincp.styles.templates":1,"admincp.styles.templates.edit":1,"admincp.styles.images":1,"admincp.update":1,"admincp.users":1,"admincp.users.edit":1,"admincp.groups":1,"admincp.groups.self":1,"admincp.widgets":1,"modcp.ip_lookup":1,"modcp.punishments":1,"modcp.punishments.warn":1,"modcp.punishments.ban":1,"modcp.punishments.banip":1,"modcp.punishments.revoke":1,"modcp.reports":1,"modcp.profile_banner_reset":1,"usercp.messaging":1,"usercp.signature":1,"admincp.forums":1,"usercp.private_profile":1,"usercp.nickname":1,"usercp.profile_banner":1,"profile.private.bypass":1, "admincp.security.all":1,"admincp.core.hooks":1,"admincp.security.group_sync":1,"admincp.core.emails_mass_message":1,"modcp.punishments.reset_avatar":1,"usercp.gif_avatar":1}', 'order' => 1, 'staff' => true, ]); @@ -51,7 +50,6 @@ private function initialiseGroups(): void { 'name' => 'Moderator', 'group_html' => 'Moderator', 'admin_cp' => true, - 'permissions' => '{"modcp.ip_lookup":1,"modcp.punishments":1,"modcp.punishments.warn":1,"modcp.punishments.ban":1,"modcp.punishments.banip":1,"modcp.punishments.revoke":1,"modcp.reports":1,"admincp.users":1,"modcp.profile_banner_reset":1,"usercp.messaging":1,"usercp.signature":1,"usercp.private_profile":1,"usercp.nickname":1,"usercp.profile_banner":1,"profile.private.bypass":1}', 'order' => 2, 'staff' => true, ]); @@ -60,11 +58,21 @@ private function initialiseGroups(): void { 'name' => 'Unconfirmed Member', 'group_html' => 'Unconfirmed Member', 'group_username_color' => '#6c757d', - 'permissions' => '{}', 'default_group' => true, 'order' => 4 ]); + $permission_cache = NamelessContainer::getInstance() + ->get(PermissionCache::class); + foreach (PermissionRegistry::DEFAULT_GROUP_PERMISSIONS as $group_id => $permissions) { + $group = Group::find($group_id); + if ($group === null) { + continue; + } + + $permission_cache->upsert(Group::class, $group->id, $permissions); + } + Settings::set('member_list_viewable_groups', json_encode([1, 2, 3, 4]), 'Members'); } diff --git a/core/classes/Misc/Report.php b/core/classes/Misc/Report.php index 03087e6ab3..882ebed78b 100644 --- a/core/classes/Misc/Report.php +++ b/core/classes/Misc/Report.php @@ -33,6 +33,7 @@ public static function create(Language $language, User $user_reporting, User $re $id = $db->lastId(); // Alert moderators + // todo $moderator_groups = DB::getInstance()->query('SELECT id FROM nl2_groups WHERE permissions LIKE \'%"modcp.reports":1%\'')->results(); if (count($moderator_groups)) { diff --git a/core/classes/Permissions/PermissionCache.php b/core/classes/Permissions/PermissionCache.php new file mode 100644 index 0000000000..782df1b8a5 --- /dev/null +++ b/core/classes/Permissions/PermissionCache.php @@ -0,0 +1,97 @@ +_db = $db; + $this->_cache = $cache; + } + + public function getOrLoad(string $permissible, int $id): array + { + $this->_cache->setCache('permission_cache'); + + if ($this->_cache->isCached($cache_key = $this->cacheKey($permissible, $id))) { + return $this->_cache->retrieve($cache_key); + } + + $this->load($permissible, $id); + + return $this->_cache->retrieve($cache_key); + } + + public function flush(string $permissible = null, int $id = null): void + { + $this->_cache->setCache('permission_cache'); + + if ($permissible === null || $id === null) { + $this->_cache->eraseAll(); + return; + } + + if ($this->_cache->isCached($cache_key = $this->cacheKey($permissible, $id))) { + $this->_cache->erase($cache_key); + } + } + + public function upsert(string $permissible, int $id, array $permissions): void + { + if (!count($permissions)) { + return; + } + + $values_sql = ''; + $values = []; + foreach ($permissions as $permission => $value) { + if (!in_array($value, [PermissionTristate::TRUE, PermissionTristate::FALSE, PermissionTristate::INHERIT])) { + continue; + } + + $values_sql .= '(?, ?, ?, ?), '; + $values[] = "$permissible"; + $values[] = $id; + $values[] = "$permission"; + $values[] = $value; + } + + $this->_db->query( + 'INSERT INTO nl2_permissions (permissible, permissible_id, permission, `value`) VALUES ' . rtrim($values_sql, ', ') . ' ON DUPLICATE KEY UPDATE `value` = VALUES(`value`)', + $values, + ); + + $this->flush($permissible, $id); + } + + private function load(string $permissible, int $id): array + { + $permissions = $this->_db->query( + 'SELECT permission, `value` FROM nl2_permissions WHERE permissible = ? AND permissible_id = ?', [$permissible, $id] + )->results(); + + $loaded_permissions = []; + foreach ($permissions as $permission) { + $loaded_permissions[$permission->permission] = $permission->value; + } + + $this->_cache->setCache('permission_cache'); + $this->_cache->store($this->cacheKey($permissible, $id), $loaded_permissions); + + return $loaded_permissions; + } + + private function cacheKey(string $permissible, int $id): string + { + return $permissible . '_' . $id; + } +} \ No newline at end of file diff --git a/core/classes/Permissions/PermissionCalculator.php b/core/classes/Permissions/PermissionCalculator.php new file mode 100644 index 0000000000..3f9f5da158 --- /dev/null +++ b/core/classes/Permissions/PermissionCalculator.php @@ -0,0 +1,83 @@ +_permission_cache = $permission_cache; + } + + public function userHasPermission(User $user, string $permission): bool + { + $user_permissions = $this->_permission_cache->getOrLoad(User::class, $user->data()->id); + + $result = $user_permissions[$permission] ?? PermissionTristate::INHERIT; + + if ($result === PermissionTristate::TRUE) { + return true; + } + + if ($result === PermissionTristate::FALSE) { + return false; + } + + if ($result === PermissionTristate::INHERIT) { + foreach ($user->getGroups() as $group) { + $group_permissions = $this->_permission_cache->getOrLoad(Group::class, $group->id); + + $result = $group_permissions[$permission] ?? PermissionTristate::INHERIT; + + if ($result === PermissionTristate::TRUE) { + return true; + } + + if ($result === PermissionTristate::FALSE) { + return false; + } + } + } + + return false; + } + + public function groupHasPermission(Group $group, string $permission): bool + { + $result = $this->_permission_cache->getOrLoad(Group::class, $group->id)[$permission] ?? PermissionTristate::INHERIT; + + if ($result === PermissionTristate::TRUE) { + return true; + } + + if ($result === PermissionTristate::FALSE) { + return false; + } + + if ($result === PermissionTristate::INHERIT) { + // if any of the groups with a lower order have the permission set to true or false, then this group inherits that value + $inherit = false; + $lower_order_groups = DB::getInstance()->query('SELECT id FROM nl2_groups WHERE `order` < ? ORDER BY `order`', [$group->order]); + foreach ($lower_order_groups as $lower_order_group) { + $result2 = $this->_permission_cache->getOrLoad(Group::class, $lower_order_group->id)[$permission] ?? PermissionTristate::INHERIT; + if ($result2 === PermissionTristate::TRUE) { + $inherit = true; + break; + } elseif ($result2 === PermissionTristate::FALSE) { + $inherit = false; + break; + } + } + return $inherit; + } + + return false; + } +} \ No newline at end of file diff --git a/core/classes/Permissions/PermissionRegistry.php b/core/classes/Permissions/PermissionRegistry.php new file mode 100644 index 0000000000..93b67bf166 --- /dev/null +++ b/core/classes/Permissions/PermissionRegistry.php @@ -0,0 +1,50 @@ + ["usercp.messaging" => PermissionTristate::TRUE, "usercp.signature" => PermissionTristate::TRUE, "usercp.nickname" => PermissionTristate::TRUE, "usercp.private_profile" => PermissionTristate::TRUE, "usercp.profile_banner" => PermissionTristate::TRUE], + // Admin + 2 => ["administrator" => PermissionTristate::TRUE, "admincp.core" => PermissionTristate::TRUE, "admincp.core.api" => PermissionTristate::TRUE, "admincp.core.seo" => PermissionTristate::TRUE, "admincp.core.general" => PermissionTristate::TRUE, "admincp.core.avatars" => PermissionTristate::TRUE, "admincp.core.fields" => PermissionTristate::TRUE, "admincp.core.debugging" => PermissionTristate::TRUE, "admincp.core.emails" => PermissionTristate::TRUE, "admincp.core.queue" => PermissionTristate::TRUE, "admincp.core.navigation" => PermissionTristate::TRUE, "admincp.core.announcements" => PermissionTristate::TRUE, "admincp.core.reactions" => PermissionTristate::TRUE, "admincp.core.registration" => PermissionTristate::TRUE, "admincp.core.social_media" => PermissionTristate::TRUE, "admincp.core.terms" => PermissionTristate::TRUE, "admincp.errors" => PermissionTristate::TRUE, "admincp.core.placeholders" => PermissionTristate::TRUE, "admincp.members" => PermissionTristate::TRUE, "admincp.integrations" => PermissionTristate::TRUE, "admincp.integrations.edit" => PermissionTristate::TRUE, "admincp.discord" => PermissionTristate::TRUE, "admincp.minecraft" => PermissionTristate::TRUE, "admincp.minecraft.authme" => PermissionTristate::TRUE, "admincp.minecraft.servers" => PermissionTristate::TRUE, "admincp.minecraft.query_errors" => PermissionTristate::TRUE, "admincp.minecraft.banners" => PermissionTristate::TRUE, "admincp.modules" => PermissionTristate::TRUE, "admincp.pages" => PermissionTristate::TRUE, "admincp.security" => PermissionTristate::TRUE, "admincp.security.acp_logins" => PermissionTristate::TRUE, "admincp.security.template" => PermissionTristate::TRUE, "admincp.styles" => PermissionTristate::TRUE, "admincp.styles.panel_templates" => PermissionTristate::TRUE, "admincp.styles.templates" => PermissionTristate::TRUE, "admincp.styles.templates.edit" => PermissionTristate::TRUE, "admincp.styles.images" => PermissionTristate::TRUE, "admincp.update" => PermissionTristate::TRUE, "admincp.users" => PermissionTristate::TRUE, "admincp.users.edit" => PermissionTristate::TRUE, "admincp.groups" => PermissionTristate::TRUE, "admincp.groups.self" => PermissionTristate::TRUE, "admincp.widgets" => PermissionTristate::TRUE, "modcp.ip_lookup" => PermissionTristate::TRUE, "modcp.punishments" => PermissionTristate::TRUE, "modcp.punishments.warn" => PermissionTristate::TRUE, "modcp.punishments.ban" => PermissionTristate::TRUE, "modcp.punishments.banip" => PermissionTristate::TRUE, "modcp.punishments.revoke" => PermissionTristate::TRUE, "modcp.reports" => PermissionTristate::TRUE, "modcp.profile_banner_reset" => PermissionTristate::TRUE, "usercp.messaging" => PermissionTristate::TRUE, "usercp.signature" => PermissionTristate::TRUE, "admincp.forums" => PermissionTristate::TRUE, "usercp.private_profile" => PermissionTristate::TRUE, "usercp.nickname" => PermissionTristate::TRUE, "usercp.profile_banner" => PermissionTristate::TRUE, "profile.private.bypass", "admincp.security.all" => PermissionTristate::TRUE, "admincp.core.hooks" => PermissionTristate::TRUE, "admincp.security.group_sync" => PermissionTristate::TRUE, "admincp.core.emails_mass_message" => PermissionTristate::TRUE, "modcp.punishments.reset_avatar" => PermissionTristate::TRUE, "usercp.gif_avatar" => PermissionTristate::TRUE], + // Moderator + 3 => ["modcp.ip_lookup" => PermissionTristate::TRUE, "modcp.punishments" => PermissionTristate::TRUE, "modcp.punishments.warn" => PermissionTristate::TRUE, "modcp.punishments.ban" => PermissionTristate::TRUE, "modcp.punishments.banip" => PermissionTristate::TRUE, "modcp.punishments.revoke" => PermissionTristate::TRUE, "modcp.reports" => PermissionTristate::TRUE, "admincp.users" => PermissionTristate::TRUE, "modcp.profile_banner_reset" => PermissionTristate::TRUE, "usercp.messaging" => PermissionTristate::TRUE, "usercp.signature" => PermissionTristate::TRUE, "usercp.private_profile" => PermissionTristate::TRUE, "usercp.nickname" => PermissionTristate::TRUE, "usercp.profile_banner" => PermissionTristate::TRUE, "profile.private.bypass" => PermissionTristate::TRUE], + // Unconfirmed Member + 4 => [], + ]; + + /** + * @var array> All registered permissions. + */ + private static array $_permissions; + + /** + * Register a permission for display in the StaffCP. + * + * @param string $section Permission section to add permission to. + * @param array $permissions List of unique permissions to register. + */ + public static function registerPermissions(string $section, array $permissions): void { + foreach ($permissions as $permission => $title) { + if (!isset(self::$_permissions[$section][$permission])) { + self::$_permissions[$section][$permission] = $title; + } + } + } + + /** + * Get all registered permissions. + * + * @return array> Permission array. + */ + public static function getPermissions(): array { + return self::$_permissions; + } +} diff --git a/core/classes/Permissions/PermissionTristate.php b/core/classes/Permissions/PermissionTristate.php new file mode 100644 index 0000000000..2cef242bf4 --- /dev/null +++ b/core/classes/Permissions/PermissionTristate.php @@ -0,0 +1,15 @@ +query( diff --git a/core/classes/Queue/Task.php b/core/classes/Queue/Task.php index 1aa14a27c6..7aa1f52afa 100644 --- a/core/classes/Queue/Task.php +++ b/core/classes/Queue/Task.php @@ -232,10 +232,10 @@ public function setOutput(array $output = []) { } /** - * @param Container $container + * @param NamelessContainer $container * @return void */ - public function setContainer(Container $container) { + public function setContainer(NamelessContainer $container) { $this->_container = $container; } diff --git a/core/init.php b/core/init.php index b02d0778a1..c3d39fca24 100644 --- a/core/init.php +++ b/core/init.php @@ -68,14 +68,7 @@ * Initialise */ - $container = new \DI\Container(); - $container->set(Cache::class, function () { - return new Cache([ - 'name' => 'nameless', - 'extension' => '.cache', - 'path' => ROOT_PATH . '/cache/' - ]); - }); + $container = NamelessContainer::getInstance(); $cache = $container->get(Cache::class); @@ -417,6 +410,9 @@ $endpoints = $container->get(Endpoints::class); $announcements = $container->get(Announcements::class); + $permission_cache = $container->get(PermissionCache::class); + // $permission_cache->flush(); + $permission_calculator = $container->get(PermissionCalculator::class); // Modules $cache->setCache('modulescache'); diff --git a/core/migrations/20231222011251_create_permissions_table.php b/core/migrations/20231222011251_create_permissions_table.php new file mode 100644 index 0000000000..a3fd3e23c8 --- /dev/null +++ b/core/migrations/20231222011251_create_permissions_table.php @@ -0,0 +1,28 @@ +table('nl2_permissions', ['id' => false, 'primary_key' => ['permissible', 'permissible_id', 'permission']]) + ->addColumn('permissible', 'string', ['limit' => 255]) + ->addColumn('permissible_id', 'integer') + ->addColumn('permission', 'string', ['limit' => 255]) + ->addColumn('value', 'integer') + ->addIndex(['permissible', 'permissible_id', 'permission'], ['unique' => true]) + ->create(); + + foreach (Group::all() as $group) { + NamelessContainer::getInstance() + ->get(PermissionCache::class) + ->upsert(Group::class, $group->id, json_decode($group->permissions, true)); + } + + $this->table('nl2_groups') + ->removeColumn('permissions') + ->update(); + } +} diff --git a/core/migrations/phinx.php b/core/migrations/phinx.php index c5f47cc8bf..4afcbb90c2 100644 --- a/core/migrations/phinx.php +++ b/core/migrations/phinx.php @@ -23,7 +23,7 @@ 'port' => $config['port'], 'charset' => 'utf8mb4', 'collation' => 'utf8mb4_unicode_ci', - 'default_migration_table' => $table, + 'migration_table' => $table, ], ], 'feature_flags' => [ diff --git a/custom/panel_templates/Default/core/groups_permissions.tpl b/custom/panel_templates/Default/core/groups_permissions.tpl index d62ead14d7..84aff80081 100644 --- a/custom/panel_templates/Default/core/groups_permissions.tpl +++ b/custom/panel_templates/Default/core/groups_permissions.tpl @@ -49,37 +49,38 @@ {include file='includes/alerts.tpl'} -
- {foreach from=$ALL_PERMISSIONS key=key item=item} + {foreach from=$ALL_PERMISSIONS key=key item=item}

{$key|escape}

+ {if $item|@count > 1} - {$SELECT_ALL} - | - {$DESELECT_ALL} + {$SELECT_ALL} + {$INHERIT_ALL} + {$DESELECT_ALL} {/if} - -
- {foreach from=$item key=permission item=title} -
- - -
- {/foreach} -
- {/foreach} -
+ + + {foreach from=$item key=permission item=title} + + + + + {/foreach} + +
{$title} + + + +
+ {/foreach} + +
+ + - -
- - + + +
@@ -104,32 +105,51 @@ {include file='scripts.tpl'} diff --git a/modules/Cookie Consent/module.php b/modules/Cookie Consent/module.php index 877db29b4d..40a0d9a8e6 100644 --- a/modules/Cookie Consent/module.php +++ b/modules/Cookie Consent/module.php @@ -56,7 +56,7 @@ public function onPageLoad(User $user, Pages $pages, Cache $cache, Smarty $smart $language = $this->_language; // AdminCP - PermissionHandler::registerPermissions($language->get('moderator', 'staff_cp'), [ + PermissionRegistry::registerPermissions($language->get('moderator', 'staff_cp'), [ 'admincp.cookies' => $this->_cookie_language->get('cookie', 'cookies') ]); diff --git a/modules/Core/hooks/CloneGroupHook.php b/modules/Core/hooks/CloneGroupHook.php index 9aaa2449e1..43622a29e2 100644 --- a/modules/Core/hooks/CloneGroupHook.php +++ b/modules/Core/hooks/CloneGroupHook.php @@ -10,9 +10,24 @@ class CloneGroupHook { public static function execute(GroupClonedEvent $event): void { + // Clone group permissions + $new_group_id = $event->group->id; + $permissions = DB::getInstance()->query('SELECT * FROM nl2_permissions WHERE permissible = ? AND permissible_id = ?', [get_class($event->cloned_group), $event->cloned_group->id]); + if ($permissions->count()) { + $permissions = $permissions->results(); + + $inserts = []; + foreach ($permissions as $permission) { + $inserts[] = '('.$new_group_id.',"'.get_class($event->group).'",' . $permission->permissible_id . ',"' . $permission->permission . '",' . $permission->value . ')'; + } + + $query = 'INSERT INTO nl2_permissions (permissible_id, permissible, permissible_id, permission, value) VALUES '; + $query .= implode(',', $inserts); + + DB::getInstance()->query($query); + } // Clone group permissions for custom pages - $new_group_id = $event->group->id; $permissions = DB::getInstance()->query('SELECT * FROM nl2_custom_pages_permissions WHERE group_id = ?', [$event->cloned_group->id]); if ($permissions->count()) { $permissions = $permissions->results(); diff --git a/modules/Core/language/en_UK.json b/modules/Core/language/en_UK.json index cc1b0ea6f6..b209b5065e 100644 --- a/modules/Core/language/en_UK.json +++ b/modules/Core/language/en_UK.json @@ -755,6 +755,7 @@ "admin/debugging_enabled": "Debugging is enabled, sensitive information may be exposed to unauthorised visitors. Debugging should only be used for development, not on public sites.", "admin/require_minecraft_username_on_registration": "Require Minecraft Username on registration?", "admin/integration_settings_does_not_exist": "Integration settings file for integration {{integration}} does not exist", + "admin/inherit_all": "Inherit all", "api/account_validated": "Account validated successfully", "api/finish_registration_email": "Please check your emails to complete registration.", "api/finish_registration_link": "Please click on the following link to complete registration:", diff --git a/modules/Core/module.php b/modules/Core/module.php index 8008679297..d4f9db7fe1 100644 --- a/modules/Core/module.php +++ b/modules/Core/module.php @@ -591,12 +591,12 @@ public function onPageLoad(User $user, Pages $pages, Cache $cache, Smarty $smart $language = $this->_language; // Permissions - PermissionHandler::registerPermissions($language->get('admin', 'administrator'), [ + PermissionRegistry::registerPermissions($language->get('admin', 'administrator'), [ 'administrator' => $language->get('admin', 'administrator') . ' » ' . $language->get('admin', 'administrator_permission_info'), ]); // AdminCP - PermissionHandler::registerPermissions($language->get('moderator', 'staff_cp'), [ + PermissionRegistry::registerPermissions($language->get('moderator', 'staff_cp'), [ 'admincp.core' => $language->get('admin', 'core'), 'admincp.core.api' => $language->get('admin', 'core') . ' » ' . $language->get('admin', 'api'), 'admincp.core.seo' => $language->get('admin', 'core') . ' » ' . $language->get('admin', 'seo'), @@ -654,7 +654,7 @@ public function onPageLoad(User $user, Pages $pages, Cache $cache, Smarty $smart ]); // UserCP - PermissionHandler::registerPermissions('UserCP', [ + PermissionRegistry::registerPermissions('UserCP', [ 'usercp.messaging' => $language->get('user', 'messaging'), 'usercp.signature' => $language->get('user', 'profile_settings') . ' » ' . $language->get('user', 'signature'), 'usercp.private_profile' => $language->get('user', 'profile_settings') . ' » ' . $language->get('user', 'private_profile'), @@ -664,7 +664,7 @@ public function onPageLoad(User $user, Pages $pages, Cache $cache, Smarty $smart ]); // Profile Page - PermissionHandler::registerPermissions('Profile', [ + PermissionRegistry::registerPermissions('Profile', [ 'profile.private.bypass' => $language->get('general', 'bypass') . ' » ' . $language->get('user', 'private_profile') ]); diff --git a/modules/Core/pages/panel/groups.php b/modules/Core/pages/panel/groups.php index dc63dbab2f..923ccda033 100644 --- a/modules/Core/pages/panel/groups.php +++ b/modules/Core/pages/panel/groups.php @@ -354,7 +354,6 @@ 'group_username_css' => ($_POST['username_css'] ? Input::get('username_css') : null), 'admin_cp' => Input::get('staff'), 'staff' => Input::get('staff'), - 'permissions' => $group->permissions, 'default_group' => $default, 'order' => Input::get('order'), 'force_tfa' => Input::get('tfa') @@ -446,17 +445,8 @@ if (Token::check()) { // Token valid - // Build new JSON object for permissions - $perms = []; - if (isset($_POST['permissions']) && count($_POST['permissions'])) { - foreach ($_POST['permissions'] as $permission => $value) { - $perms[$permission] = 1; - } - } - $perms_json = json_encode($perms); - try { - DB::getInstance()->update('groups', $group->id, ['permissions' => $perms_json]); + $permission_cache->upsert(Group::class, $group->id, json_decode($_POST['permissions'], true)); Session::flash('admin_groups', $language->get('admin', 'permissions_updated_successfully')); Redirect::to(URL::build('/panel/core/groups/', 'action=edit&group=' . urlencode($group->id))); @@ -472,10 +462,13 @@ 'PERMISSIONS' => $language->get('admin', 'permissions'), 'BACK' => $language->get('general', 'back'), 'BACK_LINK' => URL::build('/panel/core/groups/', 'action=edit&group=' . urlencode($group->id)), - 'PERMISSIONS_VALUES' => json_decode($group->permissions, true), - 'ALL_PERMISSIONS' => PermissionHandler::getPermissions(), + 'GROUP_PERMISSIONS' => $permission_cache->getOrLoad(Group::class, $group->id), + 'GROUP_PERMISSIONS_JSON' => json_encode($permission_cache->getOrLoad(Group::class, $group->id)), + 'ALL_PERMISSIONS' => PermissionRegistry::getPermissions(), 'SELECT_ALL' => $language->get('admin', 'select_all'), - 'DESELECT_ALL' => $language->get('admin', 'deselect_all') + 'DESELECT_ALL' => $language->get('admin', 'deselect_all'), + 'INHERIT_ALL' => $language->get('admin', 'inherit_all'), + 'PERMISSION_UPDATE_URL' => URL::build('/panel/core/groups/', 'action=permissions&group=' . urlencode($group->id)), ]); $template_file = 'core/groups_permissions.tpl'; diff --git a/modules/Core/pages/panel/users_punishments.php b/modules/Core/pages/panel/users_punishments.php index 392f387688..7365786739 100644 --- a/modules/Core/pages/panel/users_punishments.php +++ b/modules/Core/pages/panel/users_punishments.php @@ -212,56 +212,54 @@ } // Send alerts - $groups_query = DB::getInstance()->query('SELECT id FROM nl2_groups WHERE permissions LIKE \'%"modcp.punishments":1%\''); - if ($groups_query->count()) { - $groups_query = $groups_query->results(); - - $groups = '('; - foreach ($groups_query as $group) { - if (is_numeric($group->id)) { - $groups .= ((int)$group->id) . ','; + // todo + $groups_with_permission = NamelessContainer::getInstance() + ->get(PermissionCache::class) + ->getGroupIdsWithPermission('modcp.punishments'); + + $groups = '('; + foreach ($groups_with_permission as $group_id) { + $groups .= ((int)$group_id) . ','; + } + $groups = rtrim($groups, ',') . ')'; + + // Get users in this group + $users = DB::getInstance()->query('SELECT DISTINCT(nl2_users.id) AS id FROM nl2_users LEFT JOIN nl2_users_groups ON nl2_users.id = nl2_users_groups.user_id WHERE group_id in ' . $groups); + + if ($users->count()) { + $users = $users->results(); + + foreach ($users as $item) { + if ($user->data()->id == $item->id) { + continue; } - } - $groups = rtrim($groups, ',') . ')'; - - // Get users in this group - $users = DB::getInstance()->query('SELECT DISTINCT(nl2_users.id) AS id FROM nl2_users LEFT JOIN nl2_users_groups ON nl2_users.id = nl2_users_groups.user_id WHERE group_id in ' . $groups); - - if ($users->count()) { - $users = $users->results(); - - foreach ($users as $item) { - if ($user->data()->id == $item->id) { - continue; - } - - // Send alert - Alert::create( - $item->id, - 'punishment', - [ - 'path' => 'core', - 'file' => 'moderator', - 'term' => 'user_punished_alert', - 'replace' => ['{{staffUser}}', '{{user}}'], - 'replace_with' => [ - Output::getClean($user->data()->nickname), - Output::getClean($query->nickname), - ], + + // Send alert + Alert::create( + $item->id, + 'punishment', + [ + 'path' => 'core', + 'file' => 'moderator', + 'term' => 'user_punished_alert', + 'replace' => ['{{staffUser}}', '{{user}}'], + 'replace_with' => [ + Output::getClean($user->data()->nickname), + Output::getClean($query->nickname), ], - [ - 'path' => 'core', - 'file' => 'moderator', - 'term' => 'user_punished_alert', - 'replace' => ['{{staffUser}}', '{{user}}'], - 'replace_with' => [ - Output::getClean($user->data()->nickname), - Output::getClean($query->nickname) - ], + ], + [ + 'path' => 'core', + 'file' => 'moderator', + 'term' => 'user_punished_alert', + 'replace' => ['{{staffUser}}', '{{user}}'], + 'replace_with' => [ + Output::getClean($user->data()->nickname), + Output::getClean($query->nickname) ], - URL::build('/panel/users/punishments/', 'user=' . urlencode($query->id)) - ); - } + ], + URL::build('/panel/users/punishments/', 'user=' . urlencode($query->id)) + ); } } Redirect::to(URL::build('/panel/users/punishments/', 'user=' . urlencode($query->id))); diff --git a/modules/Core/queries/debug_link.php b/modules/Core/queries/debug_link.php index 0c3411259e..03b28bfae0 100755 --- a/modules/Core/queries/debug_link.php +++ b/modules/Core/queries/debug_link.php @@ -130,6 +130,7 @@ 'group_html' => $group->group_html, 'admin_cp' => (bool)$group->admin_cp, 'staff' => (bool)$group->staff, + // todo 'permissions' => json_decode($group->permissions, true) ?? [], 'default_group' => (bool)$group->default_group, 'order' => (int)$group->order, diff --git a/modules/Core/queries/queue.php b/modules/Core/queries/queue.php index 8fb06a0b4c..f50f5dd2cd 100644 --- a/modules/Core/queries/queue.php +++ b/modules/Core/queries/queue.php @@ -3,7 +3,7 @@ * Queue query runs any pending tasks * * @var Cache $cache - * @var \DI\Container $container + * @var NamelessContainer $container * @var Navigation $cc_nav * @var Navigation $navigation * @var Navigation $staffcp_nav diff --git a/modules/Discord Integration/module.php b/modules/Discord Integration/module.php index 31639b5dbb..7106cff50d 100644 --- a/modules/Discord Integration/module.php +++ b/modules/Discord Integration/module.php @@ -51,7 +51,7 @@ public function onEnable() { } public function onPageLoad(User $user, Pages $pages, Cache $cache, Smarty $smarty, $navs, Widgets $widgets, ?TemplateBase $template) { - PermissionHandler::registerPermissions($this->getName(), [ + PermissionRegistry::registerPermissions($this->getName(), [ 'admincp.discord' => $this->_language->get('admin', 'integrations') . ' » ' . Discord::getLanguageTerm('discord'), ]); diff --git a/modules/Forum/module.php b/modules/Forum/module.php index 4ea279ffc6..8a1318ae99 100644 --- a/modules/Forum/module.php +++ b/modules/Forum/module.php @@ -187,7 +187,7 @@ public function onDisable() { public function onPageLoad($user, $pages, $cache, $smarty, $navs, $widgets, $template) { // AdminCP - PermissionHandler::registerPermissions('Forum', [ + PermissionRegistry::registerPermissions('Forum', [ 'admincp.forums' => $this->_language->get('moderator', 'staff_cp') . ' » ' . $this->_forum_language->get('forum', 'forum') ]); diff --git a/modules/Members/module.php b/modules/Members/module.php index 4ccfbe36a7..0e6c26a4fd 100644 --- a/modules/Members/module.php +++ b/modules/Members/module.php @@ -52,7 +52,7 @@ public function onPageLoad(User $user, Pages $pages, Cache $cache, Smarty $smart $language = $this->_language; // AdminCP - PermissionHandler::registerPermissions($language->get('moderator', 'staff_cp'), [ + PermissionRegistry::registerPermissions($language->get('moderator', 'staff_cp'), [ 'admincp.members' => $this->_members_language->get('members', 'member_lists') ]); From 62dfe7f8c91970c7c8c344537eb48c9b863bcdcc Mon Sep 17 00:00:00 2001 From: Tadhg Boyle Date: Sat, 8 Jun 2024 12:50:27 -0700 Subject: [PATCH 2/2] Use groupHasPermission --- core/classes/Permissions/PermissionCalculator.php | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/core/classes/Permissions/PermissionCalculator.php b/core/classes/Permissions/PermissionCalculator.php index 3f9f5da158..ed75db3011 100644 --- a/core/classes/Permissions/PermissionCalculator.php +++ b/core/classes/Permissions/PermissionCalculator.php @@ -32,17 +32,9 @@ public function userHasPermission(User $user, string $permission): bool if ($result === PermissionTristate::INHERIT) { foreach ($user->getGroups() as $group) { - $group_permissions = $this->_permission_cache->getOrLoad(Group::class, $group->id); - - $result = $group_permissions[$permission] ?? PermissionTristate::INHERIT; - - if ($result === PermissionTristate::TRUE) { + if ($this->groupHasPermission($group, $permission)) { return true; } - - if ($result === PermissionTristate::FALSE) { - return false; - } } }