From fc707b1abf7415baef9dc35670b39fa059412110 Mon Sep 17 00:00:00 2001 From: Craig Williams Date: Wed, 31 May 2023 14:00:52 +0100 Subject: [PATCH] Added hasRole twig extension and Auditer role & permission set --- README.md | 4 +- src/Database/Seeds/DisableAuditer.php | 140 +++++++++++++ src/Database/Seeds/EnableAuditer.php | 193 ++++++++++++++++++ src/Database/Seeds/EnableExclusiveAuditer.php | 49 +++++ src/ServicesProvider/ServicesProvider.php | 14 ++ src/Twig/HasRoleExtension.php | 65 ++++++ templates/pages/dashboard.html.twig | 2 +- 7 files changed, 465 insertions(+), 2 deletions(-) create mode 100644 src/Database/Seeds/DisableAuditer.php create mode 100644 src/Database/Seeds/EnableAuditer.php create mode 100644 src/Database/Seeds/EnableExclusiveAuditer.php create mode 100644 src/Twig/HasRoleExtension.php diff --git a/README.md b/README.md index f6450d4..89620f1 100644 --- a/README.md +++ b/README.md @@ -10,4 +10,6 @@ Fixes/tweaks a few "issues" with the default UserFrosting installation, includin - Adds a user creation button to the group admin page - Fixes showing user activity depending on the current user's permission to view it - Allow site-admins to view roles & permissions -- Allow site-admins to edit basic role details (name, slug & description) \ No newline at end of file +- Allow site-admins to edit basic role details (name, slug & description) +- Added 'hasRole' twig function to check if a user has a role (if a role doesn't exist, always returns false) +- Added 'Auditer' role and split the activities permission away from site-admins (exclusive only) and everyone else \ No newline at end of file diff --git a/src/Database/Seeds/DisableAuditer.php b/src/Database/Seeds/DisableAuditer.php new file mode 100644 index 0000000..aac82ca --- /dev/null +++ b/src/Database/Seeds/DisableAuditer.php @@ -0,0 +1,140 @@ +modifyBasePermissions(); + + // Get permissions + $permissions = $this->getPermissions(); + $this->deletePermissions($permissions); + + // Get and save roles + $roles = $this->getRoles(); + $this->deleteRoles($roles); + } + + /** + * @return array Roles to seed + */ + protected function getRoles() + { + return [ + Role::where([ + 'slug' => 'auditer' + ])->first(), + ]; + } + + /** + * @return array Permissions to seed + */ + protected function getPermissions() + { + $siteAdminRole = Role::where('slug', 'site-admin')->first(); + $groupAdminRole = Role::where('slug', 'group-admin')->first(); + + return [ + Permission::where([ + 'slug' => 'view_user_field', + 'conditions' => "in(property,['activities'])" + ])->first(), + Permission::where([ + 'slug' => 'view_user_field', + 'conditions' => "equals_num(self.group_id,user.group_id) && !is_master(user.id) && !has_role(user.id,{$siteAdminRole->id}) && (!has_role(user.id,{$groupAdminRole->id}) || equals_num(self.id,user.id)) && in(property,['activities'])" + ])->first(), + ]; + } + + /** + * Save roles. + * + * @param array $roles + */ + protected function deleteRoles(array $roles) + { + for ($idx = count($roles) - 1; $idx >= 0; $idx--) { + if ($roles[$idx]) { + $roles[$idx]->users()->detach(); + $roles[$idx]->delete(); + } + } + } + + /** + * Save permissions. + * + * @param array $permissions + */ + protected function deletePermissions(array $permissions) + { + for ($idx = count($permissions) - 1; $idx >= 0; $idx--) { + if ($permissions[$idx]) { + $permissions[$idx]->roles()->detach(); + $permissions[$idx]->delete(); + } + } + } + + + /** + * Modify base permissions. + * + * @param array $permissions + */ + protected function modifyBasePermissions() + { + $siteAdminRole = Role::where('slug', 'site-admin')->first(); + $groupAdminRole = Role::where('slug', 'group-admin')->first(); + + $viewPermissionsAny = Permission::query() + ->where('slug', 'view_user_field') + ->where('conditions', "in(property,['user_name','name','email','locale','theme','roles','group'])") + ->first(); + if ($viewPermissionsAny) { + $viewPermissionsAny->conditions = "in(property,['user_name','name','email','locale','theme','roles','group','activities'])"; + $viewPermissionsAny->save(); + } + + $viewPermissionsGroup = Permission::query() + ->where('slug', 'view_user_field') + ->where('conditions', "equals_num(self.group_id,user.group_id) && !is_master(user.id) && !has_role(user.id,{$siteAdminRole->id}) && (!has_role(user.id,{$groupAdminRole->id}) || equals_num(self.id,user.id)) && in(property,['user_name','name','email','locale','roles','group'])") + ->first(); + if ($viewPermissionsGroup) { + $viewPermissionsGroup->conditions = "equals_num(self.group_id,user.group_id) && !is_master(user.id) && !has_role(user.id,{$siteAdminRole->id}) && (!has_role(user.id,{$groupAdminRole->id}) || equals_num(self.id,user.id)) && in(property,['user_name','name','email','locale','roles','group','activities'])"; + $viewPermissionsGroup->save(); + } + + // Make sure site admin still has activities page permissions + $siteAdmin = Role::where('slug', 'site-admin')->first(); + if ($siteAdmin) { + $siteAdmin->permissions()->syncWithoutDetaching([ + Permission::where('slug', 'uri_activities')->first()->id, + ]); + } + } +} diff --git a/src/Database/Seeds/EnableAuditer.php b/src/Database/Seeds/EnableAuditer.php new file mode 100644 index 0000000..affd66d --- /dev/null +++ b/src/Database/Seeds/EnableAuditer.php @@ -0,0 +1,193 @@ +getRoles(); + $this->saveRoles($roles); + + // Get and save permissions + $permissions = $this->getPermissions(); + $this->savePermissions($permissions); + + // Modify the base permissions to exclude activities + $this->modifyBasePermissions(); + + // Add default mappings to permissions + $this->syncPermissionsRole($permissions); + } + + /** + * @return array Roles to seed + */ + protected function getRoles() + { + return [ + new Role([ + 'slug' => 'auditer', + 'name' => 'Audit Viewer', + 'description' => 'This role is meant for "auditers", who are allowed to view the activity log.', + ]), + ]; + } + + /** + * @return array Permissions to seed + */ + protected function getPermissions() + { + $siteAdminRole = Role::where('slug', 'site-admin')->first(); + $groupAdminRole = Role::where('slug', 'group-admin')->first(); + + return [ + 'view_user_field_activities_any' => new Permission([ + 'slug' => 'view_user_field', + 'name' => 'View user activities', + 'conditions' => "in(property,['activities'])", + 'description' => 'View any user\'s activities log', + ]), + 'view_user_field_activities_group' => new Permission([ + 'slug' => 'view_user_field', + 'name' => 'View user activities', + 'conditions' => "equals_num(self.group_id,user.group_id) && !is_master(user.id) && !has_role(user.id,{$siteAdminRole->id}) && (!has_role(user.id,{$groupAdminRole->id}) || equals_num(self.id,user.id)) && in(property,['activities'])", + 'description' => 'View activities log of any user in your own group, except the master user and Site and Group Administrators (except yourself).', + ]), + ]; + } + + /** + * Save roles. + * + * @param array $roles + */ + protected function saveRoles(array &$roles) + { + foreach ($roles as $role) { + // Trying to find if the role already exist + $existingRole = Role::where(['slug' => $role->slug])->first(); + + // Don't save if already exist, use existing role reference + // otherwise to re-sync permissions and roles + if ($existingRole == null) { + $role->save(); + } else { + $roles[$slug] = $existingRole; + } + } + } + + /** + * Save permissions. + * + * @param array $permissions + */ + protected function savePermissions(array &$permissions) + { + foreach ($permissions as $slug => $permission) { + + // Trying to find if the permission already exist + $existingPermission = Permission::where(['slug' => $permission->slug, 'conditions' => $permission->conditions])->first(); + + // Don't save if already exist, use existing permission reference + // otherwise to re-sync permissions and roles + if ($existingPermission == null) { + $permission->save(); + } else { + $permissions[$slug] = $existingPermission; + } + } + } + + + /** + * Modify base permissions. + * + * @param array $permissions + */ + protected function modifyBasePermissions() + { + $siteAdminRole = Role::where('slug', 'site-admin')->first(); + $groupAdminRole = Role::where('slug', 'group-admin')->first(); + + $viewPermissionsAny = Permission::query() + ->where('slug', 'view_user_field') + ->where('conditions', "in(property,['user_name','name','email','locale','theme','roles','group','activities'])") + ->first(); + if ($viewPermissionsAny) { + $viewPermissionsAny->conditions = "in(property,['user_name','name','email','locale','theme','roles','group'])"; + $viewPermissionsAny->save(); + } + + $viewPermissionsGroup = Permission::query() + ->where('slug', 'view_user_field') + ->where('conditions', "equals_num(self.group_id,user.group_id) && !is_master(user.id) && !has_role(user.id,{$siteAdminRole->id}) && (!has_role(user.id,{$groupAdminRole->id}) || equals_num(self.id,user.id)) && in(property,['user_name','name','email','locale','roles','group','activities'])") + ->first(); + if ($viewPermissionsGroup) { + $viewPermissionsGroup->conditions = "equals_num(self.group_id,user.group_id) && !is_master(user.id) && !has_role(user.id,{$siteAdminRole->id}) && (!has_role(user.id,{$groupAdminRole->id}) || equals_num(self.id,user.id)) && in(property,['user_name','name','email','locale','roles','group'])"; + $viewPermissionsGroup->save(); + } + } + + /** + * Sync permissions with default roles. + * + * @param array $permissions + */ + protected function syncPermissionsRole(array $permissions) + { + $siteAdmin = Role::where('slug', 'site-admin')->first(); + if ($siteAdmin) { + $siteAdmin->permissions()->syncWithoutDetaching([ + $permissions['view_user_field_activities_any']->id, + $permissions['view_user_field_activities_group']->id, + ]); + } + + $groupAdmin = Role::where('slug', 'group-admin')->first(); + if ($groupAdmin) { + $groupAdmin->permissions()->syncWithoutDetaching([ + $permissions['view_user_field_activities_group']->id, + ]); + } + + $roleAuditer = Role::where('slug', 'auditer')->first(); + if ($roleAuditer) { + $roleAuditer->permissions()->sync([ + Permission::where('slug', 'uri_dashboard')->first()->id, + Permission::where('slug', 'uri_activities')->first()->id, + $permissions['view_user_field_activities_any']->id, + $permissions['view_user_field_activities_group']->id, + ]); + } + } +} diff --git a/src/Database/Seeds/EnableExclusiveAuditer.php b/src/Database/Seeds/EnableExclusiveAuditer.php new file mode 100644 index 0000000..b42b24c --- /dev/null +++ b/src/Database/Seeds/EnableExclusiveAuditer.php @@ -0,0 +1,49 @@ +first(); + if ($siteAdmin) { + $siteAdmin->permissions()->detach([ + Permission::where('slug', 'uri_activities')->first()->id, + ]); + } + + $roleAuditer = Role::where('slug', 'auditer')->first(); + if ($roleAuditer) { + $roleAuditer->permissions()->sync([ + Permission::where('slug', 'uri_dashboard')->first()->id, + Permission::where('slug', 'uri_activities')->first()->id, + $permissions['view_user_field_activities_any']->id, + $permissions['view_user_field_activities_group']->id, + ]); + } + } +} diff --git a/src/ServicesProvider/ServicesProvider.php b/src/ServicesProvider/ServicesProvider.php index 84c5606..cde06d2 100644 --- a/src/ServicesProvider/ServicesProvider.php +++ b/src/ServicesProvider/ServicesProvider.php @@ -12,6 +12,7 @@ namespace UserFrosting\Sprinkle\UFTweaks\ServicesProvider; use Psr\Container\ContainerInterface; use Psr\Http\Message\ResponseInterface as Response; use Psr\Http\Message\ServerRequestInterface as Request; +use UserFrosting\Sprinkle\UFTweaks\Twig\HasRoleExtension; /** @@ -103,5 +104,18 @@ class ServicesProvider return $authorizer; }); + + /* + * Extends the 'view' service with the HasRole extension for Twig. + * + * @return \Slim\Views\Twig + */ + $container->extend('view', function ($view, $c) { + $twig = $view->getEnvironment(); + + $twig->addExtension(new HasRoleExtension($c)); + + return $view; + }); } } \ No newline at end of file diff --git a/src/Twig/HasRoleExtension.php b/src/Twig/HasRoleExtension.php new file mode 100644 index 0000000..bdcd08c --- /dev/null +++ b/src/Twig/HasRoleExtension.php @@ -0,0 +1,65 @@ +services = $services; + $this->config = $services->config; + } + + public function getName() + { + return 'avsdev/uf-tweaks-hasRole'; + } + + public function getFunctions() + { + return [ + // Add Twig function for checking permissions during dynamic menu rendering + new TwigFunction('hasRole', function ($roleSlug) { + $currentUser = $this->services->currentUser; + return $currentUser->roles()->where('slug', $roleSlug)->count() > 0; + }), + ]; + } + + public function getGlobals() + { + return []; + } +} diff --git a/templates/pages/dashboard.html.twig b/templates/pages/dashboard.html.twig index c7f0d62..9354ab7 100644 --- a/templates/pages/dashboard.html.twig +++ b/templates/pages/dashboard.html.twig @@ -155,7 +155,7 @@ {% if checkAccess('uri_activities') %} {% set dashboard_is_empty = false %} -
+

{{translate('ACTIVITY', 2)}}