diff --git a/application/Espo/Classes/Acl/Email/AssignmentChecker.php b/application/Espo/Classes/Acl/Email/AssignmentChecker.php index 4d7bfa643a..97894c223d 100644 --- a/application/Espo/Classes/Acl/Email/AssignmentChecker.php +++ b/application/Espo/Classes/Acl/Email/AssignmentChecker.php @@ -29,19 +29,30 @@ namespace Espo\Classes\Acl\Email; +use Espo\Core\Acl\AssignmentChecker as AssignmentCheckerInterface; +use Espo\Entities\Email; use Espo\Entities\User; use Espo\ORM\Entity; -use Espo\Core\Acl\DefaultAssignmentChecker; -class AssignmentChecker extends DefaultAssignmentChecker +/** + * @implements AssignmentCheckerInterface + */ +class AssignmentChecker implements AssignmentCheckerInterface { - protected function isPermittedAssignedUser(User $user, Entity $entity): bool - { - return true; - } + public function __construct( + private AssignmentCheckerInterface\Helper $helper, + ) {} - protected function isPermittedAssignedUsers(User $user, Entity $entity): bool + public function check(User $user, Entity $entity): bool { + if ($entity->getAssignedUser() && !$this->helper->checkAssignedUser($user, $entity)) { + return false; + } + + if ($entity->getTeams()->getIdList() !== [] && !$this->helper->checkTeams($user, $entity)) { + return false; + } + return true; } } diff --git a/application/Espo/Core/Acl/AssignmentChecker/Helper.php b/application/Espo/Core/Acl/AssignmentChecker/Helper.php new file mode 100644 index 0000000000..24f49cb5e4 --- /dev/null +++ b/application/Espo/Core/Acl/AssignmentChecker/Helper.php @@ -0,0 +1,339 @@ +. + * + * The interactive user interfaces in modified source and object code versions + * of this program must display Appropriate Legal Notices, as required under + * Section 5 of the GNU Affero General Public License version 3. + * + * In accordance with Section 7(b) of the GNU Affero General Public License version 3, + * these Appropriate Legal Notices must retain the display of the "EspoCRM" word. + ************************************************************************/ + +namespace Espo\Core\Acl\AssignmentChecker; + +use Espo\Core\Acl\Permission; +use Espo\Core\Acl\Table; +use Espo\Core\AclManager; +use Espo\Core\Name\Field; +use Espo\Core\ORM\Entity as CoreEntity; +use Espo\Core\ORM\Type\FieldType; +use Espo\Core\Utils\Metadata; +use Espo\Entities\User; +use Espo\ORM\Defs; +use Espo\ORM\Entity; +use Espo\ORM\EntityManager; +use Espo\ORM\Name\Attribute; +use Espo\Repositories\User as UserRepository; + +class Helper +{ + private const ATTR_ASSIGNED_USER_ID = Field::ASSIGNED_USER . 'Id'; + private const FIELD_TEAMS = Field::TEAMS; + private const FIELD_ASSIGNED_USERS = Field::ASSIGNED_USERS; + private const FIELD_COLLABORATORS = Field::COLLABORATORS; + + public function __construct( + private EntityManager $entityManager, + private AclManager $aclManager, + private Defs $ormDefs, + private Metadata $metadata, + ) {} + + public function checkAssignedUser(User $user, Entity $entity): bool + { + if (!$entity->hasAttribute(self::ATTR_ASSIGNED_USER_ID)) { + return true; + } + + if ($user->isPortal()) { + return !$entity->isAttributeChanged(self::ATTR_ASSIGNED_USER_ID); + } + + $assignmentPermission = $this->aclManager->getPermissionLevel($user, Permission::ASSIGNMENT); + + if ($assignmentPermission === Table::LEVEL_ALL) { + return true; + } + + $toProcess = false; + + if (!$entity->isNew()) { + if ($entity->isAttributeChanged(self::ATTR_ASSIGNED_USER_ID)) { + $toProcess = true; + } + } else { + $toProcess = true; + } + + if (!$toProcess) { + return true; + } + + $assignedUserId = $entity->get(self::ATTR_ASSIGNED_USER_ID); + + if (!$assignedUserId) { + if ($assignmentPermission === Table::LEVEL_NO && !$user->isApi()) { + return false; + } + + return true; + } + + if ($assignmentPermission === Table::LEVEL_NO) { + return $user->getId() === $assignedUserId; + } + + if ($assignmentPermission === Table::LEVEL_TEAM) { + $teamIdList = $user->getTeamIdList(); + + return $this->getUserRepository()->checkBelongsToAnyOfTeams($assignedUserId, $teamIdList); + } + + return false; + } + + public function checkTeams(User $user, Entity $entity): bool + { + $assignmentPermission = $this->aclManager->getPermissionLevel($user, Permission::ASSIGNMENT); + + if (!in_array($assignmentPermission, [Table::LEVEL_TEAM, Table::LEVEL_NO])) { + return true; + } + + if (!$entity instanceof CoreEntity) { + return true; + } + + if (!$entity->hasLinkMultipleField(self::FIELD_TEAMS)) { + return true; + } + + $teamIdList = $entity->getLinkMultipleIdList(self::FIELD_TEAMS); + + if ($teamIdList === []) { + return $this->isPermittedTeamsEmpty($user, $entity); + } + + $newIdList = []; + + if (!$entity->isNew()) { + $existingIdList = []; + + $teamCollection = $this->entityManager + ->getRelation($entity, self::FIELD_TEAMS) + ->select(Attribute::ID) + ->find(); + + foreach ($teamCollection as $team) { + $existingIdList[] = $team->getId(); + } + + foreach ($teamIdList as $id) { + if (!in_array($id, $existingIdList)) { + $newIdList[] = $id; + } + } + } else { + $newIdList = $teamIdList; + } + + if ($newIdList === []) { + return true; + } + + $userTeamIdList = $user->getLinkMultipleIdList(self::FIELD_TEAMS); + + foreach ($newIdList as $id) { + if (!in_array($id, $userTeamIdList)) { + return false; + } + } + + return true; + } + + public function checkUsers(User $user, Entity $entity, string $field): bool + { + if (!$entity instanceof CoreEntity) { + return true; + } + + $idsAttr = $field . 'Ids'; + + if (!$entity->hasLinkMultipleField($field)) { + return true; + } + + if ($user->isPortal()) { + if (!$entity->isAttributeChanged($idsAttr)) { + return true; + } + + return false; + } + + $assignmentPermission = $this->aclManager->getPermissionLevel($user, Permission::ASSIGNMENT); + + if ($assignmentPermission === Table::LEVEL_ALL) { + if (!$this->hasOnlyInternalUsers($user, $entity, $field)) { + return false; + } + + return true; + } + + $toProcess = $entity->isNew() || $entity->isAttributeChanged($idsAttr); + + if (!$toProcess) { + return true; + } + + $userIds = $entity->getLinkMultipleIdList($field); + + if ($userIds === []) { + if ($assignmentPermission === Table::LEVEL_NO && !$user->isApi()) { + return false; + } + + return true; + } + + if ($assignmentPermission === Table::LEVEL_NO) { + return $this->isPermittedUsersLevelNo($user, $entity, $field); + } + + if ($assignmentPermission === Table::LEVEL_TEAM) { + return $this->isPermittedUsersLevelTeam($user, $entity, $field); + } + + return false; + } + + private function isPermittedUsersLevelNo(User $user, CoreEntity $entity, string $field): bool + { + $userIds = $this->getAddedLinkMultipleIds($entity, $field); + + foreach ($userIds as $userId) { + if ($user->getId() !== $userId) { + return false; + } + } + + return true; + } + + private function isPermittedUsersLevelTeam(User $user, CoreEntity $entity, string $field): bool + { + $teamIds = $user->getLinkMultipleIdList(self::FIELD_TEAMS); + $userIds = $this->getAddedLinkMultipleIds($entity, $field); + + foreach ($userIds as $userId) { + if (!$this->getUserRepository()->checkBelongsToAnyOfTeams($userId, $teamIds)) { + return false; + } + } + + return true; + } + + private function hasOnlyInternalUsers(User $user, CoreEntity $entity, string $field): bool + { + $ids = array_diff($this->getAddedLinkMultipleIds($entity, $field), [$user->getId()]); + $ids = array_values($ids); + + $count = $this->entityManager + ->getRDBRepositoryByClass(User::class) + ->where([ + 'type!=' => [ + User::TYPE_REGULAR, + User::TYPE_ADMIN, + ], + Attribute::ID => $ids, + ]) + ->count(); + + return $count === 0; + } + + /** + * @return string[] + */ + private function getAddedLinkMultipleIds(CoreEntity $entity, string $field): array + { + /** @var string[] $previousIds */ + $previousIds = $entity->getFetched(self::FIELD_COLLABORATORS . 'Ids') ?? []; + + return array_values(array_diff($entity->getLinkMultipleIdList($field), $previousIds)); + } + + private function isPermittedTeamsEmpty(User $user, CoreEntity $entity): bool + { + $assignmentPermission = $this->aclManager->getPermissionLevel($user, Permission::ASSIGNMENT); + + if ($assignmentPermission !== Table::LEVEL_TEAM) { + return true; + } + + if ($entity->hasLinkMultipleField(self::FIELD_ASSIGNED_USERS)) { + $assignedUserIdList = $entity->getLinkMultipleIdList(self::FIELD_ASSIGNED_USERS); + + if ($assignedUserIdList === []) { + return false; + } + } else if ($entity->hasAttribute(self::ATTR_ASSIGNED_USER_ID)) { + if (!$entity->get(self::ATTR_ASSIGNED_USER_ID)) { + return false; + } + } + + return true; + } + + public function hasAssignedUsersField(string $entityType): bool + { + $entityDefs = $this->ormDefs->getEntity($entityType); + + return + $entityDefs->hasField(self::FIELD_ASSIGNED_USERS) && + $entityDefs->getField(self::FIELD_ASSIGNED_USERS)->getType() === FieldType::LINK_MULTIPLE && + $entityDefs->hasRelation(self::FIELD_ASSIGNED_USERS) && + $entityDefs->getRelation(self::FIELD_ASSIGNED_USERS)->getForeignEntityType() === User::ENTITY_TYPE; + } + + public function hasCollaboratorsField(string $entityType): bool + { + if (!$this->metadata->get("scopes.$entityType.collaborators")) { + return false; + } + + $entityDefs = $this->ormDefs->getEntity($entityType); + + return + $entityDefs->tryGetField(self::FIELD_COLLABORATORS)?->getType() === FieldType::LINK_MULTIPLE && + $entityDefs->tryGetRelation(self::FIELD_COLLABORATORS)?->tryGetForeignEntityType() === User::ENTITY_TYPE; + } + + private function getUserRepository(): UserRepository + { + /** @var UserRepository */ + return $this->entityManager->getRepository(User::ENTITY_TYPE); + } +} diff --git a/application/Espo/Core/Acl/DefaultAssignmentChecker.php b/application/Espo/Core/Acl/DefaultAssignmentChecker.php index d7bf408a18..905616cd51 100644 --- a/application/Espo/Core/Acl/DefaultAssignmentChecker.php +++ b/application/Espo/Core/Acl/DefaultAssignmentChecker.php @@ -29,17 +29,11 @@ namespace Espo\Core\Acl; +use Espo\Core\Acl\AssignmentChecker\Helper; use Espo\Core\Name\Field; use Espo\Core\ORM\Entity as CoreEntity; -use Espo\Core\ORM\Type\FieldType; -use Espo\Core\Utils\Metadata; -use Espo\ORM\Name\Attribute; -use Espo\Repositories\User as UserRepository; -use Espo\ORM\Defs; use Espo\ORM\Entity; -use Espo\ORM\EntityManager; use Espo\Entities\User; -use Espo\Core\AclManager; /** * @implements AssignmentChecker @@ -47,15 +41,10 @@ use Espo\Core\AclManager; class DefaultAssignmentChecker implements AssignmentChecker { protected const FIELD_ASSIGNED_USERS = Field::ASSIGNED_USERS; - protected const FIELD_TEAMS = Field::TEAMS; - protected const ATTR_ASSIGNED_USER_ID = 'assignedUserId'; private const FIELD_COLLABORATORS = Field::COLLABORATORS; public function __construct( - private AclManager $aclManager, - private EntityManager $entityManager, - private Defs $ormDefs, - private Metadata $metadata, + private Helper $helper, ) {} public function check(User $user, Entity $entity): bool @@ -68,14 +57,14 @@ class DefaultAssignmentChecker implements AssignmentChecker return false; } - if ($this->hasAssignedUsersField($entity->getEntityType())) { + if ($this->helper->hasAssignedUsersField($entity->getEntityType())) { if (!$this->isPermittedAssignedUsers($user, $entity)) { return false; } } - if ($this->hasCollaboratorsField($entity->getEntityType())) { - if (!$this->isPermittedUsers($user, $entity, self::FIELD_COLLABORATORS)) { + if ($this->helper->hasCollaboratorsField($entity->getEntityType())) { + if (!$this->helper->checkUsers($user, $entity, self::FIELD_COLLABORATORS)) { return false; } } @@ -83,182 +72,14 @@ class DefaultAssignmentChecker implements AssignmentChecker return true; } - private function hasCollaboratorsField(string $entityType): bool - { - if (!$this->metadata->get("scopes.$entityType.collaborators")) { - return false; - } - - $entityDefs = $this->ormDefs->getEntity($entityType); - - return - $entityDefs->tryGetField(self::FIELD_COLLABORATORS)?->getType() === FieldType::LINK_MULTIPLE && - $entityDefs->tryGetRelation(self::FIELD_COLLABORATORS)?->tryGetForeignEntityType() === User::ENTITY_TYPE; - } - - private function hasAssignedUsersField(string $entityType): bool - { - $entityDefs = $this->ormDefs->getEntity($entityType); - - return - $entityDefs->hasField(self::FIELD_ASSIGNED_USERS) && - $entityDefs->getField(self::FIELD_ASSIGNED_USERS)->getType() === FieldType::LINK_MULTIPLE && - $entityDefs->hasRelation(self::FIELD_ASSIGNED_USERS) && - $entityDefs->getRelation(self::FIELD_ASSIGNED_USERS)->getForeignEntityType() === User::ENTITY_TYPE; - } - protected function isPermittedAssignedUser(User $user, Entity $entity): bool { - if (!$entity->hasAttribute(self::ATTR_ASSIGNED_USER_ID)) { - return true; - } - - $assignedUserId = $entity->get(self::ATTR_ASSIGNED_USER_ID); - - if ($user->isPortal()) { - if (!$entity->isAttributeChanged(self::ATTR_ASSIGNED_USER_ID)) { - return true; - } - - return false; - } - - $assignmentPermission = $this->aclManager->getPermissionLevel($user, Permission::ASSIGNMENT); - - if ( - $assignmentPermission === Table::LEVEL_YES || - !in_array($assignmentPermission, [Table::LEVEL_TEAM, Table::LEVEL_NO]) - ) { - return true; - } - - $toProcess = false; - - if (!$entity->isNew()) { - if ($entity->isAttributeChanged(self::ATTR_ASSIGNED_USER_ID)) { - $toProcess = true; - } - } else { - $toProcess = true; - } - - if (!$toProcess) { - return true; - } - - if (empty($assignedUserId)) { - if ($assignmentPermission === Table::LEVEL_NO && !$user->isApi()) { - return false; - } - - return true; - } - - if ($assignmentPermission === Table::LEVEL_NO) { - if ($user->getId() !== $assignedUserId) { - return false; - } - } else if ($assignmentPermission === Table::LEVEL_TEAM) { - $teamIdList = $user->getTeamIdList(); - - if ( - !$this->getUserRepository()->checkBelongsToAnyOfTeams($assignedUserId, $teamIdList) - ) { - return false; - } - } - - return true; - } - - private function getUserRepository(): UserRepository - { - /** @var UserRepository */ - return $this->entityManager->getRepository('User'); + return $this->helper->checkAssignedUser($user, $entity); } protected function isPermittedTeams(User $user, Entity $entity): bool { - $assignmentPermission = $this->aclManager->getPermissionLevel($user, Permission::ASSIGNMENT); - - if (!in_array($assignmentPermission, [Table::LEVEL_TEAM, Table::LEVEL_NO])) { - return true; - } - - if (!$entity instanceof CoreEntity) { - return true; - } - - if (!$entity->hasLinkMultipleField(self::FIELD_TEAMS)) { - return true; - } - - $teamIdList = $entity->getLinkMultipleIdList(self::FIELD_TEAMS); - - if (empty($teamIdList)) { - return $this->isPermittedTeamsEmpty($user, $entity); - } - - $newIdList = []; - - if (!$entity->isNew()) { - $existingIdList = []; - - $teamCollection = $this->entityManager - ->getRDBRepository($entity->getEntityType()) - ->getRelation($entity, self::FIELD_TEAMS) - ->select(Attribute::ID) - ->find(); - - foreach ($teamCollection as $team) { - $existingIdList[] = $team->getId(); - } - - foreach ($teamIdList as $id) { - if (!in_array($id, $existingIdList)) { - $newIdList[] = $id; - } - } - } else { - $newIdList = $teamIdList; - } - - if (empty($newIdList)) { - return true; - } - - $userTeamIdList = $user->getLinkMultipleIdList(self::FIELD_TEAMS); - - foreach ($newIdList as $id) { - if (!in_array($id, $userTeamIdList)) { - return false; - } - } - - return true; - } - - private function isPermittedTeamsEmpty(User $user, CoreEntity $entity): bool - { - $assignmentPermission = $this->aclManager->getPermissionLevel($user, Permission::ASSIGNMENT); - - if ($assignmentPermission !== Table::LEVEL_TEAM) { - return true; - } - - if ($entity->hasLinkMultipleField(self::FIELD_ASSIGNED_USERS)) { - $assignedUserIdList = $entity->getLinkMultipleIdList(self::FIELD_ASSIGNED_USERS); - - if (empty($assignedUserIdList)) { - return false; - } - } else if ($entity->hasAttribute(self::ATTR_ASSIGNED_USER_ID)) { - if (!$entity->get(self::ATTR_ASSIGNED_USER_ID)) { - return false; - } - } - - return true; + return $this->helper->checkTeams($user, $entity); } /** @@ -266,120 +87,6 @@ class DefaultAssignmentChecker implements AssignmentChecker */ protected function isPermittedAssignedUsers(User $user, Entity $entity): bool { - return $this->isPermittedUsers($user, $entity, self::FIELD_ASSIGNED_USERS); - } - - private function isPermittedUsers(User $user, Entity $entity, string $field): bool - { - if (!$entity instanceof CoreEntity) { - return true; - } - - $idsAttr = $field . 'Ids'; - - if (!$entity->hasLinkMultipleField($field)) { - return true; - } - - if ($user->isPortal()) { - if (!$entity->isAttributeChanged($idsAttr)) { - return true; - } - - return false; - } - - $assignmentPermission = $this->aclManager->getPermissionLevel($user, Permission::ASSIGNMENT); - - if ($assignmentPermission === Table::LEVEL_ALL) { - if (!$this->hasOnlyInternalUsers($user, $entity, $field)) { - return false; - } - - return true; - } - - $toProcess = $entity->isNew() || $entity->isAttributeChanged($idsAttr); - - if (!$toProcess) { - return true; - } - - $userIds = $entity->getLinkMultipleIdList($field); - - if ($userIds === []) { - if ($assignmentPermission === Table::LEVEL_NO && !$user->isApi()) { - return false; - } - - return true; - } - - if ($assignmentPermission === Table::LEVEL_NO) { - return $this->isPermittedUsersLevelNo($user, $entity, $field); - } - - if ($assignmentPermission === Table::LEVEL_TEAM) { - return $this->isPermittedUsersLevelTeam($user, $entity, $field); - } - - return false; - } - - private function isPermittedUsersLevelNo(User $user, CoreEntity $entity, string $field): bool - { - $userIds = $this->getAddedLinkMultipleIds($entity, $field); - - foreach ($userIds as $userId) { - if ($user->getId() !== $userId) { - return false; - } - } - - return true; - } - - private function isPermittedUsersLevelTeam(User $user, CoreEntity $entity, string $field): bool - { - $teamIds = $user->getLinkMultipleIdList(self::FIELD_TEAMS); - $userIds = $this->getAddedLinkMultipleIds($entity, $field); - - foreach ($userIds as $userId) { - if (!$this->getUserRepository()->checkBelongsToAnyOfTeams($userId, $teamIds)) { - return false; - } - } - - return true; - } - - private function hasOnlyInternalUsers(User $user, CoreEntity $entity, string $field): bool - { - $ids = array_diff($this->getAddedLinkMultipleIds($entity, $field), [$user->getId()]); - $ids = array_values($ids); - - $count = $this->entityManager - ->getRDBRepositoryByClass(User::class) - ->where([ - 'type!=' => [ - User::TYPE_REGULAR, - User::TYPE_ADMIN, - ], - Attribute::ID => $ids, - ]) - ->count(); - - return $count === 0; - } - - /** - * @return string[] - */ - private function getAddedLinkMultipleIds(CoreEntity $entity, string $field): array - { - /** @var string[] $previousIds */ - $previousIds = $entity->getFetched(self::FIELD_COLLABORATORS . 'Ids') ?? []; - - return array_values(array_diff($entity->getLinkMultipleIdList($field), $previousIds)); + return $this->helper->checkUsers($user, $entity, self::FIELD_ASSIGNED_USERS); } }