From be9e88aa2d7cbddb25ece609283a698baf7322a5 Mon Sep 17 00:00:00 2001 From: Yuri Kuznetsov Date: Fri, 31 Oct 2025 13:19:55 +0200 Subject: [PATCH] foreign access filters support collaborators and multiple assigned users --- .../AccessControl/Filters/ForeignOnlyOwn.php | 85 +++++++- .../AccessControl/Filters/ForeignOnlyTeam.php | 183 +++++++++++++----- 2 files changed, 213 insertions(+), 55 deletions(-) diff --git a/application/Espo/Core/Select/AccessControl/Filters/ForeignOnlyOwn.php b/application/Espo/Core/Select/AccessControl/Filters/ForeignOnlyOwn.php index e6fb10404f..7cb008c0fe 100644 --- a/application/Espo/Core/Select/AccessControl/Filters/ForeignOnlyOwn.php +++ b/application/Espo/Core/Select/AccessControl/Filters/ForeignOnlyOwn.php @@ -29,10 +29,14 @@ namespace Espo\Core\Select\AccessControl\Filters; +use Espo\Core\Acl\AssignmentChecker\Helper; use Espo\Core\Name\Field; use Espo\Core\Select\AccessControl\Filter; use Espo\ORM\Defs; use Espo\ORM\Name\Attribute; +use Espo\ORM\Query\Part\Condition as Cond; +use Espo\ORM\Query\Part\Expression as Expr; +use Espo\ORM\Query\Part\Where\OrGroup; use Espo\ORM\Query\SelectBuilder; use Espo\Core\Utils\Metadata; use Espo\Entities\User; @@ -45,7 +49,8 @@ class ForeignOnlyOwn implements Filter private string $entityType, private User $user, private Metadata $metadata, - private Defs $defs + private Defs $defs, + private Helper $helper, ) {} public function apply(SelectBuilder $queryBuilder): void @@ -53,7 +58,7 @@ class ForeignOnlyOwn implements Filter $link = $this->metadata->get(['aclDefs', $this->entityType, 'link']); if (!$link) { - throw new LogicException("No `link` in aclDefs for {$this->entityType}."); + throw new LogicException("No `link` in aclDefs for $this->entityType."); } $alias = $link . 'Access'; @@ -67,18 +72,82 @@ class ForeignOnlyOwn implements Filter $foreignEntityDefs = $this->defs->getEntity($foreignEntityType); + $orBuilder = OrGroup::createBuilder(); + + if ($this->helper->hasCollaboratorsField($foreignEntityType)) { + $orBuilder->add( + Cond::equal( + Expr::column("$alias." . Attribute::ID), + SelectBuilder::create() + ->from(User::RELATIONSHIP_ENTITY_COLLABORATOR, 's') + ->select('s.entityId') + ->where( + Cond::and( + Cond::equal( + Expr::column('s.entityType'), + $foreignEntityType, + ), + Cond::equal( + Expr::column('s.userId'), + $this->user->getId(), + ) + ) + ) + ->build() + ) + ); + } + + if ($this->helper->hasAssignedUsersField($foreignEntityType)) { + $orBuilder->add( + Cond::equal( + Expr::column("$alias." . Attribute::ID), + SelectBuilder::create() + ->from(User::RELATIONSHIP_ENTITY_USER, 's') + ->select('s.entityId') + ->where( + Cond::and( + Cond::equal( + Expr::column('s.entityType'), + $foreignEntityType, + ), + Cond::equal( + Expr::column('s.userId'), + $this->user->getId(), + ) + ) + ) + ->build() + ) + ); + + $queryBuilder->where($orBuilder->build()); + + return; + } + if ($foreignEntityDefs->hasField(Field::ASSIGNED_USER)) { - $queryBuilder->where([ - "{$alias}.assignedUserId" => $this->user->getId(), - ]); + $orBuilder->add( + Cond::equal( + Expr::column("$alias.assignedUserId"), + $this->user->getId() + ) + ); + + $queryBuilder->where($orBuilder->build()); return; } if ($foreignEntityDefs->hasField(Field::CREATED_BY)) { - $queryBuilder->where([ - "{$alias}.createdById" => $this->user->getId(), - ]); + $orBuilder->add( + Cond::equal( + Expr::column("$alias.createdById"), + $this->user->getId() + ) + ); + + $queryBuilder->where($orBuilder->build()); return; } diff --git a/application/Espo/Core/Select/AccessControl/Filters/ForeignOnlyTeam.php b/application/Espo/Core/Select/AccessControl/Filters/ForeignOnlyTeam.php index 5f3acbd585..40fb871f3f 100644 --- a/application/Espo/Core/Select/AccessControl/Filters/ForeignOnlyTeam.php +++ b/application/Espo/Core/Select/AccessControl/Filters/ForeignOnlyTeam.php @@ -29,13 +29,15 @@ namespace Espo\Core\Select\AccessControl\Filters; +use Espo\Core\Acl\AssignmentChecker\Helper; use Espo\Core\Name\Field; use Espo\Core\Select\AccessControl\Filter; use Espo\Entities\Team; use Espo\ORM\Name\Attribute; -use Espo\ORM\Query\Part\Condition; -use Espo\ORM\Query\Part\Expression; +use Espo\ORM\Query\Part\Condition as Cond; +use Espo\ORM\Query\Part\Expression as Expr; use Espo\ORM\Query\Part\Where\OrGroup; +use Espo\ORM\Query\Part\Where\OrGroupBuilder; use Espo\ORM\Query\SelectBuilder; use Espo\Core\Utils\Metadata; use Espo\ORM\Defs; @@ -52,7 +54,8 @@ class ForeignOnlyTeam implements Filter private string $entityType, private User $user, private Metadata $metadata, - private Defs $defs + private Defs $defs, + private Helper $helper, ) {} public function apply(SelectBuilder $queryBuilder): void @@ -65,33 +68,149 @@ class ForeignOnlyTeam implements Filter $alias = "{$link}Access"; - $ownerAttribute = $this->getOwnerAttribute($link); + $foreignEntityType = $this->getForeignEntityType($link); - if (!$ownerAttribute) { + $queryBuilder->leftJoin($link, $alias); + + $orBuilder = OrGroup::createBuilder(); + + $this->applyCollaborators($foreignEntityType, $orBuilder, $alias); + $this->applyAssignedUsers($foreignEntityType, $orBuilder, $alias); + $this->applyAssignedUser($foreignEntityType, $orBuilder, $alias); + $this->applyCreatedBy($foreignEntityType, $orBuilder, $alias); + $this->applyTeams($foreignEntityType, $orBuilder, $alias); + + if ($orBuilder->build()->getItemCount() === 0) { $queryBuilder->where([Attribute::ID => null]); return; } - $teamIdList = $this->user->getTeamIdList(); + $queryBuilder + ->where($orBuilder->build()) + ->where(["$alias.id!=" => null]); + } - if (count($teamIdList) === 0) { - $queryBuilder - ->leftJoin($link, $alias) - ->where(["$alias.$ownerAttribute" => $this->user->getId()]); + private function getForeignEntityType(string $link): string + { + return $this->defs + ->getEntity($this->entityType) + ->getRelation($link) + ->getForeignEntityType(); + } + private function applyCollaborators(string $foreignEntityType, OrGroupBuilder $orBuilder, string $alias): void + { + if (!$this->helper->hasCollaboratorsField($foreignEntityType)) { return; } - $foreignEntityType = $this->getForeignEntityType($link); + $orBuilder->add( + Cond::equal( + Expr::column("$alias." . Attribute::ID), + SelectBuilder::create() + ->from(User::RELATIONSHIP_ENTITY_COLLABORATOR, 's') + ->select('s.entityId') + ->where( + Cond::and( + Cond::equal( + Expr::column('s.entityType'), + $foreignEntityType, + ), + Cond::equal( + Expr::column('s.userId'), + $this->user->getId(), + ) + ) + ) + ->build() + ) + ); + } - $orGroup = OrGroup::create( - Condition::equal( - Expression::column("$alias.$ownerAttribute"), + private function applyAssignedUsers( + string $foreignEntityType, + OrGroupBuilder $orBuilder, + string $alias, + ): void { + + if (!$this->helper->hasAssignedUsersField($foreignEntityType)) { + return; + } + + $orBuilder->add( + Cond::equal( + Expr::column("$alias." . Attribute::ID), + SelectBuilder::create() + ->from(User::RELATIONSHIP_ENTITY_USER, 's') + ->select('s.entityId') + ->where( + Cond::and( + Cond::equal( + Expr::column('s.entityType'), + $foreignEntityType, + ), + Cond::equal( + Expr::column('s.userId'), + $this->user->getId(), + ) + ) + ) + ->build() + ) + ); + } + + private function applyAssignedUser(string $foreignEntityType, OrGroupBuilder $orBuilder, string $alias): void + { + $foreignEntityDefs = $this->defs->getEntity($foreignEntityType); + + if ( + $this->helper->hasAssignedUsersField($foreignEntityType) || + !$foreignEntityDefs->hasField(Field::ASSIGNED_USER) + ) { + return; + } + + $orBuilder->add( + Cond::equal( + Expr::column("$alias.assignedUserId"), $this->user->getId() - ), - Condition::in( - Expression::column("$alias.id"), + ) + ); + } + + private function applyCreatedBy(string $foreignEntityType, OrGroupBuilder $orBuilder, string $alias): void + { + $foreignEntityDefs = $this->defs->getEntity($foreignEntityType); + + if ( + $this->helper->hasAssignedUsersField($foreignEntityType) || + $foreignEntityDefs->hasField(Field::ASSIGNED_USER) || + !$foreignEntityDefs->hasField(Field::CREATED_BY) + ) { + return; + } + + $orBuilder->add( + Cond::equal( + Expr::column("$alias.createdById"), + $this->user->getId() + ) + ); + } + + private function applyTeams(string $foreignEntityType, OrGroupBuilder $orBuilder, string $alias): void + { + $teamIdList = $this->user->getTeamIdList(); + + if (count($teamIdList) === 0) { + return; + } + + $orBuilder->add( + Cond::in( + Expr::column("$alias.id"), SelectBuilder::create() ->from(Team::RELATIONSHIP_ENTITY_TEAM) ->select('entityId') @@ -102,35 +221,5 @@ class ForeignOnlyTeam implements Filter ->build() ) ); - - $queryBuilder - ->leftJoin($link, $alias) - ->where($orGroup) - ->where(["$alias.id!=" => null]); - } - - private function getOwnerAttribute(string $link): ?string - { - $foreignEntityType = $this->getForeignEntityType($link); - - $foreignEntityDefs = $this->defs->getEntity($foreignEntityType); - - if ($foreignEntityDefs->hasField(Field::ASSIGNED_USER)) { - return 'assignedUserId'; - } - - if ($foreignEntityDefs->hasField(Field::CREATED_BY)) { - return 'createdById'; - } - - return null; - } - - private function getForeignEntityType(string $link): string - { - return $this->defs - ->getEntity($this->entityType) - ->getRelation($link) - ->getForeignEntityType(); } }