acl refactoring

This commit is contained in:
Yuri Kuznetsov
2021-04-03 14:41:29 +03:00
parent a8d437720b
commit 1b899f2228
44 changed files with 1548 additions and 672 deletions
+1 -1
View File
@@ -39,6 +39,6 @@ class ActionHistoryRecord extends Acl
{
public function checkIsOwner(EntityUser $user, Entity $entity)
{
return $entity->get('userId') === $user->id;
return $entity->get('userId') === $user->getId();
}
}
+60 -20
View File
@@ -29,15 +29,23 @@
namespace Espo\Acl;
use Espo\Entities\User as EntityUser;
use Espo\Entities\{
User as EntityUser,
Note,
};
use Espo\ORM\Entity;
use Espo\Core\Acl\Acl;
use Espo\Core\{
Acl\Acl,
Acl\ScopeData,
Acl\Table,
Acl\EntityReadAcl,
};
class Attachment extends Acl
class Attachment extends Acl implements EntityReadAcl
{
public function checkEntityRead(EntityUser $user, Entity $entity, $data)
public function checkEntityRead(EntityUser $user, Entity $entity, ScopeData $data): bool
{
if ($user->isAdmin()) {
return true;
@@ -62,12 +70,8 @@ class Attachment extends Acl
$parent = $this->entityManager->getEntity($entity->get('relatedType'), $entity->get('relatedId'));
}
if (!$hasParent) {
return true;
}
if (!$parent) {
if ($this->checkEntity($user, $entity, $data, 'read')) {
if (!$parent || !$hasParent) {
if ($this->checkEntity($user, $entity, $data, Table::ACTION_READ)) {
return true;
}
@@ -75,31 +79,67 @@ class Attachment extends Acl
}
if ($parent->getEntityType() === 'Note') {
if (!$parent->get('parentId') || !$parent->get('parentType')) {
return true;
}
$result = $this->checkEntityReadNoteParent($user, $parent);
$parentOfParent = $this->entityManager
->getEntity($parent->get('parentType'), $parent->get('parentId'));
if ($parentOfParent && $this->aclManager->checkEntity($user, $parentOfParent)) {
return true;
if ($result !== null) {
return $result;
}
}
else if ($this->aclManager->checkEntity($user, $parent)) {
return true;
}
if ($this->checkEntity($user, $entity, $data, 'read')) {
if ($this->checkEntity($user, $entity, $data, Table::ACTION_READ)) {
return true;
}
return false;
}
protected function checkEntityReadNoteParent(EntityUser $user, Note $note): ?bool
{
if ($note->getTargetType() === Note::TARGET_TEAMS) {
$intersect = array_intersect(
$note->getLinkMultipleIdList('teams'),
$user->getLinkMultipleIdList('teams')
);
if (count($intersect)) {
return true;
}
return null;
}
if ($note->getTargetType() === Note::TARGET_USERS) {
$isRelated = $this->entityManager
->getRDBRepository('Note')
->getRelation($note, 'users')
->isRelated($user);
if ($isRelated) {
return true;
}
return null;
}
if (!$note->getParentId() || !$note->getParentType()) {
return null;
}
$parent = $this->entityManager->getEntity($note->getParentType(), $note->getParentId());
if ($parent && $this->aclManager->checkEntity($user, $parent)) {
return true;
}
return null;
}
public function checkIsOwner(EntityUser $user, Entity $entity)
{
if ($user->getId() === $entity->get('createdById')) {
if ($user->getId() === $entity->get(self::ATTR_CREATED_BY_ID)) {
return true;
}
+14 -9
View File
@@ -33,23 +33,28 @@ use Espo\Entities\User as EntityUser;
use Espo\ORM\Entity;
use Espo\Core\Acl\Acl;
use Espo\Core\{
Acl\Acl,
Acl\ScopeData,
Acl\Table,
Acl\EntityReadAcl,
};
class Email extends Acl
class Email extends Acl implements EntityReadAcl
{
protected $ownerUserIdAttribute = 'usersIds';
public function checkEntityRead(EntityUser $user, Entity $entity, $data)
public function checkEntityRead(EntityUser $user, Entity $entity, ScopeData $data): bool
{
if ($this->checkEntity($user, $entity, $data, 'read')) {
return true;
}
if (!$data) {
if (!$data->isFalse()) {
return false;
}
if ($data->read === false || $data->read === 'no') {
if ($data->getRead() === Table::LEVEL_NO) {
return false;
}
@@ -83,13 +88,13 @@ class Email extends Acl
return false;
}
public function checkEntityDelete(EntityUser $user, Entity $entity, $data)
public function checkEntityDelete(EntityUser $user, Entity $entity, ScopeData $data): bool
{
if ($user->isAdmin()) {
return true;
}
if (!$data) {
if (!$data->isFalse()) {
return false;
}
@@ -114,11 +119,11 @@ class Email extends Acl
return false;
}
if ($this->checkEntity($user, $entity, $data, 'delete')) {
if ($this->checkEntity($user, $entity, $data, Table::ACTION_DELETE)) {
return true;
}
if ($data->edit === 'no' && $data->create === 'no') {
if ($data->getEdit() === Table::LEVEL_NO && $data->getCreate() === Table::LEVEL_NO) {
return false;
}
+10 -6
View File
@@ -33,30 +33,34 @@ use Espo\Entities\User as EntityUser;
use Espo\ORM\Entity;
use Espo\Core\Acl\Acl;
use Espo\Core\{
Acl\Acl,
Acl\ScopeData,
Acl\EntityReadAcl,
};
class Import extends Acl
class Import extends Acl implements EntityReadAcl
{
public function checkEntityRead(EntityUser $user, Entity $entity, $data)
public function checkEntityRead(EntityUser $user, Entity $entity, ScopeData $data): bool
{
if ($user->isAdmin()) {
return true;
}
if ($user->id === $entity->get('createdById')) {
if ($user->getId() === $entity->get('createdById')) {
return true;
}
return false;
}
public function checkEntityDelete(EntityUser $user, Entity $entity, $data)
public function checkEntityDelete(EntityUser $user, Entity $entity, ScopeData $data): bool
{
if ($user->isAdmin()) {
return true;
}
if ($user->id === $entity->get('createdById')) {
if ($user->getId() === $entity->get('createdById')) {
return true;
}
+12 -7
View File
@@ -33,12 +33,17 @@ use Espo\Entities\User as EntityUser;
use Espo\ORM\Entity;
use Espo\Core\Acl\Acl;
use Espo\Core\{
Acl\Acl,
Acl\ScopeData,
Acl\Table,
Acl\EntityEditAcl,
};
use Exception;
use DateTime;
class Note extends Acl
class Note extends Acl implements EntityEditAcl
{
protected $deleteThresholdPeriod = '1 month';
@@ -53,7 +58,7 @@ class Note extends Acl
return false;
}
public function checkEntityCreate(EntityUser $user, Entity $entity, $data)
public function checkEntityCreate(EntityUser $user, Entity $entity, ScopeData $data): bool
{
if (!$entity->get('parentId') || !$entity->get('parentType')) {
return true;
@@ -68,13 +73,13 @@ class Note extends Acl
return false;
}
public function checkEntityEdit(EntityUser $user, Entity $entity, $data)
public function checkEntityEdit(EntityUser $user, Entity $entity, ScopeData $data): bool
{
if ($user->isAdmin()) {
return true;
}
if (!$this->checkEntity($user, $entity, $data, 'edit')) {
if (!$this->checkEntity($user, $entity, $data, Table::ACTION_EDIT)) {
return false;
}
@@ -107,13 +112,13 @@ class Note extends Acl
return true;
}
public function checkEntityDelete(EntityUser $user, Entity $entity, $data)
public function checkEntityDelete(EntityUser $user, Entity $entity, ScopeData $data): bool
{
if ($user->isAdmin()) {
return true;
}
if (!$this->checkEntity($user, $entity, $data, 'delete')) {
if (!$this->checkEntity($user, $entity, $data, Table::ACTION_DELETE)) {
return false;
}
+1 -1
View File
@@ -39,7 +39,7 @@ class Notification extends Acl
{
public function checkIsOwner(EntityUser $user, Entity $entity)
{
if ($user->id === $entity->get('userId')) {
if ($user->getId() === $entity->get('userId')) {
return true;
}
+17 -10
View File
@@ -33,43 +33,50 @@ use Espo\Entities\User as EntityUser;
use Espo\ORM\Entity;
use Espo\Core\Acl\Acl;
use Espo\Core\{
Acl\Acl,
Acl\ScopeData,
Acl\Table,
Acl\EntityCreateAcl,
Acl\EntityReadAcl,
Acl\EntityEditAcl,
};
class ScheduledJob extends Acl
class ScheduledJob extends Acl implements EntityCreateAcl, EntityReadAcl, EntityEditAcl
{
public function checkEntityRead(EntityUser $user, Entity $entity, $data)
public function checkEntityRead(EntityUser $user, Entity $entity, ScopeData $data): bool
{
if ($entity->get('isInternal')) {
return false;
}
return $this->checkEntity($user, $entity, $data, 'read');
return $this->checkEntity($user, $entity, $data, Table::ACTION_READ);
}
public function checkEntityEdit(EntityUser $user, Entity $entity, $data)
public function checkEntityEdit(EntityUser $user, Entity $entity, ScopeData $data): bool
{
if ($entity->get('isInternal')) {
return false;
}
return $this->checkEntity($user, $entity, $data, 'edit');
return $this->checkEntity($user, $entity, $data, Table::ACTION_EDIT);
}
public function checkEntityDelete(EntityUser $user, Entity $entity, $data)
public function checkEntityDelete(EntityUser $user, Entity $entity, ScopeData $data): bool
{
if ($entity->get('isInternal')) {
return false;
}
return $this->checkEntity($user, $entity, $data, 'delete');
return $this->checkEntity($user, $entity, $data, Table::ACTION_DELETE);
}
public function checkEntityCreate(EntityUser $user, Entity $entity, $data)
public function checkEntityCreate(EntityUser $user, Entity $entity, ScopeData $data): bool
{
if ($entity->get('isInternal')) {
return false;
}
return $this->checkEntity($user, $entity, $data, 'create');
return $this->checkEntity($user, $entity, $data, Table::ACTION_CREATE);
}
}
+1 -1
View File
@@ -41,6 +41,6 @@ class Team extends Acl
{
$userTeamIdList = $user->getLinkMultipleIdList('teams');
return in_array($entity->id, $userTeamIdList);
return in_array($entity->getId(), $userTeamIdList);
}
}
+20 -13
View File
@@ -33,27 +33,34 @@ use Espo\ORM\Entity;
use Espo\Entities\User as EntityUser;
use Espo\Core\Acl\Acl;
use Espo\Core\{
Acl\Acl,
Acl\ScopeData,
Acl\Table,
Acl\EntityCreateAcl,
Acl\EntityReadAcl,
Acl\EntityEditAcl,
};
class User extends Acl
class User extends Acl implements EntityCreateAcl, EntityReadAcl, EntityEditAcl
{
public function checkIsOwner(EntityUser $user, Entity $entity)
{
return $user->id === $entity->id;
return $user->getId() === $entity->getId();
}
public function checkEntityRead(EntityUser $user, Entity $entity, $data)
public function checkEntityRead(EntityUser $user, Entity $entity, ScopeData $data): bool
{
if (!$user->isAdmin() && $entity->isPortal()) {
if ($this->getAclManager()->get($user, 'portalPermission') === 'yes') {
if ($this->getAclManager()->get($user, 'portal') === Table::LEVEL_YES) {
return true;
}
}
return $this->checkEntity($user, $entity, $data, 'read');
return $this->checkEntity($user, $entity, $data, Table::ACTION_READ);
}
public function checkEntityCreate(EntityUser $user, Entity $entity, $data)
public function checkEntityCreate(EntityUser $user, Entity $entity, ScopeData $data): bool
{
if (!$user->isAdmin()) {
return false;
@@ -63,12 +70,12 @@ class User extends Acl
return false;
}
return $this->checkEntity($user, $entity, $data, 'create');
return $this->checkEntity($user, $entity, $data, Table::ACTION_CREATE);
}
public function checkEntityDelete(EntityUser $user, Entity $entity, $data)
public function checkEntityDelete(EntityUser $user, Entity $entity, ScopeData $data): bool
{
if ($entity->id === 'system') {
if ($entity->getId() === 'system') {
return false;
}
@@ -87,7 +94,7 @@ class User extends Acl
return parent::checkEntityDelete($user, $entity, $data);
}
public function checkEntityEdit(EntityUser $user, Entity $entity, $data)
public function checkEntityEdit(EntityUser $user, Entity $entity, ScopeData $data): bool
{
if ($entity->id === 'system') {
return false;
@@ -97,7 +104,7 @@ class User extends Acl
}
if (!$user->isAdmin()) {
if ($user->id !== $entity->id) {
if ($user->getId() !== $entity->getId()) {
return false;
}
}
@@ -106,6 +113,6 @@ class User extends Acl
return false;
}
return $this->checkEntity($user, $entity, $data, 'edit');
return $this->checkEntity($user, $entity, $data, Table::ACTION_EDIT);
}
}
+16 -10
View File
@@ -33,22 +33,28 @@ use Espo\Entities\User as EntityUser;
use Espo\ORM\Entity;
use Espo\Core\Acl\Acl;
use Espo\Core\{
Acl\Acl,
Acl\ScopeData,
Acl\EntityCreateAcl,
Acl\EntityReadAcl,
Acl\EntityEditAcl,
};
class Webhook extends Acl
class Webhook extends Acl implements EntityCreateAcl, EntityReadAcl, EntityEditAcl
{
public function checkIsOwner(EntityUser $user, Entity $entity)
{
return $user->id === $entity->get('userId') && $user->isApi();
}
public function checkEntityCreate(EntityUser $user, Entity $entity, $data)
public function checkEntityCreate(EntityUser $user, Entity $entity, ScopeData $data): bool
{
if ($user->isAdmin()) {
return true;
}
if (!$data) {
if ($data->isFalse()) {
return false;
}
@@ -59,13 +65,13 @@ class Webhook extends Acl
return false;
}
public function checkEntityRead(EntityUser $user, Entity $entity, $data)
public function checkEntityRead(EntityUser $user, Entity $entity, ScopeData $data): bool
{
if ($user->isAdmin()) {
return true;
}
if (!$data) {
if ($data->isFalse()) {
return false;
}
@@ -76,13 +82,13 @@ class Webhook extends Acl
return false;
}
public function checkEntityDelete(EntityUser $user, Entity $entity, $data)
public function checkEntityDelete(EntityUser $user, Entity $entity, ScopeData $data): bool
{
if ($user->isAdmin()) {
return true;
}
if (!$data) {
if ($data->isFalse()) {
return false;
}
@@ -93,13 +99,13 @@ class Webhook extends Acl
return false;
}
public function checkEntityEdit(EntityUser $user, Entity $entity, $data)
public function checkEntityEdit(EntityUser $user, Entity $entity, ScopeData $data): bool
{
if ($user->isAdmin()) {
return true;
}
if (!$data) {
if ($data->isFalse()) {
return false;
}
+79 -31
View File
@@ -29,65 +29,113 @@
namespace Espo\AclPortal;
use \Espo\Entities\User as EntityUser;
use \Espo\ORM\Entity;
use Espo\Entities\User as EntityUser;
use Espo\Entities\Note;
class Attachment extends \Espo\Core\AclPortal\Base
use Espo\ORM\Entity;
use Espo\Core\{
Acl\ScopeData,
Acl\Table,
Acl\EntityReadAcl,
AclPortal\Acl as Acl,
};
class Attachment extends Acl implements EntityReadAcl
{
public function checkEntityRead(EntityUser $user, Entity $entity, $data)
public function checkEntityRead(EntityUser $user, Entity $entity, ScopeData $data): bool
{
if ($user->isAdmin()) {
return true;
}
if ($entity->get('parentType') === 'Settings') {
return true;
}
$parent = null;
$hasParent = false;
if ($entity->get('parentId') && $entity->get('parentType')) {
$hasParent = true;
$parent = $this->getEntityManager()->getEntity($entity->get('parentType'), $entity->get('parentId'));
} else if ($entity->get('relatedId') && $entity->get('relatedType')) {
$parent = $this->entityManager->getEntity($entity->get('parentType'), $entity->get('parentId'));
}
else if ($entity->get('relatedId') && $entity->get('relatedType')) {
$hasParent = true;
$parent = $this->getEntityManager()->getEntity($entity->get('relatedType'), $entity->get('relatedId'));
$parent = $this->entityManager->getEntity($entity->get('relatedType'), $entity->get('relatedId'));
}
if ($hasParent) {
if ($parent) {
if ($parent->getEntityType() === 'Note') {
if ($parent->get('parentId') && $parent->get('parentType')) {
$parentOfParent = $this->getEntityManager()->getEntity($parent->get('parentType'), $parent->get('parentId'));
if ($parentOfParent && $this->getAclManager()->checkEntity($user, $parentOfParent)) {
return true;
}
} else {
return true;
}
} else {
if ($this->getAclManager()->checkEntity($user, $parent)) {
return true;
}
}
if (!$hasParent) {
return false;
}
if ($parent->getEntityType() === 'Note') {
$result = $this->checkEntityReadNoteParent($user, $parent);
if ($result !== null) {
return $result;
}
} else {
}
else if ($this->aclManager->checkEntity($user, $parent)) {
return true;
}
if ($this->checkEntity($user, $entity, $data, 'read')) {
if ($this->checkEntity($user, $entity, $data, Table::ACTION_READ)) {
return true;
}
return false;
}
protected function checkEntityReadNoteParent(EntityUser $user, Note $note): ?bool
{
if ($note->isInternal()) {
return false;
}
if ($note->getTargetType() === Note::TARGET_PORTALS) {
$intersect = array_intersect(
$note->getLinkMultipleIdList('portals'),
$user->getLinkMultipleIdList('portals')
);
if (count($intersect)) {
return true;
}
return false;
}
if ($note->getTargetType() === Note::TARGET_USERS) {
$isRelated = $this->entityManager
->getRDBRepository('Note')
->getRelation($note, 'users')
->isRelated($user);
if ($isRelated) {
return true;
}
return false;
}
if (!$note->getParentId() || !$note->getParentType()) {
return null;
}
$parent = $this->entityManager->getEntity($note->getParentType(), $note->getParentId());
if ($parent && $this->aclManager->checkEntity($user, $parent)) {
return true;
}
return null;
}
public function checkIsOwner(EntityUser $user, Entity $entity)
{
if ($user->id === $entity->get('createdById')) {
if ($user->getId() === $entity->get('createdById')) {
return true;
}
return false;
}
}
+23 -12
View File
@@ -29,43 +29,54 @@
namespace Espo\AclPortal;
use \Espo\Entities\User as EntityUser;
use \Espo\ORM\Entity;
use Espo\Entities\User as EntityUser;
class Email extends \Espo\Core\AclPortal\Base
use Espo\ORM\Entity;
use Espo\Core\{
Acl\ScopeData,
Acl\Table,
Acl\EntityReadAcl,
AclPortal\Acl as Acl,
};
class Email extends Acl implements EntityReadAcl
{
protected $ownerUserIdAttribute = 'usersIds';
public function checkEntityRead(EntityUser $user, Entity $entity, $data)
public function checkEntityRead(EntityUser $user, Entity $entity, ScopeData $data): bool
{
if ($this->checkEntity($user, $entity, $data, 'read')) {
if ($this->checkEntity($user, $entity, $data, Table::ACTION_READ)) {
return true;
}
if ($data === false) {
if ($data->isFalse()) {
return false;
}
if (is_object($data)) {
if ($data->read === false || $data->read === 'no') {
return false;
}
if ($data->getRead() === Table::LEVEL_NO) {
return false;
}
if (!$entity->has('usersIds')) {
$entity->loadLinkMultipleField('users');
}
$userIdList = $entity->get('usersIds');
if (is_array($userIdList) && in_array($user->id, $userIdList)) {
if (is_array($userIdList) && in_array($user->getId(), $userIdList)) {
return true;
}
return false;
}
public function checkIsOwner(EntityUser $user, Entity $entity)
{
if ($user->id === $entity->get('createdById')) {
if ($user->getId() === $entity->get('createdById')) {
return true;
}
return false;
}
}
+11 -5
View File
@@ -29,14 +29,19 @@
namespace Espo\AclPortal;
use \Espo\Entities\User as EntityUser;
use \Espo\ORM\Entity;
use Espo\Entities\User as EntityUser;
use Espo\ORM\Entity;
class EmailAddress extends \Espo\Core\AclPortal\Base
use Espo\Core\{
Acl\Table,
AclPortal\Acl as Acl,
};
class EmailAddress extends Acl
{
public function checkEditInEntity(EntityUser $user, Entity $entity, Entity $excludeEntity) : bool
{
$id = $entity->id;
$id = $entity->getId();
$isFobidden = false;
@@ -46,13 +51,14 @@ class EmailAddress extends \Espo\Core\AclPortal\Base
$entityWithSameAddressList = $repository->getEntityListByAddressId($id, $excludeEntity);
foreach ($entityWithSameAddressList as $e) {
if (!$this->getAclManager()->check($user, $e, 'edit')) {
if (!$this->getAclManager()->check($user, $e, Table::ACTION_EDIT)) {
$isFobidden = true;
break;
}
}
}
return !$isFobidden;
}
}
+17 -6
View File
@@ -29,22 +29,32 @@
namespace Espo\AclPortal;
use \Espo\Entities\User as EntityUser;
use \Espo\ORM\Entity;
use Espo\Entities\User as EntityUser;
use Espo\ORM\Entity;
class Note extends \Espo\Core\AclPortal\Base
use Espo\Core\{
Acl\ScopeData,
Acl\Table,
Acl\EntityCreateAcl,
AclPortal\Acl as Acl,
};
class Note extends Acl implements EntityCreateAcl
{
public function checkIsOwner(EntityUser $user, Entity $entity)
{
if ($entity->get('type') === 'Post' && $user->id === $entity->get('createdById')) {
return true;
}
return false;
}
public function checkEntityCreate(EntityUser $user, Entity $entity, $data)
public function checkEntityCreate(EntityUser $user, Entity $entity, ScopeData $data): bool
{
if ($entity->get('type') !== 'Post') return false;
if ($entity->get('type') !== 'Post') {
return false;
}
if ($entity->get('type') === 'Post' && $entity->get('targetType')) {
return false;
@@ -55,8 +65,9 @@ class Note extends \Espo\Core\AclPortal\Base
}
$parent = $this->getEntityManager()->getEntity($entity->get('parentType'), $entity->get('parentId'));
if ($parent) {
if ($this->getAclManager()->checkEntity($user, $parent, 'stream')) {
if ($this->getAclManager()->checkEntity($user, $parent, Table::ACTION_STREAM)) {
return true;
}
}
+9 -5
View File
@@ -29,17 +29,21 @@
namespace Espo\AclPortal;
use \Espo\Entities\User as EntityUser;
use \Espo\ORM\Entity;
use Espo\Entities\User as EntityUser;
use Espo\ORM\Entity;
class Notification extends \Espo\Core\AclPortal\Base
use Espo\Core\{
AclPortal\Acl as Acl,
};
class Notification extends Acl
{
public function checkIsOwner(EntityUser $user, Entity $entity)
{
if ($user->id === $entity->get('userId')) {
if ($user->getId() === $entity->get('userId')) {
return true;
}
return false;
}
}
+10 -5
View File
@@ -29,14 +29,19 @@
namespace Espo\AclPortal;
use \Espo\Entities\User as EntityUser;
use \Espo\ORM\Entity;
use Espo\Entities\User as EntityUser;
use Espo\ORM\Entity;
class PhoneNumber extends \Espo\Core\AclPortal\Base
use Espo\Core\{
Acl\Table,
AclPortal\Acl as Acl,
};
class PhoneNumber extends Acl
{
public function checkEditInEntity(EntityUser $user, Entity $entity, Entity $excludeEntity) : bool
{
$id = $entity->id;
$id = $entity->getId();
$isFobidden = false;
@@ -46,7 +51,7 @@ class PhoneNumber extends \Espo\Core\AclPortal\Base
$entityWithSameNumberList = $repository->getEntityListByPhoneNumberId($id, $excludeEntity);
foreach ($entityWithSameNumberList as $e) {
if (!$this->getAclManager()->check($user, $e, 'edit')) {
if (!$this->getAclManager()->check($user, $e, Table::ACTION_EDIT)) {
$isFobidden = true;
break;
+9 -5
View File
@@ -29,13 +29,17 @@
namespace Espo\AclPortal;
use \Espo\ORM\Entity;
use Espo\ORM\Entity;
use Espo\Entities\User as UserEntity;
class User extends \Espo\Core\AclPortal\Base
use Espo\Core\{
AclPortal\Acl as Acl,
};
class User extends Acl
{
public function checkIsOwner(\Espo\Entities\User $user, Entity $entity)
public function checkIsOwner(UserEntity $user, Entity $entity)
{
return $user->id === $entity->id;
return $user->getId() === $entity->getId();
}
}
+70 -24
View File
@@ -53,7 +53,7 @@ class Acl
$this->user = $user;
}
public function getMap() : StdClass
public function getMap(): StdClass
{
return $this->aclManager->getMap($this->user);
}
@@ -61,7 +61,7 @@ class Acl
/**
* Get an access level for a specific scope and action.
*/
public function getLevel(string $scope, string $action) : string
public function getLevel(string $scope, string $action): string
{
return $this->aclManager->getLevel($this->user, $scope, $action);
}
@@ -69,7 +69,7 @@ class Acl
/**
* Get a permission. E.g. 'assignment' permission.
*/
public function get(string $permission) : ?string
public function get(string $permission): ?string
{
return $this->aclManager->get($this->user, $permission);
}
@@ -77,7 +77,7 @@ class Acl
/**
* Whether there's no 'read' access for a specific scope.
*/
public function checkReadNo(string $scope) : bool
public function checkReadNo(string $scope): bool
{
return $this->aclManager->checkReadNo($this->user, $scope);
}
@@ -85,7 +85,7 @@ class Acl
/**
* Whether 'read' access is set to 'team' for a specific scope.
*/
public function checkReadOnlyTeam(string $scope) : bool
public function checkReadOnlyTeam(string $scope): bool
{
return $this->aclManager->checkReadOnlyTeam($this->user, $scope);
}
@@ -93,7 +93,7 @@ class Acl
/**
* Whether 'read' access is set to 'own' for a specific scope.
*/
public function checkReadOnlyOwn(string $scope) : bool
public function checkReadOnlyOwn(string $scope): bool
{
return $this->aclManager->checkReadOnlyOwn($this->user, $scope);
}
@@ -101,7 +101,7 @@ class Acl
/**
* Check a scope or entity. If $action is omitted, it will check whether a scope level is set to 'enabled'.
*/
public function check($subject, ?string $action = null) : bool
public function check($subject, ?string $action = null): bool
{
return $this->aclManager->check($this->user, $subject, $action);
}
@@ -109,23 +109,63 @@ class Acl
/**
* Check access to scope. If $action is omitted, it will check whether a scope level is set to 'enabled'.
*/
public function checkScope(string $scope, ?string $action = null) : bool
public function checkScope(string $scope, ?string $action = null): bool
{
return $this->aclManager->checkScope($this->user, $scope, $action);
}
/**
* Check access to a specific entity (record).
* Check access to a specific entity.
*/
public function checkEntity(Entity $entity, string $action = Table::ACTION_READ) : bool
public function checkEntity(Entity $entity, string $action = Table::ACTION_READ): bool
{
return $this->aclManager->checkEntity($this->user, $entity, $action);
}
/**
* Check 'read' access to a specific entity.
*/
public function checkEntityRead(Entity $entity): bool
{
return $this->checkEntity($entity, Table::ACTION_READ);
}
/**
* Check 'create' access to a specific entity.
*/
public function checkEntityCreate(Entity $entity): bool
{
return $this->checkEntity($entity, Table::ACTION_CREATE);
}
/**
* Check 'edit' access to a specific entity.
*/
public function checkEntityEdit(Entity $entity): bool
{
return $this->checkEntity($entity, Table::ACTION_EDIT);
}
/**
* Check 'delete' access to a specific entity.
*/
public function checkEntityDelete(Entity $entity): bool
{
return $this->checkEntity($entity, Table::ACTION_DELETE);
}
/**
* Check 'stream' access to a specific entity.
*/
public function checkEntityStream(Entity $entity): bool
{
return $this->checkEntity($entity, Table::ACTION_STREAM);
}
/**
* @deprecated
*/
public function checkUser(string $permission, User $entity) : bool
public function checkUser(string $permission, User $entity): bool
{
return $this->aclManager->checkUser($this->user, $permission, $entity);
}
@@ -133,7 +173,7 @@ class Acl
/**
* Whether a user is owned of an entity (record). Usually 'assignedUser' field is used for checking.
*/
public function checkIsOwner(Entity $entity) : bool
public function checkIsOwner(Entity $entity): bool
{
return $this->aclManager->checkIsOwner($this->user, $entity);
}
@@ -141,7 +181,7 @@ class Acl
/**
* Whether a user team list overlaps with teams set in an entity.
*/
public function checkInTeam(Entity $entity) : bool
public function checkInTeam(Entity $entity): bool
{
return $this->aclManager->checkInTeam($this->user, $entity);
}
@@ -150,8 +190,10 @@ class Acl
* Get attributes forbidden for a user.
*/
public function getScopeForbiddenAttributeList(
string $scope, string $action = Table::ACTION_READ, string $thresholdLevel = Table::LEVEL_NO
) : array {
string $scope,
string $action = Table::ACTION_READ,
string $thresholdLevel = Table::LEVEL_NO
): array {
return $this->aclManager
->getScopeForbiddenAttributeList($this->user, $scope, $action, $thresholdLevel);
@@ -161,8 +203,10 @@ class Acl
* Get fields forbidden for a user.
*/
public function getScopeForbiddenFieldList(
string $scope, string $action = Table::ACTION_READ, string $thresholdLevel = Table::LEVEL_NO
) : array {
string $scope,
string $action = Table::ACTION_READ,
string $thresholdLevel = Table::LEVEL_NO
): array {
return $this->aclManager
->getScopeForbiddenFieldList($this->user, $scope, $action, $thresholdLevel);
@@ -172,8 +216,10 @@ class Acl
* Get links forbidden for a user.
*/
public function getScopeForbiddenLinkList(
string $scope, string $action = Table::ACTION_READ, string $thresholdLevel = Table::LEVEL_NO
) : array {
string $scope,
string $action = Table::ACTION_READ,
string $thresholdLevel = Table::LEVEL_NO
): array {
return $this->aclManager->getScopeForbiddenLinkList($this->user, $scope, $action, $thresholdLevel);
}
@@ -183,7 +229,7 @@ class Acl
*
* @param User|string $target User entity or user ID.
*/
public function checkUserPermission($target, string $permissionType = 'user') : bool
public function checkUserPermission($target, string $permissionType = 'user'): bool
{
return $this->aclManager->checkUserPermission($this->user, $target, $permissionType);
}
@@ -193,22 +239,22 @@ class Acl
*
* @param User|string $target User entity or user ID.
*/
public function checkAssignmentPermission($target) : bool
public function checkAssignmentPermission($target): bool
{
return $this->aclManager->checkAssignmentPermission($this->user, $target);
}
public function getScopeRestrictedFieldList(string $scope, $type) : array
public function getScopeRestrictedFieldList(string $scope, $type): array
{
return $this->aclManager->getScopeRestrictedFieldList($scope, $type);
}
public function getScopeRestrictedAttributeList(string $scope, $type) : array
public function getScopeRestrictedAttributeList(string $scope, $type): array
{
return $this->aclManager->getScopeRestrictedAttributeList($scope, $type);
}
public function getScopeRestrictedLinkList(string $scope, $type) : array
public function getScopeRestrictedLinkList(string $scope, $type): array
{
return $this->aclManager->getScopeRestrictedLinkList($scope, $type);
}
+116 -102
View File
@@ -43,99 +43,83 @@ use Espo\Core\{
/**
* An implementation for access checking for entities. Can be overridden in `Acl` namespace.
*/
class Acl implements ScopeAcl, EntityAcl
class Acl implements ScopeAcl, EntityAcl, EntityDeleteAcl
{
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 $entityManager;
protected $checkIsOwnerentityManager;
protected $aclManager;
protected $config;
public function __construct(string $scope, EntityManager $entityManager, AclManager $aclManager, Config $config)
{
$this->scope = $scope;
public function __construct(
EntityManager $entityManager,
AclManager $aclManager,
Config $config,
string $scope = null
) {
$this->entityManager = $entityManager;
$this->aclManager = $aclManager;
$this->config = $config;
$this->scope = $scope;
}
protected function getConfig()
public function checkEntity(User $user, Entity $entity, ScopeData $data, string $action = Table::ACTION_READ): bool
{
return $this->config;
return $this->checkScopeInternal($user, $data, $action, $entity);
}
protected function getEntityManager()
public function checkScope(User $user, ScopeData $data, ?string $action = null) : bool
{
return $this->entityManager;
return $this->checkScopeInternal($user, $data, $action);
}
protected function getAclManager()
public function checkReadOnlyTeam(User $user, ScopeData $data) : bool
{
return $this->aclManager;
return $data->getRead() === Table::LEVEL_TEAM;
}
public function checkReadOnlyTeam(User $user, $data)
public function checkReadNo(User $user, ScopeData $data) : bool
{
if (empty($data) || !is_object($data) || !isset($data->read)) {
return $data->getRead() === Table::LEVEL_NO;
}
public function checkReadOnlyOwn(User $user, ScopeData $data) : bool
{
return $data->getRead() === Table::LEVEL_OWN;
}
protected function checkScopeInternal(
User $user,
ScopeData $data,
?string $action = null,
?Entity $entity = null,
array $entityAccessData = []
) : bool {
if ($data->isFalse()) {
return false;
}
return $data->read === Table::LEVEL_TEAM;
}
public function checkReadNo(User $user, $data)
{
if (empty($data) || !is_object($data) || !isset($data->read)) {
return false;
}
return $data->read === Table::LEVEL_NO;
}
public function checkReadOnlyOwn(User $user, $data)
{
if (empty($data) || !is_object($data) || !isset($data->read)) {
return false;
}
return $data->read === Table::LEVEL_OWN;
}
public function checkEntity(User $user, Entity $entity, $data, $action)
{
if ($user->isAdmin()) {
return true;
}
return $this->checkScope($user, $data, $action, $entity);
}
public function checkScope(User $user, $data, $action = null, Entity $entity = null, $entityAccessData = [])
{
if ($user->isAdmin()) {
return true;
}
if (is_null($data)) {
return false;
}
if ($data === false) {
return false;
}
if ($data === true) {
return true;
}
if (is_string($data)) {
if ($data->isTrue()) {
return true;
}
@@ -152,20 +136,20 @@ class Acl implements ScopeAcl, EntityAcl
}
if (is_null($action)) {
return true;
}
if ($data->hasNotNo()) {
return true;
}
if (!isset($data->$action)) {
return false;
}
$value = $data->$action;
$value = $data->get($action);
if ($value === Table::LEVEL_ALL || $value === Table::LEVEL_YES || $value === true) {
if ($value === Table::LEVEL_ALL || $value === Table::LEVEL_YES) {
return true;
}
if (!$value || $value === Table::LEVEL_NO) {
if ($value === Table::LEVEL_NO) {
return false;
}
@@ -197,25 +181,30 @@ class Acl implements ScopeAcl, EntityAcl
return false;
}
/**
* @return bool
*/
public function checkIsOwner(User $user, Entity $entity)
{
if ($entity->hasAttribute('assignedUserId')) {
if ($entity->has('assignedUserId')) {
if ($user->id === $entity->get('assignedUserId')) {
return true;
}
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('createdById')) {
if ($entity->has('createdById')) {
if ($user->id === $entity->get('createdById')) {
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('assignedUsers')) {
if ($entity->hasLinkMultipleId('assignedUsers', $user->id)) {
if ($entity->hasLinkMultipleField(self::FIELD_ASSIGNED_USERS)) {
if ($entity->hasLinkMultipleId(self::FIELD_ASSIGNED_USERS, $user->getId())) {
return true;
}
}
@@ -223,15 +212,18 @@ class Acl implements ScopeAcl, EntityAcl
return false;
}
/**
* @return bool
*/
public function checkInTeam(User $user, Entity $entity)
{
$userTeamIdList = $user->getLinkMultipleIdList('teams');
$userTeamIdList = $user->getLinkMultipleIdList(self::FIELD_TEAMS);
if (!$entity->hasRelation('teams') || !$entity->hasAttribute('teamsIds')) {
if (!$entity->hasRelation(self::FIELD_TEAMS) || !$entity->hasAttribute(self::ATTR_ASSIGNED_TEAMS_IDS)) {
return false;
}
$entityTeamIdList = $entity->getLinkMultipleIdList('teams');
$entityTeamIdList = $entity->getLinkMultipleIdList(self::FIELD_TEAMS);
if (empty($entityTeamIdList)) {
return false;
@@ -246,7 +238,7 @@ class Acl implements ScopeAcl, EntityAcl
return false;
}
public function checkEntityDelete(User $user, Entity $entity, $data)
public function checkEntityDelete(User $user, Entity $entity, ScopeData $data) : bool
{
if ($user->isAdmin()) {
return true;
@@ -256,32 +248,28 @@ class Acl implements ScopeAcl, EntityAcl
return true;
}
if (!is_object($data)) {
return false;
}
if ($data->edit === Table::LEVEL_NO && $data->create === Table::LEVEL_NO) {
if ($data->getEdit() === Table::LEVEL_NO && $data->getCreate() === Table::LEVEL_NO) {
return false;
}
if (
!$this->config->get('aclAllowDeleteCreated') ||
!$entity->has('createdById') ||
!$entity->get('createdById') !== $user->getId()
!$entity->has(self::ATTR_CREATED_BY_ID) ||
!$entity->get(self::ATTR_CREATED_BY_ID) !== $user->getId()
) {
return false;
}
$isDeletedAllowed = false;
if (!$entity->has('assignedUserId')) {
if (!$entity->has(self::ATTR_ASSIGNED_USER_ID)) {
$isDeletedAllowed = true;
}
else {
if (!$entity->get('assignedUserId')) {
if (!$entity->get(self::ATTR_ASSIGNED_USER_ID)) {
$isDeletedAllowed = true;
}
else if ($entity->get('assignedUserId') === $entity->get('createdById')) {
else if ($entity->get(self::ATTR_ASSIGNED_USER_ID) === $entity->get(self::ATTR_CREATED_BY_ID)) {
$isDeletedAllowed = true;
}
}
@@ -308,22 +296,48 @@ class Acl implements ScopeAcl, EntityAcl
return true;
}
public function getOwnerUserIdAttribute(Entity $entity)
public function getOwnerUserIdAttribute(Entity $entity) : ?string
{
if ($this->ownerUserIdAttribute) {
return $this->ownerUserIdAttribute;
}
if ($entity->hasLinkMultipleField('assignedUsers')) {
return 'assignedUsersIds';
if ($entity->hasLinkMultipleField(self::FIELD_ASSIGNED_USERS)) {
return self::ATTR_ASSIGNED_USERS_IDS;
}
if ($entity->hasAttribute('assignedUserId')) {
return 'assignedUserId';
if ($entity->hasAttribute(self::ATTR_ASSIGNED_USER_ID)) {
return self::ATTR_ASSIGNED_USER_ID;
}
if ($entity->hasAttribute('createdById')) {
return 'createdById';
if ($entity->hasAttribute(self::ATTR_CREATED_BY_ID)) {
return self::ATTR_CREATED_BY_ID;
}
return null;
}
/**
* @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;
}
}
+63
View File
@@ -0,0 +1,63 @@
<?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,
InjectableFactory,
};
class AclFactory
{
protected $baseClassName = Acl::class;
private $classFinder;
private $injectableFactory;
public function __construct(ClassFinder $classFinder, InjectableFactory $injectableFactory)
{
$this->classFinder = $classFinder;
$this->injectableFactory = $injectableFactory;
}
public function create(string $scope) : ScopeAcl
{
$className = $this->classFinder->find('Acl', $scope);
if (!$className) {
$className = $this->baseClassName;
}
return $this->injectableFactory->createWith($className, [
'scope' => $scope, // todo remove ?
]);
}
}
+8 -7
View File
@@ -31,9 +31,6 @@ namespace Espo\Core\Acl;
use Espo\Core\Interfaces\Injectable;
use Espo\Entities\User;
use Espo\ORM\Entity;
use Espo\Core\{
ORM\EntityManager,
AclManager,
@@ -49,10 +46,15 @@ class Base extends Acl implements Injectable
protected $injections = [];
public function __construct(string $scope, EntityManager $entityManager, AclManager $aclManager, Config $config)
{
public function __construct(
EntityManager $entityManager,
AclManager $aclManager,
Config $config,
?string $scope = null
) {
$this->scope = $scope;
parent::__construct($scope, $entityManager, $aclManager, $config);
parent::__construct($entityManager, $aclManager, $config, $scope);
$this->init();
}
@@ -62,7 +64,6 @@ class Base extends Acl implements Injectable
$this->injections[$name] = $object;
}
protected function init()
{
}
+5 -3
View File
@@ -29,11 +29,13 @@
namespace Espo\Core\Acl;
use Espo\ORM\Entity;
use Espo\Entities\User;
/**
* @todo Add methods.
*/
interface EntityAcl
{
public function checkEntity(User $user, Entity $entity, ScopeData $data, string $action = Table::ACTION_READ): bool;
public function getOwnerUserIdAttribute(Entity $entity): ?string;
}
@@ -0,0 +1,39 @@
<?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 EntityCreateAcl
{
public function checkEntityCreate(User $user, Entity $entity, ScopeData $data): bool;
}
@@ -0,0 +1,39 @@
<?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 EntityDeleteAcl
{
public function checkEntityDelete(User $user, Entity $entity, ScopeData $data): bool;
}
@@ -0,0 +1,39 @@
<?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 EntityEditAcl
{
public function checkEntityEdit(User $user, Entity $entity, ScopeData $data): bool;
}
@@ -0,0 +1,39 @@
<?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 EntityReadAcl
{
public function checkEntityRead(User $user, Entity $entity, ScopeData $data): bool;
}
@@ -0,0 +1,39 @@
<?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 EntityStreamAcl
{
public function checkEntityStream(User $user, Entity $entity, ScopeData $data): bool;
}
+3 -4
View File
@@ -29,10 +29,9 @@
namespace Espo\Core\Acl;
/**
* @todo Add methods.
*/
use Espo\Entities\User;
interface ScopeAcl
{
public function checkScope(User $user, ScopeData $data, ?string $action = null): bool;
}
+42 -1
View File
@@ -50,7 +50,7 @@ class ScopeData
/**
* Get a raw value.
*
* @return StdClass|boo
* @return StdClass|bool
*/
public function getRaw()
{
@@ -61,11 +61,17 @@ class ScopeData
return $this->raw;
}
/**
* Is of boolean type.
*/
public function isBoolean() : bool
{
return $this->isBoolean;
}
/**
* Is true.
*/
public function isTrue() : bool
{
if (!$this->isBoolean) {
@@ -75,6 +81,9 @@ class ScopeData
return $this->raw === true;
}
/**
* Is false.
*/
public function isFalse() : bool
{
if (!$this->isBoolean) {
@@ -84,31 +93,63 @@ class ScopeData
return $this->raw === false;
}
/**
* Has any level other than 'no'.
*/
public function hasNotNo() : bool
{
foreach ($this->actionData as $level) {
if ($level !== Table::LEVEL_NO) {
return true;
}
}
return false;
}
/**
* Get a level for an action.
*/
public function get(string $action) : string
{
return $this->actionData[$action] ?? Table::LEVEL_NO;
}
/**
* Get a 'read' level.
*/
public function getRead() : string
{
return $this->get(Table::ACTION_READ);
}
/**
* Get a 'stream' level.
*/
public function getStream() : string
{
return $this->get(Table::ACTION_STREAM);
}
/**
* Get a 'create' level.
*/
public function getCreate() : string
{
return $this->get(Table::ACTION_CREATE);
}
/**
* Get an 'edit' level.
*/
public function getEdit() : string
{
return $this->get(Table::ACTION_EDIT);
}
/**
* Get a 'delete' level.
*/
public function getDelete() : string
{
return $this->get(Table::ACTION_DELETE);
+12 -11
View File
@@ -39,6 +39,7 @@ use Espo\Core\{
Utils\Metadata,
Utils\FieldUtil,
Utils\DataCache,
Utils\ObjectUtil,
};
use StdClass;
@@ -170,22 +171,22 @@ class Table
public function getMap() : StdClass
{
return $this->data;
return ObjectUtil::clone($this->data);
}
public function getScopeData(string $scope)
public function getScopeData(string $scope) : ScopeData
{
if (isset($this->data->table->$scope)) {
$data = $this->data->table->$scope;
if (is_string($data)) {
return $this->getScopeData($data);
}
return $data;
if (!isset($this->data->table->$scope)) {
return ScopeData::fromRaw(false);
}
return null;
$data = $this->data->table->$scope;
if (is_string($data)) {
return $this->getScopeData($data);
}
return ScopeData::fromRaw($data);
}
public function get(string $permission) : ?string
+145 -83
View File
@@ -29,21 +29,23 @@
namespace Espo\Core;
use Espo\Core\Exceptions\Error;
use Espo\ORM\Entity;
use Espo\Entities\User;
use Espo\Core\{
Utils\ClassFinder,
ORM\EntityManager,
Acl\Acl as BaseAcl,
Acl\AclFactory,
Acl\ScopeAcl,
Acl\GlobalRestrictonFactory,
Acl\GlobalRestricton,
Acl as UserAclWrapper,
Acl\Table as Table,
Acl\EntityCreateAcl,
Acl\EntityReadAcl,
Acl\EntityEditAcl,
Acl\EntityDeleteAcl,
Acl\EntityStreamAcl,
};
use StdClass;
@@ -53,7 +55,7 @@ use StdClass;
*/
class AclManager
{
private $implementationHashMap = [];
protected $implementationHashMap = [];
private $tableHashMap = [];
@@ -61,61 +63,60 @@ class AclManager
protected $userAclClassName = UserAclWrapper::class;
protected $baseImplementationClassName = BaseAcl::class;
protected const PERMISSION_ASSIGNMENT = 'assignment';
protected $globalRestricton;
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,
];
protected $injectableFactory;
protected $classFinder;
protected $entityManager;
protected $aclFactory;
protected $globalRestricton;
public function __construct(
InjectableFactory $injectableFactory,
ClassFinder $classFinder,
EntityManager $entityManager,
AclFactory $aclFactory,
GlobalRestrictonFactory $globalRestrictonFactory
) {
$this->injectableFactory = $injectableFactory;
$this->classFinder = $classFinder;
$this->entityManager = $entityManager;
$this->aclFactory = $aclFactory;
$this->globalRestricton = $globalRestrictonFactory->create();
}
public function getImplementation(string $scope) : ScopeAcl
/**
* Get an ACL implementation for a scope.
*
* @return ScopeAcl|EntityAcl
*/
public function getImplementation(string $scope): ScopeAcl
{
if (empty($this->implementationHashMap[$scope])) {
$className = $this->classFinder->find('Acl', $scope);
if (!$className) {
$className = $this->baseImplementationClassName;
}
if (!class_exists($className)) {
throw new Error("{$className} does not exist.");
}
$acl = $this->injectableFactory->createWith($className, [
'scope' => $scope,
]);
$this->implementationHashMap[$scope] = $acl;
if (!array_key_exists($scope, $this->implementationHashMap)) {
$this->implementationHashMap[$scope] = $this->aclFactory->create($scope);
}
return $this->implementationHashMap[$scope];
}
protected function getTable(User $user) : Table
protected function getTable(User $user): Table
{
$key = $user->id;
$key = $user->getId();
if (empty($key)) {
if (!$key) {
$key = spl_object_hash($user);
}
if (empty($this->tableHashMap[$key])) {
if (!array_key_exists($key, $this->tableHashMap)) {
$this->tableHashMap[$key] = $this->injectableFactory->createWith($this->tableClassName, [
'user' => $user,
]);
@@ -124,7 +125,7 @@ class AclManager
return $this->tableHashMap[$key];
}
public function getMap(User $user) : StdClass
public function getMap(User $user): StdClass
{
return $this->getTable($user)->getMap();
}
@@ -132,7 +133,7 @@ class AclManager
/**
* Get an access level for a specific scope and action.
*/
public function getLevel(User $user, string $scope, string $action) : string
public function getLevel(User $user, string $scope, string $action): string
{
if ($user->isAdmin()) {
return $this->getTable($user)->getHighestLevel($scope, $action);
@@ -144,7 +145,7 @@ class AclManager
/**
* Get a permission. E.g. 'assignment' permission.
*/
public function get(User $user, string $permission) : ?string
public function get(User $user, string $permission): ?string
{
if (substr($permission, -10) !== 'Permission') {
$permission .= 'Permission';
@@ -156,49 +157,39 @@ class AclManager
/**
* Whether there's no 'read' access for a specific scope.
*/
public function checkReadNo(User $user, string $scope) : bool
public function checkReadNo(User $user, string $scope): bool
{
if ($user->isAdmin()) {
return false;
}
$data = $this->getTable($user)->getScopeData($scope);
return (bool) $this->getImplementation($scope)->checkReadNo($user, $data);
return $this->getImplementation($scope)->checkReadNo($user, $data);
}
/**
* Whether 'read' access is set to 'team' for a specific scope.
*/
public function checkReadOnlyTeam(User $user, string $scope) : bool
public function checkReadOnlyTeam(User $user, string $scope): bool
{
if ($user->isAdmin()) {
return false;
}
$data = $this->getTable($user)->getScopeData($scope);
return (bool) $this->getImplementation($scope)->checkReadOnlyTeam($user, $data);
return $this->getImplementation($scope)->checkReadOnlyTeam($user, $data);
}
/**
* Whether 'read' access is set to 'own' for a specific scope.
*/
public function checkReadOnlyOwn(User $user, string $scope) : bool
public function checkReadOnlyOwn(User $user, string $scope): bool
{
if ($user->isAdmin()) {
return false;
}
$data = $this->getTable($user)->getScopeData($scope);
return (bool) $this->getImplementation($scope)->checkReadOnlyOwn($user, $data);
return $this->getImplementation($scope)->checkReadOnlyOwn($user, $data);
}
/**
* 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.
*/
public function check(User $user, $subject, ?string $action = null) : bool
public function check(User $user, $subject, ?string $action = null): bool
{
if (is_string($subject)) {
return $this->checkScope($user, $subject, $action);
@@ -216,9 +207,9 @@ class AclManager
}
/**
* Check access to a specific entity (record).
* Check access to a specific entity.
*/
public function checkEntity(User $user, Entity $entity, string $action = Table::ACTION_READ) : bool
public function checkEntity(User $user, Entity $entity, string $action = Table::ACTION_READ): bool
{
$scope = $entity->getEntityType();
@@ -232,17 +223,64 @@ class AclManager
$methodName = 'checkEntity' . ucfirst($action);
if (method_exists($impl, $methodName)) {
return (bool) $impl->$methodName($user, $entity, $data);
$interface = $this->actionInterfaceMap[$action] ?? null;
if ($interface && $impl instanceof $interface) {
return $impl->$methodName($user, $entity, $data);
}
return (bool) $impl->checkEntity($user, $entity, $data, $action);
if (method_exists($impl, $methodName)) {
// For backward compatibility.
return $impl->$methodName($user, $entity, $data);
}
return $impl->checkEntity($user, $entity, $data, $action);
}
/**
* Check 'read' access to a specific entity.
*/
public function checkEntityRead(User $user, Entity $entity): bool
{
return $this->checkEntity($user, $entity, Table::ACTION_READ);
}
/**
* Check 'create' access to a specific entity.
*/
public function checkEntityCreate(User $user, Entity $entity): bool
{
return $this->checkEntity($user, $entity, Table::ACTION_CREATE);
}
/**
* Check 'edit' access to a specific entity.
*/
public function checkEntityEdit(User $user, Entity $entity): bool
{
return $this->checkEntity($user, $entity, Table::ACTION_EDIT);
}
/**
* Check 'delete' access to a specific entity.
*/
public function checkEntityDelete(User $user, Entity $entity): bool
{
return $this->checkEntity($user, $entity, Table::ACTION_DELETE);
}
/**
* Check 'stream' access to a specific entity.
*/
public function checkEntityStream(User $user, Entity $entity): bool
{
return $this->checkEntity($entity, Table::ACTION_STREAM);
}
/**
* Whether a user is owned of an entity (record). Usually 'assignedUser' field is used for checking.
*/
public function checkIsOwner(User $user, Entity $entity) : bool
public function checkIsOwner(User $user, Entity $entity): bool
{
return (bool) $this->getImplementation($entity->getEntityType())->checkIsOwner($user, $entity);
}
@@ -250,7 +288,7 @@ class AclManager
/**
* Whether a user team list overlaps with teams set in an entity.
*/
public function checkInTeam(User $user, Entity $entity) : bool
public function checkInTeam(User $user, Entity $entity): bool
{
return (bool) $this->getImplementation($entity->getEntityType())->checkInTeam($user, $entity);
}
@@ -258,17 +296,17 @@ class AclManager
/**
* Check access to scope. If $action is omitted, it will check whether a scope level is set to 'enabled'.
*/
public function checkScope(User $user, string $scope, ?string $action = null) : bool
public function checkScope(User $user, string $scope, ?string $action = null): bool
{
$data = $this->getTable($user)->getScopeData($scope);
return (bool) $this->getImplementation($scope)->checkScope($user, $data, $action);
return $this->getImplementation($scope)->checkScope($user, $data, $action);
}
/**
* @deprecated Use checkUserPermission instead.
*/
public function checkUser(User $user, string $permission, User $target) : bool
public function checkUser(User $user, string $permission, User $target): bool
{
if ($this->get($user, $permission) === Table::LEVEL_ALL) {
return true;
@@ -309,7 +347,7 @@ class AclManager
return false;
}
protected function getGlobalRestrictionTypeList(User $user, string $action = Table::ACTION_READ) : array
protected function getGlobalRestrictionTypeList(User $user, string $action = Table::ACTION_READ): array
{
$typeList = [
GlobalRestricton::TYPE_FORBIDDEN,
@@ -338,8 +376,11 @@ class AclManager
* Get attributes forbidden for a user.
*/
public function getScopeForbiddenAttributeList(
User $user, string $scope, string $action = Table::ACTION_READ, string $thresholdLevel = Table::LEVEL_NO
) : array {
User $user,
string $scope,
string $action = Table::ACTION_READ,
string $thresholdLevel = Table::LEVEL_NO
): array {
$list = [];
@@ -350,7 +391,10 @@ class AclManager
if ($thresholdLevel === Table::LEVEL_NO) {
$list = array_merge(
$list,
$this->getScopeRestrictedAttributeList($scope, $this->getGlobalRestrictionTypeList($user, $action))
$this->getScopeRestrictedAttributeList(
$scope,
$this->getGlobalRestrictionTypeList($user, $action)
)
);
$list = array_values($list);
@@ -363,8 +407,11 @@ class AclManager
* Get fields forbidden for a user.
*/
public function getScopeForbiddenFieldList(
User $user, string $scope, string $action = Table::ACTION_READ, string $thresholdLevel = Table::LEVEL_NO
) : array {
User $user,
string $scope,
string $action = Table::ACTION_READ,
string $thresholdLevel = Table::LEVEL_NO
): array {
$list = [];
@@ -375,7 +422,10 @@ class AclManager
if ($thresholdLevel === Table::LEVEL_NO) {
$list = array_merge(
$list,
$this->getScopeRestrictedFieldList($scope, $this->getGlobalRestrictionTypeList($user, $action))
$this->getScopeRestrictedFieldList(
$scope,
$this->getGlobalRestrictionTypeList($user, $action)
)
);
$list = array_values($list);
@@ -388,15 +438,21 @@ class AclManager
* Get links forbidden for a user.
*/
public function getScopeForbiddenLinkList(
User $user, string $scope, string $action = Table::ACTION_READ, string $thresholdLevel = Table::LEVEL_NO
) : array {
User $user,
string $scope,
string $action = Table::ACTION_READ,
string $thresholdLevel = Table::LEVEL_NO
): array {
$list = [];
if ($thresholdLevel === Table::LEVEL_NO) {
$list = array_merge(
$list,
$this->getScopeRestrictedLinkList($scope, $this->getGlobalRestrictionTypeList($user, $action))
$this->getScopeRestrictedLinkList(
$scope,
$this->getGlobalRestrictionTypeList($user, $action)
)
);
$list = array_values($list);
@@ -410,13 +466,14 @@ class AclManager
*
* @param User|string $target User entity or user ID.
*/
public function checkUserPermission(User $user, $target, string $permissionType = 'user') : bool
public function checkUserPermission(User $user, $target, string $permissionType = 'user'): bool
{
$permission = $this->get($user, $permissionType);
if (is_object($target)) {
$userId = $target->id;
} else {
}
else {
$userId = $target;
}
@@ -435,7 +492,11 @@ class AclManager
if ($permission === Table::LEVEL_TEAM) {
$teamIdList = $user->getLinkMultipleIdList('teams');
if (!$this->entityManager->getRepository('User')->checkBelongsToAnyOfTeams($userId, $teamIdList)) {
if (
!$this->entityManager
->getRepository('User')
->checkBelongsToAnyOfTeams($userId, $teamIdList)
) {
return false;
}
}
@@ -448,23 +509,24 @@ class AclManager
*
* @param User|string $target User entity or user ID.
*/
public function checkAssignmentPermission(User $user, $target) : bool
public function checkAssignmentPermission(User $user, $target): bool
{
return $this->checkUserPermission($user, $target, 'assignment');
return $this->checkUserPermission($user, $target, self::PERMISSION_ASSIGNMENT);
}
/**
* Create a wrapper for a specific user.
*/
public function createUserAcl(User $user) : UserAclWrapper
public function createUserAcl(User $user): UserAclWrapper
{
$className = $this->userAclClassName;
$acl = new $className($this, $user);
return $acl;
}
public function getScopeRestrictedFieldList(string $scope, $type) : array
public function getScopeRestrictedFieldList(string $scope, $type): array
{
if (is_array($type)) {
$typeList = $type;
@@ -481,7 +543,7 @@ class AclManager
return $this->globalRestricton->getScopeRestrictedFieldList($scope, $type);
}
public function getScopeRestrictedAttributeList(string $scope, $type) : array
public function getScopeRestrictedAttributeList(string $scope, $type): array
{
if (is_array($type)) {
$typeList = $type;
@@ -498,7 +560,7 @@ class AclManager
return $this->globalRestricton->getScopeRestrictedAttributeList($scope, $type);
}
public function getScopeRestrictedLinkList(string $scope, $type) : array
public function getScopeRestrictedLinkList(string $scope, $type): array
{
if (is_array($type)) {
$typeList = $type;
@@ -0,0 +1,63 @@
<?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\Core\{
Utils\ClassFinder,
InjectableFactory,
};
class AclFactory
{
protected $baseClassName = Acl::class;
private $classFinder;
private $injectableFactory;
public function __construct(ClassFinder $classFinder, InjectableFactory $injectableFactory)
{
$this->classFinder = $classFinder;
$this->injectableFactory = $injectableFactory;
}
public function create(string $scope) : PortalScopeAcl
{
$className = $this->classFinder->find('AclPortal', $scope);
if (!$className) {
$className = $this->baseClassName;
}
return $this->injectableFactory->createWith($className, [
'scope' => $scope, // todo remove ?
]);
}
}
+35 -40
View File
@@ -30,29 +30,28 @@
namespace Espo\Core\AclPortal;
use Espo\Entities\User;
use Espo\ORM\Entity;
use Espo\Core\{
Acl\ScopeData,
};
trait Portal
{
public function checkScope(User $user, $data, $action = null, Entity $entity = null, $entityAccessData = [])
{
if ($user->isAdmin()) {
return true;
}
protected function checkScopeInternal(
User $user,
ScopeData $data,
?string $action = null,
?Entity $entity = null,
array $entityAccessData = []
) : bool {
if (is_null($data)) {
if ($data->isFalse()) {
return false;
}
if ($data === false) {
return false;
}
if ($data === true) {
return true;
}
if (is_string($data)) {
if ($data->isTrue()) {
return true;
}
@@ -78,13 +77,9 @@ trait Portal
return true;
}
if (!isset($data->$action)) {
return false;
}
$value = $data->get($action);
$value = $data->$action;
if ($value === Table::LEVEL_ALL || $value === Table::LEVEL_YES || $value === true) {
if ($value === Table::LEVEL_ALL || $value === Table::LEVEL_YES) {
return true;
}
@@ -115,14 +110,13 @@ trait Portal
if ($inAccount) {
return true;
}
else {
if (is_null($isOwnContact) && $entity) {
$isOwnContact = $this->checkIsOwnContact($user, $entity);
}
if ($isOwnContact) {
return true;
}
if (is_null($isOwnContact) && $entity) {
$isOwnContact = $this->checkIsOwnContact($user, $entity);
}
if ($isOwnContact) {
return true;
}
}
@@ -139,24 +133,19 @@ trait Portal
return false;
}
public function checkReadOnlyAccount(User $user, $data)
public function checkReadOnlyAccount(User $user, ScopeData $data) : bool
{
if (empty($data) || !is_object($data) || !isset($data->read)) {
return false;
}
return $data->read === Table::LEVEL_ACCOUNT;
return $data->getRead() === Table::LEVEL_ACCOUNT;
}
public function checkReadOnlyContact(User $user, $data)
public function checkReadOnlyContact(User $user, ScopeData $data) : bool
{
if (empty($data) || !is_object($data) || !isset($data->read)) {
return false;
}
return $data->read === Table::LEVEL_CONTACT;
return $data->getRead() === Table::LEVEL_CONTACT;
}
/**
* @return bool
*/
public function checkIsOwner(User $user, Entity $entity)
{
if ($entity->hasAttribute('createdById')) {
@@ -168,6 +157,9 @@ trait Portal
return false;
}
/**
* @return bool
*/
public function checkInAccount(User $user, Entity $entity)
{
$accountIdList = $user->getLinkMultipleIdList('accounts');
@@ -204,6 +196,9 @@ trait Portal
return false;
}
/**
* @return bool
*/
public function checkIsOwnContact(User $user, Entity $entity)
{
$contactId = $user->get('contactId');
+4 -4
View File
@@ -35,22 +35,22 @@ use Espo\Core\Acl as BaseAcl;
class Acl extends BaseAcl
{
public function checkReadOnlyAccount(string $scope) : bool
public function checkReadOnlyAccount(string $scope): bool
{
return $this->aclManager->checkReadOnlyAccount($this->user, $scope);
}
public function checkReadOnlyContact(string $scope) : bool
public function checkReadOnlyContact(string $scope): bool
{
return $this->aclManager->checkReadOnlyContact($this->user, $scope);
}
public function checkInAccount(Entity $entity) : bool
public function checkInAccount(Entity $entity): bool
{
return $this->aclManager->checkInAccount($this->user, $entity);
}
public function checkIsOwnContact(Entity $entity) : bool
public function checkIsOwnContact(Entity $entity): bool
{
return $this->aclManager->checkIsOwnContact($this->user, $entity);
}
+74 -85
View File
@@ -31,18 +31,22 @@ namespace Espo\Core\Portal;
use Espo\ORM\Entity;
use Espo\Entities\User;
use Espo\Entities\Portal;
use Espo\Entities\{
User,
Portal,
};
use Espo\Core\{
Exceptions\Error,
AclPortal\Table as Table,
AclPortal\Acl as BasePortalAcl,
AclPortal\PortalScopeAcl,
AclPortal\AclFactory as PortalAclFactory,
Acl\ScopeAcl,
Acl\GlobalRestrictonFactory,
Acl\Table as TableBase,
Portal\Acl as UserAclWrapper,
AclManager as BaseAclManager,
InjectableFactory,
ORM\EntityManager,
};
use StdClass;
@@ -52,57 +56,44 @@ class AclManager extends BaseAclManager
{
protected $tableClassName = Table::class;
private $mainManager = null;
private $portal = null;
protected $userAclClassName = UserAclWrapper::class;
protected $baseImplementationClassName = BasePortalAcl::class;
public function getImplementation(string $scope) : ScopeAcl
private $mainManager = null;
private $portal = null;
public function __construct(
InjectableFactory $injectableFactory,
EntityManager $entityManager,
PortalAclFactory $aclFactory,
GlobalRestrictonFactory $globalRestrictonFactory,
BaseAclManager $mainManager
) {
$this->injectableFactory = $injectableFactory;
$this->entityManager = $entityManager;
$this->aclFactory = $aclFactory;
$this->mainManager = $mainManager;
$this->globalRestricton = $globalRestrictonFactory->create();
}
public function getImplementation(string $scope): ScopeAcl
{
if (empty($this->implementationHashMap[$scope])) {
$className = $this->classFinder->find('AclPortal', $scope);
if (!$className) {
$className = $this->baseImplementationClassName;
}
if (!class_exists($className)) {
throw new Error("{$className} does not exist.");
}
$acl = $this->injectableFactory->createWith($className, [
'scope' => $scope,
]);
$this->implementationHashMap[$scope] = $acl;
if (!$acl instanceof PortalScopeAcl) {
throw new Error("Portal\AclManager: Implementation should be instance of PortalScopeAcl.");
}
if (!array_key_exists($scope, $this->implementationHashMap)) {
$this->implementationHashMap[$scope] = $this->aclFactory->create($scope);
}
return $this->implementationHashMap[$scope];
}
public function setMainManager(BaseAclManager $mainManager) : void
{
$this->mainManager = $mainManager;
}
protected function getMainManager() : BaseAclManager
{
return $this->mainManager;
}
public function setPortal(Portal $portal) : void
public function setPortal(Portal $portal): void
{
$this->portal = $portal;
}
protected function getPortal() : Portal
protected function getPortal(): Portal
{
if (!$this->portal) {
throw new RuntimeException("Portal is not set.");
@@ -111,7 +102,7 @@ class AclManager extends BaseAclManager
return $this->portal;
}
protected function getTable(User $user) : TableBase
protected function getTable(User $user): TableBase
{
$key = $user->id;
@@ -129,175 +120,173 @@ class AclManager extends BaseAclManager
return $this->tableHashMap[$key];
}
public function checkReadOnlyAccount(User $user, string $scope) : bool
public function checkReadOnlyAccount(User $user, string $scope): bool
{
if ($user->isAdmin()) {
return false;
}
$data = $this->getTable($user)->getScopeData($scope);
return $this->getImplementation($scope)->checkReadOnlyAccount($user, $data);
}
public function checkReadOnlyContact(User $user, string $scope) : bool
public function checkReadOnlyContact(User $user, string $scope): bool
{
if ($user->isAdmin()) {
return false;
}
$data = $this->getTable($user)->getScopeData($scope);
return $this->getImplementation($scope)->checkReadOnlyContact($user, $data);
}
public function checkInAccount(User $user, Entity $entity) : bool
public function checkInAccount(User $user, Entity $entity): bool
{
return (bool) $this->getImplementation($entity->getEntityType())->checkInAccount($user, $entity);
}
public function checkIsOwnContact(User $user, Entity $entity) : bool
public function checkIsOwnContact(User $user, Entity $entity): bool
{
return (bool) $this->getImplementation($entity->getEntityType())->checkIsOwnContact($user, $entity);
}
public function getMap(User $user) : StdClass
public function getMap(User $user): StdClass
{
if ($this->checkUserIsNotPortal($user)) {
return $this->getMainManager()->getMap($user);
return $this->mainManager->getMap($user);
}
return parent::getMap($user);
}
public function getLevel(User $user, string $scope, string $action) : string
public function getLevel(User $user, string $scope, string $action): string
{
if ($this->checkUserIsNotPortal($user)) {
return $this->getMainManager()->getLevel($user, $scope, $action);
return $this->mainManager->getLevel($user, $scope, $action);
}
return parent::getLevel($user, $scope, $action);
}
public function get(User $user, string $permission) : ?string
public function get(User $user, string $permission): ?string
{
if ($this->checkUserIsNotPortal($user)) {
return $this->getMainManager()->get($user, $permission);
return $this->mainManager->get($user, $permission);
}
return parent::get($user, $permission);
}
public function checkReadOnlyTeam(User $user, string $scope) : bool
public function checkReadOnlyTeam(User $user, string $scope): bool
{
if ($this->checkUserIsNotPortal($user)) {
$data = $this->getTable($user)->getScopeData($scope);
return $this->getMainManager()->checkReadOnlyTeam($user, $data);
return $this->mainManager->checkReadOnlyTeam($user, $data);
}
return parent::checkReadOnlyTeam($user, $scope);
}
public function checkReadNo(User $user, string $scope) : bool
public function checkReadNo(User $user, string $scope): bool
{
if ($this->checkUserIsNotPortal($user)) {
$data = $this->getTable($user)->getScopeData($scope);
return $this->getMainManager()->checkReadNo($user, $data);
return $this->mainManager->checkReadNo($user, $data);
}
return parent::checkReadNo($user, $scope);
}
public function checkReadOnlyOwn(User $user, string $scope) : bool
public function checkReadOnlyOwn(User $user, string $scope): bool
{
if ($this->checkUserIsNotPortal($user)) {
$data = $this->getTable($user)->getScopeData($scope);
return $this->getMainManager()->checkReadOnlyOwn($user, $data);
return $this->mainManager->checkReadOnlyOwn($user, $data);
}
return parent::checkReadOnlyOwn($user, $scope);
}
public function check(User $user, $subject, ?string $action = null) : bool
public function check(User $user, $subject, ?string $action = null): bool
{
if ($this->checkUserIsNotPortal($user)) {
return $this->getMainManager()->check($user, $subject, $action);
return $this->mainManager->check($user, $subject, $action);
}
return parent::check($user, $subject, $action);
}
public function checkEntity(User $user, Entity $entity, string $action = Table::ACTION_READ) : bool
public function checkEntity(User $user, Entity $entity, string $action = Table::ACTION_READ): bool
{
if ($this->checkUserIsNotPortal($user)) {
return $this->getMainManager()->checkEntity($user, $entity, $action);
return $this->mainManager->checkEntity($user, $entity, $action);
}
return parent::checkEntity($user, $entity, $action);
}
public function checkIsOwner(User $user, Entity $entity) : bool
public function checkIsOwner(User $user, Entity $entity): bool
{
if ($this->checkUserIsNotPortal($user)) {
return $this->getMainManager()->checkIsOwner($user, $entity);
return $this->mainManager->checkIsOwner($user, $entity);
}
return parent::checkIsOwner($user, $entity);
}
public function checkInTeam(User $user, Entity $entity) : bool
public function checkInTeam(User $user, Entity $entity): bool
{
if ($this->checkUserIsNotPortal($user)) {
return $this->getMainManager()->checkInTeam($user, $entity);
return $this->mainManager->checkInTeam($user, $entity);
}
return parent::checkInTeam($user, $entity);
}
public function checkScope(User $user, string $scope, ?string $action = null) : bool
public function checkScope(User $user, string $scope, ?string $action = null): bool
{
if ($this->checkUserIsNotPortal($user)) {
return $this->getMainManager()->checkScope($user, $scope, $action);
return $this->mainManager->checkScope($user, $scope, $action);
}
return parent::checkScope($user, $scope, $action);
}
public function checkUser(User $user, string $permission, User $entity) : bool
public function checkUser(User $user, string $permission, User $entity): bool
{
if ($this->checkUserIsNotPortal($user)) {
return $this->getMainManager()->checkUser($user, $permission, $entity);
return $this->mainManager->checkUser($user, $permission, $entity);
}
return parent::checkUser($user, $permission, $entity);
}
public function getScopeForbiddenAttributeList(
User $user, string $scope, string $action = Table::ACTION_READ, string $thresholdLevel = Table::LEVEL_NO
) : array {
User $user,
string $scope,
string $action = Table::ACTION_READ,
string $thresholdLevel = Table::LEVEL_NO
): array {
if ($this->checkUserIsNotPortal($user)) {
return $this->getMainManager()->getScopeForbiddenAttributeList($user, $scope, $action, $thresholdLevel);
return $this->mainManager->getScopeForbiddenAttributeList($user, $scope, $action, $thresholdLevel);
}
return parent::getScopeForbiddenAttributeList($user, $scope, $action, $thresholdLevel);
}
public function getScopeForbiddenFieldList(
User $user, string $scope, string $action = Table::ACTION_READ, string $thresholdLevel = Table::LEVEL_NO
) : array {
User $user,
string $scope,
string $action = Table::ACTION_READ,
string $thresholdLevel = Table::LEVEL_NO
): array {
if ($this->checkUserIsNotPortal($user)) {
return $this->getMainManager()->getScopeForbiddenFieldList($user, $scope, $action, $thresholdLevel);
return $this->mainManager->getScopeForbiddenFieldList($user, $scope, $action, $thresholdLevel);
}
return parent::getScopeForbiddenFieldList($user, $scope, $action, $thresholdLevel);
}
protected function checkUserIsNotPortal(User $user) : bool
protected function checkUserIsNotPortal(User $user): bool
{
return !$user->isPortal();
}
@@ -50,10 +50,8 @@ class AclManager implements Loader
public function load() : PortalAclManager
{
$aclManager = $this->injectableFactory->create(PortalAclManager::class);
$aclManager->setMainManager($this->internalAclManager);
return $aclManager;
return $this->injectableFactory->createWith(PortalAclManager::class,[
'mainManager' => $this->internalAclManager,
]);
}
}
+73 -11
View File
@@ -29,42 +29,99 @@
namespace Espo\Entities;
class Note extends \Espo\Core\ORM\Entity
use Espo\Core\ORM\Entity;
class Note extends Entity
{
public const TARGET_SELF = 'self';
public const TARGET_ALL = 'all';
public const TARGET_TEAMS = 'teams';
public const TARGET_USERS = 'users';
public const TARGET_PORTALS = 'portals';
public const TYPE_POST = 'Post';
private $aclIsProcessed = false;
public function setAclIsProcessed()
public function isPost(): bool
{
return $this->getType() === self::TYPE_POST;
}
public function getType(): ?string
{
return $this->get('type');
}
public function getTargetType(): ?string
{
return $this->get('targetType');
}
public function getParentType(): ?string
{
return $this->get('parentType');
}
public function getParentId(): ?string
{
return $this->get('parentId');
}
public function isInternal(): bool
{
return (bool) $this->get('isInternal');
}
public function setAclIsProcessed(): void
{
$this->aclIsProcessed = true;
}
public function isAclProcessed()
public function isAclProcessed(): bool
{
return $this->aclIsProcessed;
return (bool) $this->aclIsProcessed;
}
public function loadAttachments()
{
$data = $this->get('data');
if (!empty($data) && !empty($data->attachmentsIds) && is_array($data->attachmentsIds)) {
$attachmentsIds = $data->attachmentsIds;
$collection = $this->entityManager->getRepository('Attachment')->select(['id', 'name', 'type'])->order('createdAt')->where([
'id' => $attachmentsIds
])->find();
} else {
$collection = $this->entityManager
->getRepository('Attachment')
->select(['id', 'name', 'type'])
->order('createdAt')
->where([
'id' => $attachmentsIds
])
->find();
}
else {
$this->loadLinkMultipleField('attachments');
return;
}
$ids = array();
$names = new \stdClass();
$types = new \stdClass();
$ids = [];
$names = (object) [];
$types = (object) [];
foreach ($collection as $e) {
$id = $e->id;
$ids[] = $id;
$names->$id = $e->get('name');
$types->$id = $e->get('type');
}
$this->set('attachmentsIds', $ids);
$this->set('attachmentsNames', $names);
$this->set('attachmentsTypes', $types);
@@ -73,21 +130,26 @@ class Note extends \Espo\Core\ORM\Entity
public function addNotifiedUserId($userId)
{
$userIdList = $this->get('notifiedUserIdList');
if (!is_array($userIdList)) {
$userIdList = [];
}
if (!in_array($userId, $userIdList)) {
$userIdList[] = $userId;
}
$this->set('notifiedUserIdList', $userIdList);
}
public function isUserIdNotified($userId)
{
$userIdList = $this->get('notifiedUserIdList');
if (!is_array($userIdList)) {
$userIdList = [];
}
return in_array($userId, $userIdList);
}
}
@@ -33,9 +33,9 @@ use Espo\Entities\User;
use Espo\ORM\Entity;
use Espo\Core\Acl\Base;
use Espo\Core\Acl\Acl;
class CampaignLogRecord extends Base
class CampaignLogRecord extends Acl
{
public function checkIsOwner(User $user, Entity $entity)
{
@@ -33,9 +33,9 @@ use Espo\Entities\User;
use Espo\ORM\Entity;
use Espo\Core\Acl\Base;
use Espo\Core\Acl\Acl;
class CampaignTrackingUrl extends Base
class CampaignTrackingUrl extends Acl
{
public function checkIsOwner(User $user, Entity $entity)
{
@@ -33,9 +33,9 @@ use Espo\Entities\User;
use Espo\ORM\Entity;
use Espo\Core\Acl\Base;
use Espo\Core\Acl\Acl;
class MassEmail extends Base
class MassEmail extends Acl
{
public function checkIsOwner(User $user, Entity $entity)
{
+11 -10
View File
@@ -33,24 +33,25 @@ use Espo\Entities\User;
use Espo\ORM\Entity;
use Espo\Core\Acl\Base;
use Espo\Core\{
Acl\Acl,
Acl\ScopeData,
Acl\Table,
Acl\EntityReadAcl,
};
class Meeting extends Base
class Meeting extends Acl implements EntityReadAcl
{
protected $ownerUserIdAttribute = 'usersIds';
public function checkEntityRead(User $user, Entity $entity, $data)
public function checkEntityRead(User $user, Entity $entity, ScopeData $data): bool
{
if ($this->checkEntity($user, $entity, $data, 'read')) {
if ($this->checkEntity($user, $entity, $data, Table::ACTION_READ)) {
return true;
}
if (!$data) {
return false;
}
if ($data->read === 'own' || $data->read === 'team') {
if ($entity->hasLinkMultipleId('users', $user->id)) {
if ($data->getRead() === Table::LEVEL_OWN || $data->getRead() === Table::LEVEL_TEAM) {
if ($entity->hasLinkMultipleId('users', $user->getId())) {
return true;
}
}
@@ -33,13 +33,18 @@ use Espo\Entities\User as EntityUser;
use Espo\ORM\Entity;
use Espo\Core\AclPortal\Acl;
use Espo\Core\{
Acl\ScopeData,
Acl\Table,
Acl\EntityReadAcl,
AclPortal\Acl as Acl,
};
class KnowledgeBaseArticle extends Acl
class KnowledgeBaseArticle extends Acl implements EntityReadAcl
{
public function checkEntityRead(EntityUser $user, Entity $entity, $data)
public function checkEntityRead(EntityUser $user, Entity $entity, ScopeData $data): bool
{
if (!$this->checkEntity($user, $entity, $data, 'read')) {
if (!$this->checkEntity($user, $entity, $data, Table::ACTION_READ)) {
return false;
}
+280 -124
View File
@@ -30,11 +30,20 @@
namespace Espo\Services;
use Espo\Core\Exceptions\Forbidden;
use Espo\Core\Exceptions\NotFound;
use Espo\Core\Exceptions\BadRequest;
use Espo\Core\{
Acl\Table as AclTable,
};
use Espo\Entities\Note as NoteEntity;
use Espo\ORM\Entity;
use DateTime;
use Exception;
use StdClass;
class Note extends Record
{
protected $noteNotificationPeriod = '1 hour';
@@ -42,6 +51,7 @@ class Note extends Record
public function loadAdditionalFields(Entity $entity)
{
parent::loadAdditionalFields($entity);
$entity->loadAttachments();
}
@@ -49,74 +59,120 @@ class Note extends Record
{
parent::afterCreateEntity($entity, $data);
if ($entity->get('type') === 'Post' && $entity->get('parentType') && $entity->get('parentType')) {
$preferences = $this->getEntityManager()->getEntity('Preferences', $this->getUser()->id);
if ($preferences && $preferences->get('followEntityOnStreamPost')) {
if ($this->getMetadata()->get(['scopes', $entity->get('parentType'), 'stream'])) {
$parent = $this->getEntityManager()->getEntity($entity->get('parentType'), $entity->get('parentId'));
if ($parent && !$this->getUser()->isSystem() && !$this->getUser()->isApi()) {
$this->getServiceFactory()->create('Stream')->followEntity($parent, $this->getUser()->id);
}
}
}
}
$this->processFollowAfterCreate($entity);
}
protected function processFollowAfterCreate(NoteEntity $entity): void
{
$parentType = $entity->get('parentType');
$parentId = $entity->get('parentId');
if ($entity->get('type') !== NoteEntity::TYPE_POST || !$parentType || !$parentId) {
return;
}
if (!$this->metadata->get(['scopes', $parentType, 'stream'])) {
return;
}
$preferences = $this->entityManager->getEntity('Preferences', $this->user->getId());
if (!$preferences) {
return;
}
if (!$preferences->get('followEntityOnStreamPost')) {
return;
}
$parent = $this->entityManager->getEntity($parentType, $parentId);
if (!$parent || $this->user->isSystem() || $this->user->isApi()) {
return;
}
$this->getStreamService()->followEntity($parent, $this->user->getId());
}
/**
* @param NoteEntity $entity
* @param StdClass $data
*/
protected function beforeCreateEntity(Entity $entity, $data)
{
if (!empty($data->parentType) && !empty($data->parentId)) {
$entity = $this->getEntityManager()->getEntity($data->parentType, $data->parentId);
if ($entity) {
if (!$this->getAcl()->check($entity, 'read')) {
throw new Forbidden();
}
$parentType = $data->parentType ?? null;
$parentId = $data->parentId ?? null;
if ($parentType && $parentId) {
$entity = $this->entityManager->getEntity($data->parentType, $data->parentId);
if ($entity && !$this->acl->check($entity, AclTable::ACTION_READ)) {
throw new Forbidden();
}
}
parent::beforeCreateEntity($entity, $data);
if ($entity->get('type') === 'Post') {
if ($entity->isPost()) {
$this->handlePostText($entity);
}
$targetType = $entity->get('targetType');
$targetType = $entity->getTargetType();
$entity->clear('isGlobal');
switch ($targetType) {
case 'all':
case NoteEntity::TARGET_ALL:
$entity->clear('usersIds');
$entity->clear('teamsIds');
$entity->clear('portalsIds');
$entity->set('isGlobal', true);
break;
case 'self':
case NoteEntity::TARGET_SELF:
$entity->clear('usersIds');
$entity->clear('teamsIds');
$entity->clear('portalsIds');
$entity->set('usersIds', [$this->getUser()->id]);
$entity->set('usersIds', [$this->user->id]);
$entity->set('isForSelf', true);
break;
case 'users':
case NoteEntity::TARGET_USERS:
$entity->clear('teamsIds');
$entity->clear('portalsIds');
break;
case 'teams':
case NoteEntity::TARGET_TEAMS:
$entity->clear('usersIds');
$entity->clear('portalsIds');
break;
case 'portals':
case NoteEntity::TARGET_PORTALS:
$entity->clear('usersIds');
$entity->clear('teamsIds');
break;
}
}
/**
* @param NoteEntity $entity
* @param StdClass $data
*/
protected function beforeUpdateEntity(Entity $entity, $data)
{
parent::beforeUpdateEntity($entity, $data);
if ($entity->get('type') === 'Post') {
if ($entity->isPost()) {
$this->handlePostText($entity);
}
@@ -130,11 +186,16 @@ class Note extends Record
protected function handlePostText(Entity $entity)
{
$post = $entity->get('post');
if (empty($post)) return;
if (empty($post)) {
return;
}
$siteUrl = $this->getConfig()->getSiteUrl();
$regexp = '/' . preg_quote($siteUrl, '/') . '(\/portal|\/portal\/[a-zA-Z0-9]*)?\/#([A-Z][a-zA-Z0-9]*)\/view\/([a-zA-Z0-9]*)/';
$regexp = '/' . preg_quote($siteUrl, '/') .
'(\/portal|\/portal\/[a-zA-Z0-9]*)?\/#([A-Z][a-zA-Z0-9]*)\/view\/([a-zA-Z0-9]*)/';
$post = preg_replace($regexp, '[\2/\3](#\2/view/\3)', $post);
$entity->set('post', $post);
@@ -142,73 +203,141 @@ class Note extends Record
public function processAssignmentCheck(Entity $entity)
{
if ($entity->isNew()) {
$targetType = $entity->get('targetType');
if (!$entity->isNew()) {
return;
}
if ($targetType) {
$assignmentPermission = $this->getAcl()->get('assignmentPermission');
if ($assignmentPermission === false || $assignmentPermission === 'no') {
if ($targetType !== 'self') {
throw new Forbidden('Not permitted to post to anybody except self.');
}
}
$targetType = $entity->getTargetType();
if ($targetType === 'teams') {
$teamIdList = $entity->get('teamsIds');
if (empty($teamIdList) || !is_array($teamIdList)) {
throw new BadRequest();
}
}
if ($targetType === 'users') {
$userIdList = $entity->get('usersIds');
if (empty($userIdList) || !is_array($userIdList)) {
throw new BadRequest();
}
}
if ($targetType === 'portals') {
$portalIdList = $entity->get('portalsIds');
if (empty($portalIdList) || !is_array($portalIdList)) {
throw new BadRequest();
}
if ($this->getAcl()->get('portalPermission') !== 'yes') {
throw new Forbidden('Not permitted to post to portal users.');
}
}
if (!$targetType) {
return;
}
if ($assignmentPermission === 'team') {
if ($targetType === 'all') {
throw new Forbidden('Not permitted to post to all.');
}
$userTeamIdList = $this->user->getTeamIdList();
$userTeamIdList = $this->getUser()->getTeamIdList();
$userIdList = $entity->getLinkMultipleIdList('users');
$portalIdList = $entity->getLinkMultipleIdList('portals');
$teamIdList = $entity->getLinkMultipleIdList('teams');
if ($targetType === 'teams') {
if (empty($userTeamIdList)) {
throw new Forbidden('Not permitted to post to foreign teams.');
}
foreach ($teamIdList as $teamId) {
if (!in_array($teamId, $userTeamIdList)) {
throw new Forbidden('Not permitted to post to foreign teams.');
}
}
} else if ($targetType === 'users') {
if (empty($userTeamIdList)) {
throw new Forbidden('Not permitted to post to users from foreign teams.');
}
$targetUserList = null;
foreach ($userIdList as $userId) {
if ($userId === $this->getUser()->id) {
continue;
}
if (!$this->getEntityManager()->getRepository('User')->checkBelongsToAnyOfTeams($userId, $userTeamIdList)) {
throw new Forbidden('Not permitted to post to users from foreign teams.');
}
}
}
if ($targetType === NoteEntity::TARGET_USERS) {
$targetUserList = $this->entityManager
->getRDBRepository('User')
->select(['id', 'type'])
->where([
'id' => $userIdList,
])
->find();
}
$hasPortalTargetUser = false;
$allTargetUsersArePortal = true;
foreach ($targetUserList as $user) {
if (!$user->isPortal()) {
$allTargetUsersArePortal = false;
}
if ($user->isPortal()) {
$hasPortalTargetUser = true;
}
}
$assignmentPermission = $this->acl->get('assignment');
if ($assignmentPermission === AclTable::LEVEL_NO) {
if (
$targetType !== NoteEntity::TARGET_SELF &&
$targetType !== NoteEntity::TARGET_PORTALS &&
!(
$targetType === NoteEntity::TARGET_USERS &&
count($userIdList) === 1 &&
$userIdList[0] === $this->user->getId()
) &&
!(
$targetType === NoteEntity::TARGET_USERS && $allTargetUsersArePortal
)
) {
throw new Forbidden('Not permitted to post to anybody except self.');
}
}
if ($targetType === NoteEntity::TARGET_TEAMS) {
if (empty($teamIdList)) {
throw new BadRequest("No team IDS.");
}
}
if ($targetType === NoteEntity::TARGET_USERS) {
if (empty($userIdList)) {
throw new BadRequest("No user IDs.");
}
}
if ($targetType === NoteEntity::TARGET_PORTALS) {
if (empty($portalIdList)) {
throw new BadRequest("No portal IDs.");
}
if ($this->acl->get('portal') !== AclTable::LEVEL_YES) {
throw new Forbidden('Not permitted to post to portal users.');
}
}
if ($targetType === NoteEntity::TARGET_USERS && $this->acl->get('portal') !== AclTable::LEVEL_YES) {
if ($hasPortalTargetUser) {
throw new Forbidden('Not permitted to post to portal users.');
}
}
if ($assignmentPermission === AclTable::LEVEL_TEAM) {
if ($targetType === NoteEntity::TARGET_ALL) {
throw new Forbidden('Not permitted to post to all.');
}
}
if (
$assignmentPermission === AclTable::LEVEL_TEAM &&
$targetType === NoteEntity::TARGET_TEAMS
) {
if (empty($userTeamIdList)) {
throw new Forbidden('Not permitted to post to foreign teams.');
}
foreach ($teamIdList as $teamId) {
if (!in_array($teamId, $userTeamIdList)) {
throw new Forbidden("Not permitted to post to foreign teams.");
}
}
}
if (
$assignmentPermission === AclTable::LEVEL_TEAM &&
$targetType === NoteEntity::TARGET_USERS
) {
if (empty($userTeamIdList)) {
throw new Forbidden('Not permitted to post to users from foreign teams.');
}
foreach ($targetUserList as $user) {
if ($user->getId() === $this->user->getId()) {
continue;
}
if ($user->isPortal()) {
continue;
}
$inTeam = $this->entityManager
->getRepository('User')
->checkBelongsToAnyOfTeams($user->getId(), $userTeamIdList);
if (!$inTeam) {
throw new Forbidden('Not permitted to post to users from foreign teams.');
}
}
}
return true;
}
public function link(string $id, string $link, string $foreignId) : void
@@ -229,29 +358,39 @@ class Note extends Record
parent::unlink($id, $link, $foreignId);
}
public function processNoteAclJob($data)
public function processNoteAclJob(StdClass $data): void
{
$targetType = $data->targetType;
$targetId = $data->targetId;
if ($targetType && $targetId && $this->getEntityManager()->hasRepository($targetType)) {
$entity = $this->getEntityManager()->getEntity($targetType, $targetId);
if ($targetType && $targetId && $this->entityManager->hasRepository($targetType)) {
$entity = $this->entityManager->getEntity($targetType, $targetId);
if ($entity) {
$this->processNoteAcl($entity, true);
}
}
}
public function processNoteAcl(Entity $entity, $forceProcessNoteNotifications = false)
public function processNoteAcl(Entity $entity, bool $forceProcessNoteNotifications = false): void
{
$entityType = $entity->getEntityType();
if (in_array($entityType, ['Note', 'User', 'Team', 'Role', 'Portal', 'PortalRole'])) return;
if (in_array($entityType, ['Note', 'User', 'Team', 'Role', 'Portal', 'PortalRole'])) {
return;
}
if (!$this->getMetadata()->get(['scopes', $entityType, 'acl'])) return;
if (!$this->getMetadata()->get(['scopes', $entityType, 'object'])) return;
if (!$this->getMetadata()->get(['scopes', $entityType, 'acl'])) {
return;
}
$ownerUserIdAttribute = $this->getAclManager()->getImplementation($entityType)->getOwnerUserIdAttribute($entity);
if (!$this->getMetadata()->get(['scopes', $entityType, 'object'])) {
return;
}
$ownerUserIdAttribute = $this->getAclManager()
->getImplementation($entityType)
->getOwnerUserIdAttribute($entity);
$usersAttributeIsChanged = false;
$teamsAttributeIsChanged = false;
@@ -264,12 +403,16 @@ class Note extends Record
if ($usersAttributeIsChanged || $forceProcessNoteNotifications) {
if ($entity->getAttributeParam($ownerUserIdAttribute, 'isLinkMultipleIdList')) {
$userLink = $entity->getAttributeParam($ownerUserIdAttribute, 'relation');
$userIdList = $entity->getLinkMultipleIdList($userLink);
} else {
}
else {
$userId = $entity->get($ownerUserIdAttribute);
if ($userId) {
$userIdList = [$userId];
} else {
}
else {
$userIdList = [];
}
}
@@ -280,62 +423,75 @@ class Note extends Record
if ($entity->isAttributeChanged('teamsIds')) {
$teamsAttributeIsChanged = true;
}
if ($teamsAttributeIsChanged || $forceProcessNoteNotifications) {
$teamIdList = $entity->getLinkMultipleIdList('teams');
}
}
if ($usersAttributeIsChanged || $teamsAttributeIsChanged || $forceProcessNoteNotifications) {
$noteList = $this->getEntityManager()->getRepository('Note')->where([
'OR' => [
[
'relatedId' => $entity->id,
'relatedType' => $entityType
],
[
'parentId' => $entity->id,
'parentType' => $entityType,
'superParentId!=' => null,
'relatedId' => null
$noteList = $this->entityManager
->getRepository('Note')
->where([
'OR' => [
[
'relatedId' => $entity->id,
'relatedType' => $entityType
],
[
'parentId' => $entity->id,
'parentType' => $entityType,
'superParentId!=' => null,
'relatedId' => null,
]
]
]
])->select([
'id',
'parentType',
'parentId',
'superParentType',
'superParentId',
'isInternal',
'relatedType',
'relatedId',
'createdAt'
])->find();
])
->select([
'id',
'parentType',
'parentId',
'superParentType',
'superParentId',
'isInternal',
'relatedType',
'relatedId',
'createdAt',
])
->find();
$noteOptions = [];
if (!empty($forceProcessNoteNotifications)) {
$noteOptions['forceProcessNotifications'] = true;
}
$period = '-' . $this->getConfig()->get('noteNotificationPeriod', $this->noteNotificationPeriod);
$threshold = new \DateTime();
$threshold = new DateTime();
$threshold->modify($period);
foreach ($noteList as $note) {
if (!$entity->isNew()) {
try {
$createdAtDt = new \DateTime($note->get('createdAt'));
$createdAtDt = new DateTime($note->get('createdAt'));
if ($createdAtDt->getTimestamp() < $threshold->getTimestamp()) {
continue;
}
} catch (\Exception $e) {};
}
catch (Exception $e) {};
}
if ($teamsAttributeIsChanged || $forceProcessNoteNotifications) {
$note->set('teamsIds', $teamIdList);
}
if ($usersAttributeIsChanged || $forceProcessNoteNotifications) {
$note->set('usersIds', $userIdList);
}
$this->getEntityManager()->saveEntity($note, $noteOptions);
$this->entityManager->saveEntity($note, $noteOptions);
}
}
}
+21 -1
View File
@@ -27,7 +27,7 @@
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace tests\unit\Espo\Core\Api;
namespace tests\unit\Espo\Core\Acl;
use Espo\Core\{
Acl\ScopeData,
@@ -105,6 +105,8 @@ class ScopeDataTest extends \PHPUnit\Framework\TestCase
$this->assertEquals($raw, $data->getRaw());
$this->assertNotSame($raw, $data->getRaw());
$this->assertTrue($data->hasNotNo());
}
public function testRecordEmpty()
@@ -114,5 +116,23 @@ class ScopeDataTest extends \PHPUnit\Framework\TestCase
$data = ScopeData::fromRaw($raw);
$this->assertEquals(Table::LEVEL_NO, $data->getDelete());
$this->assertFalse($data->hasNotNo());
}
public function testRecordOnlyNo()
{
$raw = (object) [
Table::ACTION_CREATE => Table::LEVEL_NO,
Table::ACTION_READ => Table::LEVEL_NO,
Table::ACTION_EDIT => Table::LEVEL_NO,
Table::ACTION_DELETE => Table::LEVEL_NO,
];
$data = ScopeData::fromRaw($raw);
$this->assertEquals(Table::LEVEL_NO, $data->getDelete());
$this->assertFalse($data->hasNotNo());
}
}