diff --git a/Module.php b/Module.php index e62f7f5..3d8117c 100644 --- a/Module.php +++ b/Module.php @@ -13,6 +13,10 @@ use Omeka\Api\Adapter\SiteAdapter; use Omeka\Entity\EntityInterface; use Omeka\Permissions\Acl; +use Omeka\Permissions\Assertion\AssertionNegation; +use Omeka\Permissions\Assertion\IsSelfAssertion; +use Omeka\Permissions\Assertion\OwnsEntityAssertion; +use Teams\Acl\TeamRolePermissionAssertion; use Teams\Entity\Team; use Teams\Entity\TeamAsset; use Teams\Entity\TeamResource; @@ -237,203 +241,86 @@ public function createNamedParameter( //TODO need to refactor to normalize and condense protected function addAclRules() { - $services = $this->getServiceLocator(); - $acl = $services->get('Omeka\Acl'); - - $roles = $acl->getRoles(); - //entity rights are the actions of controllers - $entityRights = ['read', 'create', 'update', 'delete']; - - //allow everyone to see their teams - $acl->allow( - $roles, - 'Teams\Controller\Index', - ['index', 'teamDetail'] - ); - //any level of core role can be given the team permission to update the team - $acl->allow( - $roles, - 'Teams\Controller\Update', - ['teamUpdate'] - ); - - //allow everyone to change their current team - $acl->allow( - $roles, - 'Teams\Controller\Update', - ['currentTeam'] - ); + $acl = $this->getServiceLocator()->get('Omeka\Acl'); + $teamRolePermissionAssertion = $this->getServiceLocator()->get(\Teams\Acl\TeamRolePermissionAssertion::class); - $acl->allow( - 'global_admin', - [ - 'Teams\Controller\Index', - 'Teams\Controller\Add', - 'Teams\Controller\Update', - ], - [ - 'index', - 'teamAdd', - 'teamDetail', - 'teamUpdate', - ] - ); - $acl->allow( - 'global_admin', - Entity\TeamRole::class, - $entityRights + // Use constants from assertion class to ensure synchronization between ACL rules and assertion logic + $entities = array_merge( + \Teams\Acl\TeamRolePermissionAssertion::RESOURCE_ENTITIES_FOR_ACL, + \Teams\Acl\TeamRolePermissionAssertion::SITE_ENTITIES_FOR_ACL ); - $acl->allow( - null, - [ - Entity\Team::class, - Entity\TeamUser::class, - Entity\TeamResource::class, - - ], - $entityRights + + $adapters = array_merge( + \Teams\Acl\TeamRolePermissionAssertion::RESOURCE_ADAPTERS_FOR_ACL, + \Teams\Acl\TeamRolePermissionAssertion::SITE_ADAPTERS_FOR_ACL ); - // Only admin can manage teams. - $adminRoles = [ - Acl::ROLE_GLOBAL_ADMIN, - Acl::ROLE_SITE_ADMIN, - + $teamResources = [ + \Teams\Entity\Team::class, + \Teams\Entity\TeamUser::class, + \Teams\Entity\TeamSite::class, + \Teams\Entity\TeamResource::class, + \Teams\Entity\TeamAsset::class, ]; - $viewerRoles = [ - Acl::ROLE_AUTHOR, - Acl::ROLE_EDITOR, - Acl::ROLE_RESEARCHER, - Acl::ROLE_REVIEWER, + $rolesToControl = $acl->getRoles(); + $rolesToControl = array_diff($rolesToControl, ["global_admin"]); + + $entityPrivileges =[ + 'update', + 'delete', + 'create', + 'batch-delete', + 'batch-update', + 'batch-create', + 'batch_delete', + 'batch-edit-all', + 'batch-update-all', + 'batch-edit', + 'batch-delete-all', + ]; + $adapterPrivileges = [ + 'create', + 'batch_delete', + 'batch_create', + 'batch_update', + 'batch_delete_all', + 'batch_update_all', ]; - - $acl->allow( - $viewerRoles, - [Api\Adapter\TeamAdapter::class, Api\Adapter\TeamResourceAdapter::class], - ['search', 'read'] - ); - $acl->allow( - $viewerRoles, - [ - Api\Adapter\TeamResourceAdapter::class, - Api\Adapter\TeamResourceTemplateAdapter::class, - Api\Adapter\TeamRoleAdapter::class, - Api\Adapter\TeamUserAdapter::class, - ], - ['search', 'read'] - ); - - $globalSettings = $this->getServiceLocator()->get('Omeka\Settings'); - if (! $globalSettings->get('teams_site_admin_make_user')) { - $acl->deny( - 'site_admin', - 'Omeka\Entity\User', - ['create', 'delete', 'change-password', 'edit-keys'] - ); - - $acl->deny( - 'site_admin', - 'Omeka\Api\Adapter\UserAdapter', - ['create', 'delete'] - ); + foreach ($entities as $resource) { + foreach ($rolesToControl as $role) { + foreach ( $entityPrivileges as $privilege) { + $acl->deny($role, $resource, $privilege, new AssertionNegation($teamRolePermissionAssertion)); + } + } } - $acl->allow( - $viewerRoles, - [Entity\TeamResource::class], - ['read', 'create', 'update', 'delete'] - ); - - $acl->allow( - $viewerRoles, - [Entity\Team::class], - ['read', 'create', 'update', 'delete'] - ); - - $acl->allow( - $viewerRoles, - [TeamUser::class, Entity\TeamResource::class], - ['read', 'create', 'update', 'delete'] - ); - - $acl->allow( - $viewerRoles, - [Entity\Team::class], - ['read', 'create', 'update', 'delete'] - ); - $acl->allow( - $viewerRoles, - [Entity\TeamUser::class, Entity\TeamResource::class, Entity\TeamRole::class], - ['read', 'create', 'update', 'delete'] - ); - $acl->allow( - $viewerRoles, - [Api\Adapter\TeamRoleAdapter::class], - ['search', 'read'] - ); - $acl->allow( - $viewerRoles, - [Api\Adapter\TeamAdapter::class], - ['search', 'read', 'create', 'update', 'delete'] - ); - $acl->allow( - $viewerRoles, - [Controller\AddController::class], - ['show', 'browse', 'add', 'edit', 'delete', 'delete-confirm'] - ); - $acl->allow( - $viewerRoles, - [Controller\DeleteController::class], - ['show', 'browse', 'add', 'edit', 'deleteRole', 'delete-confirm'] - ); - $acl->allow( - $viewerRoles, - [Controller\IndexController::class], - ['show', 'browse', 'add', 'edit', 'delete', 'delete-confirm'] - ); + //there are some display adapters that use this permission to show add/edit links + foreach ($adapters as $adapter) { + foreach ($rolesToControl as $role) { + foreach ( $adapterPrivileges as $privilege) { + $acl->deny($role, $adapter, $privilege, new AssertionNegation($teamRolePermissionAssertion)); + } + } + } - $acl->allow( - $viewerRoles, - [Controller\UpdateController::class], - ['show', 'browse', 'add', 'edit', 'delete', 'delete-confirm'] - ); - $acl->allow( - $adminRoles, - [Controller\IndexController::class], - ['roleIndex'] - ); - $acl->allow( - $viewerRoles, - [Controller\IndexController::class], - ['roleIndex'] - ); +// --- Team specific controls. --- + $acl->allow(null, 'Teams\Controller\Index', ['index', 'teamDetail', 'currentTeam']); + $acl->allow('global_admin', [ + 'Teams\Controller\Index', + 'Teams\Controller\Add', + 'Teams\Controller\Update', + ]); + $acl->allow('global_admin', \Teams\Entity\TeamRole::class); $globalSettings = $this->getServiceLocator()->get('Omeka\Settings'); - if (! $globalSettings->get('teams_site_admin_make_site')) { - $acl->deny( - 'site_admin', - 'Omeka\Entity\Site', - 'create' - ); + if (!$globalSettings->get('teams_site_admin_make_site')) { + $acl->deny('site_admin', \Omeka\Entity\Site::class, 'create'); } - if (!$globalSettings->get('teams_editor_make_site')) { - $acl->deny( - 'editor', - 'Omeka\Entity\Site', - 'create' - ); + $acl->deny('editor', \Omeka\Entity\Site::class, 'create'); } - - $acl->deny( - ['site_admin', 'editor', 'reviewer', 'author', 'researcher'], - 'Teams\Controller\Trash', - 'update' - ); - } - /** * @param Event $event * The default teams behavior is to filter API class, including the ones that populate the available sites in forms. @@ -2104,95 +1991,12 @@ public function siteEdit(Event $event) echo $view->partial('teams/partial/site-admin/edit', ['site_teams' => $site_teams, 'team_ids' => $team_ids]); } - /** - * Returns the expected string for proxied resource class in cases where the class returned is the doctrine proxy. - * - * @param $resource - * @return false|string - */ - public function getResourceClass($resource) - { - $doctrine_ent = 'DoctrineProxies\__CG__'; - $doctrine_test = strpos(get_class($resource), $doctrine_ent); - if ($doctrine_test === false) { - $res_class = get_class($resource); - } else { - $res_class = substr(get_class($resource), strlen($doctrine_ent) + 1); - } - return $res_class; - } public function getModules() { $manager = $this->getServiceLocator()->get('Omeka\ModuleManager'); } - /** - * Check to see if the user and the object they are attempting to access or change are part of the same team. - * - * @param $resource - * @param $team_user - * @return bool - */ - public function inTeam($resource, $team_user) - { - $em = $this->getServiceLocator()->get('Omeka\EntityManager'); - $resource_domains = ['Omeka\Entity\Item', 'Omeka\Entity\ItemSet', 'Omeka\Entity\Media']; - $fk_id = $resource->getId(); - $team = $team_user->getTeam(); - $user = $team_user->getUser(); - $res_class = $this->getResourceClass($resource); - - if (in_array($res_class, $resource_domains)) { - $teamsRepo = 'Teams\Entity\TeamResource'; - $fk = 'resource'; - $criteria = ['team' => $team->getId(), $fk => $fk_id]; - } elseif ($res_class == 'Omeka\Entity\Site') { - $teamsRepo = 'Teams\Entity\TeamSite'; - $fk = 'site'; - $criteria = ['team' => $team->getId(), $fk => $fk_id]; - } elseif ($res_class == 'Omeka\Entity\SitePage') { - $teamsRepo = 'Teams\Entity\TeamSite'; - $fk = 'site'; - $fk_id = $resource->getSite()->getId(); - $criteria = ['team' => $team->getId(), $fk => $fk_id]; - } elseif ($res_class == 'Omeka\Entity\ResourceTemplate') { - $teamsRepo = 'Teams\Entity\TeamResourceTemplate'; - $fk = 'resource_template'; - $criteria = ['team' => $team->getId(), $fk => $fk_id]; - } elseif ($res_class == 'Teams\Entity\Team') { - $teamsRepo = 'Teams\Entity\TeamUser'; - $fk = 'user'; - $criteria = ['team' => $team->getId(), $fk => $user->getId()]; - } elseif ($res_class == 'Teams\Entity\TeamSite') { - $teamsRepo = 'Teams\Entity\TeamUser'; - $fk = 'user'; - $criteria = ['team' => $resource->getTeam()->getId(), $fk => $user->getId()]; - } elseif ($res_class == 'Teams\Entity\TeamResource') { - $teamsRepo = 'Teams\Entity\TeamResource'; - $fk = 'resource'; - $fk_id = $resource->getResource()->getId(); - $criteria = ['team' => $team->getId(), $fk => $fk_id]; - } - /* - * TeamRole is only accessible by global admin so doesn't need to be checked. - * Other classes should be controlled by their respective modules and components. - */ - - else { - return true; - } - - if ($team_resource = $em->getRepository($teamsRepo) - ->findOneBy($criteria)) { - $in_team = true; - } else { - $in_team = false; - } - - return $in_team; - } - public function getUser() { if ($this->getServiceLocator() @@ -2204,225 +2008,6 @@ public function getUser() } } - /** - * Checks to see if the user has the authority to do something based on their role in their current team. Users can - * have different roles with different permissions in different teams. - * - * @param EntityInterface $resource - * @param $action - * @param Event $event - * @return bool - */ - public function teamAuthority(EntityInterface $resource, $action, Event $event) - { - $user = $this->getUser(); - - /* - * first go through a couple of common cases where we don't need to judge permissions and don't bother checking - * any other condition - */ - - //if the user isn't logged in (e.g., the public), use the default settings - if (!$user) { - return true; - } - - /* - * This may make the previous rule redundant, but keeping both for now - * No matter who is looking at the front-end, don't consider team authority - * based on the user - */ - - //if it isn't on the backend, let the public vs private rules take over - if ($this->getServiceLocator()->get('Omeka\Status')->isSiteRequest()) { - return true; - } - - $is_glob_admin = ($user->getRole() === 'global_admin'); - - //if it is the global admin, bypass any team controls (but will still apply filters) - if ($is_glob_admin) { - return true; - } - $messenger = new Messenger(); - - $authorized = false; - - $res_class = $this->getResourceClass($resource); - - //case that I don't fully understand. When selecting resource template on new item form - //the Omeka\AuthenticationService->getIdentity() returns null - - $em = $this->getServiceLocator()->get('Omeka\EntityManager'); - $user_id = $user->getId(); - $team_user = $em->getRepository('Teams\Entity\TeamUser') - ->findOneBy(['is_current' => true, 'user' => $user_id]); - $team = $team_user->getTeam(); - $team_user_role = $team_user->getRole() ; - - //don't check if in same team if a create action, because items only assigned teams after creation - if ($action != 'create') { - //if resource not part of user's current team, no action at all - if (! $this->inTeam($resource, $team_user)) { - $authorized = false; - $err = sprintf( - // $this->getTranslator()->translate( - 'Permission denied. Resource "%1$s: %2$s" is not part of your current team, %3$s. - If you feel this is an error, try changing teams or talk to the administrator. - Action: %4$s - ' -// ) - , - get_class($resource), - $resource->getId(), - $team->getName(), - $action - ); - $messenger->addError($err); - throw new Exception\PermissionDeniedException( - $err - ); - } - } - - $resource_domains = [ - 'Omeka\Entity\Item', - 'Omeka\Entity\ItemSet', - 'Omeka\Entity\Media', - 'Omeka\Entity\Asset', - 'Omeka\Entity\ResourceTemplate', - 'Teams\Entity\TeamResource', - 'Teams\Entity\TeamAsset', - ]; - - if (in_array($res_class, $resource_domains)) { - if ($action == 'create') { - $authorized = $team_user_role->getCanAddItems(); - } elseif ($action == 'delete' || $action == 'batch_delete') { - $authorized = $team_user_role->getCanDeleteResources(); - } elseif ($action == 'update') { - $authorized = $team_user_role->getCanModifyResources(); - } elseif ($action == 'read' || $action == 'search') { - $authorized = true; - } - } elseif ($res_class == 'Omeka\Entity\Site') { - if ($action == 'create') { - $globalSettings = $this->getServiceLocator()->get('Omeka\Settings'); - if ($globalSettings->get('teams_site_admin_make_site') && $user->getRole() == 'site_admin') { - $authorized = true; - } elseif ($globalSettings->get('teams_editor_make_site') && $user->getRole() == 'editor') { - $authorized = true; - } else { - $authorized = $is_glob_admin; - } - } elseif ($action == 'delete' || $action == 'batch_delete') { - $authorized = $team_user_role->getCanAddSitePages(); - } elseif ($action == 'update') { - $authorized = $team_user_role->getCanAddSitePages(); - } elseif ($action == 'read') { - $authorized = true; - } - } elseif ($res_class == 'Omeka\Entity\SitePage') { - if ($action == 'create') { - $authorized = $team_user_role->getCanAddSitePages(); - } elseif ($action == 'delete' || $action == 'batch_delete') { - $authorized = $team_user_role->getCanAddSitePages(); - } elseif ($action == 'update') { - $authorized = $team_user_role->getCanAddSitePages(); - } elseif ($action == 'read') { - $authorized = true; - } - } elseif ($res_class == 'Teams\Entity\Team' || $res_class == 'Teams\Entity\TeamRole') { - if ($action == 'create') { - $authorized = $is_glob_admin; - } elseif ($action == 'delete' || $action == 'batch_delete') { - $authorized = $is_glob_admin; - - //this is going to let them update the name and description - } elseif ($action == 'update') { - $authorized = $team_user_role->getCanAddUsers(); - } elseif ($action == 'read') { - $authorized = true; - } - } elseif ($res_class == 'Teams\Entity\TeamSite') { - if ($action == 'read') { - $authorized = true; - } - //adding or removing sites from a team - else { - $authorized = $team_user_role->getCanAddSitePages(); - } - } elseif ($res_class == 'Teams\Entity\TeamUser') { - if ($action == 'read') { - $authorized = true; - //deleting a site from a team - } elseif ($action == 'create') { - $authorized = $team_user_role->getCanAddUsers(); - } elseif ($action == 'delete') { - $authorized = $team_user_role->getCanAddUsers(); - } else { - $authorized = $is_glob_admin; - } - } - - //a list of classes where we don't need to check teams - //TODO: this should be refactored and go with the checks in the beginning - elseif ($res_class == 'Omeka\Entity\User') { - return true; - } elseif ($res_class == 'Omeka\Entity\Job') { - return true; - } elseif ($res_class == 'Omeka\Entity\Property') { - return true; - } elseif ($res_class == 'Teams\Entity\TeamRole') { - $authorized = $is_glob_admin; - } - - //don't police other modules by default - elseif (substr($res_class, 1, 12) !== 'Omeka\Entity') { - return true; - } - - if (!$authorized) { - $msg = sprintf( - 'Permission denied. Your role in %5$s, %4$s, does not permit you to %3$s this resource.', - get_class($resource), - $resource->getId(), - $action, - $team_user_role->getName(), - $team->getName() - ); - $diagnostic = sprintf( - 'Diagnostic: -- Resource type: %1$s. Resource id: %2$s. Action: %3$s. Your role: %4$s', - get_class($resource), - $resource->getId(), - $action, - $team_user_role->getName(), - $team->getName() - ); - - $messenger->addError($msg); - $messenger->addError($diagnostic); - throw new Exception\PermissionDeniedException($msg); - } - return $authorized; - } - - public function teamAuthorizeOnRead(Event $event) - { - $entity = $event->getParam('entity'); - $request = $event->getParam('request'); - $operation = $request->getOperation(); - $this->teamAuthority($entity, $operation, $event); - } - - public function teamAuthorizeOnHydrate(Event $event) - { - $request = $event->getParam('request'); - $entity = $event->getParam('entity'); - $operation = $request->getOperation(); - $this->teamAuthority($entity, $operation, $event); - } - /** * Disable the auto-add field on the site edit form. Teams manages this by automatically adding items to the * appropriate team. @@ -2621,25 +2206,12 @@ public function attachListeners(SharedEventManagerInterface $sharedEventManager) [$this, 'getOrphans'] ); - $sharedEventManager->attach( - '*', - 'api.find.post', - [$this, 'teamAuthorizeOnRead'] - ); - $sharedEventManager->attach( 'Teams\Controller\Index', 'view.browse.before', [$this, 'teamSelectorBrowse'] ); - //on api calls, make sure the users has the team authority to do action as well. - $sharedEventManager->attach( - '*', - 'api.hydrate.pre', - [$this, 'teamAuthorizeOnHydrate'] - ); - $sharedEventManager->attach( ItemSetAdapter::class, 'api.execute.post', diff --git a/config/module.config.php b/config/module.config.php index 41dff7b..c334bbb 100644 --- a/config/module.config.php +++ b/config/module.config.php @@ -74,6 +74,11 @@ dirname(__DIR__) . '/data/doctrine-proxies', ], ], + 'service_manager' => [ + 'factories' => [ + \Teams\Acl\TeamRolePermissionAssertion::class => \Teams\Service\Acl\TeamRolePermissionAssertionFactory::class, + ], + ], 'form_elements' => [ 'invokables' => [ Form\TeamForm::class => Form\TeamForm::class, diff --git a/src/Acl/TeamRolePermissionAssertion.php b/src/Acl/TeamRolePermissionAssertion.php new file mode 100644 index 0000000..54941ef --- /dev/null +++ b/src/Acl/TeamRolePermissionAssertion.php @@ -0,0 +1,337 @@ +auth = $auth; + $this->entityManager = $entityManager; + $this->status = $status; + } + + /** + * Assert whether the current user has permission based on team membership and role. + * + * @param Acl $acl + * @param RoleInterface|null $role + * @param ResourceInterface|null $resource + * @param string|null $privilege + * @return bool + */ + public function assert( + Acl $acl, + RoleInterface $role = null, + ResourceInterface $resource = null, + $privilege = null + ): bool + { + $user = $this->auth->getIdentity(); + if (!$user) { + return false; + } + + $teamUser = $this->entityManager->getRepository(TeamUser::class) + ->findOneBy(['is_current' => true, 'user' => $user->getId()]); + + if (!$teamUser) { + // User has no active team, so they cannot perform any team-restricted actions. + return false; + } + + $teamUserRole = $teamUser->getRole(); + $resourceClass = $this->getResourceClass($resource); + + // Get combined resource domains dynamically + $resourceDomains = array_merge(self::RESOURCE_ENTITIES_FOR_ACL, self::RESOURCE_ADAPTERS_FOR_ACL); + $siteResourceDomains = array_merge(self::SITE_ENTITIES_FOR_ACL, self::SITE_ADAPTERS_FOR_ACL); + + // For 'create' actions, we only check if the user's role has permission, + // as the resource doesn't belong to a team yet. + if ($privilege == 'create') { + if (in_array($resourceClass, $siteResourceDomains)) { + return (bool)$teamUserRole->getCanAddSitePages(); + } elseif (in_array($resourceClass, $resourceDomains)) { + return (bool)$teamUserRole->getCanAddItems(); + } else { + // Other resources are not part of this scope + return false; + } + } + + //In practice, batch operations are handled as a series of individual operations, + //but the batch privileges are checked first and used to control certain form controls + if (in_array($privilege, ['batch_delete', 'batch_delete_all'])) { + return (bool)$teamUserRole->getCanDeleteResources(); + } + if (in_array($privilege, ['batch_update', 'batch_update_all'])) { + return (bool)$teamUserRole->getCanModifyResources(); + } + + // For all other actions, check if the resource is in the user's current team + if (!$this->isResourceInTeam($resource, $teamUser)) { + return false; + } + + // The resource is in the team. Now check if the team role grants the specific privilege. + $isAuthorized = false; + + if (in_array($resourceClass, $resourceDomains)) { + switch ($privilege) { + case 'delete': + case 'batch_delete': + $isAuthorized = (bool)$teamUserRole->getCanDeleteResources(); + break; + case 'update': + $isAuthorized = (bool)$teamUserRole->getCanModifyResources(); + break; + case 'read': + case 'search': + $isAuthorized = true; // If it's in the team, they can read it. + break; + } + } elseif (in_array($resourceClass, $siteResourceDomains)) { + switch ($privilege) { + case 'delete': + case 'batch_delete': + case 'update': + $isAuthorized = (bool)$teamUserRole->getCanAddSitePages(); + break; + case 'read': + $isAuthorized = true; + break; + } + } elseif ($resourceClass == \Teams\Entity\Team::class || $resourceClass == \Teams\Entity\TeamRole::class) { + switch ($privilege) { + case 'create': + case 'delete': + case 'batch_delete': + $isAuthorized = false; // Only global admins can do this. + break; + case 'update': + $isAuthorized = (bool)$teamUserRole->getCanAddUsers(); + break; + case 'read': + $isAuthorized = true; + break; + } + } elseif ($resourceClass == \Teams\Entity\TeamSite::class) { + $isAuthorized = ($privilege == 'read') ? true : (bool)$teamUserRole->getCanAddSitePages(); + } elseif ($resourceClass == TeamUser::class) { + switch ($privilege) { + case 'create': + case 'delete': + $isAuthorized = (bool)$teamUserRole->getCanAddUsers(); + break; + case 'read': + $isAuthorized = true; + break; + default: + $isAuthorized = false; + } + } else { + + // If it's a resource we don't explicitly police (e.g., Job, Property), + // this assertion does not authorize it. + $isAuthorized = false; + } + + return $isAuthorized; + } + + /** + * Check if a resource belongs to the user's current team. + * + * Returns false if: + * - Resource is a GenericResource (only has class identifier, no entity data) + * - Resource is not an EntityInterface (cannot access entity methods) + * - Resource is not found in the team's resource associations + * + * Requires an entity instance to verify team membership because it needs to access + * entity-specific methods like getId(), getSite(), getTeam(), and getResource(). + * + * @param mixed $resource The resource to check (accepts ResourceInterface or entity instances) + * @param TeamUser $team_user The user's team membership record + * @return bool True if the resource belongs to the user's team, false otherwise + */ + private function isResourceInTeam(mixed $resource, TeamUser $team_user): bool + { + // Can't verify team membership without an actual entity instance + if ($resource instanceof GenericResource) { + return false; + } + + // Ensure we have an entity instance with required methods + if (!$resource instanceof EntityInterface) { + return false; + } + + $fk_id = $resource->getId(); + $team = $team_user->getTeam(); + $user = $team_user->getUser(); + $res_class = $this->getResourceClass($resource); + + if (in_array($res_class, self::ITEM_ENTITIES)) { + $teamsRepo = 'Teams\Entity\TeamResource'; + $fk = 'resource'; + $criteria = ['team' => $team->getId(), $fk => $fk_id]; + } elseif ($res_class == 'Omeka\Entity\Site') { + $teamsRepo = 'Teams\Entity\TeamSite'; + $fk = 'site'; + $criteria = ['team' => $team->getId(), $fk => $fk_id]; + } elseif ($res_class == 'Omeka\Entity\SitePage') { + $teamsRepo = 'Teams\Entity\TeamSite'; + $fk = 'site'; + $fk_id = $resource->getSite()->getId(); + $criteria = ['team' => $team->getId(), $fk => $fk_id]; + } elseif ($res_class == 'Omeka\Entity\ResourceTemplate') { + $teamsRepo = 'Teams\Entity\TeamResourceTemplate'; + $fk = 'resource_template'; + $criteria = ['team' => $team->getId(), $fk => $fk_id]; + } elseif ($res_class == 'Teams\Entity\Team') { + $teamsRepo = 'Teams\Entity\TeamUser'; + $fk = 'user'; + $criteria = ['team' => $team->getId(), $fk => $user->getId()]; + } elseif ($res_class == 'Teams\Entity\TeamSite') { + $teamsRepo = 'Teams\Entity\TeamUser'; + $fk = 'user'; + $criteria = ['team' => $resource->getTeam()->getId(), $fk => $user->getId()]; + } elseif ($res_class == 'Teams\Entity\TeamResource') { + $teamsRepo = 'Teams\Entity\TeamResource'; + $fk = 'resource'; + $fk_id = $resource->getResource()->getId(); + $criteria = ['team' => $team->getId(), $fk => $fk_id]; + } else { + /* + * TeamRole is only accessible by global admin so doesn't need to be checked. + * Other classes should be controlled by their respective modules and components + * and should not be using this assertion. + */ + return false; + } + $team_resource = $this->entityManager->getRepository($teamsRepo) + ->findOneBy($criteria); + + return (bool)$team_resource; + } + + /** + * Returns the class name of a resource, handling both entity instances and ACL resources. + * + * When the resource implements ResourceInterface (e.g., GenericResource), uses getResourceId() + * to get the resource identifier (typically a class name). + * For entity instances, returns the fully qualified class name. + * + * @param mixed $resource + * @return string The class name or resource identifier + */ + private function getResourceClass($resource): string + { + // If it's an ACL resource (like GenericResource), use its resource ID + if ($resource instanceof ResourceInterface) { + return $resource->getResourceId(); + } + + // Otherwise, get the class name directly + return get_class($resource); + } +} diff --git a/src/Service/Acl/TeamRolePermissionAssertionFactory.php b/src/Service/Acl/TeamRolePermissionAssertionFactory.php new file mode 100644 index 0000000..546c684 --- /dev/null +++ b/src/Service/Acl/TeamRolePermissionAssertionFactory.php @@ -0,0 +1,18 @@ +get('Omeka\AuthenticationService'), + $services->get('Omeka\EntityManager'), + $services->get('Omeka\Status') + ); + } +}