From 31d7053eccfec26f4a70f187d6e4553916430d3c Mon Sep 17 00:00:00 2001 From: alexmerlin Date: Thu, 23 Jan 2025 07:11:20 +0200 Subject: [PATCH 1/2] Issue #26: Added support for laminas/laminas-servicemanager:4.x Signed-off-by: alexmerlin --- .laminas-ci.json | 6 ++ README.md | 53 ++++++++----- SECURITY.md | 9 ++- authorization.global.php.dist | 2 + composer.json | 23 +++--- docs/book/index.md | 2 +- docs/book/v3/configuration.md | 4 +- docs/book/v3/customization.md | 14 +++- docs/book/v3/installation.md | 6 +- docs/book/v3/usage.md | 7 +- docs/book/v4/configuration.md | 76 +++++++++++++++++++ docs/book/v4/customization.md | 31 ++++++++ docs/book/v4/installation.md | 7 ++ docs/book/v4/usage.md | 20 +++++ mkdocs.yml | 8 +- src/Assertion/AssertionPluginManager.php | 20 ++++- src/Assertion/Factory.php | 6 +- src/Role/Provider/Factory.php | 6 +- .../Provider/RoleProviderPluginManager.php | 26 +++++-- test/Assertion/FactoryTest.php | 5 +- 20 files changed, 273 insertions(+), 58 deletions(-) create mode 100644 .laminas-ci.json mode change 100644 => 120000 docs/book/index.md create mode 100644 docs/book/v4/configuration.md create mode 100644 docs/book/v4/customization.md create mode 100644 docs/book/v4/installation.md create mode 100644 docs/book/v4/usage.md diff --git a/.laminas-ci.json b/.laminas-ci.json new file mode 100644 index 0000000..82cd446 --- /dev/null +++ b/.laminas-ci.json @@ -0,0 +1,6 @@ +{ + "ignore_php_platform_requirements": { + "8.4": true + }, + "backwardCompatibilityCheck": true +} diff --git a/README.md b/README.md index fb782ad..591c206 100644 --- a/README.md +++ b/README.md @@ -1,35 +1,40 @@ # dot-rbac -Rbac authorization model implements [dot-authorization](https://github.com/dotkernel/dot-authorization)'s `AuthorizationInterface`. An authorization service is responsible for deciding if the authenticated identity or guest has access to certain parts of the application. +Rbac authorization model implements [dot-authorization](https://github.com/dotkernel/dot-authorization)'s `AuthorizationInterface`. +An authorization service is responsible for deciding if the authenticated identity or guest has access to certain parts of the application. -The RBAC model defines roles that can be assigned to users. The authorization is done on a role basis, not user basis as in ACL. Each role can have one or multiple permissions/privileges assigned. When deciding if a user is authorized, the requested permission is checked in all user roles and if at least one role has that permission, access is granted. +The RBAC model defines roles that can be assigned to users. +The authorization is done on a role basis, not user basis as in ACL. +Each role can have one or multiple permissions/privileges assigned. +When deciding if a user is authorized, the requested permission is checked in all user roles and if at least one role has that permission, access is granted. ![OSS Lifecycle](https://img.shields.io/osslifecycle/dotkernel/dot-rbac) -![PHP from Packagist (specify version)](https://img.shields.io/packagist/php-v/dotkernel/dot-rbac/3.5.2) +![PHP from Packagist (specify version)](https://img.shields.io/packagist/php-v/dotkernel/dot-rbac/4.0.0) [![GitHub issues](https://img.shields.io/github/issues/dotkernel/dot-rbac)](https://github.com/dotkernel/dot-rbac/issues) [![GitHub forks](https://img.shields.io/github/forks/dotkernel/dot-rbac)](https://github.com/dotkernel/dot-rbac/network) [![GitHub stars](https://img.shields.io/github/stars/dotkernel/dot-rbac)](https://github.com/dotkernel/dot-rbac/stargazers) -[![GitHub license](https://img.shields.io/github/license/dotkernel/dot-rbac)](https://github.com/dotkernel/dot-rbac/blob/3.0/LICENSE.md) +[![GitHub license](https://img.shields.io/github/license/dotkernel/dot-rbac)](https://github.com/dotkernel/dot-rbac/blob/4.0/LICENSE.md) -[![Build Static](https://github.com/dotkernel/dot-rbac/actions/workflows/continuous-integration.yml/badge.svg?branch=3.0)](https://github.com/dotkernel/dot-rbac/actions/workflows/continuous-integration.yml) +[![Build Static](https://github.com/dotkernel/dot-rbac/actions/workflows/continuous-integration.yml/badge.svg?branch=4.0)](https://github.com/dotkernel/dot-rbac/actions/workflows/continuous-integration.yml) [![codecov](https://codecov.io/gh/dotkernel/dot-rbac/graph/badge.svg?token=GCK6C92N83)](https://codecov.io/gh/dotkernel/dot-rbac) -[![SymfonyInsight](https://insight.symfony.com/projects/ce0cfbb2-7e97-427b-b394-531ff5be13d6/big.svg)](https://insight.symfony.com/projects/ce0cfbb2-7e97-427b-b394-531ff5be13d6) - ## Installation -Run the following command in your project root directory +Run the following command in your project root directory: -```bash -$ composer require dotkernel/dot-rbac +```shell +composer require dotkernel/dot-rbac ``` ## Configuration -Even if the authorization service can be programmatically configured, we recommend using the configuration based approach. We further describe how to configure the module, using configuration file. +Even if the authorization service can be programmatically configured, we recommend using the configuration based approach. +We further describe how to configure the module, using configuration file. -First of all, you should enable the module in your application by merging this package's `ConfigProvider` with your application's config. This ensures that all dependencies required by this module are registered in the service manager. It also defines default config values for this module. +First of all, you should enable the module in your application by merging this package's `ConfigProvider` with your application's config. +This ensures that all dependencies required by this module are registered in the service manager. +It also defines default config values for this module. Create a configuration file in your `config/autoload` folder and change the module options as needed. @@ -101,9 +106,10 @@ Create a configuration file in your `config/autoload` folder and change the modu ## Usage -Whenever you need to check if someone is authorized to take some actions, inject the `AuthorizationInterface::class` service into your class, then call the `isGranted` method with the correct parameters. There are 2 ways to call the isGranted method. +Whenever you need to check if someone is authorized to take some actions, inject the `AuthorizationInterface::class` service into your class, then call the `isGranted` method with the correct parameters. +There are 2 ways to call the isGranted method. -### First Method +### First method Specify which roles you want to check. @@ -111,9 +117,10 @@ Specify which roles you want to check. $isGranted = $this->authorizationService->isGranted($permission, $roles); ``` -### Second Method +### Second method -Do not specify the roles or send an empty array as the second parameter. This will check if the authenticated identity has permission. +Do not specify the roles or send an empty array as the second parameter. +This will check if the authenticated identity has permission. ```php $isGranted = $this->authorizationService->isGranted($permission); @@ -123,15 +130,20 @@ $isGranted = $this->authorizationService->isGranted($permission); Whenever you request an authorization check on the authenticated identity, the identity will be provided to the `AuthorizationService` through a registered `IdentityProviderInterface` service. -This is because identity is authentication dependent, so the module lets you overwrite this service, depending on your needs. If you want to get the identity from other sources instead of the dot-authentication service, just overwrite the `IdentityProviderInterface::class` service in the service manager with your own implementation of this interface. +This is because identity is authentication dependent, so the module lets you overwrite this service, depending on your needs. +If you want to get the identity from other sources instead of the dot-authentication service, just overwrite the `IdentityProviderInterface::class` service in the service manager with your own implementation of this interface. ## Custom role providers -Write your own role provider by implementing the `RoleProviderInterface` and register it in the `RoleProviderPluginManager`. After that, you can use them in the configuration file, as described above. +Write your own role provider by implementing the `RoleProviderInterface` and register it in the `RoleProviderPluginManager`. +After that, you can use them in the configuration file, as described above. ## Creating assertions -Assertions are checked after permission granting, right before returning the authorization result. Assertions can have a last word in deciding if someone is authorized for the requested action. A good assertion example could be an edit permission, but with the restriction that it should be able to edit the item just if the `user id` matches the item's `owner id`. It is up to you to write the logic inside an assertion. +Assertions are checked after permission granting, right before returning the authorization result. +Assertions can have a last word in deciding if someone is authorized for the requested action. +A good assertion example could be an edit permission, but with the restriction that it should be able to edit the item just if the `user id` matches the item's `owner id`. +It is up to you to write the logic inside an assertion. An assertion has to implement the `AssertionInterface` and be registered in the `AssertionPluginManager`. @@ -141,4 +153,5 @@ This interface defines the following method public function assert(AuthorizationInterface $authorization, $context = null); ``` -The context variable can be any external data that an assertion needs in order to decide the authorization status. The assertion must return a boolean value, reflecting the assertion pass or failure status. +The context variable can be any external data that an assertion needs in order to decide the authorization status. +The assertion must return a boolean value, reflecting the assertion pass or failure status. diff --git a/SECURITY.md b/SECURITY.md index 26ee1b7..603536f 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -3,10 +3,11 @@ ## Supported Versions -| Version | Supported | PHP Version | -|---------|--------------------|------------------------------------------------------------------------------------------------------------------| -| 3.x | :white_check_mark: | ![PHP from Packagist (specify version)](https://img.shields.io/packagist/php-v/dotkernel/dot-rbac/3.5.2) | -| <= 2.x | :x: | | +| Version | Supported | PHP Version | +|---------|--------------------|----------------------------------------------------------------------------------------------------------| +| 4.x | :white_check_mark: | ![PHP from Packagist (specify version)](https://img.shields.io/packagist/php-v/dotkernel/dot-rbac/4.0.0) | +| 3.x | :x: | ![PHP from Packagist (specify version)](https://img.shields.io/packagist/php-v/dotkernel/dot-rbac/3.5.2) | +| <= 2.x | :x: | | ## Reporting Potential Security Issues diff --git a/authorization.global.php.dist b/authorization.global.php.dist index 1d5a7f1..381ec47 100644 --- a/authorization.global.php.dist +++ b/authorization.global.php.dist @@ -1,5 +1,7 @@ [ //name of the guest role to use if no identity is provided diff --git a/composer.json b/composer.json index bb3a146..1c7b679 100644 --- a/composer.json +++ b/composer.json @@ -1,17 +1,17 @@ { "name": "dotkernel/dot-rbac", "type": "library", - "description": "DotKernel RBAC authorization component", + "description": "Dotkernel RBAC authorization component", "license": "MIT", "homepage": "https://github.com/dotkernel/dot-rbac", "keywords": [ + "authorization", "laminas", - "mezzio", "rbac" ], "authors": [ { - "name": "DotKernel Team", + "name": "Dotkernel Team", "email": "team@dotkernel.com" } ], @@ -22,16 +22,16 @@ } }, "require": { - "php": "~8.1.0 || ~8.2.0 || ~8.3.0", - "laminas/laminas-servicemanager": "^3.11", - "dotkernel/dot-authorization": "^3.4.1", - "laminas/laminas-stdlib": "^3.7", - "laminas/laminas-authentication": "2.16.0" + "php": "~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0", + "dotkernel/dot-authorization": "^3.6.0", + "laminas/laminas-authentication": "^2.16.0", + "laminas/laminas-servicemanager": "^4.0", + "laminas/laminas-stdlib": "^3.7" }, "require-dev": { + "laminas/laminas-coding-standard": "^3.0", "phpunit/phpunit": "^10.2", - "vimeo/psalm": "^5.13", - "laminas/laminas-coding-standard": "^2.5" + "vimeo/psalm": "^5.13" }, "autoload": { "psr-4": { @@ -46,7 +46,8 @@ "scripts": { "check": [ "@cs-check", - "@test" + "@test", + "@static-analysis" ], "cs-check": "phpcs", "cs-fix": "phpcbf", diff --git a/docs/book/index.md b/docs/book/index.md deleted file mode 100644 index ae42a26..0000000 --- a/docs/book/index.md +++ /dev/null @@ -1 +0,0 @@ -../../README.md diff --git a/docs/book/index.md b/docs/book/index.md new file mode 120000 index 0000000..fe84005 --- /dev/null +++ b/docs/book/index.md @@ -0,0 +1 @@ +../../README.md \ No newline at end of file diff --git a/docs/book/v3/configuration.md b/docs/book/v3/configuration.md index d7a5918..a683842 100644 --- a/docs/book/v3/configuration.md +++ b/docs/book/v3/configuration.md @@ -3,7 +3,9 @@ Even if the authorization service can be programmatically configured, we recommend using the configuration based approach. We further describe how to configure the module, using the configuration file. -First of all, you should enable the module in your application by merging this package's `ConfigProvider` with your application's config. This ensures that all dependencies required by this module are registered in the service manager. It also defines default config values for this module. +First of all, you should enable the module in your application by merging this package's `ConfigProvider` with your application's config. +This ensures that all dependencies required by this module are registered in the service manager. +It also defines default config values for this module. Create a configuration file in your `config/autoload` folder and change the module options as needed. diff --git a/docs/book/v3/customization.md b/docs/book/v3/customization.md index 6f9dee6..5a91914 100644 --- a/docs/book/v3/customization.md +++ b/docs/book/v3/customization.md @@ -4,15 +4,20 @@ Whenever you request an authorization check on the authenticated identity, the identity will be provided to the `AuthorizationService` through a registered `IdentityProviderInterface` service. -This is because identity is authentication dependent, so the module lets you overwrite this service, depending on your needs. If you want to get the identity from other sources instead of the dot-authentication service, just overwrite the `IdentityProviderInterface::class` service in the service manager with your own implementation of this interface. +This is because identity is authentication dependent, so the module lets you overwrite this service, depending on your needs. +If you want to get the identity from other sources instead of the dot-authentication service, just overwrite the `IdentityProviderInterface::class` service in the service manager with your own implementation of this interface. ## Custom role providers -Write your own role provider by implementing the `RoleProviderInterface` and register it in the `RoleProviderPluginManager`. After that, you can use them in the configuration file, as described above. +Write your own role provider by implementing the `RoleProviderInterface` and register it in the `RoleProviderPluginManager`. +After that, you can use them in the configuration file, as described above. ## Creating assertions -Assertions are checked after permission granting, right before returning the authorization result. Assertions can have a last word in deciding if someone is authorized for the requested action. A good assertion example could be an edit permission, but with the restriction that it should be able to edit the item just if the `user id` matches the item's `owner id`. It is up to you to write the logic inside an assertion. +Assertions are checked after permission granting, right before returning the authorization result. +Assertions can have a last word in deciding if someone is authorized for the requested action. +A good assertion example could be an edit permission, but with the restriction that it should be able to edit the item just if the `user id` matches the item's `owner id`. +It is up to you to write the logic inside an assertion. An assertion has to implement the `AssertionInterface` and be registered in the `AssertionPluginManager`. @@ -22,4 +27,5 @@ This interface defines the following method public function assert(AuthorizationInterface $authorization, $context = null); ``` -The context variable can be any external data that an assertion needs in order to decide the authorization status. The assertion must return a boolean value, reflecting the assertion pass or failure status. +The context variable can be any external data that an assertion needs in order to decide the authorization status. +The assertion must return a boolean value, reflecting the assertion pass or failure status. diff --git a/docs/book/v3/installation.md b/docs/book/v3/installation.md index cf86a41..6e99d9c 100644 --- a/docs/book/v3/installation.md +++ b/docs/book/v3/installation.md @@ -1,5 +1,7 @@ # Installation -Run the following command in your project root directory +Run the following command in your project root directory: - composer require dotkernel/dot-rbac +```shell +composer require dotkernel/dot-rbac +``` diff --git a/docs/book/v3/usage.md b/docs/book/v3/usage.md index 2717b52..a1ad715 100644 --- a/docs/book/v3/usage.md +++ b/docs/book/v3/usage.md @@ -1,8 +1,9 @@ # Usage -Whenever you need to check if someone is authorized to take some actions, inject the `AuthorizationInterface::class` service into your class, then call the `isGranted` method with the correct parameters. There are 2 ways to call the isGranted method. +Whenever you need to check if someone is authorized to take some actions, inject the `AuthorizationInterface::class` service into your class, then call the `isGranted` method with the correct parameters. +There are 2 ways to call the isGranted method. -## First Method +## First method Specify which roles you want to check. @@ -10,7 +11,7 @@ Specify which roles you want to check. $isGranted = $this->authorizationService->isGranted($permission, $roles); ``` -## Second Method +## Second method Do not specify the roles or send an empty array as the second parameter. This will check if the authenticated identity has permission. diff --git a/docs/book/v4/configuration.md b/docs/book/v4/configuration.md new file mode 100644 index 0000000..a683842 --- /dev/null +++ b/docs/book/v4/configuration.md @@ -0,0 +1,76 @@ +# Configuration + +Even if the authorization service can be programmatically configured, we recommend using the configuration based approach. +We further describe how to configure the module, using the configuration file. + +First of all, you should enable the module in your application by merging this package's `ConfigProvider` with your application's config. +This ensures that all dependencies required by this module are registered in the service manager. +It also defines default config values for this module. + +Create a configuration file in your `config/autoload` folder and change the module options as needed. + +## authorization.global.php + +```php +'dot_authorization' => [ + //name of the guest role to use if no identity is provided + 'guest_role' => 'guest', + + 'role_provider_manager' => [], + + //example for a flat RBAC model using the InMemoryRoleProvider + 'role_provider' => [ + 'type' => 'InMemory', + 'options' => [ + 'roles' => [ + 'admin' => [ + 'permissions' => [ + 'edit', + 'delete', + //etc.. + ] + ], + 'user' => [ + 'permissions' => [ + //... + ] + ] + ] + ], + ], + + //example for a hierarchical model, less to write but it can be confusing sometimes + /*'role_provider' => [ + 'type' => 'InMemory', + 'options' => [ + 'roles' => [ + 'admin' => [ + 'children' => ['user'], + 'permissions' => ['create', 'delete'] + ], + 'user' => [ + 'children' => ['guest'] + 'permissions' => ['edit'] + ] + 'guest' => [ + 'permissions' => ['view'] + ] + ] + ] + ],*/ + + 'assertion_manager' => [ + 'factories' => [ + //EditAssertion::class => InvokableFactory::class, + ], + ], + + 'assertions' => [ + [ + 'type' => EditAssertion::class, + 'permissions' => ['edit'], + 'options' => [] + ] + ] +] +``` diff --git a/docs/book/v4/customization.md b/docs/book/v4/customization.md new file mode 100644 index 0000000..5a91914 --- /dev/null +++ b/docs/book/v4/customization.md @@ -0,0 +1,31 @@ +# Customization + +## Customize the IdentityProvider + +Whenever you request an authorization check on the authenticated identity, the identity will be provided to the `AuthorizationService` through a registered `IdentityProviderInterface` service. + +This is because identity is authentication dependent, so the module lets you overwrite this service, depending on your needs. +If you want to get the identity from other sources instead of the dot-authentication service, just overwrite the `IdentityProviderInterface::class` service in the service manager with your own implementation of this interface. + +## Custom role providers + +Write your own role provider by implementing the `RoleProviderInterface` and register it in the `RoleProviderPluginManager`. +After that, you can use them in the configuration file, as described above. + +## Creating assertions + +Assertions are checked after permission granting, right before returning the authorization result. +Assertions can have a last word in deciding if someone is authorized for the requested action. +A good assertion example could be an edit permission, but with the restriction that it should be able to edit the item just if the `user id` matches the item's `owner id`. +It is up to you to write the logic inside an assertion. + +An assertion has to implement the `AssertionInterface` and be registered in the `AssertionPluginManager`. + +This interface defines the following method + +```php +public function assert(AuthorizationInterface $authorization, $context = null); +``` + +The context variable can be any external data that an assertion needs in order to decide the authorization status. +The assertion must return a boolean value, reflecting the assertion pass or failure status. diff --git a/docs/book/v4/installation.md b/docs/book/v4/installation.md new file mode 100644 index 0000000..6e99d9c --- /dev/null +++ b/docs/book/v4/installation.md @@ -0,0 +1,7 @@ +# Installation + +Run the following command in your project root directory: + +```shell +composer require dotkernel/dot-rbac +``` diff --git a/docs/book/v4/usage.md b/docs/book/v4/usage.md new file mode 100644 index 0000000..a1ad715 --- /dev/null +++ b/docs/book/v4/usage.md @@ -0,0 +1,20 @@ +# Usage + +Whenever you need to check if someone is authorized to take some actions, inject the `AuthorizationInterface::class` service into your class, then call the `isGranted` method with the correct parameters. +There are 2 ways to call the isGranted method. + +## First method + +Specify which roles you want to check. + +```php +$isGranted = $this->authorizationService->isGranted($permission, $roles); +``` + +## Second method + +Do not specify the roles or send an empty array as the second parameter. This will check if the authenticated identity has permission. + +```php +$isGranted = $this->authorizationService->isGranted($permission); +``` diff --git a/mkdocs.yml b/mkdocs.yml index c089a6a..77c1b3e 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -2,11 +2,17 @@ docs_dir: docs/book site_dir: docs/html extra: project: Packages - current_version: v3 + current_version: v4 versions: + - v4 - v3 nav: - Home: index.md + - v4: + - Installation: v4/installation.md + - Configuration: v4/configuration.md + - Usage: v4/usage.md + - Customization: v4/customization.md - v3: - Installation: v3/installation.md - Configuration: v3/configuration.md diff --git a/src/Assertion/AssertionPluginManager.php b/src/Assertion/AssertionPluginManager.php index d8c4aff..2316cf9 100644 --- a/src/Assertion/AssertionPluginManager.php +++ b/src/Assertion/AssertionPluginManager.php @@ -5,12 +5,28 @@ namespace Dot\Rbac\Assertion; use Laminas\ServiceManager\AbstractPluginManager; +use Laminas\ServiceManager\Exception\InvalidServiceException; + +use function gettype; +use function is_object; +use function sprintf; /** * @template-extends AbstractPluginManager */ class AssertionPluginManager extends AbstractPluginManager { - /** @var string */ - protected $instanceOf = AssertionInterface::class; + protected string $instanceOf = AssertionInterface::class; + + public function validate(mixed $instance): void + { + if (! $instance instanceof $this->instanceOf) { + throw new InvalidServiceException(sprintf( + '%s can only create instances of %s; %s is invalid', + static::class, + $this->instanceOf, + is_object($instance) ? $instance::class : gettype($instance) + )); + } + } } diff --git a/src/Assertion/Factory.php b/src/Assertion/Factory.php index 2cd8c48..0d8953d 100644 --- a/src/Assertion/Factory.php +++ b/src/Assertion/Factory.php @@ -5,6 +5,7 @@ namespace Dot\Rbac\Assertion; use Dot\Rbac\Exception\RuntimeException; +use Psr\Container\ContainerExceptionInterface; use Psr\Container\ContainerInterface; use function sprintf; @@ -17,6 +18,9 @@ public function __construct( ) { } + /** + * @throws ContainerExceptionInterface + */ public function create(array $specs): AssertionInterface { $type = $specs['type'] ?? ''; @@ -24,7 +28,7 @@ public function create(array $specs): AssertionInterface throw new RuntimeException(sprintf('Invalid assertion type `%s`', $type)); } - return $this->getAssertionPluginManager()->get($type, $specs['options'] ?? null); + return $this->getAssertionPluginManager()->build($type, $specs['options'] ?? null); } public function getAssertionPluginManager(): AssertionPluginManager diff --git a/src/Role/Provider/Factory.php b/src/Role/Provider/Factory.php index b0ea068..797beca 100644 --- a/src/Role/Provider/Factory.php +++ b/src/Role/Provider/Factory.php @@ -5,6 +5,7 @@ namespace Dot\Rbac\Role\Provider; use Dot\Rbac\Exception\RuntimeException; +use Psr\Container\ContainerExceptionInterface; use Psr\Container\ContainerInterface; class Factory @@ -15,6 +16,9 @@ public function __construct( ) { } + /** + * @throws ContainerExceptionInterface + */ public function create(array $specs): RoleProviderInterface { $type = $specs['type'] ?? ''; @@ -23,7 +27,7 @@ public function create(array $specs): RoleProviderInterface } $roleProviderManager = $this->getRoleProviderPluginManager(); - return $roleProviderManager->get($type, $specs['options'] ?? null); + return $roleProviderManager->build($type, $specs['options'] ?? null); } public function getRoleProviderPluginManager(): RoleProviderPluginManager diff --git a/src/Role/Provider/RoleProviderPluginManager.php b/src/Role/Provider/RoleProviderPluginManager.php index 70837b4..d11e0e7 100644 --- a/src/Role/Provider/RoleProviderPluginManager.php +++ b/src/Role/Provider/RoleProviderPluginManager.php @@ -5,23 +5,25 @@ namespace Dot\Rbac\Role\Provider; use Laminas\ServiceManager\AbstractPluginManager; +use Laminas\ServiceManager\Exception\InvalidServiceException; use Laminas\ServiceManager\Factory\InvokableFactory; +use function gettype; +use function is_object; +use function sprintf; + /** * @template-extends AbstractPluginManager */ class RoleProviderPluginManager extends AbstractPluginManager { - /** @var string */ - protected $instanceOf = RoleProviderInterface::class; + protected string $instanceOf = RoleProviderInterface::class; - /** @var string[] */ - protected $factories = [ + protected array $factories = [ InMemoryRoleProvider::class => InvokableFactory::class, ]; - /** @var string[] */ - protected $aliases = [ + protected array $aliases = [ 'inmemoryroleprovider' => InMemoryRoleProvider::class, 'inMemoryRoleProvider' => InMemoryRoleProvider::class, 'InMemoryRoleProvider' => InMemoryRoleProvider::class, @@ -29,4 +31,16 @@ class RoleProviderPluginManager extends AbstractPluginManager 'inMemory' => InMemoryRoleProvider::class, 'InMemory' => InMemoryRoleProvider::class, ]; + + public function validate(mixed $instance): void + { + if (! $instance instanceof $this->instanceOf) { + throw new InvalidServiceException(sprintf( + '%s can only create instances of %s; %s is invalid', + static::class, + $this->instanceOf, + is_object($instance) ? $instance::class : gettype($instance) + )); + } + } } diff --git a/test/Assertion/FactoryTest.php b/test/Assertion/FactoryTest.php index a083228..08c67ef 100644 --- a/test/Assertion/FactoryTest.php +++ b/test/Assertion/FactoryTest.php @@ -11,11 +11,13 @@ use Dot\Rbac\Exception\RuntimeException; use PHPUnit\Framework\MockObject\Exception; use PHPUnit\Framework\TestCase; +use Psr\Container\ContainerExceptionInterface; use Psr\Container\ContainerInterface; class FactoryTest extends TestCase { /** + * @throws ContainerExceptionInterface * @throws Exception */ public function testCreateRuntimeException(): void @@ -29,6 +31,7 @@ public function testCreateRuntimeException(): void } /** + * @throws ContainerExceptionInterface * @throws Exception */ public function testCreate(): void @@ -37,7 +40,7 @@ public function testCreate(): void $assertionPluginManager = $this->createMock(AssertionPluginManager::class); $assertionPluginManager->expects($this->once()) - ->method('get') + ->method('build') ->with('testType', null) ->willReturn(new class implements AssertionInterface { public function assert(AuthorizationInterface $authorization, mixed $context = null): bool From 208bc4baaf11a95ffd991e0d9c2fa37200b510bd Mon Sep 17 00:00:00 2001 From: alexmerlin Date: Thu, 23 Jan 2025 07:35:53 +0200 Subject: [PATCH 2/2] Added RoleProviderPluginManager test Signed-off-by: alexmerlin --- .../RoleProviderPluginManagerTest.php | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 test/Role/Provider/RoleProviderPluginManagerTest.php diff --git a/test/Role/Provider/RoleProviderPluginManagerTest.php b/test/Role/Provider/RoleProviderPluginManagerTest.php new file mode 100644 index 0000000..a92c55a --- /dev/null +++ b/test/Role/Provider/RoleProviderPluginManagerTest.php @@ -0,0 +1,47 @@ +createMock(ContainerInterface::class); + + $container->expects($this->once()) + ->method('get') + ->with('config') + ->willReturn(['dot_authorization' => ['role_provider_manager' => []]]); + + $this->expectException(InvalidServiceException::class); + $this->expectExceptionMessage( + sprintf( + '%s can only create instances of %s; string is invalid', + RoleProviderPluginManager::class, + RoleProviderInterface::class + ) + ); + + $service = (new RoleProviderPluginManagerFactory())($container); + $service->validate('invalid'); + } +}