diff --git a/.github/workflows/bc.yml b/.github/workflows/bc.yml index a3e06952..d13f960f 100644 --- a/.github/workflows/bc.yml +++ b/.github/workflows/bc.yml @@ -12,4 +12,4 @@ jobs: os: >- ['ubuntu-latest'] php: >- - ['8.1'] + ['8.3'] diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 27aedcb3..e98a8d8a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -36,11 +36,15 @@ jobs: php: - 8.1 - 8.2 + - 8.3 exclude: - os: windows-latest php: 8.2 + - os: windows-latest + php: 8.3 + steps: - name: Checkout. uses: actions/checkout@v3 diff --git a/.github/workflows/composer-require-checker.yml b/.github/workflows/composer-require-checker.yml index 8b8b1b12..825dbd93 100644 --- a/.github/workflows/composer-require-checker.yml +++ b/.github/workflows/composer-require-checker.yml @@ -31,5 +31,5 @@ jobs: os: >- ['ubuntu-latest'] php: >- - ['8.1', '8.2'] + ['8.3'] extensions: uopz diff --git a/.github/workflows/mutation.yml b/.github/workflows/mutation.yml index 57dbc03d..a7a2b211 100644 --- a/.github/workflows/mutation.yml +++ b/.github/workflows/mutation.yml @@ -26,7 +26,7 @@ jobs: os: >- ['ubuntu-latest'] php: >- - ['8.1'] + ['8.3'] extensions: uopz min-covered-msi: 100 secrets: diff --git a/.github/workflows/rector.yml b/.github/workflows/rector.yml index 0202799d..6a2162d1 100644 --- a/.github/workflows/rector.yml +++ b/.github/workflows/rector.yml @@ -18,5 +18,5 @@ jobs: os: >- ['ubuntu-latest'] php: >- - ['8.1'] + ['8.3'] extensions: uopz diff --git a/.github/workflows/static.yml b/.github/workflows/static.yml index ae5a02cc..61c7bd6a 100644 --- a/.github/workflows/static.yml +++ b/.github/workflows/static.yml @@ -28,5 +28,5 @@ jobs: os: >- ['ubuntu-latest'] php: >- - ['8.1', '8.2'] + ['8.1', '8.2', '8.3'] extensions: uopz diff --git a/src/ItemsStorageInterface.php b/src/ItemsStorageInterface.php index 9be7d81c..9f595b74 100644 --- a/src/ItemsStorageInterface.php +++ b/src/ItemsStorageInterface.php @@ -8,7 +8,7 @@ * A storage for RBAC roles and permissions used in {@see Manager}. * * @psalm-type ItemsIndexedByName = array - * @psalm-type AccessTree = non-empty-array * }> diff --git a/src/ManagerInterface.php b/src/ManagerInterface.php index 8bedadcd..58b8c4fd 100644 --- a/src/ManagerInterface.php +++ b/src/ManagerInterface.php @@ -199,8 +199,6 @@ public function updateRole(string $name, Role $role): self; public function removeRole(string $name): self; /** - * @param Permission $permission - * * @throws ItemAlreadyExistsException */ public function addPermission(Permission $permission): self; diff --git a/src/SimpleItemsStorage.php b/src/SimpleItemsStorage.php index 53d62496..43e00f51 100644 --- a/src/SimpleItemsStorage.php +++ b/src/SimpleItemsStorage.php @@ -4,8 +4,6 @@ namespace Yiisoft\Rbac; -use RuntimeException; - /** * @psalm-type RawItem = array{ * type: Item::TYPE_*, @@ -110,8 +108,8 @@ public function getParents(string $name): array public function getAccessTree(string $name): array { - if (!$this->exists($name)) { - throw new RuntimeException('Base item not found.'); + if (!array_key_exists($name, $this->items)) { + return []; } $result = [$name => ['item' => $this->items[$name], 'children' => []]]; diff --git a/tests/Common/ItemsStorageTestTrait.php b/tests/Common/ItemsStorageTestTrait.php index 07995a33..ae16a790 100644 --- a/tests/Common/ItemsStorageTestTrait.php +++ b/tests/Common/ItemsStorageTestTrait.php @@ -28,12 +28,16 @@ protected function setUp(): void ClockMock::freeze(new DateTime('2023-05-10 08:24:39')); } + if ($this->name() === 'testGetAccessTree') { + ClockMock::freeze(new DateTime('2023-12-24 17:51:18')); + } + $this->populateItemsStorage(); } protected function tearDown(): void { - if ($this->name() === 'testAddWithCurrentTimestamps') { + if (in_array($this->name(), ['testAddWithCurrentTimestamps', 'testGetAccessTree'], strict: true)) { ClockMock::reset(); } @@ -390,6 +394,94 @@ public function testGetParents(string $childName, array $expectedParents): void } } + public static function dataGetAccessTree(): array + { + $createdAt = (new DateTime('2023-12-24 17:51:18'))->getTimestamp(); + $postsViewPermission = (new Permission('posts.view'))->withCreatedAt($createdAt)->withUpdatedAt($createdAt); + $postsCreatePermission = (new Permission('posts.create'))->withCreatedAt($createdAt)->withUpdatedAt($createdAt); + $postsDeletePermission = (new Permission('posts.delete'))->withCreatedAt($createdAt)->withUpdatedAt($createdAt); + $postsViewerRole = (new Role('posts.viewer'))->withCreatedAt($createdAt)->withUpdatedAt($createdAt); + $postsRedactorRole = (new Role('posts.redactor'))->withCreatedAt($createdAt)->withUpdatedAt($createdAt); + $postsAdminRole = (new Role('posts.admin'))->withCreatedAt($createdAt)->withUpdatedAt($createdAt); + + return [ + [ + 'posts.view', + [ + 'posts.view' => ['item' => $postsViewPermission, 'children' => []], + 'posts.viewer' => ['item' => $postsViewerRole, 'children' => ['posts.view' => $postsViewPermission]], + 'posts.redactor' => [ + 'item' => $postsRedactorRole, + 'children' => ['posts.view' => $postsViewPermission, 'posts.viewer' => $postsViewerRole], + ], + 'posts.admin' => [ + 'item' => $postsAdminRole, + 'children' => [ + 'posts.view' => $postsViewPermission, + 'posts.viewer' => $postsViewerRole, + 'posts.redactor' => $postsRedactorRole, + ], + ], + ], + ], + [ + 'posts.create', + [ + 'posts.create' => ['item' => $postsCreatePermission, 'children' => []], + 'posts.redactor' => [ + 'item' => $postsRedactorRole, + 'children' => ['posts.create' => $postsCreatePermission], + ], + 'posts.admin' => [ + 'item' => $postsAdminRole, + 'children' => [ + 'posts.create' => $postsCreatePermission, + 'posts.redactor' => $postsRedactorRole, + ], + ], + ], + ], + [ + 'posts.delete', + [ + 'posts.delete' => ['item' => $postsDeletePermission, 'children' => []], + 'posts.admin' => [ + 'item' => $postsAdminRole, + 'children' => [ + 'posts.delete' => $postsDeletePermission, + ], + ], + ], + ], + [ + 'posts.viewer', + [ + 'posts.viewer' => ['item' => $postsViewerRole, 'children' => []], + 'posts.redactor' => [ + 'item' => $postsRedactorRole, + 'children' => ['posts.viewer' => $postsViewerRole], + ], + 'posts.admin' => [ + 'item' => $postsAdminRole, + 'children' => [ + 'posts.viewer' => $postsViewerRole, + 'posts.redactor' => $postsRedactorRole, + ], + ], + ], + ], + ['non-existing', []], + ]; + } + + /** + * @dataProvider dataGetAccessTree + */ + public function testGetAccessTree(string $name, array $expectedAccessTree): void + { + $this->assertEquals($expectedAccessTree, $this->getItemsStorage()->getAccessTree($name)); + } + public function testRemoveChildren(): void { $testStorage = $this->getItemsStorageForModificationAssertions(); @@ -610,7 +702,6 @@ protected function getItemsStorageForModificationAssertions(): ItemsStorageInter protected function getFixtures(): array { - $time = time(); $itemsMap = [ 'Parent 1' => Item::TYPE_ROLE, 'Parent 2' => Item::TYPE_ROLE, @@ -638,6 +729,7 @@ protected function getFixtures(): array 'posts.update' => Item::TYPE_PERMISSION, 'posts.delete' => Item::TYPE_PERMISSION, ]; + $time = time(); $items = []; foreach ($itemsMap as $name => $type) {