From 4591d8f6342bc53529a63473bc0faafd257efb11 Mon Sep 17 00:00:00 2001 From: Yuri Kuznetsov Date: Thu, 2 May 2024 18:03:09 +0300 Subject: [PATCH] formula acl functions --- .../ExtGroup/AclGroup/CheckEntityType.php | 92 ++++++++++++++++++ .../ExtGroup/AclGroup/CheckScopeType.php | 80 ++++++++++++++++ .../ExtGroup/AclGroup/GetLevelType.php | 96 +++++++++++++++++++ .../AclGroup/GetPermissionLevelType.php | 76 +++++++++++++++ .../Espo/Resources/metadata/app/formula.json | 27 +++++- .../Espo/Core/Formula/FormulaTest.php | 57 +++++++++++ 6 files changed, 426 insertions(+), 2 deletions(-) create mode 100644 application/Espo/Core/Formula/Functions/ExtGroup/AclGroup/CheckEntityType.php create mode 100644 application/Espo/Core/Formula/Functions/ExtGroup/AclGroup/CheckScopeType.php create mode 100644 application/Espo/Core/Formula/Functions/ExtGroup/AclGroup/GetLevelType.php create mode 100644 application/Espo/Core/Formula/Functions/ExtGroup/AclGroup/GetPermissionLevelType.php diff --git a/application/Espo/Core/Formula/Functions/ExtGroup/AclGroup/CheckEntityType.php b/application/Espo/Core/Formula/Functions/ExtGroup/AclGroup/CheckEntityType.php new file mode 100644 index 0000000000..e1d9ea58b4 --- /dev/null +++ b/application/Espo/Core/Formula/Functions/ExtGroup/AclGroup/CheckEntityType.php @@ -0,0 +1,92 @@ +. + * + * The interactive user interfaces in modified source and object code versions + * of this program must display Appropriate Legal Notices, as required under + * Section 5 of the GNU Affero General Public License version 3. + * + * In accordance with Section 7(b) of the GNU Affero General Public License version 3, + * these Appropriate Legal Notices must retain the display of the "EspoCRM" word. + ************************************************************************/ + +namespace Espo\Core\Formula\Functions\ExtGroup\AclGroup; + +use Espo\Core\Acl\Table; +use Espo\Core\Formula\EvaluatedArgumentList; +use Espo\Core\Formula\Exceptions\BadArgumentType; +use Espo\Core\Formula\Exceptions\TooFewArguments; +use Espo\Core\Formula\Func; +use Espo\Core\Utils\Acl\UserAclManagerProvider; +use Espo\Entities\User; +use Espo\ORM\EntityManager; + +/** + * @noinspection PhpUnused + */ +class CheckEntityType implements Func +{ + public function __construct( + private UserAclManagerProvider $userAclManagerProvider, + private EntityManager $entityManager, + ) {} + + public function process(EvaluatedArgumentList $arguments): bool + { + if (count($arguments) < 3) { + throw TooFewArguments::create(3); + } + + $userId = $arguments[0]; + $entityType = $arguments[1]; + $id = $arguments[2]; + $action = $arguments[3] ?? Table::ACTION_READ; + + if (!is_string($userId)) { + throw BadArgumentType::create(1, 'string'); + } + + if (!is_string($entityType)) { + throw BadArgumentType::create(2, 'string'); + } + + if (!is_string($id)) { + throw BadArgumentType::create(3, 'string'); + } + + if (!is_string($action)) { + throw BadArgumentType::create(4, 'string'); + } + + $user = $this->entityManager->getRDBRepositoryByClass(User::class)->getById($userId); + + if (!$user) { + return false; + } + + $entity = $this->entityManager->getEntityById($entityType, $id); + + if (!$entity) { + return false; + } + + return $this->userAclManagerProvider->get($user)->checkEntity($user, $entity, $action); + } +} diff --git a/application/Espo/Core/Formula/Functions/ExtGroup/AclGroup/CheckScopeType.php b/application/Espo/Core/Formula/Functions/ExtGroup/AclGroup/CheckScopeType.php new file mode 100644 index 0000000000..d3a012c640 --- /dev/null +++ b/application/Espo/Core/Formula/Functions/ExtGroup/AclGroup/CheckScopeType.php @@ -0,0 +1,80 @@ +. + * + * The interactive user interfaces in modified source and object code versions + * of this program must display Appropriate Legal Notices, as required under + * Section 5 of the GNU Affero General Public License version 3. + * + * In accordance with Section 7(b) of the GNU Affero General Public License version 3, + * these Appropriate Legal Notices must retain the display of the "EspoCRM" word. + ************************************************************************/ + +namespace Espo\Core\Formula\Functions\ExtGroup\AclGroup; + +use Espo\Core\Formula\EvaluatedArgumentList; +use Espo\Core\Formula\Exceptions\BadArgumentType; +use Espo\Core\Formula\Exceptions\TooFewArguments; +use Espo\Core\Formula\Func; +use Espo\Core\Utils\Acl\UserAclManagerProvider; +use Espo\Entities\User; +use Espo\ORM\EntityManager; + +/** + * @noinspection PhpUnused + */ +class CheckScopeType implements Func +{ + public function __construct( + private UserAclManagerProvider $userAclManagerProvider, + private EntityManager $entityManager, + ) {} + + public function process(EvaluatedArgumentList $arguments): bool + { + if (count($arguments) < 2) { + throw TooFewArguments::create(2); + } + + $userId = $arguments[0]; + $scope = $arguments[1]; + $action = $arguments[2] ?? null; + + if (!is_string($userId)) { + throw BadArgumentType::create(1, 'string'); + } + + if (!is_string($scope)) { + throw BadArgumentType::create(2, 'string'); + } + + if ($action !== null && !is_string($action)) { + throw BadArgumentType::create(3, 'string'); + } + + $user = $this->entityManager->getRDBRepositoryByClass(User::class)->getById($userId); + + if (!$user) { + return false; + } + + return $this->userAclManagerProvider->get($user)->checkScope($user, $scope, $action); + } +} diff --git a/application/Espo/Core/Formula/Functions/ExtGroup/AclGroup/GetLevelType.php b/application/Espo/Core/Formula/Functions/ExtGroup/AclGroup/GetLevelType.php new file mode 100644 index 0000000000..dfebfc800b --- /dev/null +++ b/application/Espo/Core/Formula/Functions/ExtGroup/AclGroup/GetLevelType.php @@ -0,0 +1,96 @@ +. + * + * The interactive user interfaces in modified source and object code versions + * of this program must display Appropriate Legal Notices, as required under + * Section 5 of the GNU Affero General Public License version 3. + * + * In accordance with Section 7(b) of the GNU Affero General Public License version 3, + * these Appropriate Legal Notices must retain the display of the "EspoCRM" word. + ************************************************************************/ + +namespace Espo\Core\Formula\Functions\ExtGroup\AclGroup; + +use Espo\Core\Acl\Table; +use Espo\Core\Formula\EvaluatedArgumentList; +use Espo\Core\Formula\Exceptions\BadArgumentType; +use Espo\Core\Formula\Exceptions\BadArgumentValue; +use Espo\Core\Formula\Exceptions\TooFewArguments; +use Espo\Core\Formula\Func; +use Espo\Core\Utils\Acl\UserAclManagerProvider; +use Espo\Entities\User; +use Espo\ORM\EntityManager; + +/** + * @noinspection PhpUnused + */ +class GetLevelType implements Func +{ + public function __construct( + private UserAclManagerProvider $userAclManagerProvider, + private EntityManager $entityManager, + ) {} + + public function process(EvaluatedArgumentList $arguments): string + { + if (count($arguments) < 3) { + throw TooFewArguments::create(3); + } + + $userId = $arguments[0]; + $scope = $arguments[1]; + $action = $arguments[2]; + + if (!is_string($userId)) { + throw BadArgumentType::create(1, 'string'); + } + + if (!is_string($scope)) { + throw BadArgumentType::create(2, 'string'); + } + + if (!is_string($action)) { + throw BadArgumentType::create(3, 'string'); + } + + if ( + !in_array($action, [ + Table::ACTION_READ, + Table::ACTION_CREATE, + Table::ACTION_EDIT, + Table::ACTION_STREAM, + Table::ACTION_DELETE, + ]) + ) { + throw BadArgumentValue::create(3); + } + + /** @var Table::ACTION_* $action */ + + $user = $this->entityManager->getRDBRepositoryByClass(User::class)->getById($userId); + + if (!$user) { + return Table::LEVEL_NO; + } + + return $this->userAclManagerProvider->get($user)->getLevel($user, $scope, $action); + } +} diff --git a/application/Espo/Core/Formula/Functions/ExtGroup/AclGroup/GetPermissionLevelType.php b/application/Espo/Core/Formula/Functions/ExtGroup/AclGroup/GetPermissionLevelType.php new file mode 100644 index 0000000000..1195b13514 --- /dev/null +++ b/application/Espo/Core/Formula/Functions/ExtGroup/AclGroup/GetPermissionLevelType.php @@ -0,0 +1,76 @@ +. + * + * The interactive user interfaces in modified source and object code versions + * of this program must display Appropriate Legal Notices, as required under + * Section 5 of the GNU Affero General Public License version 3. + * + * In accordance with Section 7(b) of the GNU Affero General Public License version 3, + * these Appropriate Legal Notices must retain the display of the "EspoCRM" word. + ************************************************************************/ + +namespace Espo\Core\Formula\Functions\ExtGroup\AclGroup; + +use Espo\Core\Acl\Table; +use Espo\Core\Formula\EvaluatedArgumentList; +use Espo\Core\Formula\Exceptions\BadArgumentType; +use Espo\Core\Formula\Exceptions\TooFewArguments; +use Espo\Core\Formula\Func; +use Espo\Core\Utils\Acl\UserAclManagerProvider; +use Espo\Entities\User; +use Espo\ORM\EntityManager; + +/** + * @noinspection PhpUnused + */ +class GetPermissionLevelType implements Func +{ + public function __construct( + private UserAclManagerProvider $userAclManagerProvider, + private EntityManager $entityManager, + ) {} + + public function process(EvaluatedArgumentList $arguments): string + { + if (count($arguments) < 2) { + throw TooFewArguments::create(2); + } + + $userId = $arguments[0]; + $permission = $arguments[1]; + + if (!is_string($userId)) { + throw BadArgumentType::create(1, 'string'); + } + + if (!is_string($permission)) { + throw BadArgumentType::create(2, 'string'); + } + + $user = $this->entityManager->getRDBRepositoryByClass(User::class)->getById($userId); + + if (!$user) { + return Table::LEVEL_NO; + } + + return $this->userAclManagerProvider->get($user)->getPermissionLevel($user, $permission); + } +} diff --git a/application/Espo/Resources/metadata/app/formula.json b/application/Espo/Resources/metadata/app/formula.json index bbd8c782ee..74eac69904 100644 --- a/application/Espo/Resources/metadata/app/formula.json +++ b/application/Espo/Resources/metadata/app/formula.json @@ -548,13 +548,36 @@ "name": "ext\\currency\\convert", "insertText": "ext\\currency\\convert(AMOUNT, FROM_CODE)", "returnType": "string" + }, + { + "name": "ext\\acl\\checkEntity", + "insertText": "ext\\acl\\checkEntity(USER_ID, ENTITY_TYPE, ID, ACTION)", + "returnType": "bool" + }, + { + "name": "ext\\acl\\checkScope", + "insertText": "ext\\acl\\checkScope(USER_ID, SCOPE, ACTION)", + "returnType": "bool" + }, + { + "name": "ext\\acl\\getLevel", + "insertText": "ext\\acl\\getLevel(USER_ID, SCOPE, ACTION)", + "returnType": "string" + }, + { + "name": "ext\\acl\\getPermissionLevel", + "insertText": "ext\\acl\\getPermissionLevel(USER_ID, PERMISSION)", + "returnType": "string" } ], "functionClassNameMap": { "log\\info": "Espo\\Core\\Formula\\Functions\\LogGroup\\InfoType", "log\\notice": "Espo\\Core\\Formula\\Functions\\LogGroup\\NoticeType", "log\\warning": "Espo\\Core\\Formula\\Functions\\LogGroup\\WarningType", - "log\\error": "Espo\\Core\\Formula\\Functions\\LogGroup\\ErrorType" - + "log\\error": "Espo\\Core\\Formula\\Functions\\LogGroup\\ErrorType", + "ext\\acl\\checkEntity": "Espo\\Core\\Formula\\Functions\\ExtGroup\\AclGroup\\CheckEntityType", + "ext\\acl\\checkScope": "Espo\\Core\\Formula\\Functions\\ExtGroup\\AclGroup\\CheckScopeType", + "ext\\acl\\getLevel": "Espo\\Core\\Formula\\Functions\\ExtGroup\\AclGroup\\GetLevelType", + "ext\\acl\\getPermissionLevel": "Espo\\Core\\Formula\\Functions\\ExtGroup\\AclGroup\\GetPermissionLevelType" } } diff --git a/tests/integration/Espo/Core/Formula/FormulaTest.php b/tests/integration/Espo/Core/Formula/FormulaTest.php index 4620204117..9737074e81 100644 --- a/tests/integration/Espo/Core/Formula/FormulaTest.php +++ b/tests/integration/Espo/Core/Formula/FormulaTest.php @@ -29,6 +29,7 @@ namespace tests\integration\Espo\Core\Formula; +use Espo\Core\Acl\Table; use Espo\Core\Field\DateTimeOptional; use Espo\Core\Formula\Manager; use Espo\Entities\User; @@ -903,4 +904,60 @@ class FormulaTest extends BaseTestCase /** @noinspection PhpUnhandledExceptionInspection */ $this->assertTrue($fm->run($script, $account)); } + + public function testAcl(): void + { + $em = $this->getEntityManager(); + $fm = $this->getContainer()->getByClass(Manager::class); + + $user = $this->createUser('test', [ + 'massUpdatePermission' => Table::LEVEL_YES, + 'data' => [ + Account::ENTITY_TYPE => [ + 'create' => Table::LEVEL_NO, + 'read' => Table::LEVEL_ALL, + 'edit' => Table::LEVEL_OWN, + 'delete' => Table::LEVEL_NO, + ], + ], + ]); + + $account1 = $em->createEntity(Account::ENTITY_TYPE, [ + 'assignedUserId' => $user->getId(), + ]); + + $account2 = $em->createEntity(Account::ENTITY_TYPE); + + $script = "ext\\acl\\checkEntity('{$user->getId()}', 'Account', '{$account2->getId()}')"; + /** @noinspection PhpUnhandledExceptionInspection */ + $this->assertTrue($fm->run($script)); + + $script = "ext\\acl\\checkEntity('{$user->getId()}', 'Account', '{$account1->getId()}', 'edit')"; + /** @noinspection PhpUnhandledExceptionInspection */ + $this->assertTrue($fm->run($script)); + + $script = "ext\\acl\\checkEntity('{$user->getId()}', 'Account', '{$account2->getId()}', 'edit')"; + /** @noinspection PhpUnhandledExceptionInspection */ + $this->assertFalse($fm->run($script)); + + $script = "ext\\acl\\checkScope('{$user->getId()}', 'Account')"; + /** @noinspection PhpUnhandledExceptionInspection */ + $this->assertTrue($fm->run($script)); + + $script = "ext\\acl\\checkScope('{$user->getId()}', 'Account', 'delete')"; + /** @noinspection PhpUnhandledExceptionInspection */ + $this->assertFalse($fm->run($script)); + + $script = "ext\\acl\\getLevel('{$user->getId()}', 'Account', 'delete')"; + /** @noinspection PhpUnhandledExceptionInspection */ + $this->assertEquals(Table::LEVEL_NO, $fm->run($script)); + + $script = "ext\\acl\\getLevel('{$user->getId()}', 'Account', 'read')"; + /** @noinspection PhpUnhandledExceptionInspection */ + $this->assertEquals(Table::LEVEL_ALL, $fm->run($script)); + + $script = "ext\\acl\\getPermissionLevel('{$user->getId()}', 'massUpdate')"; + /** @noinspection PhpUnhandledExceptionInspection */ + $this->assertEquals(Table::LEVEL_YES, $fm->run($script)); + } }