From f69eed63d7ae7a9b171e1588f4b882d74c8b5c0d Mon Sep 17 00:00:00 2001 From: Yuri Kuznetsov Date: Tue, 9 Jul 2024 13:11:56 +0300 Subject: [PATCH] acl resolve foreign to boolean --- .../Espo/Core/Acl/Table/DefaultTable.php | 40 ++----- .../Espo/Core/Acl/Table/ScopeDataResolver.php | 66 +++++++++++ schema/metadata/app/acl.json | 2 +- .../Espo/Core/Acl/ScopeDataResolverTest.php | 106 ++++++++++++++++++ tests/unit/Espo/Core/Acl/ScopeDataTest.php | 6 +- 5 files changed, 187 insertions(+), 33 deletions(-) create mode 100644 application/Espo/Core/Acl/Table/ScopeDataResolver.php create mode 100644 tests/unit/Espo/Core/Acl/ScopeDataResolverTest.php diff --git a/application/Espo/Core/Acl/Table/DefaultTable.php b/application/Espo/Core/Acl/Table/DefaultTable.php index 66d3dbedfc..febee30792 100644 --- a/application/Espo/Core/Acl/Table/DefaultTable.php +++ b/application/Espo/Core/Acl/Table/DefaultTable.php @@ -52,9 +52,7 @@ class DefaultTable implements Table protected string $type = 'acl'; protected string $defaultAclType = 'recordAllTeamOwnNo'; - /** - * @var string[] - */ + /** @var string[] */ private $actionList = [ self::ACTION_READ, self::ACTION_STREAM, @@ -63,16 +61,12 @@ class DefaultTable implements Table self::ACTION_CREATE, ]; - /** - * @var string[] - */ + /** @var string[] */ private $booleanActionList = [ self::ACTION_CREATE, ]; - /** - * @var string[] - */ + /** @var string[] */ protected $levelList = [ self::LEVEL_YES, self::LEVEL_ALL, @@ -81,30 +75,23 @@ class DefaultTable implements Table self::LEVEL_NO, ]; - /** - * @var string[] - */ + /** @var string[] */ private $fieldActionList = [ self::ACTION_READ, self::ACTION_EDIT, ]; - /** - * @var string[] - */ + /** @var string[] */ protected $fieldLevelList = [ self::LEVEL_YES, self::LEVEL_NO, ]; private stdClass $data; - private string $cacheKey; - - /** - * @var string[] - */ + /** @var string[] */ private $valuePermissionList = []; + private ScopeDataResolver $scopeDataResolver; public function __construct( private RoleListProvider $roleListProvider, @@ -112,7 +99,7 @@ class DefaultTable implements Table protected User $user, Config $config, protected Metadata $metadata, - DataCache $dataCache + DataCache $dataCache, ) { $this->data = (object) [ @@ -135,14 +122,15 @@ class DefaultTable implements Table $cachedData = $dataCache->get($this->cacheKey); $this->data = $cachedData; - } - else { + } else { $this->load(); if ($config->get('useCache')) { $dataCache->store($this->cacheKey, $this->data); } } + + $this->scopeDataResolver = new ScopeDataResolver($this); } /** @@ -156,11 +144,7 @@ class DefaultTable implements Table $data = $this->data->scopes->$scope; - if (is_string($data)) { - return $this->getScopeData($data); - } - - return ScopeData::fromRaw($data); + return $this->scopeDataResolver->resolve($data); } /** diff --git a/application/Espo/Core/Acl/Table/ScopeDataResolver.php b/application/Espo/Core/Acl/Table/ScopeDataResolver.php new file mode 100644 index 0000000000..f3e63d6890 --- /dev/null +++ b/application/Espo/Core/Acl/Table/ScopeDataResolver.php @@ -0,0 +1,66 @@ +. + * + * 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\Acl\Table; + +use Espo\Core\Acl\ScopeData; +use Espo\Core\Acl\Table; + +/** + * @internal + */ +class ScopeDataResolver +{ + public function __construct( + private Table $table, + ) {} + + public function resolve(mixed $data): ScopeData + { + if (!is_string($data)) { + return ScopeData::fromRaw($data); + } + + $foreignScope = $data; + $isBoolean = false; + + if (str_starts_with($data, 'boolean:')) { + [, $foreignScope] = explode(':', $data, 2); + $isBoolean = true; + } + + $scopeData = $this->table->getScopeData($foreignScope); + + if ($isBoolean && !$scopeData->isBoolean()) { + return ScopeData::fromRaw($scopeData->hasNotNo()); + } + + return $scopeData; + } +} diff --git a/schema/metadata/app/acl.json b/schema/metadata/app/acl.json index 840dc960b6..c67a3a64db 100644 --- a/schema/metadata/app/acl.json +++ b/schema/metadata/app/acl.json @@ -96,7 +96,7 @@ "type": "object", "description": "A scope level.", "additionalProperties": { - "description": "A scope. True enables access to scopes that don't have actions. False fully restricts access to a scope. A string value makes the framework to use roles from another scope and apply it to our scope. It can be useful to have roles only for one parent scope, when child scopes uses roles of the parent scope.", + "description": "A scope. True enables access to scopes that don't have actions. False fully restricts access to a scope. A string value makes the framework to use roles from another scope and apply it to our scope. It can be useful to have roles only for one parent scope, when child scopes uses roles of the parent scope. `boolean:{scope}` format will convert a foreign scope data to the boolean type.", "anyOf": [ { "type": "object", diff --git a/tests/unit/Espo/Core/Acl/ScopeDataResolverTest.php b/tests/unit/Espo/Core/Acl/ScopeDataResolverTest.php new file mode 100644 index 0000000000..a5d77e5df0 --- /dev/null +++ b/tests/unit/Espo/Core/Acl/ScopeDataResolverTest.php @@ -0,0 +1,106 @@ +. + * + * 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 tests\unit\Espo\Core\Acl; + +use Espo\Core\Acl\ScopeData; +use Espo\Core\Acl\Table; +use PHPUnit\Framework\TestCase; + +class ScopeDataResolverTest extends TestCase +{ + public function testResolve1(): void + { + $table = $this->createMock(Table::class); + $resolver = new Table\ScopeDataResolver($table); + + $table->expects($this->once()) + ->method('getScopeData') + ->with('Test') + ->willReturn(ScopeData::fromRaw(true)); + + $this->assertTrue($resolver->resolve('Test')->isTrue()); + } + + public function testResolve2(): void + { + $table = $this->createMock(Table::class); + $resolver = new Table\ScopeDataResolver($table); + + $table->expects($this->once()) + ->method('getScopeData') + ->willReturnMap([ + ['Test', ScopeData::fromRaw(false)] + ]); + + $this->assertTrue($resolver->resolve('Test')->isFalse()); + } + + public function testResolve3(): void + { + $table = $this->createMock(Table::class); + $resolver = new Table\ScopeDataResolver($table); + + $table->expects($this->once()) + ->method('getScopeData') + ->with('Test') + ->willReturn(ScopeData::fromRaw((object) ['create' => 'yes', 'edit' => 'no'])); + + $result = $resolver->resolve('Test'); + + $this->assertEquals('yes', $result->getCreate()); + $this->assertEquals('no', $result->getEdit()); + } + + public function testResolve4(): void + { + $table = $this->createMock(Table::class); + $resolver = new Table\ScopeDataResolver($table); + + $table->expects($this->once()) + ->method('getScopeData') + ->with('Test') + ->willReturn(ScopeData::fromRaw((object) ['create' => 'yes', 'edit' => 'no'])); + + $this->assertTrue($resolver->resolve('boolean:Test')->isTrue()); + } + + public function testResolve5(): void + { + $table = $this->createMock(Table::class); + $resolver = new Table\ScopeDataResolver($table); + + $table->expects($this->once()) + ->method('getScopeData') + ->with('Test') + ->willReturn(ScopeData::fromRaw((object) ['create' => 'no', 'edit' => 'no'])); + + $this->assertTrue($resolver->resolve('boolean:Test')->isFalse()); + } +} diff --git a/tests/unit/Espo/Core/Acl/ScopeDataTest.php b/tests/unit/Espo/Core/Acl/ScopeDataTest.php index caef857b94..023e20621e 100644 --- a/tests/unit/Espo/Core/Acl/ScopeDataTest.php +++ b/tests/unit/Espo/Core/Acl/ScopeDataTest.php @@ -29,10 +29,8 @@ namespace tests\unit\Espo\Core\Acl; -use Espo\Core\{ - Acl\ScopeData, - Acl\Table, -}; +use Espo\Core\Acl\ScopeData; +use Espo\Core\Acl\Table; use InvalidArgumentException; use RuntimeException;