grand acl refactoring
This commit is contained in:
@@ -37,10 +37,9 @@ use Espo\Core\{
|
||||
Acl\Acl,
|
||||
Acl\ScopeData,
|
||||
Acl\Table,
|
||||
Acl\EntityReadAcl,
|
||||
};
|
||||
|
||||
class Email extends Acl implements EntityReadAcl
|
||||
class Email extends Acl
|
||||
{
|
||||
public function checkEntityRead(EntityUser $user, Entity $entity, ScopeData $data): bool
|
||||
{
|
||||
|
||||
@@ -37,6 +37,9 @@ use Espo\Core\{
|
||||
Acl\Table,
|
||||
};
|
||||
|
||||
/**
|
||||
* @todo Move to another class.
|
||||
*/
|
||||
class EmailAddress extends Acl
|
||||
{
|
||||
/**
|
||||
@@ -53,7 +56,7 @@ class EmailAddress extends Acl
|
||||
$entityWithSameAddressList = $repository->getEntityListByAddressId($entity->getId(), $excludeEntity);
|
||||
|
||||
foreach ($entityWithSameAddressList as $e) {
|
||||
if ($this->getAclManager()->check($user, $e, Table::ACTION_EDIT)) {
|
||||
if ($this->aclManager->check($user, $e, Table::ACTION_EDIT)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -36,10 +36,9 @@ use Espo\ORM\Entity;
|
||||
use Espo\Core\{
|
||||
Acl\Acl,
|
||||
Acl\ScopeData,
|
||||
Acl\EntityReadAcl,
|
||||
};
|
||||
|
||||
class Import extends Acl implements EntityReadAcl
|
||||
class Import extends Acl
|
||||
{
|
||||
public function checkEntityRead(EntityUser $user, Entity $entity, ScopeData $data): bool
|
||||
{
|
||||
|
||||
@@ -37,13 +37,12 @@ use Espo\Core\{
|
||||
Acl\Acl,
|
||||
Acl\ScopeData,
|
||||
Acl\Table,
|
||||
Acl\EntityEditAcl,
|
||||
};
|
||||
|
||||
use Exception;
|
||||
use DateTime;
|
||||
|
||||
class Note extends Acl implements EntityEditAcl
|
||||
class Note extends Acl
|
||||
{
|
||||
protected $deleteThresholdPeriod = '1 month';
|
||||
|
||||
@@ -66,7 +65,7 @@ class Note extends Acl implements EntityEditAcl
|
||||
|
||||
$parent = $this->entityManager->getEntity($entity->get('parentType'), $entity->get('parentId'));
|
||||
|
||||
if ($parent && $this->getAclManager()->checkEntity($user, $parent, 'stream')) {
|
||||
if ($parent && $this->aclManager->checkEntity($user, $parent, 'stream')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -133,7 +132,7 @@ class Note extends Acl implements EntityEditAcl
|
||||
}
|
||||
|
||||
$deleteThresholdPeriod =
|
||||
'-' . $this->getConfig()->get('noteDeleteThresholdPeriod', $this->deleteThresholdPeriod);
|
||||
'-' . $this->config->get('noteDeleteThresholdPeriod', $this->deleteThresholdPeriod);
|
||||
|
||||
$dt = new DateTime();
|
||||
|
||||
|
||||
@@ -38,6 +38,9 @@ use Espo\Core\{
|
||||
Acl\Table,
|
||||
};
|
||||
|
||||
/**
|
||||
* @todo Move to another class.
|
||||
*/
|
||||
class PhoneNumber extends Acl
|
||||
{
|
||||
/**
|
||||
@@ -54,7 +57,7 @@ class PhoneNumber extends Acl
|
||||
$entityWithSameNumberList = $repository->getEntityListByPhoneNumberId($entity->getId(), $excludeEntity);
|
||||
|
||||
foreach ($entityWithSameNumberList as $e) {
|
||||
if ($this->getAclManager()->check($user, $e, Table::ACTION_EDIT)) {
|
||||
if ($this->aclManager->check($user, $e, Table::ACTION_EDIT)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -37,12 +37,9 @@ use Espo\Core\{
|
||||
Acl\Acl,
|
||||
Acl\ScopeData,
|
||||
Acl\Table,
|
||||
Acl\EntityCreateAcl,
|
||||
Acl\EntityReadAcl,
|
||||
Acl\EntityEditAcl,
|
||||
};
|
||||
|
||||
class ScheduledJob extends Acl implements EntityCreateAcl, EntityReadAcl, EntityEditAcl
|
||||
class ScheduledJob extends Acl
|
||||
{
|
||||
public function checkEntityRead(EntityUser $user, Entity $entity, ScopeData $data): bool
|
||||
{
|
||||
|
||||
@@ -37,12 +37,9 @@ use Espo\Core\{
|
||||
Acl\Acl,
|
||||
Acl\ScopeData,
|
||||
Acl\Table,
|
||||
Acl\EntityCreateAcl,
|
||||
Acl\EntityReadAcl,
|
||||
Acl\EntityEditAcl,
|
||||
};
|
||||
|
||||
class User extends Acl implements EntityCreateAcl, EntityReadAcl, EntityEditAcl
|
||||
class User extends Acl
|
||||
{
|
||||
public function checkIsOwner(EntityUser $user, Entity $entity)
|
||||
{
|
||||
@@ -52,7 +49,7 @@ class User extends Acl implements EntityCreateAcl, EntityReadAcl, EntityEditAcl
|
||||
public function checkEntityRead(EntityUser $user, Entity $entity, ScopeData $data): bool
|
||||
{
|
||||
if (!$user->isAdmin() && $entity->isPortal()) {
|
||||
if ($this->getAclManager()->get($user, 'portal') === Table::LEVEL_YES) {
|
||||
if ($this->aclManager->get($user, 'portal') === Table::LEVEL_YES) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,12 +36,9 @@ use Espo\ORM\Entity;
|
||||
use Espo\Core\{
|
||||
Acl\Acl,
|
||||
Acl\ScopeData,
|
||||
Acl\EntityCreateAcl,
|
||||
Acl\EntityReadAcl,
|
||||
Acl\EntityEditAcl,
|
||||
};
|
||||
|
||||
class Webhook extends Acl implements EntityCreateAcl, EntityReadAcl, EntityEditAcl
|
||||
class Webhook extends Acl
|
||||
{
|
||||
public function checkIsOwner(EntityUser $user, Entity $entity)
|
||||
{
|
||||
|
||||
@@ -37,11 +37,10 @@ use Espo\ORM\Entity;
|
||||
use Espo\Core\{
|
||||
Acl\ScopeData,
|
||||
Acl\Table,
|
||||
Acl\EntityReadAcl,
|
||||
AclPortal\Acl as Acl,
|
||||
};
|
||||
|
||||
class Attachment extends Acl implements EntityReadAcl
|
||||
class Attachment extends Acl
|
||||
{
|
||||
public function checkEntityRead(EntityUser $user, Entity $entity, ScopeData $data): bool
|
||||
{
|
||||
|
||||
@@ -36,11 +36,10 @@ use Espo\ORM\Entity;
|
||||
use Espo\Core\{
|
||||
Acl\ScopeData,
|
||||
Acl\Table,
|
||||
Acl\EntityReadAcl,
|
||||
AclPortal\Acl as Acl,
|
||||
};
|
||||
|
||||
class Email extends Acl implements EntityReadAcl
|
||||
class Email extends Acl
|
||||
{
|
||||
public function checkEntityRead(EntityUser $user, Entity $entity, ScopeData $data): bool
|
||||
{
|
||||
|
||||
@@ -37,6 +37,9 @@ use Espo\Core\{
|
||||
AclPortal\Acl as Acl,
|
||||
};
|
||||
|
||||
/**
|
||||
* @todo Move to another place.
|
||||
*/
|
||||
class EmailAddress extends Acl
|
||||
{
|
||||
public function checkEditInEntity(EntityUser $user, Entity $entity, Entity $excludeEntity) : bool
|
||||
@@ -45,13 +48,13 @@ class EmailAddress extends Acl
|
||||
|
||||
$isFobidden = false;
|
||||
|
||||
$repository = $this->getEntityManager()->getRepository('EmailAddress');
|
||||
$repository = $this->entityManager->getRepository('EmailAddress');
|
||||
|
||||
if (!$user->isAdmin()) {
|
||||
$entityWithSameAddressList = $repository->getEntityListByAddressId($id, $excludeEntity);
|
||||
|
||||
foreach ($entityWithSameAddressList as $e) {
|
||||
if (!$this->getAclManager()->check($user, $e, Table::ACTION_EDIT)) {
|
||||
if (!$this->aclManager->check($user, $e, Table::ACTION_EDIT)) {
|
||||
$isFobidden = true;
|
||||
|
||||
break;
|
||||
|
||||
@@ -35,11 +35,10 @@ use Espo\ORM\Entity;
|
||||
use Espo\Core\{
|
||||
Acl\ScopeData,
|
||||
Acl\Table,
|
||||
Acl\EntityCreateAcl,
|
||||
AclPortal\Acl as Acl,
|
||||
};
|
||||
|
||||
class Note extends Acl implements EntityCreateAcl
|
||||
class Note extends Acl
|
||||
{
|
||||
public function checkIsOwner(EntityUser $user, Entity $entity)
|
||||
{
|
||||
@@ -64,10 +63,10 @@ class Note extends Acl implements EntityCreateAcl
|
||||
return false;
|
||||
}
|
||||
|
||||
$parent = $this->getEntityManager()->getEntity($entity->get('parentType'), $entity->get('parentId'));
|
||||
$parent = $this->entityManager->getEntity($entity->get('parentType'), $entity->get('parentId'));
|
||||
|
||||
if ($parent) {
|
||||
if ($this->getAclManager()->checkEntity($user, $parent, Table::ACTION_STREAM)) {
|
||||
if ($this->aclManager->checkEntity($user, $parent, Table::ACTION_STREAM)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,6 +37,9 @@ use Espo\Core\{
|
||||
AclPortal\Acl as Acl,
|
||||
};
|
||||
|
||||
/**
|
||||
* @todo Move to another place.
|
||||
*/
|
||||
class PhoneNumber extends Acl
|
||||
{
|
||||
public function checkEditInEntity(EntityUser $user, Entity $entity, Entity $excludeEntity) : bool
|
||||
@@ -45,13 +48,13 @@ class PhoneNumber extends Acl
|
||||
|
||||
$isFobidden = false;
|
||||
|
||||
$repository = $this->getEntityManager()->getRepository('PhoneNumber');
|
||||
$repository = $this->entityManager->getRepository('PhoneNumber');
|
||||
|
||||
if (!$user->isAdmin()) {
|
||||
$entityWithSameNumberList = $repository->getEntityListByPhoneNumberId($id, $excludeEntity);
|
||||
|
||||
foreach ($entityWithSameNumberList as $e) {
|
||||
if (!$this->getAclManager()->check($user, $e, Table::ACTION_EDIT)) {
|
||||
if (!$this->aclManager->check($user, $e, Table::ACTION_EDIT)) {
|
||||
$isFobidden = true;
|
||||
|
||||
break;
|
||||
|
||||
+28
-23
@@ -27,30 +27,44 @@
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Acl;
|
||||
namespace Espo\Classes\Acl\Attachment;
|
||||
|
||||
use Espo\Entities\{
|
||||
User as EntityUser,
|
||||
User,
|
||||
Note,
|
||||
};
|
||||
|
||||
use Espo\ORM\Entity;
|
||||
|
||||
use Espo\Core\{
|
||||
Acl\Acl,
|
||||
ORM\EntityManager,
|
||||
AclManager,
|
||||
Acl\ScopeData,
|
||||
Acl\Table,
|
||||
Acl\EntityReadAcl,
|
||||
Acl\DefaultAccessChecker,
|
||||
Acl\AccessEntityCREDChecker,
|
||||
Acl\Traits\DefaultAccessCheckerDependency,
|
||||
};
|
||||
|
||||
class Attachment extends Acl implements EntityReadAcl
|
||||
class AccessChecker implements AccessEntityCREDChecker
|
||||
{
|
||||
public function checkEntityRead(EntityUser $user, Entity $entity, ScopeData $data): bool
|
||||
{
|
||||
if ($user->isAdmin()) {
|
||||
return true;
|
||||
}
|
||||
use DefaultAccessCheckerDependency;
|
||||
|
||||
private $aclManager;
|
||||
|
||||
private $entityManager;
|
||||
|
||||
public function __construct(
|
||||
DefaultAccessChecker $defaultAccessChecker,
|
||||
AclManager $aclManager,
|
||||
EntityManager $entityManager
|
||||
) {
|
||||
$this->defaultAccessChecker = $defaultAccessChecker;
|
||||
$this->aclManager = $aclManager;
|
||||
$this->entityManager = $entityManager;
|
||||
}
|
||||
|
||||
public function checkEntityRead(User $user, Entity $entity, ScopeData $data): bool
|
||||
{
|
||||
if ($entity->get('parentType') === 'Settings') {
|
||||
return true;
|
||||
}
|
||||
@@ -71,7 +85,7 @@ class Attachment extends Acl implements EntityReadAcl
|
||||
}
|
||||
|
||||
if (!$parent || !$hasParent) {
|
||||
if ($this->checkEntity($user, $entity, $data, Table::ACTION_READ)) {
|
||||
if ($this->defaultAccessChecker->checkEntityRead($user, $entity, $data)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -89,14 +103,14 @@ class Attachment extends Acl implements EntityReadAcl
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($this->checkEntity($user, $entity, $data, Table::ACTION_READ)) {
|
||||
if ($this->defaultAccessChecker->checkEntityRead($user, $entity, $data)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected function checkEntityReadNoteParent(EntityUser $user, Note $note): ?bool
|
||||
private function checkEntityReadNoteParent(User $user, Note $note): ?bool
|
||||
{
|
||||
if ($note->getTargetType() === Note::TARGET_TEAMS) {
|
||||
$intersect = array_intersect(
|
||||
@@ -136,13 +150,4 @@ class Attachment extends Acl implements EntityReadAcl
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function checkIsOwner(EntityUser $user, Entity $entity)
|
||||
{
|
||||
if ($user->getId() === $entity->get(self::ATTR_CREATED_BY_ID)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2021 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* EspoCRM is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* EspoCRM is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
|
||||
*
|
||||
* 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 General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Classes\Acl\Attachment;
|
||||
|
||||
use Espo\Entities\User;
|
||||
|
||||
use Espo\ORM\Entity;
|
||||
|
||||
use Espo\Core\{
|
||||
Acl\OwnershipOwnChecker,
|
||||
};
|
||||
|
||||
class OwnershipChecker implements OwnershipOwnChecker
|
||||
{
|
||||
private const ATTR_CREATED_BY_ID = 'createdById';
|
||||
|
||||
public function checkOwn(User $user, Entity $entity): bool
|
||||
{
|
||||
if ($user->getId() === $entity->get(self::ATTR_CREATED_BY_ID)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -31,6 +31,7 @@ namespace Espo\Core;
|
||||
|
||||
use Espo\Core\{
|
||||
Acl\Table,
|
||||
Acl\Exceptions\NotImplemented,
|
||||
};
|
||||
|
||||
use Espo\ORM\Entity;
|
||||
@@ -39,7 +40,7 @@ use Espo\Entities\User;
|
||||
use StdClass;
|
||||
|
||||
/**
|
||||
* A wrapper for AclManager. To check access for a current user.
|
||||
* A wrapper for `AclManager` for a current user. A central access point for access checking.
|
||||
*/
|
||||
class Acl
|
||||
{
|
||||
@@ -53,6 +54,9 @@ class Acl
|
||||
$this->user = $user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a full access data map.
|
||||
*/
|
||||
public function getMap(): StdClass
|
||||
{
|
||||
return $this->aclManager->getMap($this->user);
|
||||
@@ -69,9 +73,9 @@ class Acl
|
||||
/**
|
||||
* Get a permission. E.g. 'assignment' permission.
|
||||
*/
|
||||
public function get(string $permission): ?string
|
||||
public function getPermissionLevel(string $permission): string
|
||||
{
|
||||
return $this->aclManager->get($this->user, $permission);
|
||||
return $this->aclManager->getPermissionLevel($this->user, $permission);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -107,7 +111,10 @@ class Acl
|
||||
}
|
||||
|
||||
/**
|
||||
* Check a scope or entity. If $action is omitted, it will check whether a scope level is set to 'enabled'.
|
||||
* Check a scope or entity. If $action is omitted, it will check
|
||||
* whether a scope level is set to 'enabled'.
|
||||
*
|
||||
* @throws NotImplemented
|
||||
*/
|
||||
public function check($subject, ?string $action = null): bool
|
||||
{
|
||||
@@ -115,7 +122,10 @@ class Acl
|
||||
}
|
||||
|
||||
/**
|
||||
* Check access to scope. If $action is omitted, it will check whether a scope level is set to 'enabled'.
|
||||
* Check access to scope. If $action is omitted, it will check
|
||||
* whether a scope level is set to 'enabled'.
|
||||
*
|
||||
* @throws NotImplemented
|
||||
*/
|
||||
public function checkScope(string $scope, ?string $action = null): bool
|
||||
{
|
||||
@@ -171,32 +181,25 @@ class Acl
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* Check whether a user is an owner of an entity.
|
||||
*/
|
||||
public function checkUser(string $permission, User $entity): bool
|
||||
public function checkOwnershipOwn(Entity $entity): bool
|
||||
{
|
||||
return $this->aclManager->checkUser($this->user, $permission, $entity);
|
||||
return $this->aclManager->checkOwnershipOwn($this->user, $entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether a user is owned of an entity (record). Usually 'assignedUser' field is used for checking.
|
||||
* Check whether an entity belongs to a user team.
|
||||
*/
|
||||
public function checkIsOwner(Entity $entity): bool
|
||||
public function checkOwnershipTeam(Entity $entity): bool
|
||||
{
|
||||
return $this->aclManager->checkIsOwner($this->user, $entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether a user team list overlaps with teams set in an entity.
|
||||
*/
|
||||
public function checkInTeam(Entity $entity): bool
|
||||
{
|
||||
return $this->aclManager->checkInTeam($this->user, $entity);
|
||||
return $this->aclManager->checkOwnershipTeam($this->user, $entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get attributes forbidden for a user.
|
||||
*
|
||||
* @param $thresholdLevel Should not be used. Stands for possible future enhancements. *
|
||||
* @return array<string>
|
||||
*/
|
||||
public function getScopeForbiddenAttributeList(
|
||||
@@ -212,6 +215,7 @@ class Acl
|
||||
/**
|
||||
* Get fields forbidden for a user.
|
||||
*
|
||||
* @param $thresholdLevel Should not be used. Stands for possible future enhancements.
|
||||
* @return array<string>
|
||||
*/
|
||||
public function getScopeForbiddenFieldList(
|
||||
@@ -227,6 +231,7 @@ class Acl
|
||||
/**
|
||||
* Get links forbidden for a user.
|
||||
*
|
||||
* @param $thresholdLevel Should not be used. Stands for possible future enhancements.
|
||||
* @return array<string>
|
||||
*/
|
||||
public function getScopeForbiddenLinkList(
|
||||
@@ -290,4 +295,36 @@ class Acl
|
||||
{
|
||||
return $this->aclManager->getScopeRestrictedLinkList($scope, $type);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use `getPermissionLevel` instead.
|
||||
*/
|
||||
public function get(string $permission): string
|
||||
{
|
||||
return $this->getPermissionLevel($permission);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use `checkOwnershipOwn` instead.
|
||||
*/
|
||||
public function checkIsOwner(Entity $entity): bool
|
||||
{
|
||||
return $this->aclManager->checkIsOwner($this->user, $entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use `checkOwnershipTeam` instead.
|
||||
*/
|
||||
public function checkInTeam(Entity $entity): bool
|
||||
{
|
||||
return $this->aclManager->checkInTeam($this->user, $entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
public function checkUser(string $permission, User $entity): bool
|
||||
{
|
||||
return $this->aclManager->checkUser($this->user, $permission, $entity);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2021 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* EspoCRM is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* EspoCRM is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
|
||||
*
|
||||
* 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 General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Acl;
|
||||
|
||||
use Espo\Entities\User;
|
||||
|
||||
interface AccessChecker
|
||||
{
|
||||
/**
|
||||
* Check access to scope. Should return TRUE if at least one action is allowed.
|
||||
*/
|
||||
public function check(User $user, ScopeData $data): bool;
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2021 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* EspoCRM is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* EspoCRM is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
|
||||
*
|
||||
* 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 General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Acl;
|
||||
|
||||
use Espo\Core\{
|
||||
Utils\ClassFinder,
|
||||
Utils\Metadata,
|
||||
InjectableFactory,
|
||||
Acl\Exceptions\NotImplemented,
|
||||
AclManager,
|
||||
Binding\BindingContainer,
|
||||
Binding\Binder,
|
||||
Binding\BindingData,
|
||||
};
|
||||
|
||||
class AccessCheckerFactory
|
||||
{
|
||||
private $defaultClassName = DefaultAccessChecker::class;
|
||||
|
||||
private $classFinder;
|
||||
|
||||
private $metadata;
|
||||
|
||||
private $injectableFactory;
|
||||
|
||||
public function __construct(
|
||||
ClassFinder $classFinder,
|
||||
Metadata $metadata,
|
||||
InjectableFactory $injectableFactory
|
||||
) {
|
||||
$this->classFinder = $classFinder;
|
||||
$this->metadata = $metadata;
|
||||
$this->injectableFactory = $injectableFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an access checker.
|
||||
*
|
||||
* @throws NotImplemented
|
||||
*/
|
||||
public function create(string $scope, AclManager $aclManager): AccessChecker
|
||||
{
|
||||
$className = $this->getClassName($scope);
|
||||
|
||||
$bindingContainer = $this->createBindingContainer($aclManager);
|
||||
|
||||
return $this->injectableFactory->createWithBinding($className, $bindingContainer);
|
||||
}
|
||||
|
||||
private function getClassName(string $scope): string
|
||||
{
|
||||
$className1 = $this->metadata->get(['aclDefs', $scope, 'accessCheckerClassName']);
|
||||
|
||||
if ($className1) {
|
||||
return $className1;
|
||||
}
|
||||
|
||||
if (!$this->metadata->get(['scopes', $scope])) {
|
||||
throw new NotImplemented();
|
||||
}
|
||||
|
||||
// For backward compatibility.
|
||||
$className2 = $this->classFinder->find('Acl', $scope);
|
||||
|
||||
if ($className2) {
|
||||
return $className2;
|
||||
}
|
||||
|
||||
return $this->defaultClassName;
|
||||
}
|
||||
|
||||
private function createBindingContainer(AclManager $aclManager): BindingContainer
|
||||
{
|
||||
$bindingData = new BindingData();
|
||||
|
||||
$binder = new Binder($bindingData);
|
||||
|
||||
$binder->bindCallback(
|
||||
AclManager::class,
|
||||
function () use ($aclManager): AclManager {
|
||||
return $aclManager;
|
||||
}
|
||||
);
|
||||
|
||||
return new BindingContainer($bindingData);
|
||||
}
|
||||
}
|
||||
+3
-3
@@ -31,10 +31,10 @@ namespace Espo\Core\Acl;
|
||||
|
||||
use Espo\Entities\User;
|
||||
|
||||
interface LevelProvider
|
||||
interface AccessCreateChecker extends AccessChecker
|
||||
{
|
||||
/**
|
||||
* Get an access level for a specific action.
|
||||
* Check 'create' access.
|
||||
*/
|
||||
public function getLevel(User $user, ScopeData $data, string $action) : string;
|
||||
public function checkCreate(User $user, ScopeData $data): bool;
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2021 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* EspoCRM is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* EspoCRM is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
|
||||
*
|
||||
* 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 General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Acl;
|
||||
|
||||
use Espo\Entities\User;
|
||||
|
||||
interface AccessDeleteChecker extends AccessChecker
|
||||
{
|
||||
/**
|
||||
* Check 'delete' access.
|
||||
*/
|
||||
public function checkDelete(User $user, ScopeData $data): bool;
|
||||
}
|
||||
+3
-3
@@ -31,10 +31,10 @@ namespace Espo\Core\Acl;
|
||||
|
||||
use Espo\Entities\User;
|
||||
|
||||
interface ScopeAcl
|
||||
interface AccessEditChecker extends AccessChecker
|
||||
{
|
||||
/**
|
||||
* Check access to a scope.
|
||||
* Check 'edit' access.
|
||||
*/
|
||||
public function checkScope(User $user, ScopeData $data, ?string $action = null): bool;
|
||||
public function checkEdit(User $user, ScopeData $data): bool;
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2021 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* EspoCRM is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* EspoCRM is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
|
||||
*
|
||||
* 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 General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Acl;
|
||||
|
||||
interface AccessEntityCREDChecker extends
|
||||
|
||||
AccessEntityCreateChecker,
|
||||
AccessEntityReadChecker,
|
||||
AccessEntityEditChecker,
|
||||
AccessEntityDeleteChecker
|
||||
{
|
||||
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2021 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* EspoCRM is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* EspoCRM is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
|
||||
*
|
||||
* 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 General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Acl;
|
||||
|
||||
interface AccessEntityCREDSChecker extends
|
||||
|
||||
AccessEntityCreateChecker,
|
||||
AccessEntityReadChecker,
|
||||
AccessEntityEditChecker,
|
||||
AccessEntityDeleteChecker,
|
||||
AccessEntityStreamChecker
|
||||
{
|
||||
|
||||
}
|
||||
+2
-2
@@ -33,10 +33,10 @@ use Espo\ORM\Entity;
|
||||
|
||||
use Espo\Entities\User;
|
||||
|
||||
interface EntityCreateAcl
|
||||
interface AccessEntityCreateChecker extends AccessCreateChecker
|
||||
{
|
||||
/**
|
||||
* Check 'create' access.
|
||||
* Check 'create' access for an entity.
|
||||
*/
|
||||
public function checkEntityCreate(User $user, Entity $entity, ScopeData $data): bool;
|
||||
}
|
||||
+2
-2
@@ -33,10 +33,10 @@ use Espo\ORM\Entity;
|
||||
|
||||
use Espo\Entities\User;
|
||||
|
||||
interface EntityDeleteAcl
|
||||
interface AccessEntityDeleteChecker extends AccessDeleteChecker
|
||||
{
|
||||
/**
|
||||
* Check 'delete' access.
|
||||
* Check 'delete' access for an entity.
|
||||
*/
|
||||
public function checkEntityDelete(User $user, Entity $entity, ScopeData $data): bool;
|
||||
}
|
||||
+2
-2
@@ -33,10 +33,10 @@ use Espo\ORM\Entity;
|
||||
|
||||
use Espo\Entities\User;
|
||||
|
||||
interface EntityEditAcl
|
||||
interface AccessEntityEditChecker extends AccessEditChecker
|
||||
{
|
||||
/**
|
||||
* Check 'edit' access.
|
||||
* Check 'edit' access for an entity.
|
||||
*/
|
||||
public function checkEntityEdit(User $user, Entity $entity, ScopeData $data): bool;
|
||||
}
|
||||
+2
-2
@@ -33,10 +33,10 @@ use Espo\ORM\Entity;
|
||||
|
||||
use Espo\Entities\User;
|
||||
|
||||
interface EntityReadAcl
|
||||
interface AccessEntityReadChecker extends AccessReadChecker
|
||||
{
|
||||
/**
|
||||
* Check 'read' access.
|
||||
* Check 'read' access for entity.
|
||||
*/
|
||||
public function checkEntityRead(User $user, Entity $entity, ScopeData $data): bool;
|
||||
}
|
||||
+2
-2
@@ -33,10 +33,10 @@ use Espo\ORM\Entity;
|
||||
|
||||
use Espo\Entities\User;
|
||||
|
||||
interface EntityStreamAcl
|
||||
interface AccessEntityStreamChecker extends AccessStreamChecker
|
||||
{
|
||||
/**
|
||||
* Check 'stream' access.
|
||||
* Check 'stream' access for an entity.
|
||||
*/
|
||||
public function checkEntityStream(User $user, Entity $entity, ScopeData $data): bool;
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2021 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* EspoCRM is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* EspoCRM is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
|
||||
*
|
||||
* 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 General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Acl;
|
||||
|
||||
use Espo\Entities\User;
|
||||
|
||||
interface AccessReadChecker extends AccessChecker
|
||||
{
|
||||
/**
|
||||
* Check 'read' access.
|
||||
*/
|
||||
public function checkRead(User $user, ScopeData $data): bool;
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2021 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* EspoCRM is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* EspoCRM is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
|
||||
*
|
||||
* 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 General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Acl;
|
||||
|
||||
use Espo\Entities\User;
|
||||
|
||||
interface AccessStreamChecker extends AccessChecker
|
||||
{
|
||||
/**
|
||||
* Check 'stream' access.
|
||||
*/
|
||||
public function checkStream(User $user, ScopeData $data): bool;
|
||||
}
|
||||
@@ -29,281 +29,10 @@
|
||||
|
||||
namespace Espo\Core\Acl;
|
||||
|
||||
use Espo\Entities\User;
|
||||
use Espo\ORM\Entity;
|
||||
|
||||
use Espo\Core\{
|
||||
Acl\Table,
|
||||
ORM\EntityManager,
|
||||
AclManager,
|
||||
Utils\Config,
|
||||
Utils\DateTime as DateTimeUtil,
|
||||
};
|
||||
|
||||
/**
|
||||
* An implementation for access checking for entities. Can be overridden in `Acl` namespace.
|
||||
* @deprecated Use AccessChecker interfaces instead.
|
||||
*/
|
||||
class Acl implements ScopeAcl, EntityAcl, EntityDeleteAcl
|
||||
class Acl extends Base
|
||||
{
|
||||
protected const ATTR_CREATED_BY_ID = 'createdById';
|
||||
|
||||
protected const ATTR_ASSIGNED_USER_ID = 'assignedUserId';
|
||||
|
||||
protected const ATTR_ASSIGNED_USERS_IDS = 'assignedUsersIds';
|
||||
|
||||
protected const ATTR_ASSIGNED_TEAMS_IDS = 'teamsIds';
|
||||
|
||||
protected const FIELD_TEAMS = 'teams';
|
||||
|
||||
protected const FIELD_ASSIGNED_USERS = 'assignedUsers';
|
||||
|
||||
protected $scope;
|
||||
|
||||
protected $ownerUserIdAttribute = null;
|
||||
|
||||
protected $allowDeleteCreatedThresholdPeriod = '24 hours';
|
||||
|
||||
protected $checkIsOwnerentityManager;
|
||||
|
||||
protected $entityManager;
|
||||
|
||||
protected $aclManager;
|
||||
|
||||
protected $config;
|
||||
|
||||
public function __construct(
|
||||
EntityManager $entityManager,
|
||||
AclManager $aclManager,
|
||||
Config $config,
|
||||
string $scope = null
|
||||
) {
|
||||
$this->entityManager = $entityManager;
|
||||
$this->aclManager = $aclManager;
|
||||
$this->config = $config;
|
||||
|
||||
$this->scope = $scope;
|
||||
}
|
||||
|
||||
public function checkEntity(User $user, Entity $entity, ScopeData $data, string $action): bool
|
||||
{
|
||||
return $this->checkScopeInternal($user, $data, $action, $entity);
|
||||
}
|
||||
|
||||
public function checkScope(User $user, ScopeData $data, ?string $action = null): bool
|
||||
{
|
||||
return $this->checkScopeInternal($user, $data, $action);
|
||||
}
|
||||
|
||||
protected function checkScopeInternal(
|
||||
User $user,
|
||||
ScopeData $data,
|
||||
?string $action = null,
|
||||
?Entity $entity = null,
|
||||
array $entityAccessData = []
|
||||
): bool {
|
||||
|
||||
if ($data->isFalse()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($data->isTrue()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$isOwner = null;
|
||||
|
||||
if (isset($entityAccessData['isOwner'])) {
|
||||
$isOwner = $entityAccessData['isOwner'];
|
||||
}
|
||||
|
||||
$inTeam = null;
|
||||
|
||||
if (isset($entityAccessData['inTeam'])) {
|
||||
$inTeam = $entityAccessData['inTeam'];
|
||||
}
|
||||
|
||||
if (is_null($action)) {
|
||||
if ($data->hasNotNo()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$value = $data->get($action);
|
||||
|
||||
if ($value === Table::LEVEL_ALL || $value === Table::LEVEL_YES) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($value === Table::LEVEL_NO) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (is_null($isOwner)) {
|
||||
if ($entity) {
|
||||
$isOwner = $this->checkIsOwner($user, $entity);
|
||||
}
|
||||
else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if ($isOwner) {
|
||||
if ($value === Table::LEVEL_OWN || $value === Table::LEVEL_TEAM) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (is_null($inTeam) && $entity) {
|
||||
$inTeam = $this->checkInTeam($user, $entity);
|
||||
}
|
||||
|
||||
if ($inTeam) {
|
||||
if ($value === Table::LEVEL_TEAM) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function checkIsOwner(User $user, Entity $entity)
|
||||
{
|
||||
if ($entity->hasAttribute(self::ATTR_ASSIGNED_USER_ID)) {
|
||||
if (
|
||||
$entity->has(self::ATTR_ASSIGNED_USER_ID) &&
|
||||
$user->getId() === $entity->get(self::ATTR_ASSIGNED_USER_ID)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if ($entity->hasAttribute(self::ATTR_CREATED_BY_ID)) {
|
||||
if (
|
||||
$entity->has(self::ATTR_CREATED_BY_ID) &&
|
||||
$user->getId() === $entity->get(self::ATTR_CREATED_BY_ID)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if ($entity->hasLinkMultipleField(self::FIELD_ASSIGNED_USERS)) {
|
||||
if ($entity->hasLinkMultipleId(self::FIELD_ASSIGNED_USERS, $user->getId())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function checkInTeam(User $user, Entity $entity)
|
||||
{
|
||||
$userTeamIdList = $user->getLinkMultipleIdList(self::FIELD_TEAMS);
|
||||
|
||||
if (!$entity->hasRelation(self::FIELD_TEAMS) || !$entity->hasAttribute(self::ATTR_ASSIGNED_TEAMS_IDS)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$entityTeamIdList = $entity->getLinkMultipleIdList(self::FIELD_TEAMS);
|
||||
|
||||
if (empty($entityTeamIdList)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach ($userTeamIdList as $id) {
|
||||
if (in_array($id, $entityTeamIdList)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function checkEntityDelete(User $user, Entity $entity, ScopeData $data): bool
|
||||
{
|
||||
if ($user->isAdmin()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($this->checkEntity($user, $entity, $data, Table::ACTION_DELETE)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($data->getEdit() === Table::LEVEL_NO && $data->getCreate() === Table::LEVEL_NO) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (
|
||||
!$this->config->get('aclAllowDeleteCreated') ||
|
||||
!$entity->has(self::ATTR_CREATED_BY_ID) ||
|
||||
$entity->get(self::ATTR_CREATED_BY_ID) !== $user->getId()
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$isDeletedAllowed = false;
|
||||
|
||||
if (!$entity->has(self::ATTR_ASSIGNED_USER_ID)) {
|
||||
$isDeletedAllowed = true;
|
||||
}
|
||||
else {
|
||||
if (!$entity->get(self::ATTR_ASSIGNED_USER_ID)) {
|
||||
$isDeletedAllowed = true;
|
||||
}
|
||||
else if ($entity->get(self::ATTR_ASSIGNED_USER_ID) === $entity->get(self::ATTR_CREATED_BY_ID)) {
|
||||
$isDeletedAllowed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$isDeletedAllowed) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$createdAt = $entity->get('createdAt');
|
||||
|
||||
if (!$createdAt) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$deleteThresholdPeriod = $this->config->get(
|
||||
'aclAllowDeleteCreatedThresholdPeriod',
|
||||
$this->allowDeleteCreatedThresholdPeriod
|
||||
);
|
||||
|
||||
if (DateTimeUtil::isAfterThreshold($createdAt, $deleteThresholdPeriod)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use `$this->config`.
|
||||
*/
|
||||
protected function getConfig(): Config
|
||||
{
|
||||
return $this->config;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use `$this->entityManager`.
|
||||
*/
|
||||
protected function getEntityManager(): EntityManager
|
||||
{
|
||||
return $this->entityManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use `$this->aclManager`.
|
||||
*/
|
||||
protected function getAclManager(): AclManager
|
||||
{
|
||||
return $this->aclManager;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,14 +31,20 @@ namespace Espo\Core\Acl;
|
||||
|
||||
use Espo\Core\Interfaces\Injectable;
|
||||
|
||||
use Espo\ORM\Entity;
|
||||
|
||||
use Espo\Entities\User;
|
||||
|
||||
use Espo\Core\{
|
||||
ORM\EntityManager,
|
||||
AclManager,
|
||||
Utils\Config,
|
||||
};
|
||||
|
||||
/** @deprecated */
|
||||
class Base extends Acl implements Injectable
|
||||
/**
|
||||
* @deprecated Use AccessChecker interfaces instead.
|
||||
*/
|
||||
class Base implements AccessChecker, Injectable
|
||||
{
|
||||
protected $dependencyList = [];
|
||||
|
||||
@@ -46,19 +52,95 @@ class Base extends Acl implements Injectable
|
||||
|
||||
protected $injections = [];
|
||||
|
||||
protected $entityManager;
|
||||
|
||||
protected $aclManager;
|
||||
|
||||
protected $config;
|
||||
|
||||
protected $defaultChecker;
|
||||
|
||||
protected $scopeChecker;
|
||||
|
||||
public function __construct(
|
||||
EntityManager $entityManager,
|
||||
AclManager $aclManager,
|
||||
Config $config,
|
||||
?string $scope = null
|
||||
DefaultAccessChecker $defaultChecker,
|
||||
ScopeChecker $scopeChecker
|
||||
) {
|
||||
$this->scope = $scope;
|
||||
|
||||
parent::__construct($entityManager, $aclManager, $config, $scope);
|
||||
$this->entityManager = $entityManager;
|
||||
$this->aclManager = $aclManager;
|
||||
$this->config = $config;
|
||||
$this->defaultChecker = $defaultChecker;
|
||||
$this->scopeChecker = $scopeChecker;
|
||||
|
||||
$this->init();
|
||||
}
|
||||
|
||||
public function check(User $user, ScopeData $data): bool
|
||||
{
|
||||
return $this->defaultChecker->check($user, $data);
|
||||
}
|
||||
|
||||
public function checkEntity(User $user, Entity $entity, ScopeData $data, string $action): bool
|
||||
{
|
||||
$checkerData = ScopeCheckerData
|
||||
::createBuilder()
|
||||
->setIsOwnChecker(
|
||||
function () use ($user, $entity): bool {
|
||||
return (bool) $this->checkIsOwner($user, $entity);
|
||||
}
|
||||
)
|
||||
->setInTeamChecker(
|
||||
function () use ($user, $entity): bool {
|
||||
return (bool) $this->checkInTeam($user, $entity);
|
||||
}
|
||||
)
|
||||
->build();
|
||||
|
||||
return $this->scopeChecker->check($data, $action, $checkerData);
|
||||
}
|
||||
|
||||
public function checkScope(User $user, ScopeData $data, ?string $action = null): bool
|
||||
{
|
||||
if (!$action) {
|
||||
return $this->defaultChecker->check($user, $data);
|
||||
}
|
||||
|
||||
if ($action === Table::ACTION_CREATE) {
|
||||
return $this->defaultChecker->checkCreate($user, $data);
|
||||
}
|
||||
|
||||
if ($action === Table::ACTION_READ) {
|
||||
return $this->defaultChecker->checkRead($user, $data);
|
||||
}
|
||||
|
||||
if ($action === Table::ACTION_EDIT) {
|
||||
return $this->defaultChecker->checkEdit($user, $data);
|
||||
}
|
||||
|
||||
if ($action === Table::ACTION_DELETE) {
|
||||
return $this->defaultChecker->checkDelete($user, $data);
|
||||
}
|
||||
|
||||
if ($action === Table::ACTION_STREAM) {
|
||||
return $this->defaultChecker->checkStream($user, $data);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function checkIsOwner(User $user, Entity $entity)
|
||||
{
|
||||
return $this->aclManager->checkOwnershipOwn($user, $entity);
|
||||
}
|
||||
|
||||
public function checkInTeam(User $user, Entity $entity)
|
||||
{
|
||||
return $this->aclManager->checkOwnershipTeam($user, $entity);
|
||||
}
|
||||
|
||||
public function inject($name, $object)
|
||||
{
|
||||
$this->injections[$name] = $object;
|
||||
@@ -89,4 +171,19 @@ class Base extends Acl implements Injectable
|
||||
{
|
||||
return array_merge($this->dependencyList, $this->dependencies);
|
||||
}
|
||||
|
||||
protected function getConfig(): Config
|
||||
{
|
||||
return $this->config;
|
||||
}
|
||||
|
||||
protected function getEntityManager(): EntityManager
|
||||
{
|
||||
return $this->entityManager;
|
||||
}
|
||||
|
||||
protected function getAclManager(): AclManager
|
||||
{
|
||||
return $this->aclManager;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,211 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2021 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* EspoCRM is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* EspoCRM is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
|
||||
*
|
||||
* 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 General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Acl;
|
||||
|
||||
use Espo\Entities\User;
|
||||
|
||||
use Espo\ORM\Entity;
|
||||
|
||||
use Espo\Core\{
|
||||
Acl\Table,
|
||||
AclManager,
|
||||
Utils\Config,
|
||||
Utils\DateTime as DateTimeUtil,
|
||||
};
|
||||
|
||||
/**
|
||||
* A default implementation for access checking.
|
||||
*/
|
||||
class DefaultAccessChecker implements
|
||||
|
||||
AccessEntityCreateChecker,
|
||||
AccessEntityReadChecker,
|
||||
AccessEntityEditChecker,
|
||||
AccessEntityDeleteChecker,
|
||||
AccessEntityStreamChecker
|
||||
{
|
||||
private const ATTR_CREATED_BY_ID = 'createdById';
|
||||
|
||||
private const ATTR_CREATED_AT = 'createdAt';
|
||||
|
||||
private const ATTR_ASSIGNED_USER_ID = 'assignedUserId';
|
||||
|
||||
private const ALLOW_DELETE_OWN_CREATED_PERIOD = '24 hours';
|
||||
|
||||
private $aclManager;
|
||||
|
||||
private $config;
|
||||
|
||||
private $scopeChecker;
|
||||
|
||||
public function __construct(
|
||||
AclManager $aclManager,
|
||||
Config $config,
|
||||
ScopeChecker $scopeChecker
|
||||
) {
|
||||
$this->aclManager = $aclManager;
|
||||
$this->config = $config;
|
||||
$this->scopeChecker = $scopeChecker;
|
||||
}
|
||||
|
||||
private function checkEntity(User $user, Entity $entity, ScopeData $data, string $action): bool
|
||||
{
|
||||
$checkerData = ScopeCheckerData
|
||||
::createBuilder()
|
||||
->setIsOwnChecker(
|
||||
function () use ($user, $entity): bool {
|
||||
return $this->aclManager->checkOwnershipOwn($user, $entity);
|
||||
}
|
||||
)
|
||||
->setInTeamChecker(
|
||||
function () use ($user, $entity): bool {
|
||||
return $this->aclManager->checkOwnershipTeam($user, $entity);
|
||||
}
|
||||
)
|
||||
->build();
|
||||
|
||||
return $this->scopeChecker->check($data, $action, $checkerData);
|
||||
}
|
||||
|
||||
private function checkScope(User $user, ScopeData $data, ?string $action = null): bool
|
||||
{
|
||||
$checkerData = ScopeCheckerData
|
||||
::createBuilder()
|
||||
->setIsOwn(true)
|
||||
->setInTeam(true)
|
||||
->build();
|
||||
|
||||
return $this->scopeChecker->check($data, $action, $checkerData);
|
||||
}
|
||||
|
||||
public function check(User $user, ScopeData $data): bool
|
||||
{
|
||||
return $this->checkScope($user, $data);
|
||||
}
|
||||
|
||||
public function checkCreate(User $user, ScopeData $data): bool
|
||||
{
|
||||
return $this->checkScope($user, $data, Table::ACTION_CREATE);
|
||||
}
|
||||
|
||||
public function checkRead(User $user, ScopeData $data): bool
|
||||
{
|
||||
return $this->checkScope($user, $data, Table::ACTION_READ);
|
||||
}
|
||||
|
||||
public function checkEdit(User $user, ScopeData $data): bool
|
||||
{
|
||||
return $this->checkScope($user, $data, Table::ACTION_EDIT);
|
||||
}
|
||||
|
||||
public function checkDelete(User $user, ScopeData $data): bool
|
||||
{
|
||||
return $this->checkScope($user, $data, Table::ACTION_DELETE);
|
||||
}
|
||||
|
||||
public function checkStream(User $user, ScopeData $data): bool
|
||||
{
|
||||
return $this->checkScope($user, $data, Table::ACTION_STREAM);
|
||||
}
|
||||
|
||||
public function checkEntityCreate(User $user, Entity $entity, ScopeData $data): bool
|
||||
{
|
||||
return $this->checkEntity($user, $entity, $data, Table::ACTION_CREATE);
|
||||
}
|
||||
|
||||
public function checkEntityRead(User $user, Entity $entity, ScopeData $data): bool
|
||||
{
|
||||
return $this->checkEntity($user, $entity, $data, Table::ACTION_READ);
|
||||
}
|
||||
|
||||
public function checkEntityEdit(User $user, Entity $entity, ScopeData $data): bool
|
||||
{
|
||||
return $this->checkEntity($user, $entity, $data, Table::ACTION_EDIT);
|
||||
}
|
||||
|
||||
public function checkEntityStream(User $user, Entity $entity, ScopeData $data): bool
|
||||
{
|
||||
return $this->checkEntity($user, $entity, $data, Table::ACTION_STREAM);
|
||||
}
|
||||
|
||||
public function checkEntityDelete(User $user, Entity $entity, ScopeData $data): bool
|
||||
{
|
||||
if ($this->checkEntity($user, $entity, $data, Table::ACTION_DELETE)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($data->getEdit() === Table::LEVEL_NO && $data->getCreate() === Table::LEVEL_NO) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (
|
||||
!$this->config->get('aclAllowDeleteCreated') ||
|
||||
!$entity->has(self::ATTR_CREATED_BY_ID) ||
|
||||
$entity->get(self::ATTR_CREATED_BY_ID) !== $user->getId()
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$isDeletedAllowed = false;
|
||||
|
||||
if (!$entity->has(self::ATTR_ASSIGNED_USER_ID)) {
|
||||
$isDeletedAllowed = true;
|
||||
}
|
||||
else {
|
||||
if (!$entity->get(self::ATTR_ASSIGNED_USER_ID)) {
|
||||
$isDeletedAllowed = true;
|
||||
}
|
||||
else if ($entity->get(self::ATTR_ASSIGNED_USER_ID) === $entity->get(self::ATTR_CREATED_BY_ID)) {
|
||||
$isDeletedAllowed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$isDeletedAllowed) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$createdAt = $entity->get(self::ATTR_CREATED_AT);
|
||||
|
||||
if (!$createdAt) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$deleteThresholdPeriod = $this->config->get(
|
||||
'aclAllowDeleteCreatedThresholdPeriod',
|
||||
self::ALLOW_DELETE_OWN_CREATED_PERIOD
|
||||
);
|
||||
|
||||
if (DateTimeUtil::isAfterThreshold($createdAt, $deleteThresholdPeriod)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2021 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* EspoCRM is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* EspoCRM is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
|
||||
*
|
||||
* 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 General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Acl;
|
||||
|
||||
use Espo\ORM\Entity;
|
||||
|
||||
use Espo\Entities\User;
|
||||
|
||||
/**
|
||||
* A default implementation for ownership checking.
|
||||
*/
|
||||
class DefaultOwnershipChecker implements OwnershipOwnChecker, OwnershipTeamChecker
|
||||
{
|
||||
private const ATTR_CREATED_BY_ID = 'createdById';
|
||||
|
||||
private const ATTR_ASSIGNED_USER_ID = 'assignedUserId';
|
||||
|
||||
private const ATTR_ASSIGNED_TEAMS_IDS = 'teamsIds';
|
||||
|
||||
private const FIELD_TEAMS = 'teams';
|
||||
|
||||
private const FIELD_ASSIGNED_USERS = 'assignedUsers';
|
||||
|
||||
public function checkOwn(User $user, Entity $entity): bool
|
||||
{
|
||||
if ($entity->hasAttribute(self::ATTR_ASSIGNED_USER_ID)) {
|
||||
if (
|
||||
$entity->has(self::ATTR_ASSIGNED_USER_ID) &&
|
||||
$user->getId() === $entity->get(self::ATTR_ASSIGNED_USER_ID)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if ($entity->hasAttribute(self::ATTR_CREATED_BY_ID)) {
|
||||
if (
|
||||
$entity->has(self::ATTR_CREATED_BY_ID) &&
|
||||
$user->getId() === $entity->get(self::ATTR_CREATED_BY_ID)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if ($entity->hasLinkMultipleField(self::FIELD_ASSIGNED_USERS)) {
|
||||
if ($entity->hasLinkMultipleId(self::FIELD_ASSIGNED_USERS, $user->getId())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function checkTeam(User $user, Entity $entity): bool
|
||||
{
|
||||
$userTeamIdList = $user->getLinkMultipleIdList(self::FIELD_TEAMS);
|
||||
|
||||
if (
|
||||
!$entity->hasRelation(self::FIELD_TEAMS) ||
|
||||
!$entity->hasAttribute(self::ATTR_ASSIGNED_TEAMS_IDS)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$entityTeamIdList = $entity->getLinkMultipleIdList(self::FIELD_TEAMS);
|
||||
|
||||
if (empty($entityTeamIdList)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach ($userTeamIdList as $id) {
|
||||
if (in_array($id, $entityTeamIdList)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,939 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2021 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* EspoCRM is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* EspoCRM is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
|
||||
*
|
||||
* 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 General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Acl;
|
||||
|
||||
|
||||
use Espo\Entities\User;
|
||||
|
||||
use Espo\Core\{
|
||||
ORM\EntityManager,
|
||||
Utils\Config,
|
||||
Utils\Metadata,
|
||||
Utils\FieldUtil,
|
||||
Utils\DataCache,
|
||||
Utils\ObjectUtil,
|
||||
};
|
||||
|
||||
use StdClass;
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* A table is generated for a user. Multiple roles are merged into a single table.
|
||||
* Stores access levels.
|
||||
*/
|
||||
class DefaultTable implements Table
|
||||
{
|
||||
protected const LEVEL_NOT_SET = 'not-set';
|
||||
|
||||
protected $type = 'acl';
|
||||
|
||||
protected $defaultAclType = 'recordAllTeamOwnNo';
|
||||
|
||||
private $data = null;
|
||||
|
||||
protected $cacheKey;
|
||||
|
||||
protected $actionList = [
|
||||
self::ACTION_READ,
|
||||
self::ACTION_STREAM,
|
||||
self::ACTION_EDIT,
|
||||
self::ACTION_DELETE,
|
||||
self::ACTION_CREATE,
|
||||
];
|
||||
|
||||
protected $booleanActionList = [
|
||||
self::ACTION_CREATE,
|
||||
];
|
||||
|
||||
protected $levelList = [
|
||||
self::LEVEL_YES,
|
||||
self::LEVEL_ALL,
|
||||
self::LEVEL_TEAM,
|
||||
self::LEVEL_OWN,
|
||||
self::LEVEL_NO,
|
||||
];
|
||||
|
||||
protected $fieldActionList = [
|
||||
self::ACTION_READ,
|
||||
self::ACTION_EDIT,
|
||||
];
|
||||
|
||||
protected $fieldLevelList = [
|
||||
self::LEVEL_YES,
|
||||
self::LEVEL_NO,
|
||||
];
|
||||
|
||||
protected $valuePermissionHighestLevels = [];
|
||||
|
||||
protected $valuePermissionList = [];
|
||||
|
||||
protected $forbiddenAttributesCache = [];
|
||||
|
||||
protected $forbiddenFieldsCache = [];
|
||||
|
||||
protected $isStrictModeForced = false;
|
||||
|
||||
protected $isStrictMode = false;
|
||||
|
||||
protected $entityManager;
|
||||
|
||||
protected $user;
|
||||
|
||||
protected $config;
|
||||
|
||||
protected $metadata;
|
||||
|
||||
protected $fieldUtil;
|
||||
|
||||
protected $dataCache;
|
||||
|
||||
public function __construct(
|
||||
EntityManager $entityManager,
|
||||
User $user,
|
||||
Config $config,
|
||||
Metadata $metadata,
|
||||
FieldUtil $fieldUtil,
|
||||
DataCache $dataCache
|
||||
) {
|
||||
$this->entityManager = $entityManager;
|
||||
|
||||
$this->data = (object) [
|
||||
'table' => (object) [],
|
||||
'fieldTable' => (object) [],
|
||||
'fieldTableQuickAccess' => (object) [],
|
||||
];
|
||||
|
||||
if ($this->isStrictModeForced) {
|
||||
$this->isStrictMode = true;
|
||||
}
|
||||
else {
|
||||
$this->isStrictMode = $config->get('aclStrictMode', true);
|
||||
}
|
||||
|
||||
$this->user = $user;
|
||||
$this->metadata = $metadata;
|
||||
$this->fieldUtil = $fieldUtil;
|
||||
$this->dataCache = $dataCache;
|
||||
|
||||
if (!$this->user->isFetched()) {
|
||||
throw new RuntimeException('User must be fetched before ACL check.');
|
||||
}
|
||||
|
||||
$this->valuePermissionList = $this->metadata
|
||||
->get(['app', $this->type, 'valuePermissionList'], []);
|
||||
|
||||
$this->valuePermissionHighestLevels = $this->metadata
|
||||
->get(['app', $this->type, 'valuePermissionHighestLevels'], []);
|
||||
|
||||
$this->initCacheKey();
|
||||
|
||||
if ($config && $config->get('useCache') && $this->dataCache->has($this->cacheKey)) {
|
||||
$this->data = $this->dataCache->get($this->cacheKey);
|
||||
}
|
||||
else {
|
||||
$this->load();
|
||||
|
||||
if ($config && $config->get('useCache')) {
|
||||
$this->buildCache();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function initCacheKey(): void
|
||||
{
|
||||
$this->cacheKey = 'acl/' . $this->user->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a full map.
|
||||
*/
|
||||
public function getMap(): StdClass
|
||||
{
|
||||
return ObjectUtil::clone($this->data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get scope data.
|
||||
*/
|
||||
public function getScopeData(string $scope): ScopeData
|
||||
{
|
||||
if (!isset($this->data->table->$scope)) {
|
||||
return ScopeData::fromRaw(false);
|
||||
}
|
||||
|
||||
$data = $this->data->table->$scope;
|
||||
|
||||
if (is_string($data)) {
|
||||
return $this->getScopeData($data);
|
||||
}
|
||||
|
||||
return ScopeData::fromRaw($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a permission level.
|
||||
*/
|
||||
public function getPermissionLevel(string $permission): string
|
||||
{
|
||||
return $this->data->$permission ?? self::LEVEL_NO;
|
||||
}
|
||||
|
||||
private function load(): void
|
||||
{
|
||||
$valuePermissionLists = (object) [];
|
||||
|
||||
foreach ($this->valuePermissionList as $permission) {
|
||||
$valuePermissionLists->$permission = [];
|
||||
}
|
||||
|
||||
$aclTableList = [];
|
||||
$fieldTableList = [];
|
||||
|
||||
if (!$this->user->isAdmin()) {
|
||||
$roleList = $this->getRoleList();
|
||||
|
||||
foreach ($roleList as $role) {
|
||||
$aclTableList[] = $role->get('data');
|
||||
$fieldTableList[] = $role->get('fieldData');
|
||||
|
||||
foreach ($this->valuePermissionList as $permission) {
|
||||
$valuePermissionLists->{$permission}[] = $role->get($permission);
|
||||
}
|
||||
}
|
||||
|
||||
$aclTable = $this->mergeTableList($aclTableList);
|
||||
$fieldTable = $this->mergeFieldTableList($fieldTableList);
|
||||
|
||||
$this->applyDefault($aclTable, $fieldTable);
|
||||
$this->applyDisabled($aclTable, $fieldTable);
|
||||
$this->applyMandatory($aclTable, $fieldTable);
|
||||
$this->applyAdditional($aclTable, $fieldTable, $valuePermissionLists);
|
||||
}
|
||||
|
||||
if ($this->user->isAdmin()) {
|
||||
$aclTable = (object) [];
|
||||
$fieldTable = (object) [];
|
||||
|
||||
$this->applyHighest($aclTable, $fieldTable);
|
||||
}
|
||||
|
||||
foreach ($aclTable as $scope => $data) {
|
||||
if (is_string($data) && isset($aclTable->$data)) {
|
||||
$aclTable->$scope = $aclTable->$data;
|
||||
}
|
||||
}
|
||||
|
||||
$this->data->table = $aclTable;
|
||||
$this->data->fieldTable = $fieldTable;
|
||||
|
||||
$this->fillFieldTableQuickAccess();
|
||||
|
||||
if (!$this->user->isAdmin()) {
|
||||
$permissionsDefaultsGroupName = 'permissionsDefaults';
|
||||
|
||||
if ($this->isStrictMode) {
|
||||
$permissionsDefaultsGroupName = 'permissionsStrictDefaults';
|
||||
}
|
||||
|
||||
foreach ($this->valuePermissionList as $permission) {
|
||||
$this->data->$permission = $this->mergeValueList(
|
||||
$valuePermissionLists->$permission,
|
||||
$this->metadata
|
||||
->get(['app', $this->type, $permissionsDefaultsGroupName, $permission, self::LEVEL_YES])
|
||||
);
|
||||
|
||||
if ($this->metadata->get('app.'.$this->type.'.mandatory.' . $permission)) {
|
||||
$this->data->$permission = $this->metadata
|
||||
->get('app.'.$this->type.'.mandatory.' . $permission);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->user->isAdmin()) {
|
||||
foreach ($this->valuePermissionList as $permission) {
|
||||
if (isset($this->valuePermissionHighestLevels[$permission])) {
|
||||
$this->data->$permission = $this->valuePermissionHighestLevels[$permission];
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->data->$permission = self::LEVEL_ALL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function getRoleList(): array
|
||||
{
|
||||
$roleList = [];
|
||||
|
||||
$userRoleList = $this->entityManager
|
||||
->getRepository('User')
|
||||
->getRelation($this->user, 'roles')
|
||||
->find();
|
||||
|
||||
foreach ($userRoleList as $role) {
|
||||
$roleList[] = $role;
|
||||
}
|
||||
|
||||
$teamList = $this->entityManager
|
||||
->getRepository('User')
|
||||
->getRelation($this->user, 'teams')
|
||||
->find();
|
||||
|
||||
foreach ($teamList as $team) {
|
||||
$teamRoleList = $this->entityManager
|
||||
->getRepository('Team')
|
||||
->getRelation($team, 'roles')
|
||||
->find();
|
||||
|
||||
foreach ($teamRoleList as $role) {
|
||||
$roleList[] = $role;
|
||||
}
|
||||
}
|
||||
|
||||
return $roleList;
|
||||
}
|
||||
|
||||
public function getScopeForbiddenAttributeList(
|
||||
string $scope,
|
||||
string $action = self::ACTION_READ,
|
||||
string $thresholdLevel = self::LEVEL_NO
|
||||
): array {
|
||||
|
||||
if (!in_array($thresholdLevel, $this->fieldLevelList) || $thresholdLevel === self::LEVEL_YES) {
|
||||
throw new RuntimeException("Bad threshold level.");
|
||||
}
|
||||
|
||||
$key = $scope . '_'. $action . '_' . $thresholdLevel;
|
||||
|
||||
if (isset($this->forbiddenAttributesCache[$key])) {
|
||||
return $this->forbiddenAttributesCache[$key];
|
||||
}
|
||||
|
||||
$fieldTableQuickAccess = $this->data->fieldTableQuickAccess;
|
||||
|
||||
if (
|
||||
!isset($fieldTableQuickAccess->$scope) || !isset($fieldTableQuickAccess->$scope->attributes) ||
|
||||
!isset($fieldTableQuickAccess->$scope->attributes->$action)
|
||||
) {
|
||||
$this->forbiddenAttributesCache[$key] = [];
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
$levelList = [];
|
||||
|
||||
foreach ($this->fieldLevelList as $level) {
|
||||
if (array_search($level, $this->fieldLevelList) >= array_search($thresholdLevel, $this->fieldLevelList)) {
|
||||
$levelList[] = $level;
|
||||
}
|
||||
}
|
||||
|
||||
$attributeList = [];
|
||||
|
||||
foreach ($levelList as $level) {
|
||||
if (!isset($fieldTableQuickAccess->$scope->attributes->$action->$level)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($fieldTableQuickAccess->$scope->attributes->$action->$level as $attribute) {
|
||||
if (in_array($attribute, $attributeList)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$attributeList[] = $attribute;
|
||||
}
|
||||
}
|
||||
|
||||
$this->forbiddenAttributesCache[$key] = $attributeList;
|
||||
|
||||
return $attributeList;
|
||||
}
|
||||
|
||||
public function getScopeForbiddenFieldList(
|
||||
string $scope,
|
||||
string $action = self::ACTION_READ,
|
||||
string $thresholdLevel = self::LEVEL_NO
|
||||
): array {
|
||||
|
||||
if (!in_array($thresholdLevel, $this->fieldLevelList) || $thresholdLevel === self::LEVEL_YES) {
|
||||
throw new RuntimeException("Bad threshold level.");
|
||||
}
|
||||
|
||||
$key = $scope . '_'. $action . '_' . $thresholdLevel;
|
||||
|
||||
if (isset($this->forbiddenFieldsCache[$key])) {
|
||||
return $this->forbiddenFieldsCache[$key];
|
||||
}
|
||||
|
||||
$fieldTableQuickAccess = $this->data->fieldTableQuickAccess;
|
||||
|
||||
if (
|
||||
!isset($fieldTableQuickAccess->$scope) || !isset($fieldTableQuickAccess->$scope->fields) ||
|
||||
!isset($fieldTableQuickAccess->$scope->fields->$action)
|
||||
) {
|
||||
$this->forbiddenFieldsCache[$key] = [];
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
$levelList = [];
|
||||
|
||||
foreach ($this->fieldLevelList as $level) {
|
||||
if (array_search($level, $this->fieldLevelList) >= array_search($thresholdLevel, $this->fieldLevelList)) {
|
||||
$levelList[] = $level;
|
||||
}
|
||||
}
|
||||
|
||||
$fieldList = [];
|
||||
|
||||
foreach ($levelList as $level) {
|
||||
if (!isset($fieldTableQuickAccess->$scope->fields->$action->$level)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($fieldTableQuickAccess->$scope->fields->$action->$level as $field) {
|
||||
if (in_array($field, $fieldList)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$fieldList[] = $field;
|
||||
}
|
||||
}
|
||||
|
||||
$this->forbiddenFieldsCache[$key] = $fieldList;
|
||||
|
||||
return $fieldList;
|
||||
}
|
||||
|
||||
protected function fillFieldTableQuickAccess(): void
|
||||
{
|
||||
$fieldTable = $this->data->fieldTable;
|
||||
|
||||
$fieldTableQuickAccess = (object) [];
|
||||
|
||||
foreach (get_object_vars($fieldTable) as $scope => $scopeData) {
|
||||
$fieldTableQuickAccess->$scope = (object) [
|
||||
'attributes' => (object) [],
|
||||
'fields' => (object) []
|
||||
];
|
||||
|
||||
foreach ($this->fieldActionList as $action) {
|
||||
$fieldTableQuickAccess->$scope->attributes->$action = (object) [];
|
||||
$fieldTableQuickAccess->$scope->fields->$action = (object) [];
|
||||
|
||||
foreach ($this->fieldLevelList as $level) {
|
||||
$fieldTableQuickAccess->$scope->attributes->$action->$level = [];
|
||||
$fieldTableQuickAccess->$scope->fields->$action->$level = [];
|
||||
}
|
||||
}
|
||||
|
||||
foreach (get_object_vars($scopeData) as $field => $fieldData) {
|
||||
$attributeList = $this->fieldUtil->getAttributeList($scope, $field);
|
||||
|
||||
foreach ($this->fieldActionList as $action) {
|
||||
if (!isset($fieldData->$action)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($this->fieldLevelList as $level) {
|
||||
if ($fieldData->$action === $level) {
|
||||
$fieldTableQuickAccess->$scope->fields->$action->{$level}[] = $field;
|
||||
|
||||
foreach ($attributeList as $attribute) {
|
||||
$fieldTableQuickAccess->$scope->attributes->$action->{$level}[] = $attribute;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->data->fieldTableQuickAccess = $fieldTableQuickAccess;
|
||||
}
|
||||
|
||||
protected function applyHighest(StdClass &$table, StdClass &$fieldTable): void
|
||||
{
|
||||
foreach ($this->getScopeList() as $scope) {
|
||||
if ($this->metadata->get(['scopes', $scope, $this->type]) === 'boolean') {
|
||||
$table->$scope = true;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$this->metadata->get(['scopes', $scope, 'entity'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$table->$scope = (object) [];
|
||||
|
||||
$actionList = $this->metadata->get(
|
||||
['scopes', $scope, $this->type . 'ActionList'],
|
||||
$this->actionList
|
||||
);
|
||||
|
||||
$highest = $this->metadata->get(
|
||||
['scopes', $scope, $this->type . 'HighestLevel'],
|
||||
self::LEVEL_ALL
|
||||
);
|
||||
|
||||
foreach ($actionList as $action) {
|
||||
$table->$scope->$action = $highest;
|
||||
|
||||
if (in_array($action, $this->booleanActionList)) {
|
||||
$table->$scope->$action = 'yes';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function applyDefault(&$table, &$fieldTable): void
|
||||
{
|
||||
if ($this->user->isAdmin()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$defaultsGroupName = 'default';
|
||||
|
||||
if ($this->isStrictMode) {
|
||||
$defaultsGroupName = 'strictDefault';
|
||||
}
|
||||
|
||||
$data = $this->metadata->get(['app', $this->type, $defaultsGroupName, 'scopeLevel'], []);
|
||||
|
||||
foreach ($data as $scope => $item) {
|
||||
if (isset($table->$scope)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$value = $item;
|
||||
|
||||
if (is_array($item)) {
|
||||
$value = (object) $item;
|
||||
}
|
||||
|
||||
$table->$scope = $value;
|
||||
}
|
||||
|
||||
$defaultFieldData = $this->metadata->get(['app', $this->type, $defaultsGroupName, 'fieldLevel'], []);
|
||||
|
||||
foreach ($this->getScopeList() as $scope) {
|
||||
if (isset($table->$scope) && $table->$scope === false) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$this->metadata->get('scopes.' . $scope . '.entity')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$fieldList = array_keys($this->metadata->get("entityDefs.{$scope}.fields", []));
|
||||
|
||||
$defaultScopeFieldData = $this->metadata
|
||||
->get('app.'.$this->type.'.'.$defaultsGroupName.'.scopeFieldLevel.' . $scope, []);
|
||||
|
||||
foreach (array_merge($defaultFieldData, $defaultScopeFieldData) as $field => $f) {
|
||||
if (!in_array($field, $fieldList)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isset($fieldTable->$scope)) {
|
||||
$fieldTable->$scope = (object) [];
|
||||
}
|
||||
|
||||
if (isset($fieldTable->$scope->$field)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$fieldTable->$scope->$field = (object) [];
|
||||
|
||||
foreach ($this->fieldActionList as $action) {
|
||||
$level = self::LEVEL_NO;
|
||||
|
||||
if ($f === true) {
|
||||
$level = self::LEVEL_YES;
|
||||
}
|
||||
else {
|
||||
if (is_array($f) && isset($f[$action])) {
|
||||
$level = $f[$action];
|
||||
}
|
||||
}
|
||||
|
||||
$fieldTable->$scope->$field->$action = $level;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($this->getScopeWithAclList() as $scope) {
|
||||
if (!isset($table->$scope)) {
|
||||
$aclType = $this->metadata->get('scopes.' . $scope . '.' . $this->type);
|
||||
|
||||
if ($aclType === true) {
|
||||
$aclType = $this->defaultAclType;
|
||||
}
|
||||
|
||||
if (!empty($aclType)) {
|
||||
$paramDefaultsName = 'scopeLevelTypesDefaults';
|
||||
|
||||
if ($this->isStrictMode) {
|
||||
$paramDefaultsName = 'scopeLevelTypesStrictDefaults';
|
||||
}
|
||||
|
||||
$defaultValue = $this->metadata
|
||||
->get(
|
||||
['app', $this->type, $paramDefaultsName, $aclType],
|
||||
$this->metadata->get(['app', $this->type, $paramDefaultsName, 'record'])
|
||||
);
|
||||
|
||||
if (is_array($defaultValue)) {
|
||||
$defaultValue = (object) $defaultValue;
|
||||
}
|
||||
|
||||
$table->$scope = $defaultValue;
|
||||
|
||||
if (is_object($table->$scope)) {
|
||||
$actionList = $this->metadata->get(['scopes', $scope, $this->type . 'ActionList']);
|
||||
|
||||
if ($actionList) {
|
||||
foreach (get_object_vars($table->$scope) as $action => $level) {
|
||||
if (!in_array($action, $actionList)) {
|
||||
unset($table->$scope->$action);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function applyMandatory(&$table, &$fieldTable): void
|
||||
{
|
||||
if ($this->user->isAdmin()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$data = $this->metadata->get('app.'.$this->type.'.mandatory.scopeLevel', []);
|
||||
|
||||
foreach ($data as $scope => $item) {
|
||||
$value = $item;
|
||||
|
||||
if (is_array($item)) {
|
||||
$value = (object) $item;
|
||||
}
|
||||
|
||||
$table->$scope = $value;
|
||||
}
|
||||
|
||||
$mandatoryFieldData = $this->metadata->get('app.'.$this->type.'.mandatory.fieldLevel', []);
|
||||
|
||||
foreach ($this->getScopeList() as $scope) {
|
||||
if (isset($table->$scope) && $table->$scope === false) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$this->metadata->get('scopes.' . $scope . '.entity')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$fieldList = array_keys($this->metadata->get("entityDefs.{$scope}.fields", []));
|
||||
|
||||
$mandatoryScopeFieldData = $this->metadata
|
||||
->get('app.'.$this->type.'.mandatory.scopeFieldLevel.' . $scope, []);
|
||||
|
||||
foreach (array_merge($mandatoryFieldData, $mandatoryScopeFieldData) as $field => $f) {
|
||||
if (!in_array($field, $fieldList)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isset($fieldTable->$scope)) {
|
||||
$fieldTable->$scope = (object) [];
|
||||
}
|
||||
|
||||
$fieldTable->$scope->$field = (object) [];
|
||||
|
||||
foreach ($this->fieldActionList as $action) {
|
||||
$level = self::LEVEL_NO;
|
||||
|
||||
if ($f === true) {
|
||||
$level = self::LEVEL_YES;
|
||||
}
|
||||
else {
|
||||
if (is_array($f) && isset($f[$action])) {
|
||||
$level = $f[$action];
|
||||
}
|
||||
}
|
||||
|
||||
$fieldTable->$scope->$field->$action = $level;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function applyDisabled(&$table, &$fieldTable): void
|
||||
{
|
||||
if ($this->user->isAdmin()) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($this->getScopeList() as $scope) {
|
||||
if ($this->metadata->get('scopes.' . $scope . '.disabled')) {
|
||||
$table->$scope = false;
|
||||
|
||||
unset($fieldTable->$scope);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function applyAdditional(&$table, &$fieldTable, &$valuePermissionLists): void
|
||||
{
|
||||
if ($this->user->isPortal()) {
|
||||
foreach ($this->getScopeList() as $scope) {
|
||||
$table->$scope = false;
|
||||
|
||||
unset($fieldTable->$scope);
|
||||
}
|
||||
|
||||
foreach ($this->valuePermissionList as $permission) {
|
||||
$valuePermissionLists->{$permission}[] = self::LEVEL_NO;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function mergeValueList(array $list, string $defaultValue): string
|
||||
{
|
||||
$result = null;
|
||||
|
||||
foreach ($list as $level) {
|
||||
if ($level === self::LEVEL_NOT_SET) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (is_null($result)) {
|
||||
$result = $level;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (
|
||||
array_search($result, $this->levelList) >
|
||||
array_search($level, $this->levelList)
|
||||
) {
|
||||
$result = $level;
|
||||
}
|
||||
}
|
||||
|
||||
if (is_null($result)) {
|
||||
$result = $defaultValue;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
protected function getScopeWithAclList(): array
|
||||
{
|
||||
$scopeList = [];
|
||||
|
||||
$scopes = $this->metadata->get('scopes');
|
||||
|
||||
foreach ($scopes as $scope => $d) {
|
||||
if (empty($d['acl'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$scopeList[] = $scope;
|
||||
}
|
||||
|
||||
return $scopeList;
|
||||
}
|
||||
|
||||
protected function getScopeList(): array
|
||||
{
|
||||
$scopeList = [];
|
||||
|
||||
$scopes = $this->metadata->get('scopes');
|
||||
|
||||
foreach ($scopes as $scope => $item) {
|
||||
$scopeList[] = $scope;
|
||||
}
|
||||
|
||||
return $scopeList;
|
||||
}
|
||||
|
||||
private function mergeTableList(array $tableList): StdClass
|
||||
{
|
||||
$data = (object) [];
|
||||
|
||||
$scopeList = $this->getScopeWithAclList();
|
||||
|
||||
foreach ($tableList as $table) {
|
||||
foreach ($scopeList as $scope) {
|
||||
if (!isset($table->$scope)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->mergeTableListItem($data, $scope, $table->$scope);
|
||||
}
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
private function mergeTableListItem(StdClass $data, string $scope, $row): void
|
||||
{
|
||||
if ($row === false || $row === null) {
|
||||
if (!isset($data->$scope)) {
|
||||
$data->$scope = false;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($row === true) {
|
||||
$data->$scope = true;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isset($data->$scope)) {
|
||||
$data->$scope = (object) [];
|
||||
}
|
||||
|
||||
if ($data->$scope === false) {
|
||||
$data->$scope = (object) [];
|
||||
}
|
||||
|
||||
if (!is_object($row)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$actionList = $this->metadata
|
||||
->get(['scopes', $scope, $this->type . 'ActionList'], $this->actionList);
|
||||
|
||||
foreach ($actionList as $i => $action) {
|
||||
if (isset($row->$action)) {
|
||||
$level = $row->$action;
|
||||
|
||||
if (!isset($data->$scope->$action)) {
|
||||
$data->$scope->$action = $level;
|
||||
}
|
||||
else if (
|
||||
array_search($data->$scope->$action, $this->levelList) >
|
||||
array_search($level, $this->levelList)
|
||||
) {
|
||||
$data->$scope->$action = $level;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($i === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// @todo Remove everything below.
|
||||
$previousAction = $this->actionList[$i - 1];
|
||||
|
||||
if (in_array($action, $this->booleanActionList)) {
|
||||
$data->$scope->$action = self::LEVEL_YES;
|
||||
}
|
||||
else if ($action === self::ACTION_STREAM && isset($data->$scope->$previousAction)) {
|
||||
$data->$scope->$action = $data->$scope->$previousAction;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function mergeFieldTableList(array $tableList): StdClass
|
||||
{
|
||||
$data = (object) [];
|
||||
|
||||
$scopeList = $this->getScopeWithAclList();
|
||||
|
||||
foreach ($tableList as $table) {
|
||||
foreach ($scopeList as $scope) {
|
||||
if (!isset($table->$scope)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isset($data->$scope)) {
|
||||
$data->$scope = (object) [];
|
||||
}
|
||||
|
||||
if (!is_object($table->$scope)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$fieldList = array_keys($this->metadata->get("entityDefs.{$scope}.fields", []));
|
||||
|
||||
foreach (get_object_vars($table->$scope) as $field => $row) {
|
||||
if (!is_object($row)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!in_array($field, $fieldList)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isset($data->$scope->$field)) {
|
||||
$data->$scope->$field = (object) [];
|
||||
}
|
||||
|
||||
foreach ($this->fieldActionList as $i => $action) {
|
||||
if (!isset($row->$action)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$level = $row->$action;
|
||||
|
||||
if (!isset($data->$scope->$field->$action)) {
|
||||
$data->$scope->$field->$action = $level;
|
||||
}
|
||||
else {
|
||||
if (
|
||||
array_search(
|
||||
$data->$scope->$field->$action,
|
||||
$this->fieldLevelList
|
||||
) > array_search($level, $this->fieldLevelList)
|
||||
) {
|
||||
$data->$scope->$field->$action = $level;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
private function buildCache(): void
|
||||
{
|
||||
$this->dataCache->store($this->cacheKey, $this->data);
|
||||
}
|
||||
}
|
||||
@@ -34,12 +34,14 @@ use Espo\Core\{
|
||||
Utils\DataCache,
|
||||
Utils\FieldUtil,
|
||||
Utils\Log,
|
||||
Utils\Config,
|
||||
};
|
||||
|
||||
use StdClass;
|
||||
|
||||
/**
|
||||
* Lists of restricted fields can be obtained from here. Restricted fields are specified in metadata > entityAcl.
|
||||
* Lists of restricted fields can be obtained from here. Restricted fields
|
||||
* are specified in metadata > entityAcl.
|
||||
*/
|
||||
class GlobalRestricton
|
||||
{
|
||||
@@ -82,7 +84,11 @@ class GlobalRestricton
|
||||
private $log;
|
||||
|
||||
public function __construct(
|
||||
Metadata $metadata, DataCache $dataCache, FieldUtil $fieldUtil, Log $log, bool $useCache = true
|
||||
Metadata $metadata,
|
||||
DataCache $dataCache,
|
||||
FieldUtil $fieldUtil,
|
||||
Log $log,
|
||||
Config $config
|
||||
) {
|
||||
$this->metadata = $metadata;
|
||||
$this->dataCache = $dataCache;
|
||||
@@ -91,17 +97,17 @@ class GlobalRestricton
|
||||
|
||||
$isFromCache = false;
|
||||
|
||||
if ($useCache) {
|
||||
if ($this->dataCache->has($this->cacheKey)) {
|
||||
$this->data = $this->dataCache->get($this->cacheKey);
|
||||
$useCache = $config->get('useCache');
|
||||
|
||||
$isFromCache = true;
|
||||
if ($useCache && $this->dataCache->has($this->cacheKey)) {
|
||||
$this->data = $this->dataCache->get($this->cacheKey);
|
||||
|
||||
if (!$this->data instanceof StdClass) {
|
||||
$this->log->error("ACL GlobalRestricton: Bad data fetched from cache.");
|
||||
$isFromCache = true;
|
||||
|
||||
$this->data = null;
|
||||
}
|
||||
if (!$this->data instanceof StdClass) {
|
||||
$this->log->error("ACL GlobalRestricton: Bad data fetched from cache.");
|
||||
|
||||
$this->data = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,19 +115,17 @@ class GlobalRestricton
|
||||
$this->buildData();
|
||||
}
|
||||
|
||||
if ($useCache) {
|
||||
if (!$isFromCache) {
|
||||
$this->storeCacheFile();
|
||||
}
|
||||
if ($useCache && !$isFromCache) {
|
||||
$this->storeCacheFile();
|
||||
}
|
||||
}
|
||||
|
||||
protected function storeCacheFile()
|
||||
protected function storeCacheFile(): void
|
||||
{
|
||||
$this->dataCache->store($this->cacheKey, $this->data, true);
|
||||
}
|
||||
|
||||
protected function buildData()
|
||||
protected function buildData(): void
|
||||
{
|
||||
$scopeList = array_keys($this->metadata->get(['entityDefs'], []));
|
||||
|
||||
@@ -160,6 +164,7 @@ class GlobalRestricton
|
||||
$scopeData->fields->$type = $resultFieldList;
|
||||
$scopeData->attributes->$type = $resultAttributeList;
|
||||
}
|
||||
|
||||
foreach ($this->linkTypeList as $type) {
|
||||
$resultLinkList = [];
|
||||
|
||||
|
||||
@@ -57,7 +57,7 @@ class OwnerUserFieldProvider
|
||||
*/
|
||||
public function get(string $entityType) : ?string
|
||||
{
|
||||
$value = $this->metadata->get(['entityAcl', $entityType, 'readOwnerUserField']);
|
||||
$value = $this->metadata->get(['aclDefs', $entityType, 'readOwnerUserField']);
|
||||
|
||||
if ($value) {
|
||||
return $value;
|
||||
|
||||
+2
-4
@@ -27,11 +27,9 @@
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\AclPortal;
|
||||
namespace Espo\Core\Acl;
|
||||
|
||||
use Espo\Core\Acl\ScopeAcl;
|
||||
|
||||
interface PortalScopeAcl extends ScopeAcl
|
||||
interface OwnershipChecker
|
||||
{
|
||||
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2021 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* EspoCRM is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* EspoCRM is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
|
||||
*
|
||||
* 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 General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Acl;
|
||||
|
||||
use Espo\Core\{
|
||||
Utils\Metadata,
|
||||
InjectableFactory,
|
||||
Acl\Exceptions\NotImplemented,
|
||||
AclManager,
|
||||
Binding\BindingContainer,
|
||||
Binding\Binder,
|
||||
Binding\BindingData,
|
||||
};
|
||||
|
||||
class OwnershipCheckerFactory
|
||||
{
|
||||
private $defaultClassName = DefaultOwnershipChecker::class;
|
||||
|
||||
private $metadata;
|
||||
|
||||
private $injectableFactory;
|
||||
|
||||
public function __construct(
|
||||
Metadata $metadata,
|
||||
InjectableFactory $injectableFactory
|
||||
) {
|
||||
$this->metadata = $metadata;
|
||||
$this->injectableFactory = $injectableFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an ownership checker.
|
||||
*
|
||||
* @throws NotImplemented
|
||||
*/
|
||||
public function create(string $scope, AclManager $aclManager): OwnershipChecker
|
||||
{
|
||||
$className = $this->getClassName($scope);
|
||||
|
||||
$bindingContainer = $this->createBindingContainer($aclManager);
|
||||
|
||||
return $this->injectableFactory->createWithBinding($className, $bindingContainer);
|
||||
}
|
||||
|
||||
private function getClassName(string $scope): string
|
||||
{
|
||||
$className = $this->metadata->get(['aclDefs', $scope, 'ownershipCheckerClassName']);
|
||||
|
||||
if ($className) {
|
||||
return $className;
|
||||
}
|
||||
|
||||
if (!$this->metadata->get(['scopes', $scope])) {
|
||||
throw new NotImplemented();
|
||||
}
|
||||
|
||||
return $this->defaultClassName;
|
||||
}
|
||||
|
||||
private function createBindingContainer(AclManager $aclManager): BindingContainer
|
||||
{
|
||||
$bindingData = new BindingData();
|
||||
|
||||
$binder = new Binder($bindingData);
|
||||
|
||||
$binder->bindCallback(
|
||||
AclManager::class,
|
||||
function () use ($aclManager): AclManager {
|
||||
return $aclManager;
|
||||
}
|
||||
);
|
||||
|
||||
return new BindingContainer($bindingData);
|
||||
}
|
||||
}
|
||||
+3
-3
@@ -33,10 +33,10 @@ use Espo\ORM\Entity;
|
||||
|
||||
use Espo\Entities\User;
|
||||
|
||||
interface EntityAcl
|
||||
interface OwnershipOwnChecker extends OwnershipChecker
|
||||
{
|
||||
/**
|
||||
* Check access to an entity.
|
||||
* Check whether a user is an owner of an entity.
|
||||
*/
|
||||
public function checkEntity(User $user, Entity $entity, ScopeData $data, string $action): bool;
|
||||
public function checkOwn(User $user, Entity $entity): bool;
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2021 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* EspoCRM is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* EspoCRM is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
|
||||
*
|
||||
* 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 General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Acl;
|
||||
|
||||
use Espo\ORM\Entity;
|
||||
|
||||
use Espo\Entities\User;
|
||||
|
||||
interface OwnershipTeamChecker extends OwnershipChecker
|
||||
{
|
||||
/**
|
||||
* Check whether an entity belongs to a user team.
|
||||
*/
|
||||
public function checkTeam(User $user, Entity $entity): bool;
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2021 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* EspoCRM is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* EspoCRM is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
|
||||
*
|
||||
* 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 General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Acl;
|
||||
|
||||
/**
|
||||
* Checks scope access.
|
||||
*/
|
||||
class ScopeChecker
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
public function check(ScopeData $data, ?string $action = null, ?ScopeCheckerData $checkerData = null): bool
|
||||
{
|
||||
if ($data->isFalse()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($data->isTrue()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($action === null) {
|
||||
if ($data->hasNotNo()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$level = $data->get($action);
|
||||
|
||||
if ($level === Table::LEVEL_ALL || $level === Table::LEVEL_YES) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($level === Table::LEVEL_NO) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!$checkerData) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($level === Table::LEVEL_OWN || $level === Table::LEVEL_TEAM) {
|
||||
if ($checkerData->isOwn()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if ($level === Table::LEVEL_TEAM) {
|
||||
if ($checkerData->inTeam()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2021 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* EspoCRM is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* EspoCRM is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
|
||||
*
|
||||
* 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 General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Acl;
|
||||
|
||||
/**
|
||||
* Scope checker data.
|
||||
*/
|
||||
class ScopeCheckerData
|
||||
{
|
||||
private $isOwnChecker;
|
||||
|
||||
private $inTeamChecker;
|
||||
|
||||
public function __construct(callable $isOwnChecker, callable $inTeamChecker)
|
||||
{
|
||||
$this->isOwnChecker = $isOwnChecker;
|
||||
$this->inTeamChecker = $inTeamChecker;
|
||||
}
|
||||
|
||||
public function isOwn(): bool
|
||||
{
|
||||
return ($this->isOwnChecker)();
|
||||
}
|
||||
|
||||
public function inTeam(): bool
|
||||
{
|
||||
return ($this->inTeamChecker)();
|
||||
}
|
||||
|
||||
public static function createBuilder(): ScopeCheckerDataBuilder
|
||||
{
|
||||
return new ScopeCheckerDataBuilder();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2021 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* EspoCRM is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* EspoCRM is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
|
||||
*
|
||||
* 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 General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Acl;
|
||||
|
||||
/**
|
||||
* Builds scope checker data.
|
||||
*/
|
||||
class ScopeCheckerDataBuilder
|
||||
{
|
||||
private $isOwnChecker;
|
||||
|
||||
private $inTeamChecker;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->isOwnChecker = function (): bool {
|
||||
return false;
|
||||
};
|
||||
|
||||
$this->inTeamChecker = function (): bool {
|
||||
return false;
|
||||
};
|
||||
}
|
||||
|
||||
public function setIsOwn(bool $value): self
|
||||
{
|
||||
if ($value) {
|
||||
$this->isOwnChecker = function (): bool {
|
||||
return true;
|
||||
};
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
$this->isOwnChecker = function (): bool {
|
||||
return false;
|
||||
};
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setInTeam(bool $value): self
|
||||
{
|
||||
if ($value) {
|
||||
$this->inTeamChecker = function (): bool {
|
||||
return true;
|
||||
};
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
$this->inTeamChecker = function (): bool {
|
||||
return false;
|
||||
};
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setIsOwnChecker(callable $checker): self
|
||||
{
|
||||
$this->isOwnChecker = $checker;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setInTeamChecker(callable $checker): self
|
||||
{
|
||||
$this->inTeamChecker = $checker;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function build(): ScopeCheckerData
|
||||
{
|
||||
return new ScopeCheckerData($this->isOwnChecker, $this->inTeamChecker);
|
||||
}
|
||||
}
|
||||
@@ -53,20 +53,6 @@ class ScopeData
|
||||
throw new RuntimeException("Accessing ScopeData properties is not allowed.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a raw value.
|
||||
*
|
||||
* @return StdClass|bool
|
||||
*/
|
||||
public function getRaw()
|
||||
{
|
||||
if (!$this->isBoolean()) {
|
||||
return clone $this->raw;
|
||||
}
|
||||
|
||||
return $this->raw;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is of boolean type.
|
||||
*/
|
||||
@@ -175,6 +161,12 @@ class ScopeData
|
||||
$obj->isBoolean = false;
|
||||
|
||||
$obj->actionData = get_object_vars($raw);
|
||||
|
||||
foreach ($obj->actionData as $item) {
|
||||
if (!is_string($item)) {
|
||||
throw new RuntimeException("Bad raw scope data.");
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (is_bool($raw)) {
|
||||
$obj->isBoolean = true;
|
||||
|
||||
@@ -29,27 +29,12 @@
|
||||
|
||||
namespace Espo\Core\Acl;
|
||||
|
||||
use Espo\Core\Exceptions\Error;
|
||||
|
||||
use Espo\Entities\User;
|
||||
|
||||
use Espo\Core\{
|
||||
ORM\EntityManager,
|
||||
Utils\Config,
|
||||
Utils\Metadata,
|
||||
Utils\FieldUtil,
|
||||
Utils\DataCache,
|
||||
Utils\ObjectUtil,
|
||||
};
|
||||
|
||||
use StdClass;
|
||||
use Traversable;
|
||||
|
||||
/**
|
||||
* A table is generated for each user. Multiple roles are merged into a single table.
|
||||
* This table is used for access checking.
|
||||
* Contains access levels for a user.
|
||||
*/
|
||||
class Table
|
||||
interface Table
|
||||
{
|
||||
public const LEVEL_YES = 'yes';
|
||||
|
||||
@@ -71,871 +56,40 @@ class Table
|
||||
|
||||
public const ACTION_CREATE = 'create';
|
||||
|
||||
protected $type = 'acl';
|
||||
|
||||
protected $defaultAclType = 'recordAllTeamOwnNo';
|
||||
|
||||
private $data = null;
|
||||
|
||||
protected $cacheKey;
|
||||
|
||||
protected $actionList = ['read', 'stream', 'edit', 'delete', 'create'];
|
||||
|
||||
protected $booleanActionList = ['create'];
|
||||
|
||||
protected $levelList = ['yes', 'all', 'team', 'own', 'no'];
|
||||
|
||||
protected $fieldActionList = ['read', 'edit'];
|
||||
|
||||
protected $fieldLevelList = ['yes', 'no'];
|
||||
|
||||
protected $valuePermissionHighestLevels = [];
|
||||
|
||||
protected $valuePermissionList = [];
|
||||
|
||||
protected $forbiddenAttributesCache = [];
|
||||
|
||||
protected $forbiddenFieldsCache = [];
|
||||
|
||||
protected $isStrictModeForced = false;
|
||||
|
||||
protected $isStrictMode = false;
|
||||
|
||||
protected $entityManager;
|
||||
|
||||
protected $user;
|
||||
|
||||
protected $config;
|
||||
|
||||
protected $metadata;
|
||||
|
||||
protected $fieldUtil;
|
||||
|
||||
protected $dataCache;
|
||||
|
||||
public function __construct(
|
||||
EntityManager $entityManager,
|
||||
User $user,
|
||||
Config $config,
|
||||
Metadata $metadata,
|
||||
FieldUtil $fieldUtil,
|
||||
DataCache $dataCache
|
||||
) {
|
||||
$this->entityManager = $entityManager;
|
||||
|
||||
$this->data = (object) [
|
||||
'table' => (object) [],
|
||||
'fieldTable' => (object) [],
|
||||
'fieldTableQuickAccess' => (object) [],
|
||||
];
|
||||
|
||||
if ($this->isStrictModeForced) {
|
||||
$this->isStrictMode = true;
|
||||
}
|
||||
else {
|
||||
$this->isStrictMode = $config->get('aclStrictMode', true);
|
||||
}
|
||||
|
||||
$this->user = $user;
|
||||
$this->metadata = $metadata;
|
||||
$this->fieldUtil = $fieldUtil;
|
||||
$this->dataCache = $dataCache;
|
||||
|
||||
if (!$this->user->isFetched()) {
|
||||
throw new Error('User must be fetched before ACL check.');
|
||||
}
|
||||
|
||||
$this->valuePermissionList = $this->metadata
|
||||
->get(['app', $this->type, 'valuePermissionList'], []);
|
||||
|
||||
$this->valuePermissionHighestLevels = $this->metadata
|
||||
->get(['app', $this->type, 'valuePermissionHighestLevels'], []);
|
||||
|
||||
$this->initCacheKey();
|
||||
|
||||
if ($config && $config->get('useCache') && $this->dataCache->has($this->cacheKey)) {
|
||||
$this->data = $this->dataCache->get($this->cacheKey);
|
||||
}
|
||||
else {
|
||||
$this->load();
|
||||
|
||||
if ($config && $config->get('useCache')) {
|
||||
$this->buildCache();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function initCacheKey(): void
|
||||
{
|
||||
$this->cacheKey = 'acl/' . $this->user->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a full map.
|
||||
*/
|
||||
public function getMap(): StdClass
|
||||
{
|
||||
return ObjectUtil::clone($this->data);
|
||||
}
|
||||
public function getMap(): StdClass;
|
||||
|
||||
/**
|
||||
* Get scope data.
|
||||
*/
|
||||
public function getScopeData(string $scope): ScopeData
|
||||
{
|
||||
if (!isset($this->data->table->$scope)) {
|
||||
return ScopeData::fromRaw(false);
|
||||
}
|
||||
|
||||
$data = $this->data->table->$scope;
|
||||
|
||||
if (is_string($data)) {
|
||||
return $this->getScopeData($data);
|
||||
}
|
||||
|
||||
return ScopeData::fromRaw($data);
|
||||
}
|
||||
public function getScopeData(string $scope): ScopeData;
|
||||
|
||||
/**
|
||||
* Get a permission.
|
||||
* Get a permission level.
|
||||
*/
|
||||
public function get(string $permission): ?string
|
||||
{
|
||||
if ($permission === 'table') {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (isset($this->data->$permission)) {
|
||||
return $this->data->$permission;
|
||||
}
|
||||
|
||||
return self::LEVEL_NO;
|
||||
}
|
||||
|
||||
private function load()
|
||||
{
|
||||
$valuePermissionLists = (object) [];
|
||||
|
||||
foreach ($this->valuePermissionList as $permission) {
|
||||
$valuePermissionLists->$permission = [];
|
||||
}
|
||||
|
||||
$aclTableList = [];
|
||||
$fieldTableList = [];
|
||||
|
||||
if (!$this->user->isAdmin()) {
|
||||
$roleList = $this->getRoleList();
|
||||
|
||||
foreach ($roleList as $role) {
|
||||
$aclTableList[] = $role->get('data');
|
||||
$fieldTableList[] = $role->get('fieldData');
|
||||
|
||||
foreach ($this->valuePermissionList as $permission) {
|
||||
$valuePermissionLists->{$permission}[] = $role->get($permission);
|
||||
}
|
||||
}
|
||||
|
||||
$aclTable = $this->mergeTableList($aclTableList);
|
||||
$fieldTable = $this->mergeFieldTableList($fieldTableList);
|
||||
|
||||
$this->applyDefault($aclTable, $fieldTable);
|
||||
$this->applyDisabled($aclTable, $fieldTable);
|
||||
$this->applyMandatory($aclTable, $fieldTable);
|
||||
$this->applyAdditional($aclTable, $fieldTable, $valuePermissionLists);
|
||||
}
|
||||
|
||||
if ($this->user->isAdmin()) {
|
||||
$aclTable = (object) [];
|
||||
$fieldTable = (object) [];
|
||||
|
||||
$this->applyHighest($aclTable, $fieldTable);
|
||||
}
|
||||
|
||||
foreach ($aclTable as $scope => $data) {
|
||||
if (is_string($data) && isset($aclTable->$data)) {
|
||||
$aclTable->$scope = $aclTable->$data;
|
||||
}
|
||||
}
|
||||
|
||||
$this->data->table = $aclTable;
|
||||
$this->data->fieldTable = $fieldTable;
|
||||
|
||||
$this->fillFieldTableQuickAccess();
|
||||
|
||||
if (!$this->user->isAdmin()) {
|
||||
$permissionsDefaultsGroupName = 'permissionsDefaults';
|
||||
|
||||
if ($this->isStrictMode) {
|
||||
$permissionsDefaultsGroupName = 'permissionsStrictDefaults';
|
||||
}
|
||||
|
||||
foreach ($this->valuePermissionList as $permission) {
|
||||
$this->data->$permission = $this->mergeValueList(
|
||||
$valuePermissionLists->$permission,
|
||||
$this->metadata
|
||||
->get(['app', $this->type, $permissionsDefaultsGroupName, $permission, self::LEVEL_YES])
|
||||
);
|
||||
|
||||
if ($this->metadata->get('app.'.$this->type.'.mandatory.' . $permission)) {
|
||||
$this->data->$permission = $this->metadata
|
||||
->get('app.'.$this->type.'.mandatory.' . $permission);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->user->isAdmin()) {
|
||||
foreach ($this->valuePermissionList as $permission) {
|
||||
if (isset($this->valuePermissionHighestLevels[$permission])) {
|
||||
$this->data->$permission = $this->valuePermissionHighestLevels[$permission];
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->data->$permission = self::LEVEL_ALL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function getRoleList()
|
||||
{
|
||||
$roleList = [];
|
||||
|
||||
$userRoleList = $this->entityManager
|
||||
->getRepository('User')
|
||||
->getRelation($this->user, 'roles')
|
||||
->find();
|
||||
|
||||
if (!$userRoleList instanceof Traversable) {
|
||||
throw new Error();
|
||||
}
|
||||
|
||||
foreach ($userRoleList as $role) {
|
||||
$roleList[] = $role;
|
||||
}
|
||||
|
||||
$teamList = $this->entityManager
|
||||
->getRepository('User')
|
||||
->getRelation($this->user, 'teams')
|
||||
->find();
|
||||
|
||||
if (! $teamList instanceof Traversable) {
|
||||
throw new Error();
|
||||
}
|
||||
|
||||
foreach ($teamList as $team) {
|
||||
$teamRoleList = $this->entityManager
|
||||
->getRepository('Team')
|
||||
->getRelation($team, 'roles')
|
||||
->find();
|
||||
|
||||
foreach ($teamRoleList as $role) {
|
||||
$roleList[] = $role;
|
||||
}
|
||||
}
|
||||
|
||||
return $roleList;
|
||||
}
|
||||
|
||||
public function getScopeForbiddenAttributeList(
|
||||
string $scope,
|
||||
string $action = self::ACTION_READ,
|
||||
string $thresholdLevel = self::LEVEL_NO
|
||||
): array {
|
||||
|
||||
$key = $scope . '_'. $action . '_' . $thresholdLevel;
|
||||
|
||||
if (isset($this->forbiddenAttributesCache[$key])) {
|
||||
return $this->forbiddenAttributesCache[$key];
|
||||
}
|
||||
|
||||
$fieldTableQuickAccess = $this->data->fieldTableQuickAccess;
|
||||
|
||||
if (
|
||||
!isset($fieldTableQuickAccess->$scope) || !isset($fieldTableQuickAccess->$scope->attributes) ||
|
||||
!isset($fieldTableQuickAccess->$scope->attributes->$action)
|
||||
) {
|
||||
$this->forbiddenAttributesCache[$key] = [];
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
$levelList = [];
|
||||
|
||||
foreach ($this->fieldLevelList as $level) {
|
||||
if (array_search($level, $this->fieldLevelList) >= array_search($thresholdLevel, $this->fieldLevelList)) {
|
||||
$levelList[] = $level;
|
||||
}
|
||||
}
|
||||
|
||||
$attributeList = [];
|
||||
|
||||
foreach ($levelList as $level) {
|
||||
if (!isset($fieldTableQuickAccess->$scope->attributes->$action->$level)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($fieldTableQuickAccess->$scope->attributes->$action->$level as $attribute) {
|
||||
if (in_array($attribute, $attributeList)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$attributeList[] = $attribute;
|
||||
}
|
||||
}
|
||||
|
||||
$this->forbiddenAttributesCache[$key] = $attributeList;
|
||||
|
||||
return $attributeList;
|
||||
}
|
||||
|
||||
public function getScopeForbiddenFieldList(
|
||||
string $scope,
|
||||
string $action = self::ACTION_READ,
|
||||
string $thresholdLevel = self::LEVEL_NO
|
||||
): array {
|
||||
|
||||
$key = $scope . '_'. $action . '_' . $thresholdLevel;
|
||||
|
||||
if (isset($this->forbiddenFieldsCache[$key])) {
|
||||
return $this->forbiddenFieldsCache[$key];
|
||||
}
|
||||
|
||||
$fieldTableQuickAccess = $this->data->fieldTableQuickAccess;
|
||||
|
||||
if (
|
||||
!isset($fieldTableQuickAccess->$scope) || !isset($fieldTableQuickAccess->$scope->fields) ||
|
||||
!isset($fieldTableQuickAccess->$scope->fields->$action)
|
||||
) {
|
||||
$this->forbiddenFieldsCache[$key] = [];
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
$levelList = [];
|
||||
|
||||
foreach ($this->fieldLevelList as $level) {
|
||||
if (array_search($level, $this->fieldLevelList) >= array_search($thresholdLevel, $this->fieldLevelList)) {
|
||||
$levelList[] = $level;
|
||||
}
|
||||
}
|
||||
|
||||
$fieldList = [];
|
||||
|
||||
foreach ($levelList as $level) {
|
||||
if (!isset($fieldTableQuickAccess->$scope->fields->$action->$level)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($fieldTableQuickAccess->$scope->fields->$action->$level as $field) {
|
||||
if (in_array($field, $fieldList)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$fieldList[] = $field;
|
||||
}
|
||||
}
|
||||
|
||||
$this->forbiddenFieldsCache[$key] = $fieldList;
|
||||
|
||||
return $fieldList;
|
||||
}
|
||||
|
||||
protected function fillFieldTableQuickAccess(): void
|
||||
{
|
||||
$fieldTable = $this->data->fieldTable;
|
||||
|
||||
$fieldTableQuickAccess = (object) [];
|
||||
|
||||
foreach (get_object_vars($fieldTable) as $scope => $scopeData) {
|
||||
$fieldTableQuickAccess->$scope = (object) [
|
||||
'attributes' => (object) [],
|
||||
'fields' => (object) []
|
||||
];
|
||||
|
||||
foreach ($this->fieldActionList as $action) {
|
||||
$fieldTableQuickAccess->$scope->attributes->$action = (object) [];
|
||||
$fieldTableQuickAccess->$scope->fields->$action = (object) [];
|
||||
|
||||
foreach ($this->fieldLevelList as $level) {
|
||||
$fieldTableQuickAccess->$scope->attributes->$action->$level = [];
|
||||
$fieldTableQuickAccess->$scope->fields->$action->$level = [];
|
||||
}
|
||||
}
|
||||
|
||||
foreach (get_object_vars($scopeData) as $field => $fieldData) {
|
||||
$attributeList = $this->fieldUtil->getAttributeList($scope, $field);
|
||||
|
||||
foreach ($this->fieldActionList as $action) {
|
||||
if (!isset($fieldData->$action)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($this->fieldLevelList as $level) {
|
||||
if ($fieldData->$action === $level) {
|
||||
$fieldTableQuickAccess->$scope->fields->$action->{$level}[] = $field;
|
||||
|
||||
foreach ($attributeList as $attribute) {
|
||||
$fieldTableQuickAccess->$scope->attributes->$action->{$level}[] = $attribute;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->data->fieldTableQuickAccess = $fieldTableQuickAccess;
|
||||
}
|
||||
|
||||
protected function applyHighest(StdClass &$table, StdClass &$fieldTable): void
|
||||
{
|
||||
foreach ($this->getScopeList() as $scope) {
|
||||
if ($this->metadata->get(['scopes', $scope, $this->type]) === 'boolean') {
|
||||
$table->$scope = true;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$this->metadata->get(['scopes', $scope, 'entity'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$table->$scope = (object) [];
|
||||
|
||||
$actionList = $this->metadata->get(
|
||||
['scopes', $scope, $this->type . 'ActionList'],
|
||||
$this->actionList
|
||||
);
|
||||
|
||||
$highest = $this->metadata->get(
|
||||
['scopes', $scope, $this->type . 'HighestLevel'],
|
||||
self::LEVEL_ALL
|
||||
);
|
||||
|
||||
foreach ($actionList as $action) {
|
||||
$table->$scope->$action = $highest;
|
||||
|
||||
if (in_array($action, $this->booleanActionList)) {
|
||||
$table->$scope->$action = 'yes';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function applyDefault(&$table, &$fieldTable): void
|
||||
{
|
||||
if ($this->user->isAdmin()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$defaultsGroupName = 'default';
|
||||
|
||||
if ($this->isStrictMode) {
|
||||
$defaultsGroupName = 'strictDefault';
|
||||
}
|
||||
|
||||
$data = $this->metadata->get(['app', $this->type, $defaultsGroupName, 'scopeLevel'], []);
|
||||
|
||||
foreach ($data as $scope => $item) {
|
||||
if (isset($table->$scope)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$value = $item;
|
||||
|
||||
if (is_array($item)) {
|
||||
$value = (object) $item;
|
||||
}
|
||||
|
||||
$table->$scope = $value;
|
||||
}
|
||||
|
||||
$defaultFieldData = $this->metadata->get(['app', $this->type, $defaultsGroupName, 'fieldLevel'], []);
|
||||
|
||||
foreach ($this->getScopeList() as $scope) {
|
||||
if (isset($table->$scope) && $table->$scope === false) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$this->metadata->get('scopes.' . $scope . '.entity')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$fieldList = array_keys($this->metadata->get("entityDefs.{$scope}.fields", []));
|
||||
|
||||
$defaultScopeFieldData = $this->metadata
|
||||
->get('app.'.$this->type.'.'.$defaultsGroupName.'.scopeFieldLevel.' . $scope, []);
|
||||
|
||||
foreach (array_merge($defaultFieldData, $defaultScopeFieldData) as $field => $f) {
|
||||
if (!in_array($field, $fieldList)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isset($fieldTable->$scope)) {
|
||||
$fieldTable->$scope = (object) [];
|
||||
}
|
||||
|
||||
if (isset($fieldTable->$scope->$field)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$fieldTable->$scope->$field = (object) [];
|
||||
|
||||
foreach ($this->fieldActionList as $action) {
|
||||
$level = self::LEVEL_NO;
|
||||
|
||||
if ($f === true) {
|
||||
$level = self::LEVEL_YES;
|
||||
}
|
||||
else {
|
||||
if (is_array($f) && isset($f[$action])) {
|
||||
$level = $f[$action];
|
||||
}
|
||||
}
|
||||
|
||||
$fieldTable->$scope->$field->$action = $level;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($this->getScopeWithAclList() as $scope) {
|
||||
if (!isset($table->$scope)) {
|
||||
$aclType = $this->metadata->get('scopes.' . $scope . '.' . $this->type);
|
||||
|
||||
if ($aclType === true) {
|
||||
$aclType = $this->defaultAclType;
|
||||
}
|
||||
|
||||
if (!empty($aclType)) {
|
||||
$paramDefaultsName = 'scopeLevelTypesDefaults';
|
||||
|
||||
if ($this->isStrictMode) {
|
||||
$paramDefaultsName = 'scopeLevelTypesStrictDefaults';
|
||||
}
|
||||
|
||||
$defaultValue = $this->metadata
|
||||
->get(
|
||||
['app', $this->type, $paramDefaultsName, $aclType],
|
||||
$this->metadata->get(['app', $this->type, $paramDefaultsName, 'record'])
|
||||
);
|
||||
|
||||
if (is_array($defaultValue)) {
|
||||
$defaultValue = (object) $defaultValue;
|
||||
}
|
||||
|
||||
$table->$scope = $defaultValue;
|
||||
|
||||
if (is_object($table->$scope)) {
|
||||
$actionList = $this->metadata->get(['scopes', $scope, $this->type . 'ActionList']);
|
||||
|
||||
if ($actionList) {
|
||||
foreach (get_object_vars($table->$scope) as $action => $level) {
|
||||
if (!in_array($action, $actionList)) {
|
||||
unset($table->$scope->$action);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function applyMandatory(&$table, &$fieldTable): void
|
||||
{
|
||||
if ($this->user->isAdmin()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$data = $this->metadata->get('app.'.$this->type.'.mandatory.scopeLevel', []);
|
||||
|
||||
foreach ($data as $scope => $item) {
|
||||
$value = $item;
|
||||
|
||||
if (is_array($item)) {
|
||||
$value = (object) $item;
|
||||
}
|
||||
|
||||
$table->$scope = $value;
|
||||
}
|
||||
|
||||
$mandatoryFieldData = $this->metadata->get('app.'.$this->type.'.mandatory.fieldLevel', []);
|
||||
|
||||
foreach ($this->getScopeList() as $scope) {
|
||||
if (isset($table->$scope) && $table->$scope === false) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$this->metadata->get('scopes.' . $scope . '.entity')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$fieldList = array_keys($this->metadata->get("entityDefs.{$scope}.fields", []));
|
||||
|
||||
$mandatoryScopeFieldData = $this->metadata
|
||||
->get('app.'.$this->type.'.mandatory.scopeFieldLevel.' . $scope, []);
|
||||
|
||||
foreach (array_merge($mandatoryFieldData, $mandatoryScopeFieldData) as $field => $f) {
|
||||
if (!in_array($field, $fieldList)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isset($fieldTable->$scope)) {
|
||||
$fieldTable->$scope = (object) [];
|
||||
}
|
||||
|
||||
$fieldTable->$scope->$field = (object) [];
|
||||
|
||||
foreach ($this->fieldActionList as $action) {
|
||||
$level = self::LEVEL_NO;
|
||||
|
||||
if ($f === true) {
|
||||
$level = self::LEVEL_YES;
|
||||
}
|
||||
else {
|
||||
if (is_array($f) && isset($f[$action])) {
|
||||
$level = $f[$action];
|
||||
}
|
||||
}
|
||||
|
||||
$fieldTable->$scope->$field->$action = $level;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function applyDisabled(&$table, &$fieldTable): void
|
||||
{
|
||||
if ($this->user->isAdmin()) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($this->getScopeList() as $scope) {
|
||||
if ($this->metadata->get('scopes.' . $scope . '.disabled')) {
|
||||
$table->$scope = false;
|
||||
|
||||
unset($fieldTable->$scope);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function applyAdditional(&$table, &$fieldTable, &$valuePermissionLists): void
|
||||
{
|
||||
if ($this->user->isPortal()) {
|
||||
foreach ($this->getScopeList() as $scope) {
|
||||
$table->$scope = false;
|
||||
|
||||
unset($fieldTable->$scope);
|
||||
}
|
||||
|
||||
foreach ($this->valuePermissionList as $permission) {
|
||||
$valuePermissionLists->{$permission}[] = self::LEVEL_NO;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function mergeValueList(array $list, string $defaultValue): string
|
||||
{
|
||||
$result = null;
|
||||
|
||||
foreach ($list as $level) {
|
||||
if ($level !== 'not-set') {
|
||||
if (is_null($result)) {
|
||||
$result = $level;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (array_search($result, $this->levelList) > array_search($level, $this->levelList)) {
|
||||
$result = $level;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (is_null($result)) {
|
||||
$result = $defaultValue;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
protected function getScopeWithAclList(): array
|
||||
{
|
||||
$scopeList = [];
|
||||
|
||||
$scopes = $this->metadata->get('scopes');
|
||||
|
||||
foreach ($scopes as $scope => $d) {
|
||||
if (empty($d['acl'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$scopeList[] = $scope;
|
||||
}
|
||||
|
||||
return $scopeList;
|
||||
}
|
||||
|
||||
protected function getScopeList(): array
|
||||
{
|
||||
$scopeList = [];
|
||||
|
||||
$scopes = $this->metadata->get('scopes');
|
||||
|
||||
foreach ($scopes as $scope => $d) {
|
||||
$scopeList[] = $scope;
|
||||
}
|
||||
|
||||
return $scopeList;
|
||||
}
|
||||
|
||||
private function mergeTableList(array $tableList): StdClass
|
||||
{
|
||||
$data = (object) [];
|
||||
|
||||
$scopeList = $this->getScopeWithAclList();
|
||||
|
||||
foreach ($tableList as $table) {
|
||||
foreach ($scopeList as $scope) {
|
||||
if (!isset($table->$scope)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->mergeTableListItem($data, $scope, $table->$scope);
|
||||
}
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
private function mergeTableListItem(StdClass $data, string $scope, $row): void
|
||||
{
|
||||
if ($row === false || $row === null) {
|
||||
if (!isset($data->$scope)) {
|
||||
$data->$scope = false;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($row === true) {
|
||||
$data->$scope = true;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isset($data->$scope)) {
|
||||
$data->$scope = (object) [];
|
||||
}
|
||||
|
||||
if ($data->$scope === false) {
|
||||
$data->$scope = (object) [];
|
||||
}
|
||||
|
||||
if (!is_object($row)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$actionList = $this->metadata
|
||||
->get(['scopes', $scope, $this->type . 'ActionList'], $this->actionList);
|
||||
|
||||
foreach ($actionList as $i => $action) {
|
||||
if (isset($row->$action)) {
|
||||
$level = $row->$action;
|
||||
|
||||
if (!isset($data->$scope->$action)) {
|
||||
$data->$scope->$action = $level;
|
||||
}
|
||||
else if (
|
||||
array_search($data->$scope->$action, $this->levelList) >
|
||||
array_search($level, $this->levelList)
|
||||
) {
|
||||
$data->$scope->$action = $level;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($i === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// @todo Remove everything below.
|
||||
$previousAction = $this->actionList[$i - 1];
|
||||
|
||||
if (in_array($action, $this->booleanActionList)) {
|
||||
$data->$scope->$action = self::LEVEL_YES;
|
||||
}
|
||||
else if ($action === self::ACTION_STREAM && isset($data->$scope->$previousAction)) {
|
||||
$data->$scope->$action = $data->$scope->$previousAction;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function mergeFieldTableList(array $tableList): StdClass
|
||||
{
|
||||
$data = (object) [];
|
||||
|
||||
$scopeList = $this->getScopeWithAclList();
|
||||
|
||||
foreach ($tableList as $table) {
|
||||
foreach ($scopeList as $scope) {
|
||||
if (!isset($table->$scope)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isset($data->$scope)) {
|
||||
$data->$scope = (object) [];
|
||||
}
|
||||
|
||||
if (!is_object($table->$scope)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$fieldList = array_keys($this->metadata->get("entityDefs.{$scope}.fields", []));
|
||||
|
||||
foreach (get_object_vars($table->$scope) as $field => $row) {
|
||||
if (!is_object($row)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!in_array($field, $fieldList)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isset($data->$scope->$field)) {
|
||||
$data->$scope->$field = (object) [];
|
||||
}
|
||||
|
||||
foreach ($this->fieldActionList as $i => $action) {
|
||||
if (!isset($row->$action)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$level = $row->$action;
|
||||
|
||||
if (!isset($data->$scope->$field->$action)) {
|
||||
$data->$scope->$field->$action = $level;
|
||||
}
|
||||
else {
|
||||
if (
|
||||
array_search(
|
||||
$data->$scope->$field->$action,
|
||||
$this->fieldLevelList
|
||||
) > array_search($level, $this->fieldLevelList)
|
||||
) {
|
||||
$data->$scope->$field->$action = $level;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
private function buildCache(): void
|
||||
{
|
||||
$this->dataCache->store($this->cacheKey, $this->data);
|
||||
}
|
||||
public function getPermissionLevel(string $permission): string;
|
||||
|
||||
/**
|
||||
* Get a list of forbidden attributes for a scope and action.
|
||||
*
|
||||
* @param $scope A scope.
|
||||
* $param $action An action.
|
||||
* @param $thresholdLevel An attribute will be treated as forbidden if the level is
|
||||
* equal to or lower than the threshold.
|
||||
* @return array<string>
|
||||
*/
|
||||
public function getScopeForbiddenAttributeList(string $scope, string $action, string $thresholdLevel): array;
|
||||
|
||||
/**
|
||||
* Get a list of forbidden fields for a scope and action.
|
||||
*
|
||||
* @param $scope A scope.
|
||||
* $param $action An action.
|
||||
* @param $thresholdLevel An attribute will be treated as forbidden if the level is
|
||||
* equal to or lower than the threshold.
|
||||
* @return array<string>
|
||||
*/
|
||||
public function getScopeForbiddenFieldList(string $scope, string $action, string $thresholdLevel): array;
|
||||
}
|
||||
|
||||
+12
-9
@@ -29,27 +29,30 @@
|
||||
|
||||
namespace Espo\Core\Acl;
|
||||
|
||||
use Espo\Entities\User;
|
||||
|
||||
use Espo\Core\{
|
||||
Utils\Config,
|
||||
InjectableFactory,
|
||||
};
|
||||
|
||||
class GlobalRestrictonFactory
|
||||
class TableFactory
|
||||
{
|
||||
private $config;
|
||||
|
||||
private $injectableFactory;
|
||||
|
||||
public function __construct(Config $config, InjectableFactory $injectableFactory)
|
||||
public function __construct(InjectableFactory $injectableFactory)
|
||||
{
|
||||
$this->config = $config;
|
||||
$this->injectableFactory = $injectableFactory;
|
||||
}
|
||||
|
||||
public function create(): GlobalRestricton
|
||||
/**
|
||||
* Create a table.
|
||||
*
|
||||
* @todo Use binding.
|
||||
*/
|
||||
public function create(User $user): Table
|
||||
{
|
||||
return $this->injectableFactory->createWith(GlobalRestricton::class, [
|
||||
'useCache' => $this->config->get('useCache'),
|
||||
return $this->injectableFactory->createWith(DefaultTable::class, [
|
||||
'user' => $user,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2021 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* EspoCRM is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* EspoCRM is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
|
||||
*
|
||||
* 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 General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Acl\Traits;
|
||||
|
||||
use Espo\ORM\Entity;
|
||||
|
||||
use Espo\Entities\User;
|
||||
|
||||
use Espo\Core\{
|
||||
Acl\ScopeData,
|
||||
Acl\DefaultAccessChecker,
|
||||
};
|
||||
|
||||
trait DefaultAccessCheckerDependency
|
||||
{
|
||||
/**
|
||||
* @var DefaultAccessChecker
|
||||
*/
|
||||
private $defaultAccessChecker;
|
||||
|
||||
public function check(User $user, ScopeData $data): bool
|
||||
{
|
||||
return $this->defaultAccessChecker->check($user, $data);
|
||||
}
|
||||
|
||||
public function checkCreate(User $user, ScopeData $data): bool
|
||||
{
|
||||
return $this->defaultAccessChecker->checkCreate($user, $data);
|
||||
}
|
||||
|
||||
public function checkRead(User $user, ScopeData $data): bool
|
||||
{
|
||||
return $this->defaultAccessChecker->checkRead($user, $data);
|
||||
}
|
||||
|
||||
public function checkEdit(User $user, ScopeData $data): bool
|
||||
{
|
||||
return $this->defaultAccessChecker->checkEdit($user, $data);
|
||||
}
|
||||
|
||||
public function checkDelete(User $user, ScopeData $data): bool
|
||||
{
|
||||
return $this->defaultAccessChecker->checkDelete($user, $data);
|
||||
}
|
||||
|
||||
public function checkStream(User $user, ScopeData $data): bool
|
||||
{
|
||||
return $this->defaultAccessChecker->checkStream($user, $data);
|
||||
}
|
||||
|
||||
public function checkEntityCreate(User $user, Entity $entity, ScopeData $data): bool
|
||||
{
|
||||
return $this->defaultAccessChecker->checkEntityCreate($user, $entity, $data);
|
||||
}
|
||||
|
||||
public function checkEntityRead(User $user, Entity $entity, ScopeData $data): bool
|
||||
{
|
||||
return $this->defaultAccessChecker->checkEntityRead($user, $entity, $data);
|
||||
}
|
||||
|
||||
public function checkEntityEdit(User $user, Entity $entity, ScopeData $data): bool
|
||||
{
|
||||
return $this->defaultAccessChecker->checkEntityEdit($user, $entity, $data);
|
||||
}
|
||||
|
||||
public function checkEntityDelete(User $user, Entity $entity, ScopeData $data): bool
|
||||
{
|
||||
return $this->defaultAccessChecker->checkEntityDelete($user, $entity, $data);
|
||||
}
|
||||
|
||||
public function checkEntityStream(User $user, Entity $entity, ScopeData $data): bool
|
||||
{
|
||||
return $this->defaultAccessChecker->checkEntityStream($user, $entity, $data);
|
||||
}
|
||||
}
|
||||
@@ -35,98 +35,114 @@ use Espo\Entities\User;
|
||||
|
||||
use Espo\Core\{
|
||||
ORM\EntityManager,
|
||||
Acl\AclFactory,
|
||||
Acl\GlobalRestrictonFactory,
|
||||
Acl,
|
||||
Acl\GlobalRestricton,
|
||||
Acl\OwnerUserFieldProvider,
|
||||
Acl as UserAclWrapper,
|
||||
Acl\Table as Table,
|
||||
Acl\ScopeAcl,
|
||||
Acl\EntityAcl,
|
||||
Acl\EntityCreateAcl,
|
||||
Acl\EntityReadAcl,
|
||||
Acl\EntityEditAcl,
|
||||
Acl\EntityDeleteAcl,
|
||||
Acl\EntityStreamAcl,
|
||||
Acl\LevelProvider,
|
||||
Acl\TableFactory,
|
||||
Acl\Table,
|
||||
Acl\OwnershipCheckerFactory,
|
||||
Acl\OwnershipChecker,
|
||||
Acl\OwnershipOwnChecker,
|
||||
Acl\OwnershipTeamChecker,
|
||||
Acl\AccessCheckerFactory,
|
||||
Acl\AccessChecker,
|
||||
Acl\AccessCreateChecker,
|
||||
Acl\AccessReadChecker,
|
||||
Acl\AccessEditChecker,
|
||||
Acl\AccessDeleteChecker,
|
||||
Acl\AccessStreamChecker,
|
||||
Acl\AccessEntityCreateChecker,
|
||||
Acl\AccessEntityReadChecker,
|
||||
Acl\AccessEntityEditChecker,
|
||||
Acl\AccessEntityDeleteChecker,
|
||||
Acl\AccessEntityStreamChecker,
|
||||
Acl\Exceptions\NotImplemented,
|
||||
};
|
||||
|
||||
use StdClass;
|
||||
use RuntimeException;
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* Used to check access for a specific user.
|
||||
* A central access point for access checking.
|
||||
*/
|
||||
class AclManager
|
||||
{
|
||||
protected $implementationHashMap = [];
|
||||
private $accessCheckerHashMap = [];
|
||||
|
||||
private $tableHashMap = [];
|
||||
private $ownershipCheckerHashMap = [];
|
||||
|
||||
protected $tableClassName = Table::class;
|
||||
protected $tableHashMap = [];
|
||||
|
||||
protected $userAclClassName = UserAclWrapper::class;
|
||||
protected $userAclClassName = Acl::class;
|
||||
|
||||
protected const PERMISSION_ASSIGNMENT = 'assignment';
|
||||
|
||||
private $actionInterfaceMap = [
|
||||
Table::ACTION_CREATE => EntityCreateAcl::class,
|
||||
Table::ACTION_READ => EntityReadAcl::class,
|
||||
Table::ACTION_EDIT => EntityEditAcl::class,
|
||||
Table::ACTION_DELETE => EntityDeleteAcl::class,
|
||||
Table::ACTION_STREAM => EntityStreamAcl::class,
|
||||
private $entityActionInterfaceMap = [
|
||||
Table::ACTION_CREATE => AccessEntityCreateChecker::class,
|
||||
Table::ACTION_READ => AccessEntityReadChecker::class,
|
||||
Table::ACTION_EDIT => AccessEntityEditChecker::class,
|
||||
Table::ACTION_DELETE => AccessEntityDeleteChecker::class,
|
||||
Table::ACTION_STREAM => AccessEntityStreamChecker::class,
|
||||
];
|
||||
|
||||
protected $injectableFactory;
|
||||
private $actionInterfaceMap = [
|
||||
Table::ACTION_CREATE => AccessCreateChecker::class,
|
||||
Table::ACTION_READ => AccessReadChecker::class,
|
||||
Table::ACTION_EDIT => AccessEditChecker::class,
|
||||
Table::ACTION_DELETE => AccessDeleteChecker::class,
|
||||
Table::ACTION_STREAM => AccessStreamChecker::class,
|
||||
];
|
||||
|
||||
protected $entityManager;
|
||||
protected $accessCheckerFactory;
|
||||
|
||||
protected $aclFactory;
|
||||
protected $ownershipCheckerFactory;
|
||||
|
||||
protected $tableFactory;
|
||||
|
||||
protected $globalRestricton;
|
||||
|
||||
protected $ownerUserFieldProvider;
|
||||
|
||||
public function __construct(
|
||||
InjectableFactory $injectableFactory,
|
||||
EntityManager $entityManager,
|
||||
AclFactory $aclFactory,
|
||||
GlobalRestrictonFactory $globalRestrictonFactory,
|
||||
OwnerUserFieldProvider $ownerUserFieldProvider
|
||||
) {
|
||||
$this->injectableFactory = $injectableFactory;
|
||||
$this->entityManager = $entityManager;
|
||||
$this->aclFactory = $aclFactory;
|
||||
$this->ownerUserFieldProvider = $ownerUserFieldProvider;
|
||||
protected $entityManager;
|
||||
|
||||
$this->globalRestricton = $globalRestrictonFactory->create();
|
||||
public function __construct(
|
||||
AccessCheckerFactory $accessCheckerFactory,
|
||||
OwnershipCheckerFactory $ownershipCheckerFactory,
|
||||
TableFactory $tableFactory,
|
||||
GlobalRestricton $globalRestricton,
|
||||
OwnerUserFieldProvider $ownerUserFieldProvider,
|
||||
EntityManager $entityManager
|
||||
) {
|
||||
$this->accessCheckerFactory = $accessCheckerFactory;
|
||||
$this->ownershipCheckerFactory = $ownershipCheckerFactory;
|
||||
$this->tableFactory = $tableFactory;
|
||||
$this->globalRestricton = $globalRestricton;
|
||||
$this->ownerUserFieldProvider = $ownerUserFieldProvider;
|
||||
$this->entityManager = $entityManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an ACL implementation for a scope.
|
||||
*
|
||||
* @return ScopeAcl|EntityAcl|LevelProvider|
|
||||
* EntityCreateAcl|EntityReadAcl|EntityEditAcl|EntityDeleteAcl|EntityStreamAcl
|
||||
* Get an access checker for a specific scope.
|
||||
*/
|
||||
public function getImplementation(string $scope): object
|
||||
protected function getAccessChecker(string $scope): AccessChecker
|
||||
{
|
||||
if (!array_key_exists($scope, $this->implementationHashMap)) {
|
||||
$this->implementationHashMap[$scope] = $this->aclFactory->create($scope);
|
||||
if (!array_key_exists($scope, $this->accessCheckerHashMap)) {
|
||||
$this->accessCheckerHashMap[$scope] = $this->accessCheckerFactory->create($scope, $this);
|
||||
}
|
||||
|
||||
return $this->implementationHashMap[$scope];
|
||||
return $this->accessCheckerHashMap[$scope];
|
||||
}
|
||||
|
||||
protected function getEntityImplementation(string $scope): EntityAcl
|
||||
/**
|
||||
* Get an ownership checker for a specific scope.
|
||||
*/
|
||||
protected function getOwnershipChecker(string $scope): OwnershipChecker
|
||||
{
|
||||
$impl = $this->getImplementation($scope);
|
||||
|
||||
if (!$impl instanceof EntityAcl) {
|
||||
throw new RuntimeException("Acl must implement EntityAcl interface.");
|
||||
if (!array_key_exists($scope, $this->ownershipCheckerHashMap)) {
|
||||
$this->ownershipCheckerHashMap[$scope] = $this->ownershipCheckerFactory->create($scope, $this);
|
||||
}
|
||||
|
||||
return $impl;
|
||||
return $this->ownershipCheckerHashMap[$scope];
|
||||
}
|
||||
|
||||
protected function getTable(User $user): Table
|
||||
@@ -138,14 +154,15 @@ class AclManager
|
||||
}
|
||||
|
||||
if (!array_key_exists($key, $this->tableHashMap)) {
|
||||
$this->tableHashMap[$key] = $this->injectableFactory->createWith($this->tableClassName, [
|
||||
'user' => $user,
|
||||
]);
|
||||
$this->tableHashMap[$key] = $this->tableFactory->create($user);
|
||||
}
|
||||
|
||||
return $this->tableHashMap[$key];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a full access data map.
|
||||
*/
|
||||
public function getMap(User $user): StdClass
|
||||
{
|
||||
return $this->getTable($user)->getMap();
|
||||
@@ -153,30 +170,26 @@ class AclManager
|
||||
|
||||
/**
|
||||
* Get an access level for a specific scope and action.
|
||||
*
|
||||
* @todo Use LevelProvider?
|
||||
*/
|
||||
public function getLevel(User $user, string $scope, string $action): string
|
||||
{
|
||||
$data = $this->getTable($user)->getScopeData($scope);
|
||||
|
||||
$impl = $this->getImplementation($scope);
|
||||
|
||||
if ($impl instanceof LevelProvider) {
|
||||
return $impl->getLevel($user, $data, $action);
|
||||
}
|
||||
|
||||
return $data->get($action);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a permission. E.g. 'assignment' permission.
|
||||
*/
|
||||
public function get(User $user, string $permission): ?string
|
||||
public function getPermissionLevel(User $user, string $permission): string
|
||||
{
|
||||
if (substr($permission, -10) !== 'Permission') {
|
||||
$permission .= 'Permission';
|
||||
}
|
||||
|
||||
return $this->getTable($user)->get($permission);
|
||||
return $this->getTable($user)->getPermissionLevel($permission);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -212,9 +225,12 @@ class AclManager
|
||||
}
|
||||
|
||||
/**
|
||||
* Check a scope or entity. If $action is omitted, it will check whether a scope level is set to 'enabled'.
|
||||
* Check a scope or entity. If $action is omitted, it will check whether
|
||||
* a scope level is set to 'enabled'.
|
||||
*
|
||||
* @param string|Entity $subject An entity type or entity.
|
||||
*
|
||||
* @throws NotImplemented
|
||||
*/
|
||||
public function check(User $user, $subject, ?string $action = null): bool
|
||||
{
|
||||
@@ -230,13 +246,13 @@ class AclManager
|
||||
return $this->checkEntity($user, $entity, $action);
|
||||
}
|
||||
|
||||
return false;
|
||||
throw new InvalidArgumentException();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check access to a specific entity.
|
||||
*
|
||||
* @throws NotImplemented If not implemented by Acl class.
|
||||
* @throws NotImplemented
|
||||
*/
|
||||
public function checkEntity(User $user, Entity $entity, string $action = Table::ACTION_READ): bool
|
||||
{
|
||||
@@ -244,7 +260,7 @@ class AclManager
|
||||
|
||||
$data = $this->getTable($user)->getScopeData($scope);
|
||||
|
||||
$impl = $this->getImplementation($scope);
|
||||
$checker = $this->getAccessChecker($scope);
|
||||
|
||||
if (!$action) {
|
||||
$action = Table::ACTION_READ;
|
||||
@@ -252,28 +268,29 @@ class AclManager
|
||||
|
||||
$methodName = 'checkEntity' . ucfirst($action);
|
||||
|
||||
$interface = $this->actionInterfaceMap[$action] ?? null;
|
||||
$interface = $this->entityActionInterfaceMap[$action] ?? null;
|
||||
|
||||
if ($interface && $impl instanceof $interface) {
|
||||
return $impl->$methodName($user, $entity, $data);
|
||||
if ($interface && $checker instanceof $interface) {
|
||||
return $checker->$methodName($user, $entity, $data);
|
||||
}
|
||||
|
||||
if (!$impl instanceof EntityAcl) {
|
||||
throw new NotImplemented("Acl for '{$scope}' does not implement EntityAcl interface.");
|
||||
}
|
||||
|
||||
if (method_exists($impl, $methodName)) {
|
||||
if (method_exists($checker, $methodName)) {
|
||||
// For backward compatibility.
|
||||
return $impl->$methodName($user, $entity, $data);
|
||||
return $checker->$methodName($user, $entity, $data);
|
||||
}
|
||||
|
||||
return $impl->checkEntity($user, $entity, $data, $action);
|
||||
if (method_exists($checker, 'checkEntity')) {
|
||||
// For backward compatibility.
|
||||
return $checker->checkEntity($user, $entity, $data, $action);
|
||||
}
|
||||
|
||||
throw new NotImplemented("No entity access checker for '{$scope}' action '{$action}'.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Check 'read' access to a specific entity.
|
||||
*
|
||||
* @throws NotImplemented If not implemented by Acl class.
|
||||
* @throws NotImplemented.
|
||||
*/
|
||||
public function checkEntityRead(User $user, Entity $entity): bool
|
||||
{
|
||||
@@ -283,7 +300,7 @@ class AclManager
|
||||
/**
|
||||
* Check 'create' access to a specific entity.
|
||||
*
|
||||
* @throws NotImplemented If not implemented by Acl class.
|
||||
* @throws NotImplemented
|
||||
*/
|
||||
public function checkEntityCreate(User $user, Entity $entity): bool
|
||||
{
|
||||
@@ -293,7 +310,7 @@ class AclManager
|
||||
/**
|
||||
* Check 'edit' access to a specific entity.
|
||||
*
|
||||
* @throws NotImplemented If not implemented by Acl class.
|
||||
* @throws NotImplemented
|
||||
*/
|
||||
public function checkEntityEdit(User $user, Entity $entity): bool
|
||||
{
|
||||
@@ -303,7 +320,7 @@ class AclManager
|
||||
/**
|
||||
* Check 'delete' access to a specific entity.
|
||||
*
|
||||
* @throws NotImplemented If not implemented by Acl class.
|
||||
* @throws NotImplemented
|
||||
*/
|
||||
public function checkEntityDelete(User $user, Entity $entity): bool
|
||||
{
|
||||
@@ -313,7 +330,7 @@ class AclManager
|
||||
/**
|
||||
* Check 'stream' access to a specific entity.
|
||||
*
|
||||
* @throws NotImplemented If not implemented by Acl class.
|
||||
* @throws NotImplemented
|
||||
*/
|
||||
public function checkEntityStream(User $user, Entity $entity): bool
|
||||
{
|
||||
@@ -321,81 +338,64 @@ class AclManager
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether a user is owned of an entity (record). Usually 'assignedUser' field is used for checking.
|
||||
* Check whether a user is an owner of an entity.
|
||||
*/
|
||||
public function checkIsOwner(User $user, Entity $entity): bool
|
||||
public function checkOwnershipOwn(User $user, Entity $entity): bool
|
||||
{
|
||||
return (bool) $this->getEntityImplementation($entity->getEntityType())->checkIsOwner($user, $entity);
|
||||
$checker = $this->getOwnershipChecker($entity->getEntityType());
|
||||
|
||||
if (!$checker instanceof OwnershipOwnChecker) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $checker->checkOwn($user, $entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether a user team list overlaps with teams set in an entity.
|
||||
* Check whether an entity belongs to a user team.
|
||||
*/
|
||||
public function checkInTeam(User $user, Entity $entity): bool
|
||||
public function checkOwnershipTeam(User $user, Entity $entity): bool
|
||||
{
|
||||
return (bool) $this->getEntityImplementation($entity->getEntityType())->checkInTeam($user, $entity);
|
||||
$checker = $this->getOwnershipChecker($entity->getEntityType());
|
||||
|
||||
if (!$checker instanceof OwnershipTeamChecker) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $checker->checkTeam($user, $entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check access to scope. If $action is omitted, it will check whether a scope level is set to 'enabled'.
|
||||
*
|
||||
* @throws NotImplemented If not implemented by Acl class.
|
||||
* @throws NotImplemented If not implemented by an access checker class.
|
||||
*/
|
||||
public function checkScope(User $user, string $scope, ?string $action = null): bool
|
||||
{
|
||||
$data = $this->getTable($user)->getScopeData($scope);
|
||||
|
||||
$impl = $this->getImplementation($scope);
|
||||
$checker = $this->getAccessChecker($scope);
|
||||
|
||||
if (!$impl instanceof ScopeAcl) {
|
||||
throw new NotImplemented("Acl for '{$scope}' does not implement ScopeAcl interface.");
|
||||
if (!$action) {
|
||||
return $checker->check($user, $data);
|
||||
}
|
||||
|
||||
return $impl->checkScope($user, $data, $action);
|
||||
}
|
||||
$methodName = 'check' . ucfirst($action);
|
||||
|
||||
/**
|
||||
* @deprecated Use checkUserPermission instead.
|
||||
*/
|
||||
public function checkUser(User $user, string $permission, User $target): bool
|
||||
{
|
||||
if ($this->get($user, $permission) === Table::LEVEL_ALL) {
|
||||
return true;
|
||||
$interface = $this->actionInterfaceMap[$action] ?? null;
|
||||
|
||||
if ($interface && $checker instanceof $interface) {
|
||||
return $checker->$methodName($user, $data);
|
||||
}
|
||||
|
||||
if ($this->get($user, $permission) === Table::LEVEL_NO) {
|
||||
if ($target->id === $user->id) {
|
||||
return true;
|
||||
}
|
||||
// For backward compatibility.
|
||||
$methodName = 'checkScope';
|
||||
|
||||
return false;
|
||||
if (!method_exists($checker, $methodName)) {
|
||||
throw new NotImplemented("No access checker for '{$scope}' action '{$action}'.");
|
||||
}
|
||||
|
||||
if ($this->get($user, $permission) === Table::LEVEL_TEAM) {
|
||||
if ($target->id === $user->id) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$targetTeamIdList = $target->getTeamIdList();
|
||||
|
||||
$inTeam = false;
|
||||
|
||||
foreach ($user->getTeamIdList() as $id) {
|
||||
if (in_array($id, $targetTeamIdList)) {
|
||||
$inTeam = true;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($inTeam) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
return $checker->$methodName($user, $data, $action);
|
||||
}
|
||||
|
||||
protected function getGlobalRestrictionTypeList(User $user, string $action = Table::ACTION_READ): array
|
||||
@@ -426,6 +426,7 @@ class AclManager
|
||||
/**
|
||||
* Get attributes forbidden for a user.
|
||||
*
|
||||
* @param $thresholdLevel Should not be used. Stands for possible future enhancements.
|
||||
* @return array<string>
|
||||
*/
|
||||
public function getScopeForbiddenAttributeList(
|
||||
@@ -435,30 +436,25 @@ class AclManager
|
||||
string $thresholdLevel = Table::LEVEL_NO
|
||||
): array {
|
||||
|
||||
$list = [];
|
||||
$list = array_merge(
|
||||
$this->getTable($user)->getScopeForbiddenAttributeList(
|
||||
$scope,
|
||||
$action,
|
||||
$thresholdLevel
|
||||
),
|
||||
$this->getScopeRestrictedAttributeList(
|
||||
$scope,
|
||||
$this->getGlobalRestrictionTypeList($user, $action)
|
||||
)
|
||||
);
|
||||
|
||||
if (!$user->isAdmin()) {
|
||||
$list = $this->getTable($user)->getScopeForbiddenAttributeList($scope, $action, $thresholdLevel);
|
||||
}
|
||||
|
||||
if ($thresholdLevel === Table::LEVEL_NO) {
|
||||
$list = array_merge(
|
||||
$list,
|
||||
$this->getScopeRestrictedAttributeList(
|
||||
$scope,
|
||||
$this->getGlobalRestrictionTypeList($user, $action)
|
||||
)
|
||||
);
|
||||
|
||||
$list = array_values($list);
|
||||
}
|
||||
|
||||
return $list;
|
||||
return array_unique($list);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get fields forbidden for a user.
|
||||
*
|
||||
* @param $thresholdLevel Should not be used. Stands for possible future enhancements.
|
||||
* @return array<string>
|
||||
*/
|
||||
public function getScopeForbiddenFieldList(
|
||||
@@ -468,30 +464,25 @@ class AclManager
|
||||
string $thresholdLevel = Table::LEVEL_NO
|
||||
): array {
|
||||
|
||||
$list = [];
|
||||
$list = array_merge(
|
||||
$this->getTable($user)->getScopeForbiddenFieldList(
|
||||
$scope,
|
||||
$action,
|
||||
$thresholdLevel
|
||||
),
|
||||
$this->getScopeRestrictedFieldList(
|
||||
$scope,
|
||||
$this->getGlobalRestrictionTypeList($user, $action)
|
||||
)
|
||||
);
|
||||
|
||||
if (!$user->isAdmin()) {
|
||||
$list = $this->getTable($user)->getScopeForbiddenFieldList($scope, $action, $thresholdLevel);
|
||||
}
|
||||
|
||||
if ($thresholdLevel === Table::LEVEL_NO) {
|
||||
$list = array_merge(
|
||||
$list,
|
||||
$this->getScopeRestrictedFieldList(
|
||||
$scope,
|
||||
$this->getGlobalRestrictionTypeList($user, $action)
|
||||
)
|
||||
);
|
||||
|
||||
$list = array_values($list);
|
||||
}
|
||||
|
||||
return $list;
|
||||
return array_unique($list);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get links forbidden for a user.
|
||||
*
|
||||
* @param $thresholdLevel Should not be used. Stands for possible future enhancements.
|
||||
* @return array<string>
|
||||
*/
|
||||
public function getScopeForbiddenLinkList(
|
||||
@@ -501,21 +492,10 @@ class AclManager
|
||||
string $thresholdLevel = Table::LEVEL_NO
|
||||
): array {
|
||||
|
||||
$list = [];
|
||||
|
||||
if ($thresholdLevel === Table::LEVEL_NO) {
|
||||
$list = array_merge(
|
||||
$list,
|
||||
$this->getScopeRestrictedLinkList(
|
||||
$scope,
|
||||
$this->getGlobalRestrictionTypeList($user, $action)
|
||||
)
|
||||
);
|
||||
|
||||
$list = array_values($list);
|
||||
}
|
||||
|
||||
return $list;
|
||||
return $this->getScopeRestrictedLinkList(
|
||||
$scope,
|
||||
$this->getGlobalRestrictionTypeList($user, $action)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -574,7 +554,7 @@ class AclManager
|
||||
/**
|
||||
* Create a wrapper for a specific user.
|
||||
*/
|
||||
public function createUserAcl(User $user): UserAclWrapper
|
||||
public function createUserAcl(User $user): Acl
|
||||
{
|
||||
$className = $this->userAclClassName;
|
||||
|
||||
@@ -597,10 +577,13 @@ class AclManager
|
||||
$list = [];
|
||||
|
||||
foreach ($typeList as $type) {
|
||||
$list = array_merge($list, $this->globalRestricton->getScopeRestrictedFieldList($scope, $type));
|
||||
$list = array_merge(
|
||||
$list,
|
||||
$this->globalRestricton->getScopeRestrictedFieldList($scope, $type)
|
||||
);
|
||||
}
|
||||
|
||||
return array_values($list);
|
||||
return array_unique($list);
|
||||
}
|
||||
|
||||
return $this->globalRestricton->getScopeRestrictedFieldList($scope, $type);
|
||||
@@ -620,10 +603,13 @@ class AclManager
|
||||
$list = [];
|
||||
|
||||
foreach ($typeList as $type) {
|
||||
$list = array_merge($list, $this->globalRestricton->getScopeRestrictedAttributeList($scope, $type));
|
||||
$list = array_merge(
|
||||
$list,
|
||||
$this->globalRestricton->getScopeRestrictedAttributeList($scope, $type)
|
||||
);
|
||||
}
|
||||
|
||||
return array_values($list);
|
||||
return array_unique($list);
|
||||
}
|
||||
|
||||
return $this->globalRestricton->getScopeRestrictedAttributeList($scope, $type);
|
||||
@@ -643,10 +629,13 @@ class AclManager
|
||||
$list = [];
|
||||
|
||||
foreach ($typeList as $type) {
|
||||
$list = array_merge($list, $this->globalRestricton->getScopeRestrictedLinkList($scope, $type));
|
||||
$list = array_merge(
|
||||
$list,
|
||||
$this->globalRestricton->getScopeRestrictedLinkList($scope, $type)
|
||||
);
|
||||
}
|
||||
|
||||
return array_values($list);
|
||||
return array_unique($list);
|
||||
}
|
||||
|
||||
return $this->globalRestricton->getScopeRestrictedLinkList($scope, $type);
|
||||
@@ -660,4 +649,80 @@ class AclManager
|
||||
{
|
||||
return $this->ownerUserFieldProvider->get($entityType);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated User `checkOwnershipOwn`.
|
||||
*/
|
||||
public function checkIsOwner(User $user, Entity $entity): bool
|
||||
{
|
||||
return $this->checkOwnershipOwn($user, $entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated User `checkOwnershipTeam`.
|
||||
*/
|
||||
public function checkInTeam(User $user, Entity $entity): bool
|
||||
{
|
||||
return $this->checkOwnershipTeam($user, $entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
public function getImplementation(string $scope): object
|
||||
{
|
||||
return $this->getAccessChecker($scope);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use `getPermissionLevel` instead.
|
||||
*/
|
||||
public function get(User $user, string $permission): string
|
||||
{
|
||||
return $this->getPermissionLevel($user, $permission);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use `checkUserPermission` instead.
|
||||
*/
|
||||
public function checkUser(User $user, string $permission, User $target): bool
|
||||
{
|
||||
if ($this->getPermissionLevel($user, $permission) === Table::LEVEL_ALL) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($this->getPermissionLevel($user, $permission) === Table::LEVEL_NO) {
|
||||
if ($target->getId() === $user->getId()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->get($user, $permission) === Table::LEVEL_TEAM) {
|
||||
if ($target->getId() === $user->getId()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$targetTeamIdList = $target->getTeamIdList();
|
||||
|
||||
$inTeam = false;
|
||||
|
||||
foreach ($user->getTeamIdList() as $id) {
|
||||
if (in_array($id, $targetTeamIdList)) {
|
||||
$inTeam = true;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($inTeam) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,9 +29,10 @@
|
||||
|
||||
namespace Espo\Core\AclPortal;
|
||||
|
||||
use Espo\Core\Acl\Acl as BaseAcl;
|
||||
|
||||
class Acl extends BaseAcl implements PortalScopeAcl
|
||||
/**
|
||||
* @deprecated Use AccessChecker interfaces instead.
|
||||
*/
|
||||
class Acl extends Base
|
||||
{
|
||||
use Portal;
|
||||
|
||||
}
|
||||
|
||||
@@ -29,9 +29,177 @@
|
||||
|
||||
namespace Espo\Core\AclPortal;
|
||||
|
||||
use Espo\Core\Interfaces\Injectable;
|
||||
|
||||
/** @deprecated */
|
||||
class Base extends \Espo\Core\Acl\Base implements PortalScopeAcl
|
||||
use Espo\ORM\Entity;
|
||||
|
||||
use Espo\Entities\User;
|
||||
|
||||
use Espo\Core\{
|
||||
ORM\EntityManager,
|
||||
Portal\AclManager as PortalAclManager,
|
||||
Utils\Config,
|
||||
Acl\AccessChecker,
|
||||
Acl\ScopeData,
|
||||
Portal\Acl\Table,
|
||||
Portal\Acl\ScopeChecker,
|
||||
Portal\Acl\ScopeCheckerData,
|
||||
Portal\Acl\DefaultAccessChecker,
|
||||
};
|
||||
|
||||
/**
|
||||
* @deprecated Use AccessChecker interfaces instead.
|
||||
*/
|
||||
class Base implements AccessChecker, Injectable
|
||||
{
|
||||
use Portal;
|
||||
protected $dependencyList = [];
|
||||
|
||||
protected $dependencies = [];
|
||||
|
||||
protected $injections = [];
|
||||
|
||||
protected $entityManager;
|
||||
|
||||
protected $aclManager;
|
||||
|
||||
protected $config;
|
||||
|
||||
protected $scopeChecker;
|
||||
|
||||
protected $defaultChecker;
|
||||
|
||||
public function __construct(
|
||||
EntityManager $entityManager,
|
||||
PortalAclManager $aclManager,
|
||||
Config $config,
|
||||
ScopeChecker $scopeChecker,
|
||||
DefaultAccessChecker $defaultChecker
|
||||
) {
|
||||
$this->entityManager = $entityManager;
|
||||
$this->aclManager = $aclManager;
|
||||
$this->config = $config;
|
||||
$this->scopeChecker = $scopeChecker;
|
||||
$this->defaultChecker = $defaultChecker;
|
||||
|
||||
$this->init();
|
||||
}
|
||||
|
||||
public function check(User $user, ScopeData $data): bool
|
||||
{
|
||||
return $this->defaultChecker->check($user, $data);
|
||||
}
|
||||
|
||||
public function checkEntity(User $user, Entity $entity, ScopeData $data, string $action): bool
|
||||
{
|
||||
$checkerData = ScopeCheckerData
|
||||
::createBuilder()
|
||||
->setIsOwnChecker(
|
||||
function () use ($user, $entity): bool {
|
||||
return (bool) $this->checkIsOwner($user, $entity);
|
||||
}
|
||||
)
|
||||
->setInAccountChecker(
|
||||
function () use ($user, $entity): bool {
|
||||
return (bool) $this->checkInAccount($user, $entity);
|
||||
}
|
||||
)
|
||||
->setInContactChecker(
|
||||
function () use ($user, $entity): bool {
|
||||
return (bool) $this->checkIsOwnContact($user, $entity);
|
||||
}
|
||||
)
|
||||
->build();
|
||||
|
||||
return $this->scopeChecker->check($data, $action, $checkerData);
|
||||
}
|
||||
|
||||
public function checkScope(User $user, ScopeData $data, ?string $action = null): bool
|
||||
{
|
||||
if (!$action) {
|
||||
return $this->defaultChecker->check($user, $data);
|
||||
}
|
||||
|
||||
if ($action === Table::ACTION_CREATE) {
|
||||
return $this->defaultChecker->checkCreate($user, $data);
|
||||
}
|
||||
|
||||
if ($action === Table::ACTION_READ) {
|
||||
return $this->defaultChecker->checkRead($user, $data);
|
||||
}
|
||||
|
||||
if ($action === Table::ACTION_EDIT) {
|
||||
return $this->defaultChecker->checkEdit($user, $data);
|
||||
}
|
||||
|
||||
if ($action === Table::ACTION_DELETE) {
|
||||
return $this->defaultChecker->checkDelete($user, $data);
|
||||
}
|
||||
|
||||
if ($action === Table::ACTION_STREAM) {
|
||||
return $this->defaultChecker->checkStream($user, $data);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function checkIsOwner(User $user, Entity $entity)
|
||||
{
|
||||
return $this->aclManager->checkOwnershipOwn($user, $entity);
|
||||
}
|
||||
|
||||
public function checkInAccount(User $user, Entity $entity)
|
||||
{
|
||||
return $this->aclManager->checkOwnershipAccount($user, $entity);
|
||||
}
|
||||
|
||||
public function checkIsOwnContact(User $user, Entity $entity)
|
||||
{
|
||||
return $this->aclManager->checkOwnershipContact($user, $entity);
|
||||
}
|
||||
|
||||
protected function getConfig(): Config
|
||||
{
|
||||
return $this->config;
|
||||
}
|
||||
|
||||
protected function getEntityManager(): EntityManager
|
||||
{
|
||||
return $this->entityManager;
|
||||
}
|
||||
|
||||
protected function getAclManager(): AclManager
|
||||
{
|
||||
return $this->aclManager;
|
||||
}
|
||||
|
||||
public function inject($name, $object)
|
||||
{
|
||||
$this->injections[$name] = $object;
|
||||
}
|
||||
|
||||
protected function init()
|
||||
{
|
||||
}
|
||||
|
||||
protected function getInjection($name)
|
||||
{
|
||||
return $this->injections[$name] ?? $this->$name ?? null;
|
||||
}
|
||||
|
||||
protected function addDependencyList(array $list)
|
||||
{
|
||||
foreach ($list as $item) {
|
||||
$this->addDependency($item);
|
||||
}
|
||||
}
|
||||
|
||||
protected function addDependency($name)
|
||||
{
|
||||
$this->dependencyList[] = $name;
|
||||
}
|
||||
|
||||
public function getDependencyList()
|
||||
{
|
||||
return array_merge($this->dependencyList, $this->dependencies);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,222 +0,0 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2021 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* EspoCRM is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* EspoCRM is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
|
||||
*
|
||||
* 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 General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\AclPortal;
|
||||
|
||||
use Espo\Entities\User;
|
||||
|
||||
use Espo\ORM\Entity;
|
||||
|
||||
use Espo\Core\{
|
||||
Acl\ScopeData,
|
||||
};
|
||||
|
||||
trait Portal
|
||||
{
|
||||
protected function checkScopeInternal(
|
||||
User $user,
|
||||
ScopeData $data,
|
||||
?string $action = null,
|
||||
?Entity $entity = null,
|
||||
array $entityAccessData = []
|
||||
) : bool {
|
||||
|
||||
if ($data->isFalse()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($data->isTrue()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$isOwner = null;
|
||||
|
||||
if (isset($entityAccessData['isOwner'])) {
|
||||
$isOwner = $entityAccessData['isOwner'];
|
||||
}
|
||||
|
||||
$inAccount = null;
|
||||
|
||||
if (isset($entityAccessData['inAccount'])) {
|
||||
$inAccount = $entityAccessData['inAccount'];
|
||||
}
|
||||
|
||||
$isOwnContact = null;
|
||||
|
||||
if (isset($entityAccessData['isOwnContact'])) {
|
||||
$isOwnContact = $entityAccessData['isOwnContact'];
|
||||
}
|
||||
|
||||
if (is_null($action)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$value = $data->get($action);
|
||||
|
||||
if ($value === Table::LEVEL_ALL || $value === Table::LEVEL_YES) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!$value || $value === Table::LEVEL_NO) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (is_null($isOwner)) {
|
||||
if ($entity) {
|
||||
$isOwner = $this->checkIsOwner($user, $entity);
|
||||
}
|
||||
else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if ($isOwner) {
|
||||
if ($value === Table::LEVEL_OWN || $value === Table::LEVEL_ACCOUNT || $value === Table::LEVEL_CONTACT) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if ($value === Table::LEVEL_ACCOUNT) {
|
||||
if (is_null($inAccount) && $entity) {
|
||||
$inAccount = $this->checkInAccount($user, $entity);
|
||||
}
|
||||
|
||||
if ($inAccount) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (is_null($isOwnContact) && $entity) {
|
||||
$isOwnContact = $this->checkIsOwnContact($user, $entity);
|
||||
}
|
||||
|
||||
if ($isOwnContact) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if ($value === Table::LEVEL_CONTACT) {
|
||||
if (is_null($isOwnContact) && $entity) {
|
||||
$isOwnContact = $this->checkIsOwnContact($user, $entity);
|
||||
}
|
||||
|
||||
if ($isOwnContact) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function checkIsOwner(User $user, Entity $entity)
|
||||
{
|
||||
if ($entity->hasAttribute('createdById')) {
|
||||
if ($entity->has('createdById') && $user->id === $entity->get('createdById')) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function checkInAccount(User $user, Entity $entity)
|
||||
{
|
||||
$accountIdList = $user->getLinkMultipleIdList('accounts');
|
||||
|
||||
if (!count($accountIdList)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($entity->hasAttribute('accountId') && $entity->getRelationParam('account', 'entity') === 'Account') {
|
||||
if (in_array($entity->get('accountId'), $accountIdList)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if ($entity->hasRelation('accounts') && $entity->getRelationParam('accounts', 'entity') === 'Account') {
|
||||
$repository = $this->getEntityManager()->getRepository($entity->getEntityType());
|
||||
|
||||
foreach ($accountIdList as $accountId) {
|
||||
if ($repository->isRelated($entity, 'accounts', $accountId)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($entity->hasAttribute('parentId') && $entity->hasRelation('parent')) {
|
||||
if (
|
||||
$entity->get('parentType') === 'Account' &&
|
||||
in_array($entity->get('parentId'), $accountIdList)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function checkIsOwnContact(User $user, Entity $entity)
|
||||
{
|
||||
$contactId = $user->get('contactId');
|
||||
|
||||
if (!$contactId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($entity->hasAttribute('contactId') && $entity->getRelationParam('contact', 'entity') === 'Contact') {
|
||||
if ($entity->get('contactId') === $contactId) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if ($entity->hasRelation('contacts') && $entity->getRelationParam('contacts', 'entity') === 'Contact') {
|
||||
$repository = $this->getEntityManager()->getRepository($entity->getEntityType());
|
||||
|
||||
if ($repository->isRelated($entity, 'contacts', $contactId)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if ($entity->hasAttribute('parentId') && $entity->hasRelation('parent')) {
|
||||
if ($entity->get('parentType') === 'Contact' && $entity->get('parentId') === $contactId) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -31,27 +31,62 @@ namespace Espo\Core\Portal;
|
||||
|
||||
use Espo\ORM\Entity;
|
||||
|
||||
use Espo\Entities\User;
|
||||
|
||||
use Espo\Core\Acl as BaseAcl;
|
||||
|
||||
class Acl extends BaseAcl
|
||||
{
|
||||
public function __construct(AclManager $aclManager, User $user)
|
||||
{
|
||||
parent::__construct($aclManager, $user);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether 'read' access is set to 'account' for a specific scope.
|
||||
*/
|
||||
public function checkReadOnlyAccount(string $scope): bool
|
||||
{
|
||||
return $this->aclManager->checkReadOnlyAccount($this->user, $scope);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether 'read' access is set to 'contact' for a specific scope.
|
||||
*/
|
||||
public function checkReadOnlyContact(string $scope): bool
|
||||
{
|
||||
return $this->aclManager->checkReadOnlyContact($this->user, $scope);
|
||||
}
|
||||
|
||||
public function checkInAccount(Entity $entity): bool
|
||||
/**
|
||||
* Check whether an entity belongs to a user account.
|
||||
*/
|
||||
public function checkOwnershipAccount(Entity $entity): bool
|
||||
{
|
||||
return $this->aclManager->checkInAccount($this->user, $entity);
|
||||
return $this->aclManager->checkOwnershipAccount($this->user, $entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether an entity belongs to a user contact.
|
||||
*/
|
||||
public function checkOwnershipContact(Entity $entity): bool
|
||||
{
|
||||
return $this->aclManager->checkOwnershipContact($this->user, $entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecate
|
||||
*/
|
||||
public function checkInAccount(Entity $entity): bool
|
||||
{
|
||||
return $this->checkOwnershipAccount($entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecate
|
||||
*/
|
||||
public function checkIsOwnContact(Entity $entity): bool
|
||||
{
|
||||
return $this->aclManager->checkIsOwnContact($this->user, $entity);
|
||||
return $this->checkOwnershipContact($entity);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2021 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* EspoCRM is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* EspoCRM is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
|
||||
*
|
||||
* 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 General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Portal\Acl;
|
||||
|
||||
use Espo\Core\{
|
||||
Utils\ClassFinder,
|
||||
Utils\Metadata,
|
||||
InjectableFactory,
|
||||
Acl\Exceptions\NotImplemented,
|
||||
Acl\AccessChecker,
|
||||
Portal\AclManager as PortalAclManager,
|
||||
Binding\BindingContainer,
|
||||
Binding\Binder,
|
||||
Binding\BindingData,
|
||||
};
|
||||
|
||||
class AccessCheckerFactory
|
||||
{
|
||||
private $defaultClassName = DefaultAccessChecker::class;
|
||||
|
||||
private $classFinder;
|
||||
|
||||
private $metadata;
|
||||
|
||||
private $injectableFactory;
|
||||
|
||||
public function __construct(
|
||||
ClassFinder $classFinder,
|
||||
Metadata $metadata,
|
||||
InjectableFactory $injectableFactory
|
||||
) {
|
||||
$this->classFinder = $classFinder;
|
||||
$this->metadata = $metadata;
|
||||
$this->injectableFactory = $injectableFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an access checker.
|
||||
*
|
||||
* @throws NotImplemented
|
||||
*/
|
||||
public function create(string $scope, PortalAclManager $aclManager): AccessChecker
|
||||
{
|
||||
$className = $this->getClassName($scope);
|
||||
|
||||
$bindingContainer = $this->createBindingContainer($aclManager);
|
||||
|
||||
return $this->injectableFactory->createWithBinding($className, $bindingContainer);
|
||||
}
|
||||
|
||||
private function getClassName(string $scope): string
|
||||
{
|
||||
$className1 = $this->metadata->get(['aclDefs', $scope, 'portalAccessCheckerClassName']);
|
||||
|
||||
if ($className1) {
|
||||
return $className1;
|
||||
}
|
||||
|
||||
if (!$this->metadata->get(['scopes', $scope])) {
|
||||
throw new NotImplemented();
|
||||
}
|
||||
|
||||
// For backward compatibility.
|
||||
$className2 = $this->classFinder->find('AclPortal', $scope);
|
||||
|
||||
if ($className2) {
|
||||
return $className2;
|
||||
}
|
||||
|
||||
return $this->defaultClassName;
|
||||
}
|
||||
|
||||
private function createBindingContainer(PortalAclManager $aclManager): BindingContainer
|
||||
{
|
||||
$bindingData = new BindingData();
|
||||
|
||||
$binder = new Binder($bindingData);
|
||||
|
||||
$binder->bindCallback(
|
||||
PortalAclManager::class,
|
||||
function () use ($aclManager): PortalAclManager {
|
||||
return $aclManager;
|
||||
}
|
||||
);
|
||||
|
||||
return new BindingContainer($bindingData);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2021 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* EspoCRM is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* EspoCRM is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
|
||||
*
|
||||
* 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 General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Portal\Acl;
|
||||
|
||||
use Espo\Entities\User;
|
||||
|
||||
use Espo\ORM\Entity;
|
||||
|
||||
use Espo\Core\{
|
||||
Utils\Config,
|
||||
Portal\AclManager as PortalAclManager,
|
||||
Acl\ScopeData,
|
||||
Acl\AccessEntityCreateChecker,
|
||||
Acl\AccessEntityReadChecker,
|
||||
Acl\AccessEntityEditChecker,
|
||||
Acl\AccessEntityDeleteChecker,
|
||||
Acl\AccessEntityStreamChecker,
|
||||
};
|
||||
|
||||
/**
|
||||
* A default implementation for access checking for portal.
|
||||
*/
|
||||
class DefaultAccessChecker implements
|
||||
|
||||
AccessEntityCreateChecker,
|
||||
AccessEntityReadChecker,
|
||||
AccessEntityEditChecker,
|
||||
AccessEntityDeleteChecker,
|
||||
AccessEntityStreamChecker
|
||||
{
|
||||
private $aclManager;
|
||||
|
||||
private $config;
|
||||
|
||||
private $scopeChecker;
|
||||
|
||||
public function __construct(
|
||||
PortalAclManager $aclManager,
|
||||
Config $config,
|
||||
ScopeChecker $scopeChecker
|
||||
) {
|
||||
$this->aclManager = $aclManager;
|
||||
$this->config = $config;
|
||||
$this->scopeChecker = $scopeChecker;
|
||||
}
|
||||
|
||||
private function checkEntity(User $user, Entity $entity, ScopeData $data, string $action): bool
|
||||
{
|
||||
$checkerData = ScopeCheckerData
|
||||
::createBuilder()
|
||||
->setIsOwnChecker(
|
||||
function () use ($user, $entity): bool {
|
||||
return $this->aclManager->checkOwnershipOwn($user, $entity);
|
||||
}
|
||||
)
|
||||
->setInAccountChecker(
|
||||
function () use ($user, $entity): bool {
|
||||
return $this->aclManager->checkOwnershipAccount($user, $entity);
|
||||
}
|
||||
)
|
||||
->setInContactChecker(
|
||||
function () use ($user, $entity): bool {
|
||||
return $this->aclManager->checkOwnershipContact($user, $entity);
|
||||
}
|
||||
)
|
||||
->build();
|
||||
|
||||
return $this->scopeChecker->check($data, $action, $checkerData);
|
||||
}
|
||||
|
||||
private function checkScope(User $user, ScopeData $data, ?string $action = null): bool
|
||||
{
|
||||
$checkerData = ScopeCheckerData
|
||||
::createBuilder()
|
||||
->setIsOwn(true)
|
||||
->setInAccount(true)
|
||||
->setInContact(true)
|
||||
->build();
|
||||
|
||||
return $this->scopeChecker->check($data, $action, $checkerData);
|
||||
}
|
||||
|
||||
public function check(User $user, ScopeData $data): bool
|
||||
{
|
||||
return $this->checkScope($user, $data);
|
||||
}
|
||||
|
||||
public function checkCreate(User $user, ScopeData $data): bool
|
||||
{
|
||||
return $this->checkScope($user, $data, Table::ACTION_CREATE);
|
||||
}
|
||||
|
||||
public function checkRead(User $user, ScopeData $data): bool
|
||||
{
|
||||
return $this->checkScope($user, $data, Table::ACTION_READ);
|
||||
}
|
||||
|
||||
public function checkEdit(User $user, ScopeData $data): bool
|
||||
{
|
||||
return $this->checkScope($user, $data, Table::ACTION_EDIT);
|
||||
}
|
||||
|
||||
public function checkDelete(User $user, ScopeData $data): bool
|
||||
{
|
||||
return $this->checkScope($user, $data, Table::ACTION_DELETE);
|
||||
}
|
||||
|
||||
public function checkStream(User $user, ScopeData $data): bool
|
||||
{
|
||||
return $this->checkScope($user, $data, Table::ACTION_STREAM);
|
||||
}
|
||||
|
||||
public function checkEntityCreate(User $user, Entity $entity, ScopeData $data): bool
|
||||
{
|
||||
return $this->checkEntity($user, $entity, $data, Table::ACTION_CREATE);
|
||||
}
|
||||
|
||||
public function checkEntityRead(User $user, Entity $entity, ScopeData $data): bool
|
||||
{
|
||||
return $this->checkEntity($user, $entity, $data, Table::ACTION_READ);
|
||||
}
|
||||
|
||||
public function checkEntityEdit(User $user, Entity $entity, ScopeData $data): bool
|
||||
{
|
||||
return $this->checkEntity($user, $entity, $data, Table::ACTION_EDIT);
|
||||
}
|
||||
|
||||
public function checkEntityStream(User $user, Entity $entity, ScopeData $data): bool
|
||||
{
|
||||
return $this->checkEntity($user, $entity, $data, Table::ACTION_STREAM);
|
||||
}
|
||||
|
||||
public function checkEntityDelete(User $user, Entity $entity, ScopeData $data): bool
|
||||
{
|
||||
return $this->checkEntity($user, $entity, $data, Table::ACTION_DELETE);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2021 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* EspoCRM is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* EspoCRM is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
|
||||
*
|
||||
* 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 General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Portal\Acl;
|
||||
|
||||
use Espo\ORM\{
|
||||
Entity,
|
||||
EntityManager,
|
||||
};
|
||||
|
||||
use Espo\Entities\User;
|
||||
|
||||
use Espo\Core\{
|
||||
Acl\OwnershipOwnChecker,
|
||||
};
|
||||
|
||||
/**
|
||||
* A default implementation for ownership checking for portal.
|
||||
*/
|
||||
class DefaultOwnershipChecker implements
|
||||
OwnershipOwnChecker,
|
||||
OwnershipAccountChecker,
|
||||
OwnershipContactChecker
|
||||
|
||||
{
|
||||
private const ENTITY_ACCOUNT = 'Account';
|
||||
|
||||
private const ENTITY_CONTACT = 'Contact';
|
||||
|
||||
private const ATTR_CREATED_BY_ID = 'createdById';
|
||||
|
||||
private const ATTR_ACCOUNT_ID = 'accountId';
|
||||
|
||||
private const ATTR_CONTACT_ID = 'contactId';
|
||||
|
||||
private const ATTR_PARENT_ID = 'parentId';
|
||||
|
||||
private const ATTR_PARENT_TYPE = 'parentType';
|
||||
|
||||
private const FIELD_CONTACT = 'contact';
|
||||
|
||||
private const FIELD_CONTACTS = 'contacts';
|
||||
|
||||
private const FIELD_ACCOUNT = 'account';
|
||||
|
||||
private const FIELD_ACCOUNTS = 'accounts';
|
||||
|
||||
private const FIELD_PARENT = 'parent';
|
||||
|
||||
private $entityManager;
|
||||
|
||||
public function __construct(EntityManager $entityManager)
|
||||
{
|
||||
$this->entityManager = $entityManager;
|
||||
}
|
||||
|
||||
public function checkOwn(User $user, Entity $entity): bool
|
||||
{
|
||||
if ($entity->hasAttribute(self::ATTR_CREATED_BY_ID)) {
|
||||
if (
|
||||
$entity->has(self::ATTR_CREATED_BY_ID) &&
|
||||
$user->getId() === $entity->get(self::ATTR_CREATED_BY_ID)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function checkAccount(User $user, Entity $entity): bool
|
||||
{
|
||||
$accountIdList = $user->getLinkMultipleIdList(self::FIELD_ACCOUNTS);
|
||||
|
||||
if (!count($accountIdList)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (
|
||||
$entity->hasAttribute(self::ATTR_ACCOUNT_ID) &&
|
||||
$entity->getRelationParam(self::FIELD_ACCOUNT, 'entity') === self::ENTITY_ACCOUNT
|
||||
) {
|
||||
if (in_array($entity->get(self::ATTR_ACCOUNT_ID), $accountIdList)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
$entity->hasRelation(self::FIELD_ACCOUNTS) &&
|
||||
$entity->getRelationParam(self::FIELD_ACCOUNTS, 'entity') === self::ENTITY_ACCOUNT
|
||||
) {
|
||||
$repository = $this->entityManager->getRepository($entity->getEntityType());
|
||||
|
||||
foreach ($accountIdList as $accountId) {
|
||||
if ($repository->isRelated($entity, self::FIELD_ACCOUNTS, $accountId)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($entity->hasAttribute(self::ATTR_PARENT_ID) && $entity->hasRelation(self::FIELD_PARENT)) {
|
||||
if (
|
||||
$entity->get(self::ATTR_PARENT_TYPE) === self::ENTITY_ACCOUNT &&
|
||||
in_array($entity->get(self::ATTR_PARENT_ID), $accountIdList)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function checkContact(User $user, Entity $entity): bool
|
||||
{
|
||||
$contactId = $user->get(self::ATTR_CONTACT_ID);
|
||||
|
||||
if (!$contactId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (
|
||||
$entity->hasAttribute(self::ATTR_CONTACT_ID) &&
|
||||
$entity->getRelationParam(self::FIELD_CONTACT, 'entity') === self::ENTITY_CONTACT
|
||||
) {
|
||||
if ($entity->get(self::ATTR_CONTACT_ID) === $contactId) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
$entity->hasRelation(self::FIELD_CONTACTS) &&
|
||||
$entity->getRelationParam(self::FIELD_CONTACTS, 'entity') === self::ENTITY_CONTACT
|
||||
) {
|
||||
$repository = $this->entityManager->getRepository($entity->getEntityType());
|
||||
|
||||
if ($repository->isRelated($entity, self::FIELD_CONTACTS, $contactId)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if ($entity->hasAttribute(self::ATTR_PARENT_ID) && $entity->hasRelation(self::FIELD_PARENT)) {
|
||||
if (
|
||||
$entity->get(self::ATTR_PARENT_TYPE) === self::ENTITY_CONTACT &&
|
||||
$entity->get(self::ATTR_PARENT_ID) === $contactId
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2021 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* EspoCRM is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* EspoCRM is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
|
||||
*
|
||||
* 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 General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Portal\Acl;
|
||||
|
||||
use Espo\ORM\Entity;
|
||||
|
||||
use Espo\Entities\User;
|
||||
|
||||
use Espo\Core\Acl\OwnershipChecker;
|
||||
|
||||
interface OwnershipAccountChecker extends OwnershipChecker
|
||||
{
|
||||
/**
|
||||
* Check whether an entity belongs to a portal user account.
|
||||
*/
|
||||
public function checkAccount(User $user, Entity $entity): bool;
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2021 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* EspoCRM is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* EspoCRM is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
|
||||
*
|
||||
* 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 General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Portal\Acl;
|
||||
|
||||
use Espo\Core\{
|
||||
Utils\Metadata,
|
||||
InjectableFactory,
|
||||
Acl\Exceptions\NotImplemented,
|
||||
Acl\OwnershipChecker,
|
||||
Portal\AclManager as PortalAclManager,
|
||||
Binding\BindingContainer,
|
||||
Binding\Binder,
|
||||
Binding\BindingData,
|
||||
};
|
||||
|
||||
class OwnershipCheckerFactory
|
||||
{
|
||||
private $defaultClassName = DefaultOwnershipChecker::class;
|
||||
|
||||
private $metadata;
|
||||
|
||||
private $injectableFactory;
|
||||
|
||||
public function __construct(
|
||||
Metadata $metadata,
|
||||
InjectableFactory $injectableFactory
|
||||
) {
|
||||
$this->metadata = $metadata;
|
||||
$this->injectableFactory = $injectableFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an ownership checker.
|
||||
*
|
||||
* @throws NotImplemented
|
||||
*/
|
||||
public function create(string $scope, PortalAclManager $aclManager): OwnershipChecker
|
||||
{
|
||||
$className = $this->getClassName($scope);
|
||||
|
||||
$bindingContainer = $this->createBindingContainer($aclManager);
|
||||
|
||||
return $this->injectableFactory->createWithBinding($className, $bindingContainer);
|
||||
}
|
||||
|
||||
private function getClassName(string $scope): string
|
||||
{
|
||||
$className = $this->metadata->get(['aclDefs', $scope, 'portalOwnershipCheckerClassName']);
|
||||
|
||||
if ($className) {
|
||||
return $className;
|
||||
}
|
||||
|
||||
if (!$this->metadata->get(['scopes', $scope])) {
|
||||
throw new NotImplemented();
|
||||
}
|
||||
|
||||
return $this->defaultClassName;
|
||||
}
|
||||
|
||||
private function createBindingContainer(PortalAclManager $aclManager): BindingContainer
|
||||
{
|
||||
$bindingData = new BindingData();
|
||||
|
||||
$binder = new Binder($bindingData);
|
||||
|
||||
$binder->bindCallback(
|
||||
PortalAclManager::class,
|
||||
function () use ($aclManager): PortalAclManager {
|
||||
return $aclManager;
|
||||
}
|
||||
);
|
||||
|
||||
return new BindingContainer($bindingData);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2021 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* EspoCRM is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* EspoCRM is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
|
||||
*
|
||||
* 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 General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Portal\Acl;
|
||||
|
||||
use Espo\ORM\Entity;
|
||||
|
||||
use Espo\Entities\User;
|
||||
|
||||
use Espo\Core\Acl\OwnershipChecker;
|
||||
|
||||
interface OwnershipContactChecker extends OwnershipChecker
|
||||
{
|
||||
/**
|
||||
* Check whether an entity belongs to a portal user contact.
|
||||
*/
|
||||
public function checkContact(User $user, Entity $entity): bool;
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2021 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* EspoCRM is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* EspoCRM is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
|
||||
*
|
||||
* 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 General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Portal\Acl;
|
||||
|
||||
use Espo\Core\{
|
||||
Acl\ScopeData,
|
||||
Portal\Acl\Table,
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks scope access.
|
||||
*/
|
||||
class ScopeChecker
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
public function check(ScopeData $data, ?string $action = null, ?ScopeCheckerData $checkerData = null): bool
|
||||
{
|
||||
if ($data->isFalse()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($data->isTrue()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($action === null) {
|
||||
if ($data->hasNotNo()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$level = $data->get($action);
|
||||
|
||||
if ($level === Table::LEVEL_ALL || $level === Table::LEVEL_YES) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($level === Table::LEVEL_NO) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!$checkerData) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($level === Table::LEVEL_OWN || $level === Table::LEVEL_ACCOUNT || $level === Table::LEVEL_CONTACT) {
|
||||
if ($checkerData->isOwn()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if ($level === Table::LEVEL_ACCOUNT || $level === Table::LEVEL_CONTACT) {
|
||||
if ($checkerData->inContact()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if ($level === Table::LEVEL_ACCOUNT) {
|
||||
if ($checkerData->inAccount()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
+27
-21
@@ -27,37 +27,43 @@
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\AclPortal;
|
||||
namespace Espo\Core\Portal\Acl;
|
||||
|
||||
use Espo\Core\{
|
||||
Utils\ClassFinder,
|
||||
InjectableFactory,
|
||||
};
|
||||
|
||||
class AclFactory
|
||||
/**
|
||||
* Scope checker data.
|
||||
*/
|
||||
class ScopeCheckerData
|
||||
{
|
||||
protected $baseClassName = Acl::class;
|
||||
private $isOwnChecker;
|
||||
|
||||
private $classFinder;
|
||||
private $inAccountChecker;
|
||||
|
||||
private $injectableFactory;
|
||||
private $inContactChecker;
|
||||
|
||||
public function __construct(ClassFinder $classFinder, InjectableFactory $injectableFactory)
|
||||
public function __construct(callable $isOwnChecker, callable $inAccountChecker, callable $inContactChecker)
|
||||
{
|
||||
$this->classFinder = $classFinder;
|
||||
$this->injectableFactory = $injectableFactory;
|
||||
$this->isOwnChecker = $isOwnChecker;
|
||||
$this->inAccountChecker = $inAccountChecker;
|
||||
$this->inContactChecker = $inContactChecker;
|
||||
}
|
||||
|
||||
public function create(string $scope) : PortalScopeAcl
|
||||
public function isOwn(): bool
|
||||
{
|
||||
$className = $this->classFinder->find('AclPortal', $scope);
|
||||
return ($this->isOwnChecker)();
|
||||
}
|
||||
|
||||
if (!$className) {
|
||||
$className = $this->baseClassName;
|
||||
}
|
||||
public function inAccount(): bool
|
||||
{
|
||||
return ($this->inAccountChecker)();
|
||||
}
|
||||
|
||||
return $this->injectableFactory->createWith($className, [
|
||||
'scope' => $scope, // todo remove ?
|
||||
]);
|
||||
public function inContact(): bool
|
||||
{
|
||||
return ($this->inContactChecker)();
|
||||
}
|
||||
|
||||
public static function createBuilder(): ScopeCheckerDataBuilder
|
||||
{
|
||||
return new ScopeCheckerDataBuilder();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2021 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* EspoCRM is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* EspoCRM is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
|
||||
*
|
||||
* 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 General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Portal\Acl;
|
||||
|
||||
/**
|
||||
* Builds scope checker data.
|
||||
*/
|
||||
class ScopeCheckerDataBuilder
|
||||
{
|
||||
private $isOwnChecker;
|
||||
|
||||
private $inAccountChecker;
|
||||
|
||||
private $inContactChecker;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->isOwnChecker = function (): bool {
|
||||
return false;
|
||||
};
|
||||
|
||||
$this->inAccountChecker = function (): bool {
|
||||
return false;
|
||||
};
|
||||
|
||||
$this->inContactChecker = function (): bool {
|
||||
return false;
|
||||
};
|
||||
}
|
||||
|
||||
public function setIsOwn(bool $value): self
|
||||
{
|
||||
if ($value) {
|
||||
$this->isOwnChecker = function (): bool {
|
||||
return true;
|
||||
};
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
$this->isOwnChecker = function (): bool {
|
||||
return false;
|
||||
};
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setInAccount(bool $value): self
|
||||
{
|
||||
if ($value) {
|
||||
$this->inAccountChecker = function (): bool {
|
||||
return true;
|
||||
};
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
$this->inAccountChecker = function (): bool {
|
||||
return false;
|
||||
};
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setInContact(bool $value): self
|
||||
{
|
||||
if ($value) {
|
||||
$this->inContactChecker = function (): bool {
|
||||
return true;
|
||||
};
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
$this->inContactChecker = function (): bool {
|
||||
return false;
|
||||
};
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setIsOwnChecker(callable $checker): self
|
||||
{
|
||||
$this->isOwnChecker = $checker;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setInAccountChecker(callable $checker): self
|
||||
{
|
||||
$this->inAccountChecker = $checker;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setInContactChecker(callable $checker): self
|
||||
{
|
||||
$this->inContactChecker = $checker;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function build(): ScopeCheckerData
|
||||
{
|
||||
return new ScopeCheckerData($this->isOwnChecker, $this->inAccountChecker, $this->inContactChecker);
|
||||
}
|
||||
}
|
||||
+22
-25
@@ -27,9 +27,7 @@
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\AclPortal;
|
||||
|
||||
use Espo\Core\Exceptions\Error;
|
||||
namespace Espo\Core\Portal\Acl;
|
||||
|
||||
use Espo\Entities\{
|
||||
User,
|
||||
@@ -37,7 +35,7 @@ use Espo\Entities\{
|
||||
};
|
||||
|
||||
use Espo\Core\{
|
||||
Acl\Table as BaseTable,
|
||||
Acl\DefaultTable as BaseTable,
|
||||
ORM\EntityManager,
|
||||
Utils\Config,
|
||||
Utils\Metadata,
|
||||
@@ -45,7 +43,7 @@ use Espo\Core\{
|
||||
Utils\DataCache,
|
||||
};
|
||||
|
||||
use Traversable;
|
||||
use RuntimeException;
|
||||
|
||||
class Table extends BaseTable
|
||||
{
|
||||
@@ -59,7 +57,14 @@ class Table extends BaseTable
|
||||
|
||||
protected $defaultAclType = 'recordAllOwnNo';
|
||||
|
||||
protected $levelList = ['yes', 'all', 'account', 'contact', 'own', 'no'];
|
||||
protected $levelList = [
|
||||
self::LEVEL_YES,
|
||||
self::LEVEL_ALL,
|
||||
self::LEVEL_ACCOUNT.
|
||||
self::LEVEL_CONTACT,
|
||||
self::LEVEL_OWN,
|
||||
self::LEVEL_NO,
|
||||
];
|
||||
|
||||
protected $isStrictModeForced = true;
|
||||
|
||||
@@ -73,7 +78,7 @@ class Table extends BaseTable
|
||||
DataCache $dataCache
|
||||
) {
|
||||
if (empty($portal)) {
|
||||
throw new Error("No portal was passed to AclPortal\\Table constructor.");
|
||||
throw new RuntimeException("No portal was passed to AclPortal\\Table constructor.");
|
||||
}
|
||||
|
||||
$this->portal = $portal;
|
||||
@@ -81,12 +86,12 @@ class Table extends BaseTable
|
||||
parent::__construct($entityManager, $user, $config, $metadata, $fieldUtil, $dataCache);
|
||||
}
|
||||
|
||||
protected function initCacheKey() : void
|
||||
protected function initCacheKey(): void
|
||||
{
|
||||
$this->cacheKey = 'aclPortal/' . $this->portal->id.'/' . $this->user->id;
|
||||
$this->cacheKey = 'aclPortal/' . $this->portal->getId() . '/' . $this->user->getId();
|
||||
}
|
||||
|
||||
protected function getRoleList() : array
|
||||
protected function getRoleList(): array
|
||||
{
|
||||
$roleList = [];
|
||||
|
||||
@@ -95,10 +100,6 @@ class Table extends BaseTable
|
||||
->getRelation($this->user, 'portalRoles')
|
||||
->find();
|
||||
|
||||
if (! $userRoleList instanceof Traversable) {
|
||||
throw new Error();
|
||||
}
|
||||
|
||||
foreach ($userRoleList as $role) {
|
||||
$roleList[] = $role;
|
||||
}
|
||||
@@ -108,10 +109,6 @@ class Table extends BaseTable
|
||||
->getRelation($this->portal, 'portalRoles')
|
||||
->find();
|
||||
|
||||
if (! $portalRoleList instanceof Traversable) {
|
||||
throw new Error();
|
||||
}
|
||||
|
||||
foreach ($portalRoleList as $role) {
|
||||
$roleList[] = $role;
|
||||
}
|
||||
@@ -119,18 +116,18 @@ class Table extends BaseTable
|
||||
return $roleList;
|
||||
}
|
||||
|
||||
protected function getScopeWithAclList() : array
|
||||
protected function getScopeWithAclList(): array
|
||||
{
|
||||
$scopeList = [];
|
||||
|
||||
$scopes = $this->metadata->get('scopes');
|
||||
|
||||
foreach ($scopes as $scope => $d) {
|
||||
if (empty($d['acl'])) {
|
||||
foreach ($scopes as $scope => $item) {
|
||||
if (empty($item['acl'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (empty($d['aclPortal'])) {
|
||||
if (empty($item['aclPortal'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -140,7 +137,7 @@ class Table extends BaseTable
|
||||
return $scopeList;
|
||||
}
|
||||
|
||||
protected function applyDefault(&$table, &$fieldTable) : void
|
||||
protected function applyDefault(&$table, &$fieldTable): void
|
||||
{
|
||||
parent::applyDefault($table, $fieldTable);
|
||||
|
||||
@@ -151,7 +148,7 @@ class Table extends BaseTable
|
||||
}
|
||||
}
|
||||
|
||||
protected function applyDisabled(&$table, &$fieldTable) : void
|
||||
protected function applyDisabled(&$table, &$fieldTable): void
|
||||
{
|
||||
foreach ($this->getScopeList() as $scope) {
|
||||
$d = $this->metadata->get('scopes.' . $scope);
|
||||
@@ -164,7 +161,7 @@ class Table extends BaseTable
|
||||
}
|
||||
}
|
||||
|
||||
protected function applyAdditional(&$table, &$fieldTable, &$valuePermissionLists) : void
|
||||
protected function applyAdditional(&$table, &$fieldTable, &$valuePermissionLists): void
|
||||
{
|
||||
}
|
||||
}
|
||||
+15
-18
@@ -27,37 +27,34 @@
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Acl;
|
||||
namespace Espo\Core\Portal\Acl;
|
||||
|
||||
use Espo\Entities\{
|
||||
User,
|
||||
Portal,
|
||||
};
|
||||
|
||||
use Espo\Core\{
|
||||
Utils\ClassFinder,
|
||||
InjectableFactory,
|
||||
};
|
||||
|
||||
class AclFactory
|
||||
class TableFactory
|
||||
{
|
||||
protected $baseClassName = Acl::class;
|
||||
|
||||
private $classFinder;
|
||||
|
||||
private $injectableFactory;
|
||||
|
||||
public function __construct(ClassFinder $classFinder, InjectableFactory $injectableFactory)
|
||||
public function __construct(InjectableFactory $injectableFactory)
|
||||
{
|
||||
$this->classFinder = $classFinder;
|
||||
$this->injectableFactory = $injectableFactory;
|
||||
}
|
||||
|
||||
public function create(string $scope): ScopeAcl
|
||||
/**
|
||||
* Create a table.
|
||||
*/
|
||||
public function create(User $user, Portal $portal): Table
|
||||
{
|
||||
$className = $this->classFinder->find('Acl', $scope);
|
||||
|
||||
if (!$className) {
|
||||
$className = $this->baseClassName;
|
||||
}
|
||||
|
||||
return $this->injectableFactory->createWith($className, [
|
||||
'scope' => $scope, // todo remove ?
|
||||
return $this->injectableFactory->createWith(Table::class, [
|
||||
'user' => $user,
|
||||
'portal' => $portal,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -29,7 +29,10 @@
|
||||
|
||||
namespace Espo\Core\Portal;
|
||||
|
||||
use Espo\ORM\Entity;
|
||||
use Espo\ORM\{
|
||||
Entity,
|
||||
EntityManager,
|
||||
};
|
||||
|
||||
use Espo\Entities\{
|
||||
User,
|
||||
@@ -37,49 +40,46 @@ use Espo\Entities\{
|
||||
};
|
||||
|
||||
use Espo\Core\{
|
||||
AclPortal\Table as Table,
|
||||
AclPortal\Acl as BasePortalAcl,
|
||||
AclPortal\AclFactory as PortalAclFactory,
|
||||
Acl\ScopeAcl,
|
||||
Acl\GlobalRestrictonFactory,
|
||||
Portal\Acl\Table as Table,
|
||||
Portal\Acl\AccessCheckerFactory,
|
||||
Portal\Acl\OwnershipCheckerFactory,
|
||||
Portal\Acl\OwnershipAccountChecker,
|
||||
Portal\Acl\OwnershipContactChecker,
|
||||
Portal\Acl\TableFactory,
|
||||
Portal\Acl,
|
||||
Acl\GlobalRestricton,
|
||||
Acl\OwnerUserFieldProvider,
|
||||
Acl\Table as TableBase,
|
||||
Portal\Acl as UserAclWrapper,
|
||||
AclManager as BaseAclManager,
|
||||
InjectableFactory,
|
||||
ORM\EntityManager,
|
||||
AclManager as InternalAclManager,
|
||||
};
|
||||
|
||||
use StdClass;
|
||||
use RuntimeException;
|
||||
|
||||
class AclManager extends BaseAclManager
|
||||
class AclManager extends InternalAclManager
|
||||
{
|
||||
protected $tableClassName = Table::class;
|
||||
protected $userAclClassName = Acl::class;
|
||||
|
||||
protected $userAclClassName = UserAclWrapper::class;
|
||||
|
||||
protected $baseImplementationClassName = BasePortalAcl::class;
|
||||
|
||||
private $mainManager = null;
|
||||
private $internalAclManager = null;
|
||||
|
||||
private $portal = null;
|
||||
|
||||
public function __construct(
|
||||
InjectableFactory $injectableFactory,
|
||||
EntityManager $entityManager,
|
||||
PortalAclFactory $aclFactory,
|
||||
GlobalRestrictonFactory $globalRestrictonFactory,
|
||||
AccessCheckerFactory $accessCheckerFactory,
|
||||
OwnershipCheckerFactory $ownershipCheckerFactory,
|
||||
TableFactory $tableFactory,
|
||||
GlobalRestricton $globalRestricton,
|
||||
OwnerUserFieldProvider $ownerUserFieldProvider,
|
||||
BaseAclManager $mainManager
|
||||
EntityManager $entityManager,
|
||||
InternalAclManager $internalAclManager
|
||||
) {
|
||||
$this->injectableFactory = $injectableFactory;
|
||||
$this->entityManager = $entityManager;
|
||||
$this->aclFactory = $aclFactory;
|
||||
$this->accessCheckerFactory = $accessCheckerFactory;
|
||||
$this->ownershipCheckerFactory = $ownershipCheckerFactory;
|
||||
$this->tableFactory = $tableFactory;
|
||||
$this->globalRestricton = $globalRestricton;
|
||||
$this->ownerUserFieldProvider = $ownerUserFieldProvider;
|
||||
$this->mainManager = $mainManager;
|
||||
|
||||
$this->globalRestricton = $globalRestrictonFactory->create();
|
||||
$this->entityManager = $entityManager;
|
||||
$this->internalAclManager = $internalAclManager;
|
||||
}
|
||||
|
||||
public function setPortal(Portal $portal): void
|
||||
@@ -98,46 +98,23 @@ class AclManager extends BaseAclManager
|
||||
|
||||
protected function getTable(User $user): TableBase
|
||||
{
|
||||
$key = $user->id;
|
||||
$key = $user->getId();
|
||||
|
||||
if (empty($key)) {
|
||||
if (!$key) {
|
||||
$key = spl_object_hash($user);
|
||||
}
|
||||
|
||||
if (empty($this->tableHashMap[$key])) {
|
||||
$this->tableHashMap[$key] = $this->injectableFactory->createWith($this->tableClassName, [
|
||||
'user' => $user,
|
||||
'portal' => $this->getPortal(),
|
||||
]);
|
||||
if (!array_key_exists($key, $this->tableHashMap)) {
|
||||
$this->tableHashMap[$key] = $this->tableFactory->create($user, $this->getPortal());
|
||||
}
|
||||
|
||||
return $this->tableHashMap[$key];
|
||||
}
|
||||
|
||||
public function checkReadOnlyAccount(User $user, string $scope): bool
|
||||
{
|
||||
return $this->getLevel($user, $scope, Table::ACTION_READ) === Table::LEVEL_ACCOUNT;
|
||||
}
|
||||
|
||||
public function checkReadOnlyContact(User $user, string $scope): bool
|
||||
{
|
||||
return $this->getLevel($user, $scope, Table::ACTION_READ)=== Table::LEVEL_CONTACT;
|
||||
}
|
||||
|
||||
public function checkInAccount(User $user, Entity $entity): bool
|
||||
{
|
||||
return (bool) $this->getEntityImplementation($entity->getEntityType())->checkInAccount($user, $entity);
|
||||
}
|
||||
|
||||
public function checkIsOwnContact(User $user, Entity $entity): bool
|
||||
{
|
||||
return (bool) $this->getEntityImplementation($entity->getEntityType())->checkIsOwnContact($user, $entity);
|
||||
}
|
||||
|
||||
public function getMap(User $user): StdClass
|
||||
{
|
||||
if ($this->checkUserIsNotPortal($user)) {
|
||||
return $this->mainManager->getMap($user);
|
||||
return $this->internalAclManager->getMap($user);
|
||||
}
|
||||
|
||||
return parent::getMap($user);
|
||||
@@ -146,25 +123,25 @@ class AclManager extends BaseAclManager
|
||||
public function getLevel(User $user, string $scope, string $action): string
|
||||
{
|
||||
if ($this->checkUserIsNotPortal($user)) {
|
||||
return $this->mainManager->getLevel($user, $scope, $action);
|
||||
return $this->internalAclManager->getLevel($user, $scope, $action);
|
||||
}
|
||||
|
||||
return parent::getLevel($user, $scope, $action);
|
||||
}
|
||||
|
||||
public function get(User $user, string $permission): ?string
|
||||
public function getPermissionLevel(User $user, string $permission): string
|
||||
{
|
||||
if ($this->checkUserIsNotPortal($user)) {
|
||||
return $this->mainManager->get($user, $permission);
|
||||
return $this->internalAclManager->getPermissionLevel($user, $permission);
|
||||
}
|
||||
|
||||
return parent::get($user, $permission);
|
||||
return parent::getPermissionLevel($user, $permission);
|
||||
}
|
||||
|
||||
public function checkReadOnlyTeam(User $user, string $scope): bool
|
||||
{
|
||||
if ($this->checkUserIsNotPortal($user)) {
|
||||
return $this->mainManager->checkReadOnlyTeam($user, $scope);
|
||||
return $this->internalAclManager->checkReadOnlyTeam($user, $scope);
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -173,7 +150,7 @@ class AclManager extends BaseAclManager
|
||||
public function checkReadNo(User $user, string $scope): bool
|
||||
{
|
||||
if ($this->checkUserIsNotPortal($user)) {
|
||||
return $this->mainManager->checkReadNo($user, $scope);
|
||||
return $this->internalAclManager->checkReadNo($user, $scope);
|
||||
}
|
||||
|
||||
return parent::checkReadNo($user, $scope);
|
||||
@@ -182,7 +159,7 @@ class AclManager extends BaseAclManager
|
||||
public function checkReadOnlyOwn(User $user, string $scope): bool
|
||||
{
|
||||
if ($this->checkUserIsNotPortal($user)) {
|
||||
return $this->mainManager->checkReadOnlyOwn($user, $scope);
|
||||
return $this->internalAclManager->checkReadOnlyOwn($user, $scope);
|
||||
}
|
||||
|
||||
return parent::checkReadOnlyOwn($user, $scope);
|
||||
@@ -191,16 +168,32 @@ class AclManager extends BaseAclManager
|
||||
public function checkReadAll(User $user, string $scope): bool
|
||||
{
|
||||
if ($this->checkUserIsNotPortal($user)) {
|
||||
return $this->mainManager->checkReadAll($user, $scope);
|
||||
return $this->internalAclManager->checkReadAll($user, $scope);
|
||||
}
|
||||
|
||||
return parent::checkReadAll($user, $scope);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether 'read' access is set to 'account' for a specific scope.
|
||||
*/
|
||||
public function checkReadOnlyAccount(User $user, string $scope): bool
|
||||
{
|
||||
return $this->getLevel($user, $scope, Table::ACTION_READ) === Table::LEVEL_ACCOUNT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether 'read' access is set to 'contact' for a specific scope.
|
||||
*/
|
||||
public function checkReadOnlyContact(User $user, string $scope): bool
|
||||
{
|
||||
return $this->getLevel($user, $scope, Table::ACTION_READ)=== Table::LEVEL_CONTACT;
|
||||
}
|
||||
|
||||
public function check(User $user, $subject, ?string $action = null): bool
|
||||
{
|
||||
if ($this->checkUserIsNotPortal($user)) {
|
||||
return $this->mainManager->check($user, $subject, $action);
|
||||
return $this->internalAclManager->check($user, $subject, $action);
|
||||
}
|
||||
|
||||
return parent::check($user, $subject, $action);
|
||||
@@ -209,34 +202,67 @@ class AclManager extends BaseAclManager
|
||||
public function checkEntity(User $user, Entity $entity, string $action = Table::ACTION_READ): bool
|
||||
{
|
||||
if ($this->checkUserIsNotPortal($user)) {
|
||||
return $this->mainManager->checkEntity($user, $entity, $action);
|
||||
return $this->internalAclManager->checkEntity($user, $entity, $action);
|
||||
}
|
||||
|
||||
return parent::checkEntity($user, $entity, $action);
|
||||
}
|
||||
|
||||
public function checkIsOwner(User $user, Entity $entity): bool
|
||||
public function checkUserPermission(User $user, $target, string $permissionType = 'user'): bool
|
||||
{
|
||||
if ($this->checkUserIsNotPortal($user)) {
|
||||
return $this->mainManager->checkIsOwner($user, $entity);
|
||||
}
|
||||
|
||||
return parent::checkIsOwner($user, $entity);
|
||||
return $this->internalAclManager->checkUserPermission($user, $target, $permissionType);
|
||||
}
|
||||
|
||||
public function checkInTeam(User $user, Entity $entity): bool
|
||||
public function checkOwnershipOwn(User $user, Entity $entity): bool
|
||||
{
|
||||
if ($this->checkUserIsNotPortal($user)) {
|
||||
return $this->mainManager->checkInTeam($user, $entity);
|
||||
return $this->internalAclManager->checkOwnershipOwn($user, $entity);
|
||||
}
|
||||
|
||||
return parent::checkInTeam($user, $entity);
|
||||
return parent::checkOwnershipOwn($user, $entity);
|
||||
}
|
||||
|
||||
public function checkOwnershipTeam(User $user, Entity $entity): bool
|
||||
{
|
||||
if ($this->checkUserIsNotPortal($user)) {
|
||||
return $this->internalAclManager->checkOwnershipTeam($user, $entity);
|
||||
}
|
||||
|
||||
return parent::checkOwnershipOwn($user, $entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether an entity belongs to a user account.
|
||||
*/
|
||||
public function checkOwnershipAccount(User $user, Entity $entity): bool
|
||||
{
|
||||
$checker = $this->getOwnershipChecker($entity->getEntityType());
|
||||
|
||||
if (!$checker instanceof OwnershipAccountChecker) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $checker->checkAccount($user, $entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether an entity belongs to a user account.
|
||||
*/
|
||||
public function checkOwnershipContact(User $user, Entity $entity): bool
|
||||
{
|
||||
$checker = $this->getOwnershipChecker($entity->getEntityType());
|
||||
|
||||
if (!$checker instanceof OwnershipContactChecker) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $checker->checkContact($user, $entity);
|
||||
}
|
||||
|
||||
public function checkScope(User $user, string $scope, ?string $action = null): bool
|
||||
{
|
||||
if ($this->checkUserIsNotPortal($user)) {
|
||||
return $this->mainManager->checkScope($user, $scope, $action);
|
||||
return $this->internalAclManager->checkScope($user, $scope, $action);
|
||||
}
|
||||
|
||||
return parent::checkScope($user, $scope, $action);
|
||||
@@ -245,7 +271,7 @@ class AclManager extends BaseAclManager
|
||||
public function checkUser(User $user, string $permission, User $entity): bool
|
||||
{
|
||||
if ($this->checkUserIsNotPortal($user)) {
|
||||
return $this->mainManager->checkUser($user, $permission, $entity);
|
||||
return $this->internalAclManager->checkUser($user, $permission, $entity);
|
||||
}
|
||||
|
||||
return parent::checkUser($user, $permission, $entity);
|
||||
@@ -259,7 +285,7 @@ class AclManager extends BaseAclManager
|
||||
): array {
|
||||
|
||||
if ($this->checkUserIsNotPortal($user)) {
|
||||
return $this->mainManager->getScopeForbiddenAttributeList($user, $scope, $action, $thresholdLevel);
|
||||
return $this->internalAclManager->getScopeForbiddenAttributeList($user, $scope, $action, $thresholdLevel);
|
||||
}
|
||||
|
||||
return parent::getScopeForbiddenAttributeList($user, $scope, $action, $thresholdLevel);
|
||||
@@ -273,7 +299,7 @@ class AclManager extends BaseAclManager
|
||||
): array {
|
||||
|
||||
if ($this->checkUserIsNotPortal($user)) {
|
||||
return $this->mainManager->getScopeForbiddenFieldList($user, $scope, $action, $thresholdLevel);
|
||||
return $this->internalAclManager->getScopeForbiddenFieldList($user, $scope, $action, $thresholdLevel);
|
||||
}
|
||||
|
||||
return parent::getScopeForbiddenFieldList($user, $scope, $action, $thresholdLevel);
|
||||
@@ -283,4 +309,44 @@ class AclManager extends BaseAclManager
|
||||
{
|
||||
return !$user->isPortal();
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use `getPermissionLevel` instead.
|
||||
*/
|
||||
public function get(User $user, string $permission): string
|
||||
{
|
||||
return $this->getPermissionLevel($user, $permission);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
public function checkIsOwner(User $user, Entity $entity): bool
|
||||
{
|
||||
return $this->checkOwnershipOwn($user, $entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
public function checkInTeam(User $user, Entity $entity): bool
|
||||
{
|
||||
return $this->checkOwnershipTeam($user, $entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
public function checkInAccount(User $user, Entity $entity): bool
|
||||
{
|
||||
return $this->checkOwnershipAccount($user, $entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
public function checkIsOwnContact(User $user, Entity $entity): bool
|
||||
{
|
||||
return $this->checkOwnershipContact($user, $entity);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,10 +48,10 @@ class AclManager implements Loader
|
||||
$this->internalAclManager = $internalAclManager;
|
||||
}
|
||||
|
||||
public function load() : PortalAclManager
|
||||
public function load(): PortalAclManager
|
||||
{
|
||||
return $this->injectableFactory->createWith(PortalAclManager::class,[
|
||||
'mainManager' => $this->internalAclManager,
|
||||
'internalAclManager' => $this->internalAclManager,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,10 +37,9 @@ use Espo\Core\{
|
||||
Acl\Acl,
|
||||
Acl\ScopeData,
|
||||
Acl\Table,
|
||||
Acl\EntityReadAcl,
|
||||
};
|
||||
|
||||
class Meeting extends Acl implements EntityReadAcl
|
||||
class Meeting extends Acl
|
||||
{
|
||||
public function checkEntityRead(User $user, Entity $entity, ScopeData $data): bool
|
||||
{
|
||||
|
||||
@@ -36,11 +36,10 @@ use Espo\ORM\Entity;
|
||||
use Espo\Core\{
|
||||
Acl\ScopeData,
|
||||
Acl\Table,
|
||||
Acl\EntityReadAcl,
|
||||
AclPortal\Acl as Acl,
|
||||
};
|
||||
|
||||
class KnowledgeBaseArticle extends Acl implements EntityReadAcl
|
||||
class KnowledgeBaseArticle extends Acl
|
||||
{
|
||||
public function checkEntityRead(EntityUser $user, Entity $entity, ScopeData $data): bool
|
||||
{
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"accessCheckerClassName": "Espo\\Classes\\Acl\\Attachment\\AccessChecker",
|
||||
"ownershipCheckerClassName": "Espo\\Classes\\Acl\\Attachment\\OwnershipChecker"
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"readOwnerUserField": "users"
|
||||
}
|
||||
@@ -22,6 +22,7 @@
|
||||
["app", "actions", "__ANY__", "implementationClassName"],
|
||||
["selectDefs"],
|
||||
["recordDefs"],
|
||||
["aclDefs"],
|
||||
["authenticationMethods", "__ANY__", "implementationClassName"]
|
||||
]
|
||||
}
|
||||
|
||||
@@ -8,6 +8,5 @@
|
||||
"users": {
|
||||
"onlyAdmin": true
|
||||
}
|
||||
},
|
||||
"readOwnerUserField": "users"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2021 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* EspoCRM is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* EspoCRM is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
|
||||
*
|
||||
* 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 General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace tests\unit\Espo\Core\Acl;
|
||||
|
||||
use Espo\Core\{
|
||||
Acl\ScopeCheckerData,
|
||||
};
|
||||
|
||||
class ScopeCheckerDataTest extends \PHPUnit\Framework\TestCase
|
||||
{
|
||||
protected function setUp() : void
|
||||
{
|
||||
}
|
||||
|
||||
public function testCheckerData0()
|
||||
{
|
||||
$checkerData = ScopeCheckerData
|
||||
::createBuilder()
|
||||
->build();
|
||||
|
||||
$this->assertEquals(false, $checkerData->isOwn());
|
||||
$this->assertEquals(false, $checkerData->inTeam());
|
||||
}
|
||||
|
||||
public function testCheckerData1()
|
||||
{
|
||||
$checkerData = ScopeCheckerData
|
||||
::createBuilder()
|
||||
->setIsOwn(true)
|
||||
->setInTeam(true)
|
||||
->build();
|
||||
|
||||
$this->assertEquals(true, $checkerData->isOwn());
|
||||
$this->assertEquals(true, $checkerData->inTeam());
|
||||
}
|
||||
|
||||
public function testCheckerData2()
|
||||
{
|
||||
$checkerData = ScopeCheckerData
|
||||
::createBuilder()
|
||||
->setIsOwn(false)
|
||||
->setInTeamChecker(
|
||||
function (): bool {
|
||||
return true;
|
||||
}
|
||||
)
|
||||
->build();
|
||||
|
||||
$this->assertEquals(false, $checkerData->isOwn());
|
||||
$this->assertEquals(true, $checkerData->inTeam());
|
||||
}
|
||||
|
||||
public function testCheckerData3()
|
||||
{
|
||||
$checkerData = ScopeCheckerData
|
||||
::createBuilder()
|
||||
->setIsOwnChecker(
|
||||
function (): bool {
|
||||
return true;
|
||||
}
|
||||
)
|
||||
->setInTeamChecker(
|
||||
function (): bool {
|
||||
return false;
|
||||
}
|
||||
)
|
||||
->build();
|
||||
|
||||
$this->assertEquals(true, $checkerData->isOwn());
|
||||
$this->assertEquals(false, $checkerData->inTeam());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,435 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2021 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* EspoCRM is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* EspoCRM is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
|
||||
*
|
||||
* 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 General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace tests\unit\Espo\Core\Acl;
|
||||
|
||||
use Espo\Core\{
|
||||
Acl\ScopeChecker,
|
||||
Acl\ScopeCheckerData,
|
||||
Acl\ScopeData,
|
||||
Acl\Table,
|
||||
};
|
||||
|
||||
class ScopeCheckerTest extends \PHPUnit\Framework\TestCase
|
||||
{
|
||||
/**
|
||||
* @var ScopeChecker
|
||||
*/
|
||||
private $scopeChecker;
|
||||
|
||||
protected function setUp() : void
|
||||
{
|
||||
$this->scopeChecker = new ScopeChecker();
|
||||
}
|
||||
|
||||
public function testCheckerNoData1()
|
||||
{
|
||||
$data = ScopeData::fromRaw(false);
|
||||
|
||||
$result = $this->scopeChecker->check($data);
|
||||
|
||||
$this->assertEquals(false, $result);
|
||||
}
|
||||
|
||||
public function testCheckerNoData2()
|
||||
{
|
||||
$data = ScopeData::fromRaw(true);
|
||||
|
||||
$result = $this->scopeChecker->check($data);
|
||||
|
||||
$this->assertEquals(true, $result);
|
||||
}
|
||||
|
||||
public function testCheckerNoData3()
|
||||
{
|
||||
$data = ScopeData::fromRaw(
|
||||
(object) [
|
||||
Table::ACTION_READ => Table::LEVEL_NO,
|
||||
],
|
||||
);
|
||||
|
||||
$result = $this->scopeChecker->check($data);
|
||||
|
||||
$this->assertEquals(false, $result);
|
||||
}
|
||||
|
||||
public function testCheckerNoData4()
|
||||
{
|
||||
$data = ScopeData::fromRaw(
|
||||
(object) [
|
||||
Table::ACTION_READ => Table::LEVEL_ALL,
|
||||
],
|
||||
);
|
||||
|
||||
$result = $this->scopeChecker->check($data);
|
||||
|
||||
$this->assertEquals(true, $result);
|
||||
}
|
||||
|
||||
public function testCheckerNoData5()
|
||||
{
|
||||
$data = ScopeData::fromRaw(
|
||||
(object) [
|
||||
Table::ACTION_READ => Table::LEVEL_TEAM,
|
||||
],
|
||||
);
|
||||
|
||||
$result = $this->scopeChecker->check($data);
|
||||
|
||||
$this->assertEquals(true, $result);
|
||||
}
|
||||
|
||||
public function testCheckerActionNoData1()
|
||||
{
|
||||
$data = ScopeData::fromRaw(false);
|
||||
|
||||
$result = $this->scopeChecker->check($data, Table::ACTION_CREATE);
|
||||
|
||||
$this->assertEquals(false, $result);
|
||||
}
|
||||
|
||||
public function testCheckerActionNoData2()
|
||||
{
|
||||
$data = ScopeData::fromRaw(true);
|
||||
|
||||
$result = $this->scopeChecker->check($data, Table::ACTION_CREATE);
|
||||
|
||||
$this->assertEquals(true, $result);
|
||||
}
|
||||
|
||||
public function testCheckerData1()
|
||||
{
|
||||
$checkerData = ScopeCheckerData
|
||||
::createBuilder()
|
||||
->setIsOwn(false)
|
||||
->setInTeam(false)
|
||||
->build();
|
||||
|
||||
$data = ScopeData::fromRaw(
|
||||
(object) [
|
||||
Table::ACTION_READ => Table::LEVEL_ALL,
|
||||
],
|
||||
);
|
||||
|
||||
$result = $this->scopeChecker->check($data, Table::ACTION_READ, $checkerData);
|
||||
|
||||
$this->assertEquals(true, $result);
|
||||
}
|
||||
|
||||
public function testCheckerData2()
|
||||
{
|
||||
$checkerData = ScopeCheckerData
|
||||
::createBuilder()
|
||||
->setIsOwn(false)
|
||||
->setInTeam(false)
|
||||
->build();
|
||||
|
||||
$data = ScopeData::fromRaw(
|
||||
(object) [
|
||||
Table::ACTION_READ => Table::LEVEL_TEAM,
|
||||
],
|
||||
);
|
||||
|
||||
$result = $this->scopeChecker->check($data, Table::ACTION_READ, $checkerData);
|
||||
|
||||
$this->assertEquals(false, $result);
|
||||
}
|
||||
|
||||
public function testCheckerData3()
|
||||
{
|
||||
$checkerData = ScopeCheckerData
|
||||
::createBuilder()
|
||||
->setIsOwn(false)
|
||||
->setInTeam(false)
|
||||
->build();
|
||||
|
||||
$data = ScopeData::fromRaw(
|
||||
(object) [
|
||||
Table::ACTION_READ => Table::LEVEL_OWN,
|
||||
],
|
||||
);
|
||||
|
||||
$result = $this->scopeChecker->check($data, Table::ACTION_READ, $checkerData);
|
||||
|
||||
$this->assertEquals(false, $result);
|
||||
}
|
||||
|
||||
public function testCheckerData4()
|
||||
{
|
||||
$checkerData = ScopeCheckerData
|
||||
::createBuilder()
|
||||
->setIsOwn(false)
|
||||
->setInTeam(false)
|
||||
->build();
|
||||
|
||||
$data = ScopeData::fromRaw(
|
||||
(object) [
|
||||
Table::ACTION_READ => Table::LEVEL_NO,
|
||||
],
|
||||
);
|
||||
|
||||
$result = $this->scopeChecker->check($data, Table::ACTION_READ, $checkerData);
|
||||
|
||||
$this->assertEquals(false, $result);
|
||||
}
|
||||
|
||||
public function testCheckerData5()
|
||||
{
|
||||
$checkerData = ScopeCheckerData
|
||||
::createBuilder()
|
||||
->setIsOwn(false)
|
||||
->setInTeam(true)
|
||||
->build();
|
||||
|
||||
$data = ScopeData::fromRaw(
|
||||
(object) [
|
||||
Table::ACTION_READ => Table::LEVEL_OWN,
|
||||
],
|
||||
);
|
||||
|
||||
$result = $this->scopeChecker->check($data, Table::ACTION_READ, $checkerData);
|
||||
|
||||
$this->assertEquals(false, $result);
|
||||
}
|
||||
|
||||
public function testCheckerData6()
|
||||
{
|
||||
$checkerData = ScopeCheckerData
|
||||
::createBuilder()
|
||||
->setIsOwn(false)
|
||||
->setInTeam(true)
|
||||
->build();
|
||||
|
||||
$data = ScopeData::fromRaw(
|
||||
(object) [
|
||||
Table::ACTION_READ => Table::LEVEL_TEAM,
|
||||
],
|
||||
);
|
||||
|
||||
$result = $this->scopeChecker->check($data, Table::ACTION_READ, $checkerData);
|
||||
|
||||
$this->assertEquals(true, $result);
|
||||
}
|
||||
|
||||
public function testCheckerData7()
|
||||
{
|
||||
$checkerData = ScopeCheckerData
|
||||
::createBuilder()
|
||||
->setIsOwn(true)
|
||||
->setInTeam(true)
|
||||
->build();
|
||||
|
||||
$data = ScopeData::fromRaw(
|
||||
(object) [
|
||||
Table::ACTION_READ => Table::LEVEL_TEAM,
|
||||
],
|
||||
);
|
||||
|
||||
$result = $this->scopeChecker->check($data, Table::ACTION_READ, $checkerData);
|
||||
|
||||
$this->assertEquals(true, $result);
|
||||
}
|
||||
|
||||
public function testCheckerData8()
|
||||
{
|
||||
$checkerData = ScopeCheckerData
|
||||
::createBuilder()
|
||||
->setIsOwn(false)
|
||||
->setInTeam(false)
|
||||
->build();
|
||||
|
||||
$data = ScopeData::fromRaw(
|
||||
(object) [
|
||||
Table::ACTION_CREATE => Table::LEVEL_YES,
|
||||
],
|
||||
);
|
||||
|
||||
$result = $this->scopeChecker->check($data, Table::ACTION_CREATE, $checkerData);
|
||||
|
||||
$this->assertEquals(true, $result);
|
||||
}
|
||||
|
||||
public function testCheckerData9()
|
||||
{
|
||||
$checkerData = ScopeCheckerData
|
||||
::createBuilder()
|
||||
->setIsOwn(true)
|
||||
->setInTeam(false)
|
||||
->build();
|
||||
|
||||
$data = ScopeData::fromRaw(
|
||||
(object) [
|
||||
Table::ACTION_READ => Table::LEVEL_OWN,
|
||||
],
|
||||
);
|
||||
|
||||
$result = $this->scopeChecker->check($data, Table::ACTION_READ, $checkerData);
|
||||
|
||||
$this->assertEquals(true, $result);
|
||||
}
|
||||
|
||||
public function testCheckerData10()
|
||||
{
|
||||
$checkerData = ScopeCheckerData
|
||||
::createBuilder()
|
||||
->setIsOwn(true)
|
||||
->setInTeam(true)
|
||||
->build();
|
||||
|
||||
$data = ScopeData::fromRaw(
|
||||
(object) [
|
||||
Table::ACTION_READ => Table::LEVEL_OWN,
|
||||
],
|
||||
);
|
||||
|
||||
$result = $this->scopeChecker->check($data, Table::ACTION_READ, $checkerData);
|
||||
|
||||
$this->assertEquals(true, $result);
|
||||
}
|
||||
|
||||
public function testCheckerData11()
|
||||
{
|
||||
$checkerData = ScopeCheckerData
|
||||
::createBuilder()
|
||||
->setIsOwn(true)
|
||||
->setInTeam(false)
|
||||
->build();
|
||||
|
||||
$data = ScopeData::fromRaw(
|
||||
(object) [
|
||||
Table::ACTION_READ => Table::LEVEL_TEAM,
|
||||
],
|
||||
);
|
||||
|
||||
$result = $this->scopeChecker->check($data, Table::ACTION_READ, $checkerData);
|
||||
|
||||
$this->assertEquals(true, $result);
|
||||
}
|
||||
|
||||
public function testCheckerData12()
|
||||
{
|
||||
$checkerData = ScopeCheckerData
|
||||
::createBuilder()
|
||||
->setIsOwn(true)
|
||||
->setInTeam(true)
|
||||
->build();
|
||||
|
||||
$data = ScopeData::fromRaw(
|
||||
(object) [],
|
||||
);
|
||||
|
||||
$result = $this->scopeChecker->check($data, Table::ACTION_READ, $checkerData);
|
||||
|
||||
$this->assertEquals(false, $result);
|
||||
}
|
||||
|
||||
public function testCheckerData13()
|
||||
{
|
||||
$checkerData = ScopeCheckerData
|
||||
::createBuilder()
|
||||
->setIsOwn(false)
|
||||
->setInTeam(false)
|
||||
->build();
|
||||
|
||||
$data = ScopeData::fromRaw(false);
|
||||
|
||||
$result = $this->scopeChecker->check($data, Table::ACTION_READ, $checkerData);
|
||||
|
||||
$this->assertEquals(false, $result);
|
||||
}
|
||||
|
||||
public function testCheckerDataNoAction1()
|
||||
{
|
||||
$checkerData = ScopeCheckerData
|
||||
::createBuilder()
|
||||
->setIsOwn(true)
|
||||
->setInTeam(true)
|
||||
->build();
|
||||
|
||||
$data = ScopeData::fromRaw(false);
|
||||
|
||||
$result = $this->scopeChecker->check($data, null, $checkerData);
|
||||
|
||||
$this->assertEquals(false, $result);
|
||||
}
|
||||
|
||||
public function testCheckerDataNoAction2()
|
||||
{
|
||||
$checkerData = ScopeCheckerData
|
||||
::createBuilder()
|
||||
->setIsOwn(false)
|
||||
->setInTeam(false)
|
||||
->build();
|
||||
|
||||
$data = ScopeData::fromRaw(true);
|
||||
|
||||
$result = $this->scopeChecker->check($data, null, $checkerData);
|
||||
|
||||
$this->assertEquals(true, $result);
|
||||
}
|
||||
|
||||
public function testCheckerDataNoAction3()
|
||||
{
|
||||
$checkerData = ScopeCheckerData
|
||||
::createBuilder()
|
||||
->setIsOwn(true)
|
||||
->setInTeam(true)
|
||||
->build();
|
||||
|
||||
$data = ScopeData::fromRaw(
|
||||
(object) [
|
||||
Table::ACTION_READ => Table::LEVEL_TEAM,
|
||||
],
|
||||
);
|
||||
|
||||
$result = $this->scopeChecker->check($data, null, $checkerData);
|
||||
|
||||
$this->assertEquals(true, $result);
|
||||
}
|
||||
|
||||
public function testCheckerDataNoAction4()
|
||||
{
|
||||
$checkerData = ScopeCheckerData
|
||||
::createBuilder()
|
||||
->setIsOwn(true)
|
||||
->setInTeam(true)
|
||||
->build();
|
||||
|
||||
$data = ScopeData::fromRaw(
|
||||
(object) [
|
||||
Table::ACTION_CREATE => Table::LEVEL_NO,
|
||||
Table::ACTION_READ => Table::LEVEL_NO,
|
||||
],
|
||||
);
|
||||
|
||||
$result = $this->scopeChecker->check($data, null, $checkerData);
|
||||
|
||||
$this->assertEquals(false, $result);
|
||||
}
|
||||
}
|
||||
@@ -39,7 +39,6 @@ use RuntimeException;
|
||||
|
||||
class ScopeDataTest extends \PHPUnit\Framework\TestCase
|
||||
{
|
||||
|
||||
protected function setUp() : void
|
||||
{
|
||||
}
|
||||
@@ -52,8 +51,6 @@ class ScopeDataTest extends \PHPUnit\Framework\TestCase
|
||||
$this->assertTrue($data->isTrue());
|
||||
$this->assertFalse($data->isFalse());
|
||||
|
||||
$this->assertTrue($data->getRaw());
|
||||
|
||||
$this->assertEquals(Table::LEVEL_NO, $data->getDelete());
|
||||
}
|
||||
|
||||
@@ -65,8 +62,6 @@ class ScopeDataTest extends \PHPUnit\Framework\TestCase
|
||||
$this->assertTrue($data->isFalse());
|
||||
$this->assertFalse($data->isTrue());
|
||||
|
||||
$this->assertFalse($data->getRaw());
|
||||
|
||||
$this->assertEquals(Table::LEVEL_NO, $data->getDelete());
|
||||
}
|
||||
|
||||
@@ -103,10 +98,6 @@ class ScopeDataTest extends \PHPUnit\Framework\TestCase
|
||||
$this->assertEquals(Table::LEVEL_TEAM, $data->getEdit());
|
||||
$this->assertEquals(Table::LEVEL_NO, $data->getDelete());
|
||||
|
||||
$this->assertEquals($raw, $data->getRaw());
|
||||
|
||||
$this->assertNotSame($raw, $data->getRaw());
|
||||
|
||||
$this->assertTrue($data->hasNotNo());
|
||||
}
|
||||
|
||||
@@ -145,4 +136,13 @@ class ScopeDataTest extends \PHPUnit\Framework\TestCase
|
||||
|
||||
$data->read;
|
||||
}
|
||||
|
||||
public function testBadKey()
|
||||
{
|
||||
$this->expectException(RuntimeException::class);
|
||||
|
||||
ScopeData::fromRaw((object) [
|
||||
Table::ACTION_CREATE => false,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2021 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* EspoCRM is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* EspoCRM is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
|
||||
*
|
||||
* 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 General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace tests\unit\Espo\Core\AclPortal;
|
||||
|
||||
use Espo\Core\{
|
||||
AclPortal\ScopeCheckerData,
|
||||
};
|
||||
|
||||
class ScopeCheckerDataTest extends \PHPUnit\Framework\TestCase
|
||||
{
|
||||
protected function setUp() : void
|
||||
{
|
||||
}
|
||||
|
||||
public function testCheckerData0()
|
||||
{
|
||||
$checkerData = ScopeCheckerData
|
||||
::createBuilder()
|
||||
->build();
|
||||
|
||||
$this->assertEquals(false, $checkerData->isOwn());
|
||||
$this->assertEquals(false, $checkerData->inAccount());
|
||||
$this->assertEquals(false, $checkerData->inContact());
|
||||
}
|
||||
|
||||
public function testCheckerData1()
|
||||
{
|
||||
$checkerData = ScopeCheckerData
|
||||
::createBuilder()
|
||||
->setIsOwn(true)
|
||||
->setInAccount(true)
|
||||
->setInContact(true)
|
||||
->build();
|
||||
|
||||
$this->assertEquals(true, $checkerData->isOwn());
|
||||
$this->assertEquals(true, $checkerData->inAccount());
|
||||
$this->assertEquals(true, $checkerData->inContact());
|
||||
}
|
||||
|
||||
public function testCheckerData2()
|
||||
{
|
||||
$checkerData = ScopeCheckerData
|
||||
::createBuilder()
|
||||
->setIsOwn(false)
|
||||
->setInAccountChecker(
|
||||
function (): bool {
|
||||
return true;
|
||||
}
|
||||
)
|
||||
->build();
|
||||
|
||||
$this->assertEquals(false, $checkerData->isOwn());
|
||||
$this->assertEquals(true, $checkerData->inAccount());
|
||||
}
|
||||
|
||||
public function testCheckerData3()
|
||||
{
|
||||
$checkerData = ScopeCheckerData
|
||||
::createBuilder()
|
||||
->setIsOwnChecker(
|
||||
function (): bool {
|
||||
return false;
|
||||
}
|
||||
)
|
||||
->setInAccountChecker(
|
||||
function (): bool {
|
||||
return false;
|
||||
}
|
||||
)
|
||||
->setInContactChecker(
|
||||
function (): bool {
|
||||
return true;
|
||||
}
|
||||
)
|
||||
->build();
|
||||
|
||||
$this->assertEquals(false, $checkerData->isOwn());
|
||||
$this->assertEquals(false, $checkerData->inAccount());
|
||||
$this->assertEquals(true, $checkerData->inContact());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,529 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2021 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* EspoCRM is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* EspoCRM is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
|
||||
*
|
||||
* 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 General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace tests\unit\Espo\Core\AclPortal;
|
||||
|
||||
use Espo\Core\{
|
||||
AclPortal\ScopeChecker,
|
||||
AclPortal\ScopeCheckerData,
|
||||
AclPortal\Table,
|
||||
Acl\ScopeData,
|
||||
};
|
||||
|
||||
class ScopeCheckerTest extends \PHPUnit\Framework\TestCase
|
||||
{
|
||||
/**
|
||||
* @var ScopeChecker
|
||||
*/
|
||||
private $scopeChecker;
|
||||
|
||||
protected function setUp() : void
|
||||
{
|
||||
$this->scopeChecker = new ScopeChecker();
|
||||
}
|
||||
|
||||
public function testCheckerNoData1()
|
||||
{
|
||||
$data = ScopeData::fromRaw(false);
|
||||
|
||||
$result = $this->scopeChecker->check($data);
|
||||
|
||||
$this->assertEquals(false, $result);
|
||||
}
|
||||
|
||||
public function testCheckerNoData2()
|
||||
{
|
||||
$data = ScopeData::fromRaw(true);
|
||||
|
||||
$result = $this->scopeChecker->check($data);
|
||||
|
||||
$this->assertEquals(true, $result);
|
||||
}
|
||||
|
||||
public function testCheckerNoData3()
|
||||
{
|
||||
$data = ScopeData::fromRaw(
|
||||
(object) [
|
||||
Table::ACTION_READ => Table::LEVEL_NO,
|
||||
],
|
||||
);
|
||||
|
||||
$result = $this->scopeChecker->check($data);
|
||||
|
||||
$this->assertEquals(false, $result);
|
||||
}
|
||||
|
||||
public function testCheckerNoData4()
|
||||
{
|
||||
$data = ScopeData::fromRaw(
|
||||
(object) [
|
||||
Table::ACTION_READ => Table::LEVEL_ALL,
|
||||
],
|
||||
);
|
||||
|
||||
$result = $this->scopeChecker->check($data);
|
||||
|
||||
$this->assertEquals(true, $result);
|
||||
}
|
||||
|
||||
public function testCheckerNoData5()
|
||||
{
|
||||
$data = ScopeData::fromRaw(
|
||||
(object) [
|
||||
Table::ACTION_READ => Table::LEVEL_ACCOUNT,
|
||||
],
|
||||
);
|
||||
|
||||
$result = $this->scopeChecker->check($data);
|
||||
|
||||
$this->assertEquals(true, $result);
|
||||
}
|
||||
|
||||
public function testCheckerActionNoData1()
|
||||
{
|
||||
$data = ScopeData::fromRaw(false);
|
||||
|
||||
$result = $this->scopeChecker->check($data, Table::ACTION_CREATE);
|
||||
|
||||
$this->assertEquals(false, $result);
|
||||
}
|
||||
|
||||
public function testCheckerActionNoData2()
|
||||
{
|
||||
$data = ScopeData::fromRaw(true);
|
||||
|
||||
$result = $this->scopeChecker->check($data, Table::ACTION_CREATE);
|
||||
|
||||
$this->assertEquals(true, $result);
|
||||
}
|
||||
|
||||
public function testCheckerData1()
|
||||
{
|
||||
$checkerData = ScopeCheckerData
|
||||
::createBuilder()
|
||||
->build();
|
||||
|
||||
$data = ScopeData::fromRaw(
|
||||
(object) [
|
||||
Table::ACTION_READ => Table::LEVEL_ALL,
|
||||
],
|
||||
);
|
||||
|
||||
$result = $this->scopeChecker->check($data, Table::ACTION_READ, $checkerData);
|
||||
|
||||
$this->assertEquals(true, $result);
|
||||
}
|
||||
|
||||
public function testCheckerData2a()
|
||||
{
|
||||
$checkerData = ScopeCheckerData
|
||||
::createBuilder()
|
||||
->build();
|
||||
|
||||
$data = ScopeData::fromRaw(
|
||||
(object) [
|
||||
Table::ACTION_READ => Table::LEVEL_ACCOUNT,
|
||||
],
|
||||
);
|
||||
|
||||
$result = $this->scopeChecker->check($data, Table::ACTION_READ, $checkerData);
|
||||
|
||||
$this->assertEquals(false, $result);
|
||||
}
|
||||
|
||||
public function testCheckerData2b()
|
||||
{
|
||||
$checkerData = ScopeCheckerData
|
||||
::createBuilder()
|
||||
->build();
|
||||
|
||||
$data = ScopeData::fromRaw(
|
||||
(object) [
|
||||
Table::ACTION_READ => Table::LEVEL_CONTACT,
|
||||
],
|
||||
);
|
||||
|
||||
$result = $this->scopeChecker->check($data, Table::ACTION_READ, $checkerData);
|
||||
|
||||
$this->assertEquals(false, $result);
|
||||
}
|
||||
|
||||
public function testCheckerData3()
|
||||
{
|
||||
$checkerData = ScopeCheckerData
|
||||
::createBuilder()
|
||||
->build();
|
||||
|
||||
$data = ScopeData::fromRaw(
|
||||
(object) [
|
||||
Table::ACTION_READ => Table::LEVEL_OWN,
|
||||
],
|
||||
);
|
||||
|
||||
$result = $this->scopeChecker->check($data, Table::ACTION_READ, $checkerData);
|
||||
|
||||
$this->assertEquals(false, $result);
|
||||
}
|
||||
|
||||
public function testCheckerData4()
|
||||
{
|
||||
$checkerData = ScopeCheckerData
|
||||
::createBuilder()
|
||||
->build();
|
||||
|
||||
$data = ScopeData::fromRaw(
|
||||
(object) [
|
||||
Table::ACTION_READ => Table::LEVEL_NO,
|
||||
],
|
||||
);
|
||||
|
||||
$result = $this->scopeChecker->check($data, Table::ACTION_READ, $checkerData);
|
||||
|
||||
$this->assertEquals(false, $result);
|
||||
}
|
||||
|
||||
public function testCheckerData5a()
|
||||
{
|
||||
$checkerData = ScopeCheckerData
|
||||
::createBuilder()
|
||||
->setInAccount(true)
|
||||
->build();
|
||||
|
||||
$data = ScopeData::fromRaw(
|
||||
(object) [
|
||||
Table::ACTION_READ => Table::LEVEL_OWN,
|
||||
],
|
||||
);
|
||||
|
||||
$result = $this->scopeChecker->check($data, Table::ACTION_READ, $checkerData);
|
||||
|
||||
$this->assertEquals(false, $result);
|
||||
}
|
||||
|
||||
public function testCheckerData5b()
|
||||
{
|
||||
$checkerData = ScopeCheckerData
|
||||
::createBuilder()
|
||||
->setInContact(true)
|
||||
->build();
|
||||
|
||||
$data = ScopeData::fromRaw(
|
||||
(object) [
|
||||
Table::ACTION_READ => Table::LEVEL_OWN,
|
||||
],
|
||||
);
|
||||
|
||||
$result = $this->scopeChecker->check($data, Table::ACTION_READ, $checkerData);
|
||||
|
||||
$this->assertEquals(false, $result);
|
||||
}
|
||||
|
||||
public function testCheckerData6a()
|
||||
{
|
||||
$checkerData = ScopeCheckerData
|
||||
::createBuilder()
|
||||
->setInAccount(true)
|
||||
->build();
|
||||
|
||||
$data = ScopeData::fromRaw(
|
||||
(object) [
|
||||
Table::ACTION_READ => Table::LEVEL_ACCOUNT,
|
||||
],
|
||||
);
|
||||
|
||||
$result = $this->scopeChecker->check($data, Table::ACTION_READ, $checkerData);
|
||||
|
||||
$this->assertEquals(true, $result);
|
||||
}
|
||||
|
||||
public function testCheckerData6b()
|
||||
{
|
||||
$checkerData = ScopeCheckerData
|
||||
::createBuilder()
|
||||
->setInContact(true)
|
||||
->build();
|
||||
|
||||
$data = ScopeData::fromRaw(
|
||||
(object) [
|
||||
Table::ACTION_READ => Table::LEVEL_ACCOUNT,
|
||||
],
|
||||
);
|
||||
|
||||
$result = $this->scopeChecker->check($data, Table::ACTION_READ, $checkerData);
|
||||
|
||||
$this->assertEquals(true, $result);
|
||||
}
|
||||
|
||||
public function testCheckerData7a()
|
||||
{
|
||||
$checkerData = ScopeCheckerData
|
||||
::createBuilder()
|
||||
->setIsOwn(true)
|
||||
->build();
|
||||
|
||||
$data = ScopeData::fromRaw(
|
||||
(object) [
|
||||
Table::ACTION_READ => Table::LEVEL_ACCOUNT,
|
||||
],
|
||||
);
|
||||
|
||||
$result = $this->scopeChecker->check($data, Table::ACTION_READ, $checkerData);
|
||||
|
||||
$this->assertEquals(true, $result);
|
||||
}
|
||||
|
||||
public function testCheckerData7b()
|
||||
{
|
||||
$checkerData = ScopeCheckerData
|
||||
::createBuilder()
|
||||
->setInContact(true)
|
||||
->build();
|
||||
|
||||
$data = ScopeData::fromRaw(
|
||||
(object) [
|
||||
Table::ACTION_READ => Table::LEVEL_ACCOUNT,
|
||||
],
|
||||
);
|
||||
|
||||
$result = $this->scopeChecker->check($data, Table::ACTION_READ, $checkerData);
|
||||
|
||||
$this->assertEquals(true, $result);
|
||||
}
|
||||
|
||||
|
||||
public function testCheckerData7c()
|
||||
{
|
||||
$checkerData = ScopeCheckerData
|
||||
::createBuilder()
|
||||
->setInContact(true)
|
||||
->build();
|
||||
|
||||
$data = ScopeData::fromRaw(
|
||||
(object) [
|
||||
Table::ACTION_READ => Table::LEVEL_CONTACT,
|
||||
],
|
||||
);
|
||||
|
||||
$result = $this->scopeChecker->check($data, Table::ACTION_READ, $checkerData);
|
||||
|
||||
$this->assertEquals(true, $result);
|
||||
}
|
||||
|
||||
public function testCheckerData8()
|
||||
{
|
||||
$checkerData = ScopeCheckerData
|
||||
::createBuilder()
|
||||
->build();
|
||||
|
||||
$data = ScopeData::fromRaw(
|
||||
(object) [
|
||||
Table::ACTION_CREATE => Table::LEVEL_YES,
|
||||
],
|
||||
);
|
||||
|
||||
$result = $this->scopeChecker->check($data, Table::ACTION_CREATE, $checkerData);
|
||||
|
||||
$this->assertEquals(true, $result);
|
||||
}
|
||||
|
||||
public function testCheckerData9()
|
||||
{
|
||||
$checkerData = ScopeCheckerData
|
||||
::createBuilder()
|
||||
->setIsOwn(true)
|
||||
->build();
|
||||
|
||||
$data = ScopeData::fromRaw(
|
||||
(object) [
|
||||
Table::ACTION_READ => Table::LEVEL_OWN,
|
||||
],
|
||||
);
|
||||
|
||||
$result = $this->scopeChecker->check($data, Table::ACTION_READ, $checkerData);
|
||||
|
||||
$this->assertEquals(true, $result);
|
||||
}
|
||||
|
||||
public function testCheckerData10()
|
||||
{
|
||||
$checkerData = ScopeCheckerData
|
||||
::createBuilder()
|
||||
->setIsOwn(true)
|
||||
->setInContact(true)
|
||||
->build();
|
||||
|
||||
$data = ScopeData::fromRaw(
|
||||
(object) [
|
||||
Table::ACTION_READ => Table::LEVEL_OWN,
|
||||
],
|
||||
);
|
||||
|
||||
$result = $this->scopeChecker->check($data, Table::ACTION_READ, $checkerData);
|
||||
|
||||
$this->assertEquals(true, $result);
|
||||
}
|
||||
|
||||
public function testCheckerData11()
|
||||
{
|
||||
$checkerData = ScopeCheckerData
|
||||
::createBuilder()
|
||||
->setIsOwn(true)
|
||||
->build();
|
||||
|
||||
$data = ScopeData::fromRaw(
|
||||
(object) [
|
||||
Table::ACTION_READ => Table::LEVEL_CONTACT,
|
||||
],
|
||||
);
|
||||
|
||||
$result = $this->scopeChecker->check($data, Table::ACTION_READ, $checkerData);
|
||||
|
||||
$this->assertEquals(true, $result);
|
||||
}
|
||||
|
||||
public function testCheckerData12()
|
||||
{
|
||||
$checkerData = ScopeCheckerData
|
||||
::createBuilder()
|
||||
->setIsOwn(true)
|
||||
->setInAccount(true)
|
||||
->setInContact(true)
|
||||
->build();
|
||||
|
||||
$data = ScopeData::fromRaw(
|
||||
(object) [],
|
||||
);
|
||||
|
||||
$result = $this->scopeChecker->check($data, Table::ACTION_READ, $checkerData);
|
||||
|
||||
$this->assertEquals(false, $result);
|
||||
}
|
||||
|
||||
public function testCheckerData13()
|
||||
{
|
||||
$checkerData = ScopeCheckerData
|
||||
::createBuilder()
|
||||
->build();
|
||||
|
||||
$data = ScopeData::fromRaw(false);
|
||||
|
||||
$result = $this->scopeChecker->check($data, Table::ACTION_READ, $checkerData);
|
||||
|
||||
$this->assertEquals(false, $result);
|
||||
}
|
||||
|
||||
public function testCheckerDataNoAction1()
|
||||
{
|
||||
$checkerData = ScopeCheckerData
|
||||
::createBuilder()
|
||||
->setIsOwn(true)
|
||||
->setInAccount(true)
|
||||
->setInContact(true)
|
||||
->build();
|
||||
|
||||
$data = ScopeData::fromRaw(false);
|
||||
|
||||
$result = $this->scopeChecker->check($data, null, $checkerData);
|
||||
|
||||
$this->assertEquals(false, $result);
|
||||
}
|
||||
|
||||
public function testCheckerDataNoAction2()
|
||||
{
|
||||
$checkerData = ScopeCheckerData
|
||||
::createBuilder()
|
||||
->build();
|
||||
|
||||
$data = ScopeData::fromRaw(true);
|
||||
|
||||
$result = $this->scopeChecker->check($data, null, $checkerData);
|
||||
|
||||
$this->assertEquals(true, $result);
|
||||
}
|
||||
|
||||
public function testCheckerDataNoAction3()
|
||||
{
|
||||
$checkerData = ScopeCheckerData
|
||||
::createBuilder()
|
||||
->setIsOwn(true)
|
||||
->setInAccount(true)
|
||||
->setInContact(true)
|
||||
->build();
|
||||
|
||||
$data = ScopeData::fromRaw(
|
||||
(object) [
|
||||
Table::ACTION_READ => Table::LEVEL_ACCOUNT,
|
||||
],
|
||||
);
|
||||
|
||||
$result = $this->scopeChecker->check($data, null, $checkerData);
|
||||
|
||||
$this->assertEquals(true, $result);
|
||||
}
|
||||
|
||||
public function testCheckerDataNoAction4()
|
||||
{
|
||||
$checkerData = ScopeCheckerData
|
||||
::createBuilder()
|
||||
->setIsOwn(true)
|
||||
->setInAccount(false)
|
||||
->setInContact(true)
|
||||
->build();
|
||||
|
||||
$data = ScopeData::fromRaw(
|
||||
(object) [
|
||||
Table::ACTION_READ => Table::LEVEL_CONTACT,
|
||||
],
|
||||
);
|
||||
|
||||
$result = $this->scopeChecker->check($data, null, $checkerData);
|
||||
|
||||
$this->assertEquals(true, $result);
|
||||
}
|
||||
|
||||
public function testCheckerDataNoAction5()
|
||||
{
|
||||
$checkerData = ScopeCheckerData
|
||||
::createBuilder()
|
||||
->setIsOwn(true)
|
||||
->setInContact(true)
|
||||
->build();
|
||||
|
||||
$data = ScopeData::fromRaw(
|
||||
(object) [
|
||||
Table::ACTION_CREATE => Table::LEVEL_NO,
|
||||
Table::ACTION_READ => Table::LEVEL_NO,
|
||||
],
|
||||
);
|
||||
|
||||
$result = $this->scopeChecker->check($data, null, $checkerData);
|
||||
|
||||
$this->assertEquals(false, $result);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user