foreign access filters support collaborators and multiple assigned users

This commit is contained in:
Yuri Kuznetsov
2025-10-31 13:19:55 +02:00
parent 15ff1dc6eb
commit be9e88aa2d
2 changed files with 213 additions and 55 deletions
@@ -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;
}
@@ -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();
}
}