From dbf5d792d7b51d8caff3155ea3f2553d60185088 Mon Sep 17 00:00:00 2001 From: "Eric Richer eric.richer@vistoconsulting.com" Date: Tue, 20 Aug 2024 13:47:26 -0400 Subject: [PATCH 1/9] Updated documentation Signed-off-by: Eric Richer eric.richer@vistoconsulting.com --- config/lmcrbac.global.php.dist | 10 +- docs/docs/Upgrading/migration.md | 20 ++ docs/docs/Upgrading/to-v2.md | 24 +++ docs/docs/assertions.md | 25 ++- docs/docs/authorization-service.md | 45 +++- docs/docs/concepts.md | 6 +- docs/docs/configuration.md | 3 +- docs/docs/gettingstarted.md | 6 +- docs/docs/quickstart.md | 14 +- docs/docs/role-providers.md | 78 +++---- docs/docusaurus.config.js | 8 +- docs/package.json | 8 +- docs/src/css/custom.css | 15 ++ docs/versioned_docs/version-1.4/assertions.md | 144 +++++++++++++ .../version-1.4/authorization-service.md | 33 +++ docs/versioned_docs/version-1.4/concepts.md | 44 ++++ .../version-1.4/configuration.md | 18 ++ .../version-1.4/gettingstarted.md | 34 +++ .../version-1.4}/migration.md | 0 docs/versioned_docs/version-1.4/quickstart.md | 129 ++++++++++++ .../version-1.4/role-providers.md | 193 ++++++++++++++++++ .../version-1.4-sidebars.json | 8 + docs/versions.json | 3 + docs/yarn.lock | 8 +- 24 files changed, 784 insertions(+), 92 deletions(-) create mode 100644 docs/docs/Upgrading/migration.md create mode 100644 docs/docs/Upgrading/to-v2.md create mode 100644 docs/versioned_docs/version-1.4/assertions.md create mode 100644 docs/versioned_docs/version-1.4/authorization-service.md create mode 100644 docs/versioned_docs/version-1.4/concepts.md create mode 100644 docs/versioned_docs/version-1.4/configuration.md create mode 100644 docs/versioned_docs/version-1.4/gettingstarted.md rename docs/{docs => versioned_docs/version-1.4}/migration.md (100%) create mode 100644 docs/versioned_docs/version-1.4/quickstart.md create mode 100644 docs/versioned_docs/version-1.4/role-providers.md create mode 100644 docs/versioned_sidebars/version-1.4-sidebars.json create mode 100644 docs/versions.json diff --git a/config/lmcrbac.global.php.dist b/config/lmcrbac.global.php.dist index c9fd207..d4ac2fd 100644 --- a/config/lmcrbac.global.php.dist +++ b/config/lmcrbac.global.php.dist @@ -25,14 +25,6 @@ declare(strict_types=1); return [ 'lmc_rbac' => [ - /** - * Key that is used to fetch the identity provider - * - * Please note that when an identity is found, it MUST implement the LmcRbac\Identity\IdentityProviderInterface - * interface, otherwise it will throw an exception. - */ - // 'identity_provider' => 'LmcRbac\Identity\AuthenticationIdentityProvider', - /** * Set the guest role * @@ -50,7 +42,7 @@ return [ * * The provider config for InMemoryRoleProvider must follow the following format: * - * 'LmcRbac\Role\InMemoryRoleProvider' => [ + * Lmc\Rbac\Role\InMemoryRoleProvider::class => [ * 'role1' => [ * 'children' => ['children1', 'children2'], // OPTIONAL * 'permissions' => ['edit', 'read'] // OPTIONAL diff --git a/docs/docs/Upgrading/migration.md b/docs/docs/Upgrading/migration.md new file mode 100644 index 0000000..29f5ac6 --- /dev/null +++ b/docs/docs/Upgrading/migration.md @@ -0,0 +1,20 @@ +--- +sidebar_label: Migration from ZF-Commons Rbac +sidebar_position: 2 +title: Migrating from ZF-Commons RBAC v3 +--- + +The ZF-Commons Rbac was created for the Zend Framework. When the Zend Framework was migrated to +the Laminas project, the LM-Commons organization was created to provide components formerly provided by ZF-Commons. + +When ZfcRbac was moved to LM-Commons, it was split into two repositories: + +- [LmcRbacMvc](https://github.com/LM-Commons/LmcRbacMvc) contains the old version 2 of ZfcRbac. +- LmcRbac contains the version 3 of ZfcRbac, which was only released as v3.alpha.1. + +To upgrade + +- Uninstall `zf-commons/zfc-rbac:3.0.0-alpha.1`. +- Install `lm-commons/lmc-rbac:~1.0` +- Change `zfc-rbac.global.php` to `lmcrbac.global.php` and update the key `zfc_rbac` to `lmc_rbac`. +- Review your code for usages of the `ZfcRbac/*` namespace to `LmcRbac/*` namespace. diff --git a/docs/docs/Upgrading/to-v2.md b/docs/docs/Upgrading/to-v2.md new file mode 100644 index 0000000..2d18db0 --- /dev/null +++ b/docs/docs/Upgrading/to-v2.md @@ -0,0 +1,24 @@ +--- +sidebar_label: To LmcRbac v2 +sidebar_position: 1 +title: Upgrading to LmcRbac v2 +--- + +LmcRbac v2 is a major version upgrade with many breaking changes that prevent +straightforward upgrading. + +### Namespace change + +The namespace has been changed from LmcRbac to Lmc\Rbac. + +Please review your code to replace references to the `LmcRbac` namespace +by the `Lmc\Rbac` namespace. + +### Deprecations + +- `Lmc\Rbac\HierarchicalRole` has been deprecated since `Lmc\Rbac\Role` now supports hierarchical roles. Flat roles + are just a simplified version of a hierarchical roles. Use `Lmc\Rbac\Role` instead. +- The factories for services have been refactored from the `Lmc\Rbac\Container` namespace + to be colocated with the service that a factory is creating. All factories in the `Lmc\Rbac\Container` namespace have +been deprecated. +- The `AssertionContainer` class, interface and factory have been deprecated and replaced by `AssertionPluginManager` class, interface and factory. diff --git a/docs/docs/assertions.md b/docs/docs/assertions.md index 552a549..f106c21 100644 --- a/docs/docs/assertions.md +++ b/docs/docs/assertions.md @@ -14,13 +14,13 @@ You can define dynamic assertion functions and assigned them to permission via c ## Defining a dynamic assertion function -A dynamic assertion must implement the `LmcRbac\Assertion\AssertionInterace` which defines only one method: +A dynamic assertion must implement the `Lmc\Rbac\Assertion\AssertionInterace` which defines only one method: ```php public function assert( - string $permission, - IdentityInterface $identity = null, - $context = null + PermissionInterface|string $permission, + ?IdentityInterface $identity = null, + mixed $context = null ): bool ``` The assertion returns `true` when the access is granted, `false` otherwise. @@ -31,9 +31,9 @@ represented by `$permission` owns the resource represented by `$context`. ```php [ @@ -66,7 +67,7 @@ return [ ], 'assertion_manager' => [ 'factories' => [ - \My\Namespace\MyAssertion::class => \Laminas\ServiceManager\Factory\InvokableFactory::class + \My\Namespace\MyAssertion::class => InvokableFactory::class ], ], ], @@ -77,11 +78,13 @@ It is also possible to configure an assertion using a callable instead of a clas ```php [ /* the rest of the file */ 'assertion_map' => [ - 'edit' => function assert(string $permission, IdentityInterface $identity = null, $context = null): bool + 'edit' => function assert(PermissionInterface|string $permission, ?IdentityInterface $identity = null, $context = null): bool { // for 'edit' permission if ('edit' === $permission) { @@ -113,15 +116,15 @@ return [ \My\Namespace\AssertionB::class, ], 'read' => [ - 'condition' => \LmcRbac\Assertion\AssertionSet::CONDITION_OR, + 'condition' => \Lmc\Rbac\Assertion\AssertionSet::CONDITION_OR, \My\Namespace\AssertionC::class, \My\Namespace\AssertionD::class, ], 'delete' => [ - 'condition' => \LmcRbac\Assertion\AssertionSet::CONDITION_OR, + 'condition' => \Lmc\Rbac\Assertion\AssertionSet::CONDITION_OR, \My\Namespace\AssertionE::class, [ - 'condition' => \LmcRbac\Assertion\AssertionSet::CONDITION_AND, + 'condition' => \Lmc\Rbac\Assertion\AssertionSet::CONDITION_AND, \My\Namespace\AssertionF::class, \My\Namespace\AssertionC::class, ], diff --git a/docs/docs/authorization-service.md b/docs/docs/authorization-service.md index a8914e9..358e516 100644 --- a/docs/docs/authorization-service.md +++ b/docs/docs/authorization-service.md @@ -7,25 +7,48 @@ title: Authorization Service ### Usage The Authorization service can be retrieved from the service manager using the name -`LmcRbac\Service\AuthorizationServiceInterface` and injected into your code: +`Lmc\Rbac\Service\AuthorizationServiceInterface` and injected into your code: ```php get(LmcRbac\Service\AuthorizationServiceInterface::class); + $authorizationService = $container->get(Lmc\Rbac\Service\AuthorizationServiceInterface::class); ``` ### Reference -`LmcRbac\Service\AuthorizationServiceInterface` defines the following method: - -`isGranted(?IdentityInterface $identity, string $permission, $context = null): bool` - -| Parameter | Description | -|----------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `$identity` | The identity whose roles to checks.
If `$identity` is null, then the `guest` is used.
The `guest` role is definable via configuration and defaults to `'guest'`. | -| `$permission` | The permission to check against | -| `$context` | A context that will be passed to dynamic assertions that are defined for the permission | +`Lmc\Rbac\Service\AuthorizationServiceInterface` defines the following method: + +- `isGranted(?IdentityInterface $identity, PermissionInterface|string $permission, $context = null): bool` + | Parameter | Description | + |----------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------| + | `$identity` | The identity whose roles to checks.
If `$identity` is null, then the `guest` is used.
The `guest` role is definable via configuration and defaults to `'guest'`. | + | `$permission` | The permission to check against | + | `$context` | A context that will be passed to dynamic assertions that are defined for the permission | + +- `setAssertions(array $assertions, bool $merge = false): void` + | Parameter | Description | + |---------------|-----------------------------------------------------------------------------------------| + | `$assertions` | An array of assertions to merge or to replace | + | `$merge` | if `true` the content of `$assertions` will be merged with existing assertions. | + +- `setAssertion(PermissionInterface|string $permission, AssertionInterface|callable|string $assertion): void` + | Parameter | Description | + |---------------|----------------------------------------| + | `$permission` | Permission or a permission name | + | `$assertion` | The assertion to set for `$permission` | + +- `hasAssertion(PermissionInterface|string $permission): bool` + | Parameter | Description | + |---------------|----------------------------------------| + | `$permission` | Permission or a permission name | + +- `getAssertions(): array` + +- `getAssertion(PermissionInterface|string $permission): AssertionInterface|callable|string|null` + | Parameter | Description | + |---------------|----------------------------------------| + | `$permission` | Permission or a permission name | More on dynamic assertions can be found in the [Assertions](assertions.md) section. diff --git a/docs/docs/concepts.md b/docs/docs/concepts.md index f49a88d..583fb5f 100644 --- a/docs/docs/concepts.md +++ b/docs/docs/concepts.md @@ -35,9 +35,9 @@ resource. An identity is typically provided by an authentication process within the application. -Authentication is not in the scope of `LmcRbac` and it is assumed that an identity entity providing assigned roles -is available when using the authorization service. If no identity is available, as it would be the case when no user is "logged in", -then a guest role is assumed. +Authentication is not in the scope of `LmcRbac` and it is assumed that an identity entity that can provide the assigned +roles is available when using the authorization service. If no identity is available, as it would be the case when no +user is "logged in", then a guest role is assumed. diff --git a/docs/docs/configuration.md b/docs/docs/configuration.md index 9c1d32d..7d94088 100644 --- a/docs/docs/configuration.md +++ b/docs/docs/configuration.md @@ -14,5 +14,6 @@ a `config/autoload/lmcrbac.global.php` file. A sample configuration file is prov | Key | Description | |--|------------------------------------------------------------------------------------------------------------------------------------------------| | `guest_role` | Defines the name of the `guest` role when no identity exists.
Defaults to `'guest'`. | -| `assertion_map` | Defines the dynamic assertions that are associated to permissions.
Defaults to `[]`.
See the [Dynamic Assertions](assertions) section. | | `role_provider` | Defines the role provider.
Defaults to `[]`
See the [Role Providers](role-providers) section. | +| `assertion_map` | Defines the dynamic assertions that are associated to permissions.
Defaults to `[]`.
See the [Dynamic Assertions](assertions) section. | +| `assertion_manager` | Provides a configuration for the Assertion Plugin Manager.
Defaults to `[]`.
See the [Dynamic Assertion](assertions.md) section. | diff --git a/docs/docs/gettingstarted.md b/docs/docs/gettingstarted.md index 293ff0e..e86e40e 100644 --- a/docs/docs/gettingstarted.md +++ b/docs/docs/gettingstarted.md @@ -5,11 +5,7 @@ title: Get started --- ## Requirements -- PHP 7.3 or higher - -:::warning -The code is continuously tested against PHP 8.1 and higher only. There is no warranty that it will work for PHP 8.0 and lower. -::: +- PHP 8.1 or higher ## Installation diff --git a/docs/docs/quickstart.md b/docs/docs/quickstart.md index b19cde3..35b7a66 100644 --- a/docs/docs/quickstart.md +++ b/docs/docs/quickstart.md @@ -29,7 +29,7 @@ In the `config/autoload/lmcrbac.global.php`, add the following: return [ 'lmc_rbac' => [ 'role_provider' => [ - 'LmcRbac\Role\InMemoryRoleProvider' => [ + Lmc\Rbac\Role\InMemoryRoleProvider::class => [ 'guest', 'user' => [ 'permissions' => ['create', 'edit'], @@ -60,9 +60,9 @@ is granted to an identity: get('\LmcRbac\Service\AuthorizationServiceInterface'); + $authorizationService = $container->get('\Lmc\Rbac\Service\AuthorizationServiceInterface'); - /** @var \LmcRbac\Identity\IdentityInterface $identity */ + /** @var \Lmc\Rbac\Identity\IdentityInterface $identity */ if ($authorizationService->isGranted($identity, 'create')) { /** do something */ } @@ -78,7 +78,7 @@ can be configured in the `lmcrbac.config.php` file. More on this in the [Config :::warning `LmcRbac` does not provide any logic to instantiate an identity entity. It is assumed that -the application will instantiate an entity that implements `\LmcRbac\Identity\IdentityInterface` which defines the `getRoles()` +the application will instantiate an entity that implements `\Lmc\Rbac\Identity\IdentityInterface` which defines the `getRoles()` method. ::: @@ -88,7 +88,7 @@ Even if an identity has the `user` role granting it the `edit` permission, it sh This can be achieved using dynamic assertion. -An assertion is a function that implements the `\LmcRbac\Assertion\AssertionInterface` and is configured in the configuration +An assertion is a function that implements the `\Lmc\Rbac\Assertion\AssertionInterface` and is configured in the configuration file. Let's modify the `lmcrbac.config.php` file as follows: @@ -118,9 +118,9 @@ Then use the authorization service passing the resource (called a 'context') in get('\LmcRbac\Service\AuthorizationServiceInterface'); + $authorizationService = $container->get('\Lmc\Rbac\Service\AuthorizationServiceInterface'); - /** @var \LmcRbac\Identity\IdentityInterface $identity */ + /** @var \Lmc\Rbac\Identity\IdentityInterface $identity */ if ($authorizationService->isGranted($identity, 'edit', $resource)) { /** do something */ } diff --git a/docs/docs/role-providers.md b/docs/docs/role-providers.md index a65654e..d961d7a 100644 --- a/docs/docs/role-providers.md +++ b/docs/docs/role-providers.md @@ -1,60 +1,67 @@ --- -sidebar_label: Roles and Role providers -title: Roles and Role providers +sidebar_label: Roles, permissions and Role providers +title: Roles, Permissions and Role providers sidebar_position: 4 --- -## Role types +## Roles A role is an object that returns a list of permissions that the role has. -`LmcRbac` support two types of roles: hierarchical roles and flat roles. +Roles are defined using by the `\Lmc\Rbac\Role\Role` class or by a class +implementing `Lmc\Rbac\Role\RoleInterface`. -### Flat roles +Roles can have child roles and therefore provides a hierarchy of roles where a role inherit the permissions of all its +child roles. -A flat role is the simplest role object. It contains the list of permissions that -the role has. +For example, a 'user' role may have the 'read' and 'write' permissions, and a 'admin' role +may inherit the permissions of the 'user' role plus an additional 'delete' role. In this structure, +the 'admin' role will have 'user' as its child role. -Flat roles are defined using by the `LmcRbac\Role\Role` class or by classes -implementing the `LmcRbac\Role\RoleInterface`. -### Hierarchical roles +:::info +#### Flat roles -A hierarchical role is a role that has child roles and therefore provides -a hierarchy of roles where a role inherit the permissions of all its child roles. +Previous version of LmcRbac used to make a distinction between flat roles and hierarchical roles. +A flat role is just a simplification of a hierarchical role, i.e. a hierarchical role without children. Therefore +`Lmc\Rbac\Role\Role` is now hierarchical by default and `Lmc\Rbac\Role\HierarchicalRole` has been deprecated. -For example, a 'user' role may have the 'read' and 'write' permissions, and a 'admin' role -may inherit the permissions of the 'user' role plus an additional 'delete' role. In this structure, -the 'admin' role will have 'user' as its child role. +::: + +## Permissions -Hierarchical roles may have flat roles or hierarchical roles as children. +A permission in LmcRbac is simply a string that represents the permission such as 'read', 'write' or 'delete'. +But it can also be more precise like 'article.read' or 'article.write'. -Hierarchical roles are defined using by the `LmcRbac\Role\HierarchicalRole` class or by classes -implementing the `LmcRbac\Role\HierarchicalRoleInterface`. +Permissions can also be objects as long as they implement the `Lmc\Rbac\Permission\PermissionInterface`. This could be the +case, for example, when permissions are stored in a database where they could also have a identified and a description. ## Role Providers A role provider is an object that returns a list of roles. A role provider must implement the -`LmcRbac\Role\RoleProviderInterface` interface. The only required method is `getRoles`, and must return an array -of `LmcRbac\Role\RoleInterface` objects. +`Lmc\Rbac\Role\RoleProviderInterface` interface. The only required method is `getRoles`, and must return an array +of `Lmc\Rbac\Role\RoleInterface` objects. Roles can come from one of many sources: in memory, from a file, from a database, etc. However, you can specify only one role provider per application. ### Built-in role providers -LmcRbac comes with two built-in role providers: `LmcRbac\Role\InMemoryRoleProvider` and `LmcRbac\Role\ObjectRepositoryRoleProvider`. A role -provider must be added to the `role_provider` subkey in the configuration file: +LmcRbac comes with two built-in role providers: `Lmc\Rbac\Role\InMemoryRoleProvider` and +`Lmc\Rbac\Role\ObjectRepositoryRoleProvider`. A role provider must be added to the `role_provider` subkey in the +configuration file. For example: ```php return [ 'lmc_rbac' => [ 'role_provider' => [ - // Role provider config here! + Lmc\Rbac\Role\InMemoryRoleProvider::class => [ + // configuration + ], ] ] ]; ``` -### `LmcRbac\Role\InMemoryRoleProvider` +### `Lmc\Rbac\Role\InMemoryRoleProvider` This provider is ideal for small/medium sites with few roles/permissions. All the data is specified in a simple associative array in a PHP file. @@ -65,7 +72,7 @@ Here is an example of the format you need to use: return [ 'lmc_rbac' => [ 'role_provider' => [ - 'LmcRbac\Role\InMemoryRoleProvider' => [ + Lmc\Rbac\Role\InMemoryRoleProvider::class => [ 'admin' => [ 'children' => ['member'], 'permissions' => ['article.delete'] @@ -83,9 +90,8 @@ return [ ]; ``` -The `children` and `permissions` subkeys are entirely optional. Internally, the `LmcRbac\Role\InMemoryRoleProvider` creates -either a `LmcRbac\Role\Role` object if the role does not have any children, or a `LmcRbac\Role\HierarchicalRole` if -the role has at least one child. +The `children` and `permissions` subkeys are entirely optional. Internally, the `Lmc\Rbac\Role\InMemoryRoleProvider` creates +`Lmc\Rbac\Role\Role` objects with children, if any. If you are more confident with flat RBAC, the previous config can be re-written to remove any inheritence between roles: @@ -93,7 +99,7 @@ If you are more confident with flat RBAC, the previous config can be re-written return [ 'lmc_rbac' => [ 'role_provider' => [ - 'LmcRbac\Role\InMemoryRoleProvider' => [ + Lmc\Rbac\Role\InMemoryRoleProvider::class => [ 'admin' => [ 'permissions' => [ 'article.delete', @@ -118,7 +124,7 @@ return [ ]; ``` -### `LmcRbac\Role\ObjectRepositoryRoleProvider` +### `Lmc\Rbac\Role\ObjectRepositoryRoleProvider` This provider fetches roles from a database using `Doctrine\Common\Persistence\ObjectRepository` interface. @@ -129,7 +135,7 @@ using the `object_repository` key: return [ 'lmc_rbac' => [ 'role_provider' => [ - 'LmcRbac\Role\ObjectRepositoryRoleProvider' => [ + Lmc\Rbac\Role\ObjectRepositoryRoleProvider::class => [ 'object_repository' => 'App\Repository\RoleRepository', 'role_name_property' => 'name' ], @@ -144,7 +150,7 @@ Or you can specify the `object_manager` and `class_name` options: return [ 'lmc_rbac' => [ 'role_provider' => [ - 'LmcRbac\Role\ObjectRepositoryRoleProvider' => [ + Lmc\Rbac\Role\ObjectRepositoryRoleProvider::class => [ 'object_manager' => 'doctrine.entitymanager.orm_default', 'class_name' => 'App\Entity\Role', 'role_name_property' => 'name' @@ -158,14 +164,14 @@ In both cases, you need to specify the `role_name_property` value, which is the that holds the actual role name. This is used internally to only load the identity roles, instead of loading the whole table every time. -Please note that your entity fetched from the table MUST implement the `LmcRbac\Role\RoleInterface` interface. +Please note that your entity fetched from the table MUST implement the `Lmc\Rbac\Role\RoleInterface` interface. Sample ORM entity models are provided in the `/data` folder for flat role, hierarchical role and permission. ## Creating custom role providers To create a custom role provider, you first need to create a class that implements the -`LmcRbac\Role\RoleProviderInterface` interface. +`Lmc\Rbac\Role\RoleProviderInterface` interface. Then, you need to add it to the role provider manager: @@ -173,7 +179,7 @@ Then, you need to add it to the role provider manager: return [ 'lmc_rbac' => [ 'role_provider' => [ - 'Application\Role\CustomRoleProvider' => [ + MyCustomRoleProvider::class => [ // Options ], ], @@ -185,7 +191,7 @@ And the role provider is created using the service manager: return [ 'service_manager' => [ 'factories' => [ - 'Application\Role\CustomRoleProvider' => 'Application\Factory\CustomRoleProviderFactory' + MyCustomRoleProvider::class => MyCustomRoleProviderFactory::class, ], ], ]; diff --git a/docs/docusaurus.config.js b/docs/docusaurus.config.js index 83083dc..a512cc1 100644 --- a/docs/docusaurus.config.js +++ b/docs/docusaurus.config.js @@ -71,6 +71,7 @@ themeConfig: logo: { alt: 'LM-Commons Logo', src: 'img/LMC-logo.png', +// href: 'https://lm-commons.github.io' }, items: [ { @@ -79,6 +80,10 @@ themeConfig: position: 'left', label: 'Documentation', }, + { + type: "docsVersionDropdown", + position: "right", + }, {to: '/blog', label: 'Blog', position: 'left'}, { href: 'https://lm-commons.github.io', @@ -87,8 +92,9 @@ themeConfig: }, { href: 'https://github.com/lm-commons/lmcrbac', - label: 'GitHub', +// label: 'GitHub', position: 'right', + className: 'header-github-link', }, ], }, diff --git a/docs/package.json b/docs/package.json index 481e16f..61fcfd4 100644 --- a/docs/package.json +++ b/docs/package.json @@ -14,8 +14,8 @@ "write-heading-ids": "docusaurus write-heading-ids" }, "dependencies": { - "@docusaurus/core": "^3.4.0", - "@docusaurus/preset-classic": "^3.4.0", + "@docusaurus/core": "^3.5.2", + "@docusaurus/preset-classic": "^3.5.2", "@mdx-js/react": "^3.0.0", "clsx": "^2.0.0", "prism-react-renderer": "^2.3.0", @@ -23,8 +23,8 @@ "react-dom": "^18.0.0" }, "devDependencies": { - "@docusaurus/module-type-aliases": "^3.4.0", - "@docusaurus/types": "^3.4.0" + "@docusaurus/module-type-aliases": "^3.5.2", + "@docusaurus/types": "^3.5.2" }, "browserslist": { "production": [ diff --git a/docs/src/css/custom.css b/docs/src/css/custom.css index 2bc6a4c..7c45b95 100644 --- a/docs/src/css/custom.css +++ b/docs/src/css/custom.css @@ -28,3 +28,18 @@ --ifm-color-primary-lightest: #4fddbf; --docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.3); } + +.header-github-link::before { + content: ''; + width: 24px; + height: 24px; + display: flex; + background-color: var(--ifm-navbar-link-color); + mask-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12'/%3E%3C/svg%3E"); + transition: background-color var(--ifm-transition-fast) + var(--ifm-transition-timing-default); +} + +.header-github-link:hover::before { + background-color: var(--ifm-navbar-link-hover-color); +} diff --git a/docs/versioned_docs/version-1.4/assertions.md b/docs/versioned_docs/version-1.4/assertions.md new file mode 100644 index 0000000..552a549 --- /dev/null +++ b/docs/versioned_docs/version-1.4/assertions.md @@ -0,0 +1,144 @@ +--- +sidebar_label: Dynamic Assertions +sidebar_position: 6 +title: Dynamic Assertions +--- + +Dynamic Assertions provide the capability to perform extra validations when +the authorization service's `isGranted()` method is called. + +As described in [Authorization Service](authorization-service#reference), it is possible to pass a context to the +`isGranted()` method. This context is then passed to dynamic assertion functions. This context can be any object type. + +You can define dynamic assertion functions and assigned them to permission via configuration. + +## Defining a dynamic assertion function + +A dynamic assertion must implement the `LmcRbac\Assertion\AssertionInterace` which defines only one method: + +```php +public function assert( + string $permission, + IdentityInterface $identity = null, + $context = null + ): bool +``` +The assertion returns `true` when the access is granted, `false` otherwise. + +A simple assertion could be to check that user represented by `$identity`, for the permission +represented by `$permission` owns the resource represented by `$context`. + +```php +getOwnerId() === $identity->getId(); + } + // This should not happen since this assertion should only be + // called when the 'edit' permission is checked + return true; + } +} +``` +## Configuring Assertions + +Dynamic assertions are configured in LmcRbac via an assertion map defined in the LmcRbac configuration where assertions +are associated with permissions. + +The `assertion_map` key in the configuration is used to define the assertion map. If an assertion needs to be created via +a factory, use the `assertion_manager` config key. The Assertion Manager is a standard +plugin manager and its configuration should be a service manager configuration array. + +```php + [ + /* the rest of the file */ + 'assertion_map' => [ + 'edit' => \My\Namespace\MyAssertion::class, + ], + 'assertion_manager' => [ + 'factories' => [ + \My\Namespace\MyAssertion::class => \Laminas\ServiceManager\Factory\InvokableFactory::class + ], + ], + ], +]; +``` +It is also possible to configure an assertion using a callable instead of a class: + +```php + [ + /* the rest of the file */ + 'assertion_map' => [ + 'edit' => function assert(string $permission, IdentityInterface $identity = null, $context = null): bool + { + // for 'edit' permission + if ('edit' === $permission) { + /** @var MyObjectClass $context */ + return $context->getOwnerId() === $identity->getId(); + } + // This should not happen since this assertion should only be + // called when the 'edit' permission is checked + return true; + }, + ], + ], +]; +``` +## Dynamic Assertion sets + +LmcRbac supports the creation of dynamic assertion sets where multiple assertions can be combined using 'and/or' logic. +Assertion sets are configured by associating an array of assertions to a permission in the assertion map: + +```php + [ + /* the rest of the file */ + 'assertion_map' => [ + 'edit' => [ + \My\Namespace\AssertionA::class, + \My\Namespace\AssertionB::class, + ], + 'read' => [ + 'condition' => \LmcRbac\Assertion\AssertionSet::CONDITION_OR, + \My\Namespace\AssertionC::class, + \My\Namespace\AssertionD::class, + ], + 'delete' => [ + 'condition' => \LmcRbac\Assertion\AssertionSet::CONDITION_OR, + \My\Namespace\AssertionE::class, + [ + 'condition' => \LmcRbac\Assertion\AssertionSet::CONDITION_AND, + \My\Namespace\AssertionF::class, + \My\Namespace\AssertionC::class, + ], + ], + /** the rest of the file */ + ], +]; +``` +By default, an assertion set combines assertions using a 'and' condition. This is demonstrated by the map associated with +the `'edit'` permission above. + +It is possible to combine assertions using a 'or' condition by adding a `condition` equal to `AssertionSet::CONDITION_OR` +to the assertion set as demonstrated by the map associated with the `'read'` permission above. + +Furthermore, it is possible to nest assertion sets in order to create more complex logic as demonstrated by the map +associated with the `'delete'` permission above. + +The default logic is to combine assertions using 'and' logic but this can be explicitly set as shown above for `'delete'` +permission. + diff --git a/docs/versioned_docs/version-1.4/authorization-service.md b/docs/versioned_docs/version-1.4/authorization-service.md new file mode 100644 index 0000000..22b510a --- /dev/null +++ b/docs/versioned_docs/version-1.4/authorization-service.md @@ -0,0 +1,33 @@ +--- +sidebar_label: Authorization service +sidebar_position: 5 +title: Authorization Service +--- + +### Usage + +The Authorization service can be retrieved from the service manager using the name +`LmcRbac\Service\AuthorizationServiceInterface` and injected into your code: + +```php +get(LmcRbac\Service\AuthorizationServiceInterface::class); + +``` +### Reference + +`LmcRbac\Service\AuthorizationServiceInterface` defines the following method: + +- `isGranted(?IdentityInterface $identity, string $permission, $context = null): bool` + +| Parameter | Description | +|----------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `$identity` | The identity whose roles to checks.
If `$identity` is null, then the `guest` is used.
The `guest` role is definable via configuration and defaults to `'guest'`. | +| `$permission` | The permission to check against | +| `$context` | A context that will be passed to dynamic assertions that are defined for the permission | + +More on dynamic assertions can be found in the [Assertions](assertions.md) section. + +More on the `guest` role can be found in the [Configuration](configuration.md) section. + diff --git a/docs/versioned_docs/version-1.4/concepts.md b/docs/versioned_docs/version-1.4/concepts.md new file mode 100644 index 0000000..f49a88d --- /dev/null +++ b/docs/versioned_docs/version-1.4/concepts.md @@ -0,0 +1,44 @@ +--- +sidebar_label: Concepts +sidebar_position: 2 +title: Concepts +--- + +[Role-Based Access Control (RBAC)](https://en.wikipedia.org/wiki/Role-based_access_control) +is an approach to restricting system access to authorized users by putting emphasis +on roles and their permissions. + +In the RBAC model: + +- an **identity** has one of more roles. +- a **role** has one of more permissions. +- a **permission** is typically an action like "read", "write", "delete". +- a **role** can have **child roles** thus providing a hierarchy of roles where a role will inherit the permissions of all its child roles. + +## Authorization + +An identity will be authorized to perform an action, such as accessing a resource, if it is granted +the permission that controls the execution of the action. + +For example, deleting an item could be restricted to identities that have at least one role that has the +`item.delete` permission. This could be implemented by defining a `member` role that has the `item.delete` and assigning +this role of an authenticated user. + +## Dynamic Assertions + +In some cases, just checking if the identity has the `item.delete` permission is not enough. +It would also be necessary to check, for example, that the `item` belongs to the identity. Dynamic assertion allow +to specify some extra checks before granting access to perform an action such as, in this case, being the owner of the +resource. + +## Identities + +An identity is typically provided by an authentication process within the application. + +Authentication is not in the scope of `LmcRbac` and it is assumed that an identity entity providing assigned roles +is available when using the authorization service. If no identity is available, as it would be the case when no user is "logged in", +then a guest role is assumed. + + + + diff --git a/docs/versioned_docs/version-1.4/configuration.md b/docs/versioned_docs/version-1.4/configuration.md new file mode 100644 index 0000000..9c1d32d --- /dev/null +++ b/docs/versioned_docs/version-1.4/configuration.md @@ -0,0 +1,18 @@ +--- +sidebar_label: Configuration +sidebar_position: 7 +title: Configuring LmcRbac +--- + +LmcRbac is configured via the `lmc_rbac` key in the application config. + +This is typically achieved by creating +a `config/autoload/lmcrbac.global.php` file. A sample configuration file is provided in the `config/` folder. + +## Reference + +| Key | Description | +|--|------------------------------------------------------------------------------------------------------------------------------------------------| +| `guest_role` | Defines the name of the `guest` role when no identity exists.
Defaults to `'guest'`. | +| `assertion_map` | Defines the dynamic assertions that are associated to permissions.
Defaults to `[]`.
See the [Dynamic Assertions](assertions) section. | +| `role_provider` | Defines the role provider.
Defaults to `[]`
See the [Role Providers](role-providers) section. | diff --git a/docs/versioned_docs/version-1.4/gettingstarted.md b/docs/versioned_docs/version-1.4/gettingstarted.md new file mode 100644 index 0000000..293ff0e --- /dev/null +++ b/docs/versioned_docs/version-1.4/gettingstarted.md @@ -0,0 +1,34 @@ +--- +sidebar_label: Getting Started +sidebar_position: 1 +title: Get started +--- +## Requirements + +- PHP 7.3 or higher + +:::warning +The code is continuously tested against PHP 8.1 and higher only. There is no warranty that it will work for PHP 8.0 and lower. +::: + +## Installation + +LmcRbac only officially supports installation through Composer. + +Install the module: + +```sh +$ composer require lm-commons/lmc-rbac "~1.0" +``` + +You will be prompted by the `laminas-component-installer` plugin to inject LM-Commons\LmcRbac. + +:::note +**Manual installation:** + +Enable the module by adding `LmcRbac` key to your `application.config.php` or `modules.config.php` file for Laminas MVC +applications, or to the `config/config.php` file for Mezzio applications. +::: + +Customize the module by copy-pasting +the `lmcrbac.global.php` file to your `config/autoload` folder. diff --git a/docs/docs/migration.md b/docs/versioned_docs/version-1.4/migration.md similarity index 100% rename from docs/docs/migration.md rename to docs/versioned_docs/version-1.4/migration.md diff --git a/docs/versioned_docs/version-1.4/quickstart.md b/docs/versioned_docs/version-1.4/quickstart.md new file mode 100644 index 0000000..b19cde3 --- /dev/null +++ b/docs/versioned_docs/version-1.4/quickstart.md @@ -0,0 +1,129 @@ +--- +sidebar_label: Quick start +sidebar_position: 3 +title: Quick Start +--- + +Once the library has been installed by Composer, you will need to copy the +`config/lmcrbac.global.php` file from `LmcRbac` to the `config/autoload` folder. + +:::note +On older versions of `LmcRbac`, the configuration file is named `config/config.global.php`. +::: + +## Defining roles + +By default, no roles and no permissions are defined. + +Roles and permissions are defined by a Role Provider. `LmcRbac` ships with two roles providers: +- a simple `InMemoryRoleProvider` that uses an associative array to define roles and their permission. This is the default. +- a `ObjectRepositoyRoleProvider` that is based on Doctrine ORM. + +To quickly get started, let's use the `InMemoryRoleProvider` role provider. + +In the `config/autoload/lmcrbac.global.php`, add the following: + +```php + [ + 'role_provider' => [ + 'LmcRbac\Role\InMemoryRoleProvider' => [ + 'guest', + 'user' => [ + 'permissions' => ['create', 'edit'], + ], + 'admin' => [ + 'children' => ['user'], + 'permissions' => ['delete'], + ], + ], + ], + ], +]; +``` + +This defines 3 roles: a `guest` role, a `user` role having 2 permissions, and a `admin` role which has the `user` role as +a child and with its own permission. If the hierarchy is flattened: + +- `guest` has no permission +- `user` has permissions `create` and `edit` +- `admin` has permissions `create`, `edit` and `delete` + +## Basic authorization + +The authorization service can get retrieved from service manager container and used to check if a permission +is granted to an identity: + +```php +get('\LmcRbac\Service\AuthorizationServiceInterface'); + + /** @var \LmcRbac\Identity\IdentityInterface $identity */ + if ($authorizationService->isGranted($identity, 'create')) { + /** do something */ + } +``` + +If `$identity` has the role `user` and/or `admin` then the authorization is granted. If the identity has the role `guest`, then authorization +is denied. + +:::info +If `$identity` is null (no identity), then the guest role is assumed which is set to `'guest'` by default. The guest role +can be configured in the `lmcrbac.config.php` file. More on this in the [Configuration](configuration.md) section. +::: + +:::warning +`LmcRbac` does not provide any logic to instantiate an identity entity. It is assumed that +the application will instantiate an entity that implements `\LmcRbac\Identity\IdentityInterface` which defines the `getRoles()` +method. +::: + +## Using assertions + +Even if an identity has the `user` role granting it the `edit` permission, it should not have the authorization to edit another identity's resource. + +This can be achieved using dynamic assertion. + +An assertion is a function that implements the `\LmcRbac\Assertion\AssertionInterface` and is configured in the configuration +file. + +Let's modify the `lmcrbac.config.php` file as follows: + +```php + [ + 'role_provider' => [ + /* roles and permissions + ], + 'assertion_map' => [ + 'edit' => function ($permission, IdentityInterface $identity = null, $resource = null) { + if ($resource->getOwnerId() === $identity->getId() { + return true; + } else { + return false; + } + ], + ], +]; +``` + +Then use the authorization service passing the resource (called a 'context') in addition to the permission: + +```php +get('\LmcRbac\Service\AuthorizationServiceInterface'); + + /** @var \LmcRbac\Identity\IdentityInterface $identity */ + if ($authorizationService->isGranted($identity, 'edit', $resource)) { + /** do something */ + } +``` + +Dynanmic assertions are further discussed in the [Dynamic Assertions](assertions) section. diff --git a/docs/versioned_docs/version-1.4/role-providers.md b/docs/versioned_docs/version-1.4/role-providers.md new file mode 100644 index 0000000..a65654e --- /dev/null +++ b/docs/versioned_docs/version-1.4/role-providers.md @@ -0,0 +1,193 @@ +--- +sidebar_label: Roles and Role providers +title: Roles and Role providers +sidebar_position: 4 +--- + +## Role types + +A role is an object that returns a list of permissions that the role has. + +`LmcRbac` support two types of roles: hierarchical roles and flat roles. + +### Flat roles + +A flat role is the simplest role object. It contains the list of permissions that +the role has. + +Flat roles are defined using by the `LmcRbac\Role\Role` class or by classes +implementing the `LmcRbac\Role\RoleInterface`. + +### Hierarchical roles + +A hierarchical role is a role that has child roles and therefore provides +a hierarchy of roles where a role inherit the permissions of all its child roles. + +For example, a 'user' role may have the 'read' and 'write' permissions, and a 'admin' role +may inherit the permissions of the 'user' role plus an additional 'delete' role. In this structure, +the 'admin' role will have 'user' as its child role. + +Hierarchical roles may have flat roles or hierarchical roles as children. + +Hierarchical roles are defined using by the `LmcRbac\Role\HierarchicalRole` class or by classes +implementing the `LmcRbac\Role\HierarchicalRoleInterface`. + +## Role Providers +A role provider is an object that returns a list of roles. A role provider must implement the +`LmcRbac\Role\RoleProviderInterface` interface. The only required method is `getRoles`, and must return an array +of `LmcRbac\Role\RoleInterface` objects. + +Roles can come from one of many sources: in memory, from a file, from a database, etc. However, you can specify only one role provider per application. + +### Built-in role providers + +LmcRbac comes with two built-in role providers: `LmcRbac\Role\InMemoryRoleProvider` and `LmcRbac\Role\ObjectRepositoryRoleProvider`. A role +provider must be added to the `role_provider` subkey in the configuration file: + +```php +return [ + 'lmc_rbac' => [ + 'role_provider' => [ + // Role provider config here! + ] + ] +]; +``` + +### `LmcRbac\Role\InMemoryRoleProvider` + +This provider is ideal for small/medium sites with few roles/permissions. All the data is specified in a simple associative array in a +PHP file. + +Here is an example of the format you need to use: + +```php +return [ + 'lmc_rbac' => [ + 'role_provider' => [ + 'LmcRbac\Role\InMemoryRoleProvider' => [ + 'admin' => [ + 'children' => ['member'], + 'permissions' => ['article.delete'] + ], + 'member' => [ + 'children' => ['guest'], + 'permissions' => ['article.edit', 'article.archive'] + ], + 'guest' => [ + 'permissions' => ['article.read'] + ], + ], + ], + ], +]; +``` + +The `children` and `permissions` subkeys are entirely optional. Internally, the `LmcRbac\Role\InMemoryRoleProvider` creates +either a `LmcRbac\Role\Role` object if the role does not have any children, or a `LmcRbac\Role\HierarchicalRole` if +the role has at least one child. + +If you are more confident with flat RBAC, the previous config can be re-written to remove any inheritence between roles: + +```php +return [ + 'lmc_rbac' => [ + 'role_provider' => [ + 'LmcRbac\Role\InMemoryRoleProvider' => [ + 'admin' => [ + 'permissions' => [ + 'article.delete', + 'article.edit', + 'article.archive', + 'article.read' + ] + ], + 'member' => [ + 'permissions' => [ + 'article.edit', + 'article.archive', + 'article.read' + ] + ], + 'guest' => [ + 'permissions' => ['article.read'] + ] + ] + ] + ] +]; +``` + +### `LmcRbac\Role\ObjectRepositoryRoleProvider` + +This provider fetches roles from a database using `Doctrine\Common\Persistence\ObjectRepository` interface. + +You can configure this provider by giving an object repository service name that is fetched from the service manager +using the `object_repository` key: + +```php +return [ + 'lmc_rbac' => [ + 'role_provider' => [ + 'LmcRbac\Role\ObjectRepositoryRoleProvider' => [ + 'object_repository' => 'App\Repository\RoleRepository', + 'role_name_property' => 'name' + ], + ], + ], +]; +``` + +Or you can specify the `object_manager` and `class_name` options: + +```php +return [ + 'lmc_rbac' => [ + 'role_provider' => [ + 'LmcRbac\Role\ObjectRepositoryRoleProvider' => [ + 'object_manager' => 'doctrine.entitymanager.orm_default', + 'class_name' => 'App\Entity\Role', + 'role_name_property' => 'name' + ], + ], + ], +]; +``` + +In both cases, you need to specify the `role_name_property` value, which is the name of the entity's property +that holds the actual role name. This is used internally to only load the identity roles, instead of loading +the whole table every time. + +Please note that your entity fetched from the table MUST implement the `LmcRbac\Role\RoleInterface` interface. + +Sample ORM entity models are provided in the `/data` folder for flat role, hierarchical role and permission. + +## Creating custom role providers + +To create a custom role provider, you first need to create a class that implements the +`LmcRbac\Role\RoleProviderInterface` interface. + +Then, you need to add it to the role provider manager: + +```php +return [ + 'lmc_rbac' => [ + 'role_provider' => [ + 'Application\Role\CustomRoleProvider' => [ + // Options + ], + ], + ], +]; +``` +And the role provider is created using the service manager: +```php +return [ + 'service_manager' => [ + 'factories' => [ + 'Application\Role\CustomRoleProvider' => 'Application\Factory\CustomRoleProviderFactory' + ], + ], +]; +``` + diff --git a/docs/versioned_sidebars/version-1.4-sidebars.json b/docs/versioned_sidebars/version-1.4-sidebars.json new file mode 100644 index 0000000..5f41a72 --- /dev/null +++ b/docs/versioned_sidebars/version-1.4-sidebars.json @@ -0,0 +1,8 @@ +{ + "documentationSidebar": [ + { + "type": "autogenerated", + "dirName": "." + } + ] +} diff --git a/docs/versions.json b/docs/versions.json new file mode 100644 index 0000000..df2d553 --- /dev/null +++ b/docs/versions.json @@ -0,0 +1,3 @@ +[ + "1.4" +] diff --git a/docs/yarn.lock b/docs/yarn.lock index c6c1e9c..04f9823 100644 --- a/docs/yarn.lock +++ b/docs/yarn.lock @@ -1225,7 +1225,7 @@ "@docsearch/css" "3.6.1" algoliasearch "^4.19.1" -"@docusaurus/core@3.5.2", "@docusaurus/core@^3.4.0": +"@docusaurus/core@3.5.2", "@docusaurus/core@^3.5.2": version "3.5.2" resolved "https://registry.yarnpkg.com/@docusaurus/core/-/core-3.5.2.tgz#3adedb90e7b6104592f1231043bd6bf91680c39c" integrity sha512-4Z1WkhCSkX4KO0Fw5m/Vuc7Q3NxBG53NE5u59Rs96fWkMPZVSrzEPP16/Nk6cWb/shK7xXPndTmalJtw7twL/w== @@ -1347,7 +1347,7 @@ vfile "^6.0.1" webpack "^5.88.1" -"@docusaurus/module-type-aliases@3.5.2", "@docusaurus/module-type-aliases@^3.4.0": +"@docusaurus/module-type-aliases@3.5.2", "@docusaurus/module-type-aliases@^3.5.2": version "3.5.2" resolved "https://registry.yarnpkg.com/@docusaurus/module-type-aliases/-/module-type-aliases-3.5.2.tgz#4e8f9c0703e23b2e07ebfce96598ec83e4dd2a9e" integrity sha512-Z+Xu3+2rvKef/YKTMxZHsEXp1y92ac0ngjDiExRdqGTmEKtCUpkbNYH8v5eXo5Ls+dnW88n6WTa+Q54kLOkwPg== @@ -1479,7 +1479,7 @@ sitemap "^7.1.1" tslib "^2.6.0" -"@docusaurus/preset-classic@^3.4.0": +"@docusaurus/preset-classic@^3.5.2": version "3.5.2" resolved "https://registry.yarnpkg.com/@docusaurus/preset-classic/-/preset-classic-3.5.2.tgz#977f78510bbc556aa0539149eef960bb7ab52bd9" integrity sha512-3ihfXQ95aOHiLB5uCu+9PRy2gZCeSZoDcqpnDvf3B+sTrMvMTr8qRUzBvWkoIqc82yG5prCboRjk1SVILKx6sg== @@ -1577,7 +1577,7 @@ fs-extra "^11.1.1" tslib "^2.6.0" -"@docusaurus/types@3.5.2", "@docusaurus/types@^3.4.0": +"@docusaurus/types@3.5.2", "@docusaurus/types@^3.5.2": version "3.5.2" resolved "https://registry.yarnpkg.com/@docusaurus/types/-/types-3.5.2.tgz#058019dbeffbee2d412c3f72569e412a727f9608" integrity sha512-N6GntLXoLVUwkZw7zCxwy9QiuEXIcTVzA9AkmNw16oc0AP3SXLrMmDMMBIfgqwuKWa6Ox6epHol9kMtJqekACw== From 1a728f80ee3a057b736f8b8376c95a5632709cce Mon Sep 17 00:00:00 2001 From: "Eric Richer eric.richer@vistoconsulting.com" Date: Thu, 22 Aug 2024 16:22:31 -0400 Subject: [PATCH 2/9] Removed blog from nav Signed-off-by: Eric Richer eric.richer@vistoconsulting.com --- docs/docusaurus.config.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/docusaurus.config.js b/docs/docusaurus.config.js index a512cc1..71ae911 100644 --- a/docs/docusaurus.config.js +++ b/docs/docusaurus.config.js @@ -84,7 +84,8 @@ themeConfig: type: "docsVersionDropdown", position: "right", }, - {to: '/blog', label: 'Blog', position: 'left'}, + +// {to: '/blog', label: 'Blog', position: 'right'}, { href: 'https://lm-commons.github.io', label: 'LM-Commons', From b9536a691dc68bbb043af56ecef457766252b9f4 Mon Sep 17 00:00:00 2001 From: "Eric Richer eric.richer@vistoconsulting.com" Date: Thu, 22 Aug 2024 16:22:49 -0400 Subject: [PATCH 3/9] Adding Guide section first commit Signed-off-by: Eric Richer eric.richer@vistoconsulting.com --- docs/docs/Guides/laminas-mvc.md | 6 ++++++ docs/docs/Guides/mezzio.md | 7 +++++++ 2 files changed, 13 insertions(+) create mode 100644 docs/docs/Guides/laminas-mvc.md create mode 100644 docs/docs/Guides/mezzio.md diff --git a/docs/docs/Guides/laminas-mvc.md b/docs/docs/Guides/laminas-mvc.md new file mode 100644 index 0000000..ec602df --- /dev/null +++ b/docs/docs/Guides/laminas-mvc.md @@ -0,0 +1,6 @@ +--- +sidebar_label: Laminas MVC +sidebar_position: 1 +title: Integrating in Laminas MVC applications +--- + diff --git a/docs/docs/Guides/mezzio.md b/docs/docs/Guides/mezzio.md new file mode 100644 index 0000000..66c8e2b --- /dev/null +++ b/docs/docs/Guides/mezzio.md @@ -0,0 +1,7 @@ +--- +sidebar_label: Mezzio +sidebar_position: 2 +title: Integrating in Mezzio applications +--- + + From e06c9fac8ed6e2f6d6636c68b1a4bb0b5911d110 Mon Sep 17 00:00:00 2001 From: "Eric Richer eric.richer@vistoconsulting.com" Date: Thu, 22 Aug 2024 16:52:27 -0400 Subject: [PATCH 4/9] Removed PermissionInterface Signed-off-by: Eric Richer eric.richer@vistoconsulting.com --- composer.json | 1 + composer.lock | 122 +++++++++++++----- data/FlatRole.php.dist | 11 +- data/HierarchicalRole.php.dist | 8 +- data/Permission.php.dist | 3 +- data/Role.php.dist | 12 +- src/Assertion/AssertionInterface.php | 3 +- src/Assertion/AssertionSet.php | 4 +- src/Permission/PermissionInterface.php | 17 --- src/Rbac.php | 3 +- src/RbacInterface.php | 3 +- src/Role/Role.php | 10 +- src/Role/RoleInterface.php | 6 +- src/Service/AuthorizationService.php | 23 ++-- src/Service/AuthorizationServiceInterface.php | 9 +- test/Asset/Permission.php | 14 +- test/Asset/Role.php | 35 ++--- test/Asset/SimpleAssertion.php | 5 +- 18 files changed, 145 insertions(+), 144 deletions(-) delete mode 100644 src/Permission/PermissionInterface.php diff --git a/composer.json b/composer.json index fa787da..fc1b1ae 100644 --- a/composer.json +++ b/composer.json @@ -40,6 +40,7 @@ ], "require": { "php": "^8.1 || ^8.2 || ^8.3", + "laminas/laminas-permissions-rbac": "^3.0", "laminas/laminas-servicemanager": "^3.3", "laminas/laminas-stdlib": "^3.1", "doctrine/persistence": "^2.0 || ^3.0" diff --git a/composer.lock b/composer.lock index e651ca7..c23f0a1 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "27f8cc606345644071b208a518951361", + "content-hash": "6c21a5408719a4bb6be4cc83ac4d7ac6", "packages": [ { "name": "doctrine/event-manager", @@ -194,6 +194,66 @@ ], "time": "2024-06-20T10:14:30+00:00" }, + { + "name": "laminas/laminas-permissions-rbac", + "version": "3.6.0", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-permissions-rbac.git", + "reference": "6699e9b95fb360b921b2e6d655977d4786da0443" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-permissions-rbac/zipball/6699e9b95fb360b921b2e6d655977d4786da0443", + "reference": "6699e9b95fb360b921b2e6d655977d4786da0443", + "shasum": "" + }, + "require": { + "php": "~8.1.0 || ~8.2.0 || ~8.3.0" + }, + "conflict": { + "zendframework/zend-permissions-rbac": "*" + }, + "require-dev": { + "laminas/laminas-coding-standard": "~2.5.0", + "phpunit/phpunit": "^10.1.2", + "psalm/plugin-phpunit": "^0.18.4", + "vimeo/psalm": "^5.10" + }, + "type": "library", + "autoload": { + "psr-4": { + "Laminas\\Permissions\\Rbac\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "Provides a role-based access control management", + "homepage": "https://laminas.dev", + "keywords": [ + "authorization", + "laminas", + "laminas-permissions-rbac", + "rbac" + ], + "support": { + "chat": "https://laminas.dev/chat", + "docs": "https://docs.laminas.dev/laminas-permissions-rbac/", + "forum": "https://discourse.laminas.dev", + "issues": "https://github.com/laminas/laminas-permissions-rbac/issues", + "rss": "https://github.com/laminas/laminas-permissions-rbac/releases.atom", + "source": "https://github.com/laminas/laminas-permissions-rbac" + }, + "funding": [ + { + "url": "https://funding.communitybridge.org/projects/laminas-project", + "type": "community_bridge" + } + ], + "time": "2023-11-27T21:55:58+00:00" + }, { "name": "laminas/laminas-servicemanager", "version": "3.22.1", @@ -677,26 +737,26 @@ }, { "name": "composer/pcre", - "version": "3.2.0", + "version": "3.3.0", "source": { "type": "git", "url": "https://github.com/composer/pcre.git", - "reference": "ea4ab6f9580a4fd221e0418f2c357cdd39102a90" + "reference": "1637e067347a0c40bbb1e3cd786b20dcab556a81" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/pcre/zipball/ea4ab6f9580a4fd221e0418f2c357cdd39102a90", - "reference": "ea4ab6f9580a4fd221e0418f2c357cdd39102a90", + "url": "https://api.github.com/repos/composer/pcre/zipball/1637e067347a0c40bbb1e3cd786b20dcab556a81", + "reference": "1637e067347a0c40bbb1e3cd786b20dcab556a81", "shasum": "" }, "require": { "php": "^7.4 || ^8.0" }, "conflict": { - "phpstan/phpstan": "<1.11.8" + "phpstan/phpstan": "<1.11.10" }, "require-dev": { - "phpstan/phpstan": "^1.11.8", + "phpstan/phpstan": "^1.11.10", "phpstan/phpstan-strict-rules": "^1.1", "phpunit/phpunit": "^8 || ^9" }, @@ -736,7 +796,7 @@ ], "support": { "issues": "https://github.com/composer/pcre/issues", - "source": "https://github.com/composer/pcre/tree/3.2.0" + "source": "https://github.com/composer/pcre/tree/3.3.0" }, "funding": [ { @@ -752,7 +812,7 @@ "type": "tidelift" } ], - "time": "2024-07-25T09:36:02+00:00" + "time": "2024-08-19T19:43:53+00:00" }, { "name": "composer/semver", @@ -2415,32 +2475,32 @@ }, { "name": "phpunit/php-code-coverage", - "version": "10.1.15", + "version": "10.1.16", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "5da8b1728acd1e6ffdf2ff32ffbdfd04307f26ae" + "reference": "7e308268858ed6baedc8704a304727d20bc07c77" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/5da8b1728acd1e6ffdf2ff32ffbdfd04307f26ae", - "reference": "5da8b1728acd1e6ffdf2ff32ffbdfd04307f26ae", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/7e308268858ed6baedc8704a304727d20bc07c77", + "reference": "7e308268858ed6baedc8704a304727d20bc07c77", "shasum": "" }, "require": { "ext-dom": "*", "ext-libxml": "*", "ext-xmlwriter": "*", - "nikic/php-parser": "^4.18 || ^5.0", + "nikic/php-parser": "^4.19.1 || ^5.1.0", "php": ">=8.1", - "phpunit/php-file-iterator": "^4.0", - "phpunit/php-text-template": "^3.0", - "sebastian/code-unit-reverse-lookup": "^3.0", - "sebastian/complexity": "^3.0", - "sebastian/environment": "^6.0", - "sebastian/lines-of-code": "^2.0", - "sebastian/version": "^4.0", - "theseer/tokenizer": "^1.2.0" + "phpunit/php-file-iterator": "^4.1.0", + "phpunit/php-text-template": "^3.0.1", + "sebastian/code-unit-reverse-lookup": "^3.0.0", + "sebastian/complexity": "^3.2.0", + "sebastian/environment": "^6.1.0", + "sebastian/lines-of-code": "^2.0.2", + "sebastian/version": "^4.0.1", + "theseer/tokenizer": "^1.2.3" }, "require-dev": { "phpunit/phpunit": "^10.1" @@ -2452,7 +2512,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "10.1-dev" + "dev-main": "10.1.x-dev" } }, "autoload": { @@ -2481,7 +2541,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/10.1.15" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/10.1.16" }, "funding": [ { @@ -2489,7 +2549,7 @@ "type": "github" } ], - "time": "2024-06-29T08:25:15+00:00" + "time": "2024-08-22T04:31:57+00:00" }, { "name": "phpunit/php-file-iterator", @@ -2897,16 +2957,16 @@ }, { "name": "psr/log", - "version": "3.0.0", + "version": "3.0.1", "source": { "type": "git", "url": "https://github.com/php-fig/log.git", - "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" + "reference": "79dff0b268932c640297f5208d6298f71855c03e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", - "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", + "url": "https://api.github.com/repos/php-fig/log/zipball/79dff0b268932c640297f5208d6298f71855c03e", + "reference": "79dff0b268932c640297f5208d6298f71855c03e", "shasum": "" }, "require": { @@ -2941,9 +3001,9 @@ "psr-3" ], "support": { - "source": "https://github.com/php-fig/log/tree/3.0.0" + "source": "https://github.com/php-fig/log/tree/3.0.1" }, - "time": "2021-07-14T16:46:02+00:00" + "time": "2024-08-21T13:31:24+00:00" }, { "name": "sebastian/cli-parser", diff --git a/data/FlatRole.php.dist b/data/FlatRole.php.dist index 9bd49ea..90efba8 100644 --- a/data/FlatRole.php.dist +++ b/data/FlatRole.php.dist @@ -21,7 +21,6 @@ declare(strict_types=1); use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping as ORM; -use Lmc\Rbac\Permission\PermissionInterface; use Lmc\Rbac\Role\RoleInterface; /** @@ -101,11 +100,9 @@ class FlatRole implements RoleInterface /** * @inheritDoc */ - public function addPermission(PermissionInterface|string $permission): void + public function addPermission(string $permission): void { - if (is_string($permission)) { - $permission = new Permission($permission); - } + $permission = new Permission($permission); $this->permissions[(string) $permission] = $permission; } @@ -113,12 +110,12 @@ class FlatRole implements RoleInterface /** * @inheritDoc */ - public function hasPermission(PermissionInterface|string $permission): bool + public function hasPermission(string $permission): bool { // This can be a performance problem if your role has a lot of permissions. Please refer // to the cookbook to an elegant way to solve this issue - return isset($this->permissions[(string) $permission]); + return isset($this->permissions[$permission]); } diff --git a/data/HierarchicalRole.php.dist b/data/HierarchicalRole.php.dist index ea4e54c..d3637f7 100644 --- a/data/HierarchicalRole.php.dist +++ b/data/HierarchicalRole.php.dist @@ -120,7 +120,7 @@ class HierarchicalRole implements RoleInterface * @param string|Permission $permission * @return void */ - public function addPermission(string|Permission|\Lmc\Rbac\Permission\PermissionInterface $permission): void + public function addPermission(string|Permission $permission): void { if (is_string($permission)) { $permission = new Permission($permission); @@ -130,15 +130,15 @@ class HierarchicalRole implements RoleInterface } /** - * @param $permission + * @param string $permission * @return bool */ - public function hasPermission(string|\Lmc\Rbac\Permission\PermissionInterface $permission): bool + public function hasPermission(string $permission): bool { // This can be a performance problem if your role has a lot of permissions. Please refer // to the cookbook to an elegant way to solve this issue - return isset($this->permissions[(string) $permission]); + return isset($this->permissions[$permission]); } /** diff --git a/data/Permission.php.dist b/data/Permission.php.dist index 841b383..d6fd224 100644 --- a/data/Permission.php.dist +++ b/data/Permission.php.dist @@ -19,7 +19,6 @@ declare(strict_types=1); */ use Doctrine\ORM\Mapping as ORM; -use Lmc\Rbac\Permission\PermissionInterface; /** * @ORM\Entity @@ -27,7 +26,7 @@ use Lmc\Rbac\Permission\PermissionInterface; */ #[ORM\Entity] #[ORM\Table(name: 'permissions')] -class Permission implements PermissionInterface +class Permission { /** * @var int|null diff --git a/data/Role.php.dist b/data/Role.php.dist index 5cc5202..8f1d79b 100644 --- a/data/Role.php.dist +++ b/data/Role.php.dist @@ -97,10 +97,10 @@ class Role implements RoleInterface /** * Add a permission * - * @param string|PermissionInterface $name + * @param string $name * @return void */ - public function addPermission(string|PermissionInterface $permission): void + public function addPermission(string $permission): void { $permission = new \Permission($permission); @@ -112,14 +112,14 @@ class Role implements RoleInterface return $this->name; } - public function hasPermission(string|PermissionInterface $permission): bool + public function hasPermission(string $permission): bool { - return isset($this->permissions[(string) $permission]); + return isset($this->permissions[$permission]); } - public function addChild(RoleInterface $child): void + public function addChild(RoleInterface $role): void { - $this->children[] = $child; + $this->children[] = $role; } public function getChildren(): iterable diff --git a/src/Assertion/AssertionInterface.php b/src/Assertion/AssertionInterface.php index e9f2137..b7f1485 100644 --- a/src/Assertion/AssertionInterface.php +++ b/src/Assertion/AssertionInterface.php @@ -22,7 +22,6 @@ namespace Lmc\Rbac\Assertion; use Lmc\Rbac\Identity\IdentityInterface; -use Lmc\Rbac\Permission\PermissionInterface; /** * Interface that you can implement for dynamic assertions @@ -32,7 +31,7 @@ interface AssertionInterface { public function assert( - PermissionInterface|string $permission, + string $permission, ?IdentityInterface $identity = null, mixed $context = null ): bool; diff --git a/src/Assertion/AssertionSet.php b/src/Assertion/AssertionSet.php index abfc42f..174911a 100644 --- a/src/Assertion/AssertionSet.php +++ b/src/Assertion/AssertionSet.php @@ -23,7 +23,6 @@ use Lmc\Rbac\Exception; use Lmc\Rbac\Identity\IdentityInterface; -use Lmc\Rbac\Permission\PermissionInterface; use function count; use function gettype; @@ -47,6 +46,7 @@ class AssertionSet implements AssertionInterface private string $condition = self::CONDITION_AND; /** + * @param AssertionPluginManagerInterface $assertionPluginManager * @param array $assertions */ public function __construct( @@ -70,7 +70,7 @@ public function __construct( } public function assert( - PermissionInterface|string $permission, + $permission, ?IdentityInterface $identity = null, mixed $context = null ): bool { diff --git a/src/Permission/PermissionInterface.php b/src/Permission/PermissionInterface.php deleted file mode 100644 index bebabee..0000000 --- a/src/Permission/PermissionInterface.php +++ /dev/null @@ -1,17 +0,0 @@ -permissions; } - public function addPermission(PermissionInterface|string $permission): void + public function addPermission(string $permission): void { - $this->permissions[$permission] = (string) $permission; + $this->permissions[$permission] = $permission; } - public function hasPermission(PermissionInterface|string $permission): bool + public function hasPermission(string $permission): bool { - if (isset($this->permissions[(string) $permission])) { + if (isset($this->permissions[$permission])) { return true; } diff --git a/src/Role/RoleInterface.php b/src/Role/RoleInterface.php index 6a28300..e90f885 100644 --- a/src/Role/RoleInterface.php +++ b/src/Role/RoleInterface.php @@ -21,8 +21,6 @@ namespace Lmc\Rbac\Role; -use Lmc\Rbac\Permission\PermissionInterface; - /** * Interface for a role * @@ -32,11 +30,11 @@ interface RoleInterface { public function getName(): string; - public function hasPermission(PermissionInterface|string $permission): bool; + public function hasPermission(string $permission): bool; public function addChild(RoleInterface $role): void; - public function addPermission(PermissionInterface $permission): void; + public function addPermission(string $permission): void; public function hasChildren(): bool; diff --git a/src/Service/AuthorizationService.php b/src/Service/AuthorizationService.php index bef3881..c549fde 100644 --- a/src/Service/AuthorizationService.php +++ b/src/Service/AuthorizationService.php @@ -25,7 +25,6 @@ use Lmc\Rbac\Assertion\AssertionPluginManagerInterface; use Lmc\Rbac\Assertion\AssertionSet; use Lmc\Rbac\Identity\IdentityInterface; -use Lmc\Rbac\Permission\PermissionInterface; use Lmc\Rbac\RbacInterface; use function array_merge; @@ -74,18 +73,18 @@ public function setAssertions(array $assertions, bool $merge = false): void * Set assertion for a given permission */ public function setAssertion( - PermissionInterface|string $permission, + string $permission, AssertionInterface|callable|string $assertion ): void { - $this->assertions[(string) $permission] = $assertion; + $this->assertions[$permission] = $assertion; } /** * Check if there are assertions for the permission */ - public function hasAssertion(PermissionInterface|string $permission): bool + public function hasAssertion(string $permission): bool { - return isset($this->assertions[(string) $permission]); + return isset($this->assertions[$permission]); } /** @@ -101,14 +100,14 @@ public function getAssertions(): array /** * Get the assertions for the given permission */ - public function getAssertion(PermissionInterface|string $permission): AssertionInterface|callable|string|null + public function getAssertion(string $permission): AssertionInterface|callable|string|null { - return $this->hasAssertion($permission) ? $this->assertions[(string) $permission] : null; + return $this->hasAssertion($permission) ? $this->assertions[$permission] : null; } public function isGranted( IdentityInterface|null $identity, - string|PermissionInterface $permission, + string $permission, mixed $context = null ): bool { $roles = $this->roleService->getIdentityRoles($identity, $context); @@ -121,14 +120,14 @@ public function isGranted( return false; } - if (empty($this->assertions[(string) $permission])) { + if (empty($this->assertions[$permission])) { return true; } - if (is_array($this->assertions[(string) $permission])) { - $permissionAssertions = $this->assertions[(string) $permission]; + if (is_array($this->assertions[$permission])) { + $permissionAssertions = $this->assertions[$permission]; } else { - $permissionAssertions = [$this->assertions[(string) $permission]]; + $permissionAssertions = [$this->assertions[$permission]]; } $assertionSet = new AssertionSet($this->assertionPluginManager, $permissionAssertions); diff --git a/src/Service/AuthorizationServiceInterface.php b/src/Service/AuthorizationServiceInterface.php index 1855a82..c5e6d43 100644 --- a/src/Service/AuthorizationServiceInterface.php +++ b/src/Service/AuthorizationServiceInterface.php @@ -23,7 +23,6 @@ use Lmc\Rbac\Assertion\AssertionInterface; use Lmc\Rbac\Identity\IdentityInterface; -use Lmc\Rbac\Permission\PermissionInterface; /** * Minimal interface for an authorization service @@ -35,7 +34,7 @@ interface AuthorizationServiceInterface */ public function isGranted( ?IdentityInterface $identity, - PermissionInterface|string $permission, + string $permission, mixed $context = null ): bool; @@ -48,14 +47,14 @@ public function setAssertions(array $assertions, bool $merge = false): void; * Set assertion for a given permission */ public function setAssertion( - PermissionInterface|string $permission, + string $permission, AssertionInterface|callable|string $assertion ): void; /** * Check if there are assertions for the permission */ - public function hasAssertion(PermissionInterface|string $permission): bool; + public function hasAssertion(string $permission): bool; /** * Get the assertions @@ -67,5 +66,5 @@ public function getAssertions(): array; /** * Get the assertions for the given permission */ - public function getAssertion(PermissionInterface|string $permission): AssertionInterface|callable|string|null; + public function getAssertion(string $permission): AssertionInterface|callable|string|null; } diff --git a/test/Asset/Permission.php b/test/Asset/Permission.php index 76cdc6e..c96d608 100644 --- a/test/Asset/Permission.php +++ b/test/Asset/Permission.php @@ -22,7 +22,6 @@ namespace LmcTest\Rbac\Asset; use Doctrine\ORM\Mapping as ORM; -use Lmc\Rbac\Permission\PermissionInterface; /** * @ORM\Entity @@ -30,11 +29,9 @@ */ #[ORM\Entity] #[ORM\Table(name: 'permissions')] -class Permission implements PermissionInterface +class Permission { /** - * @var int|null - * * @ORM\Id * @ORM\Column(type="integer") * @ORM\GeneratedValue(strategy="AUTO") @@ -44,17 +41,12 @@ class Permission implements PermissionInterface #[ORM\GeneratedValue(strategy: 'AUTO')] protected ?int $id; - /** - * @var string|null - * - * @ORM\Column(type="string", length=128, unique=true) - */ + /** @ORM\Column(type="string", length=128, unique=true) */ #[ORM\Column(type: 'string', length: 128, unique: true)] protected ?string $name; /** * Constructor - * @param string $name */ public function __construct(string $name) { @@ -63,8 +55,6 @@ public function __construct(string $name) /** * Get the permission identifier - * - * @return int|null */ public function getId(): ?int { diff --git a/test/Asset/Role.php b/test/Asset/Role.php index c2e8dda..745c175 100644 --- a/test/Asset/Role.php +++ b/test/Asset/Role.php @@ -1,4 +1,5 @@ name = $name; - $this->children = new ArrayCollection(); + $this->name = $name; + $this->children = new ArrayCollection(); $this->permissions = new ArrayCollection(); } /** * Get the role identifier - * - * @return int|null */ public function getId(): ?int { @@ -98,11 +84,8 @@ public function getId(): ?int /** * Add a permission - * - * @param string $name - * @return void */ - public function addPermission(string|PermissionInterface $permission): void + public function addPermission(string $permission): void { $permission = new Permission($permission); @@ -114,9 +97,9 @@ public function getName(): string return $this->name; } - public function hasPermission(PermissionInterface|string $permission): bool + public function hasPermission(string $permission): bool { - return isset($this->permissions[(string) $permission]); + return isset($this->permissions[$permission]); } public function addChild(RoleInterface $role): void diff --git a/test/Asset/SimpleAssertion.php b/test/Asset/SimpleAssertion.php index 1eba956..54d9e60 100644 --- a/test/Asset/SimpleAssertion.php +++ b/test/Asset/SimpleAssertion.php @@ -23,14 +23,11 @@ use Lmc\Rbac\Assertion\AssertionInterface; use Lmc\Rbac\Identity\IdentityInterface; -use Lmc\Rbac\Permission\PermissionInterface; class SimpleAssertion implements AssertionInterface { - /** @var int */ protected int $called = 0; - /** @var bool */ protected bool $willAssert; public function __construct(bool $willAssert = true) @@ -38,7 +35,7 @@ public function __construct(bool $willAssert = true) $this->willAssert = $willAssert; } - public function assert(string|PermissionInterface $permission, ?IdentityInterface $identity = null, $context = null): bool + public function assert(string $permission, ?IdentityInterface $identity = null, $context = null): bool { $this->called++; From 3449056284dd5a04a7d692672fa14121cf71ca0a Mon Sep 17 00:00:00 2001 From: "Eric Richer eric.richer@vistoconsulting.com" Date: Thu, 22 Aug 2024 17:41:06 -0400 Subject: [PATCH 5/9] Removed context in role service Signed-off-by: Eric Richer eric.richer@vistoconsulting.com --- src/Service/AuthorizationService.php | 2 +- src/Service/RoleService.php | 6 +++++- src/Service/RoleServiceInterface.php | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/Service/AuthorizationService.php b/src/Service/AuthorizationService.php index c549fde..ecae5ed 100644 --- a/src/Service/AuthorizationService.php +++ b/src/Service/AuthorizationService.php @@ -110,7 +110,7 @@ public function isGranted( string $permission, mixed $context = null ): bool { - $roles = $this->roleService->getIdentityRoles($identity, $context); + $roles = $this->roleService->getIdentityRoles($identity); if (empty($roles)) { return false; diff --git a/src/Service/RoleService.php b/src/Service/RoleService.php index 284b65c..a5ffa87 100644 --- a/src/Service/RoleService.php +++ b/src/Service/RoleService.php @@ -34,7 +34,9 @@ class RoleService implements RoleServiceInterface { protected RoleProviderInterface $roleProvider; + protected string $guestRole = ''; + public function __construct(RoleProviderInterface $roleProvider, string $guestRole) { $this->roleProvider = $roleProvider; @@ -43,6 +45,8 @@ public function __construct(RoleProviderInterface $roleProvider, string $guestRo /** * Set the guest role + * + * @deprecated The Guest role should be defined using the constructor */ public function setGuestRole(string $guestRole): void { @@ -62,7 +66,7 @@ public function getGuestRole(): string * * @return RoleInterface[] */ - public function getIdentityRoles(?IdentityInterface $identity = null, mixed $context = null): iterable + public function getIdentityRoles(?IdentityInterface $identity = null): iterable { // If no identity is provided, get the guest role if (null === $identity) { diff --git a/src/Service/RoleServiceInterface.php b/src/Service/RoleServiceInterface.php index ccc549b..ccd973d 100644 --- a/src/Service/RoleServiceInterface.php +++ b/src/Service/RoleServiceInterface.php @@ -34,5 +34,5 @@ interface RoleServiceInterface * * @return RoleInterface[] */ - public function getIdentityRoles(?IdentityInterface $identity = null, mixed $context = null): iterable; + public function getIdentityRoles(?IdentityInterface $identity = null): iterable; } From f0b4d5171c528a1e26ea857af5cd61de32ddd2c0 Mon Sep 17 00:00:00 2001 From: "Eric Richer eric.richer@vistoconsulting.com" Date: Mon, 26 Aug 2024 15:11:17 -0400 Subject: [PATCH 6/9] Replace role and rbac by laminas equivalent Signed-off-by: Eric Richer eric.richer@vistoconsulting.com --- UPGRADE.md | 16 ++-- data/FlatRole.php.dist | 33 ++++--- data/HierarchicalRole.php.dist | 39 +++++---- data/Permission.php.dist | 4 +- data/Role.php.dist | 27 ++++-- src/ConfigProvider.php | 3 +- src/Identity/IdentityInterface.php | 5 +- src/Rbac.php | 68 --------------- src/RbacInterface.php | 15 ---- src/Role/HierarchicalRole.php | 30 ------- src/Role/HierarchicalRoleInterface.php | 31 ------- src/Role/InMemoryRoleProvider.php | 2 + src/Role/Role.php | 87 ------------------- src/Role/RoleInterface.php | 43 --------- src/Role/RoleProviderInterface.php | 3 +- src/Service/AuthorizationService.php | 59 ++++++++----- src/Service/AuthorizationServiceFactory.php | 2 +- src/Service/AuthorizationServiceInterface.php | 4 +- src/Service/RoleService.php | 12 +-- src/Service/RoleServiceFactory.php | 16 ++-- src/Service/RoleServiceInterface.php | 2 +- test/Asset/Role.php | 24 +++-- test/ConfigProviderTest.php | 2 +- test/Rbac/{RbacTest.php => RbacTest.php.dist} | 0 test/Role/InMemoryRoleProviderTest.php | 2 +- .../Role/ObjectRepositoryRoleProviderTest.php | 4 +- test/Role/{RoleTest.php => RoleTest.php.dist} | 0 .../AuthorizationServiceFactoryTest.php | 2 +- test/Service/AuthorizationServiceTest.php | 13 ++- test/Service/RoleServiceTest.php | 25 +----- 30 files changed, 170 insertions(+), 403 deletions(-) delete mode 100644 src/Rbac.php delete mode 100644 src/RbacInterface.php delete mode 100644 src/Role/HierarchicalRole.php delete mode 100644 src/Role/HierarchicalRoleInterface.php delete mode 100644 src/Role/Role.php delete mode 100644 src/Role/RoleInterface.php rename test/Rbac/{RbacTest.php => RbacTest.php.dist} (100%) rename test/Role/{RoleTest.php => RoleTest.php.dist} (100%) diff --git a/UPGRADE.md b/UPGRADE.md index 896c6d4..7522a2d 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -2,20 +2,26 @@ ## From LmcRbac v1 to LmcRbac v2 -LmcRbac v2 is a major version upgrade with many breaking changes that prevent +LmcRbac v2 is a major version upgrade with many major breaking changes that prevent straightforward upgrading. +### Major changes + +- LmcRbac v2 is now based on Role and Rbac classes from `laminas-permissions-rbac`. All services have +been updated to use these classes and interfaces. + + ### Namespace change The namespace has been changed from LmcRbac to Lmc\Rbac. Please review your code to replace `LmcRbac` namespace by `Lmc\Rbac` namespace. -### Deprecations +### Deprecations and removals -- `Lmc\Rbac\HierarchicalRole` has been deprecated since `Lmc\Rbac\Role` now supports hierarchical roles. Flat roles -are just a simplified version of a hierarchical roles. Use `Lmc\Rbac\Role` instead. +- `LmcRbac\HierarchicalRole`, `LmcRbac\Role` and `LmcRbac\RoleInterface` has been deleted. +Use the equivalent classes from `laminas-permissions-rbac` instead. - The factories for services have been refactored from the `src/Container` folder - to be colocated with the service that a factory is creating. All factories in the `src/Container` has been deprecated. + to be colocated with the service that a factory is creating. All factories in the `src/Container` has been deleted. - The `AssertionContainer` class, interface and factory have been deprecated and replaced by `AssertionPluginManager` class, interface and factory. ## From ZfcRbac v3 to LmcRbac v1 diff --git a/data/FlatRole.php.dist b/data/FlatRole.php.dist index 90efba8..67d97ac 100644 --- a/data/FlatRole.php.dist +++ b/data/FlatRole.php.dist @@ -21,7 +21,7 @@ declare(strict_types=1); use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping as ORM; -use Lmc\Rbac\Role\RoleInterface; +use Laminas\Permissions\Rbac\RoleInterface; /** * @ORM\Entity @@ -44,12 +44,12 @@ class FlatRole implements RoleInterface protected ?int $id; /** - * @var string|null + * @var string * * @ORM\Column(type="string", length=255, unique=true) */ #[ORM\Column(name: 'name', type: 'string', length: 255, unique: true)] - protected ?string $name; + protected string $name = ''; /** * @var Permission[]|Collection @@ -100,26 +100,21 @@ class FlatRole implements RoleInterface /** * @inheritDoc */ - public function addPermission(string $permission): void + public function addPermission(string $name): void { - $permission = new Permission($permission); + $permission = new Permission($name); - $this->permissions[(string) $permission] = $permission; + $this->permissions[$name] = $permission; } /** * @inheritDoc */ - public function hasPermission(string $permission): bool + public function hasPermission(string $name): bool { - // This can be a performance problem if your role has a lot of permissions. Please refer - // to the cookbook to an elegant way to solve this issue - - return isset($this->permissions[$permission]); + return isset($this->permissions[$name]); } - - public function getChildren(): iterable { return []; @@ -130,8 +125,18 @@ class FlatRole implements RoleInterface return false; } - public function addChild(RoleInterface $role): void + public function addChild(RoleInterface $child): void { // Do nothing } + + public function addParent(RoleInterface $parent): void + { + // Do nothing + } + + public function getParents(): iterable + { + return []; + } } diff --git a/data/HierarchicalRole.php.dist b/data/HierarchicalRole.php.dist index d3637f7..560b470 100644 --- a/data/HierarchicalRole.php.dist +++ b/data/HierarchicalRole.php.dist @@ -21,7 +21,7 @@ declare(strict_types=1); use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping as ORM; -use Lmc\Rbac\Role\RoleInterface; +use Laminas\Permissions\Rbac\RoleInterface; /** * @ORM\Entity @@ -44,12 +44,12 @@ class HierarchicalRole implements RoleInterface protected ?int $id = null; /** - * @var string|null + * @var string * * @ORM\Column(type="string", length=48, unique=true) */ #[ORM\Column(name: 'name', type: 'string', length: 255, unique: true)] - protected ?string $name; + protected string $name = ''; /** * @var RoleInterface[]|Collection @@ -111,34 +111,34 @@ class HierarchicalRole implements RoleInterface * @param RoleInterface $child * @return void */ - public function addChild(RoleInterface $role): void + public function addChild(RoleInterface $child): void { - $this->children[] = $role; + $this->children[] = $child; } /** - * @param string|Permission $permission + * @param string|Permission $name * @return void */ - public function addPermission(string|Permission $permission): void + public function addPermission(string|Permission $name): void { - if (is_string($permission)) { - $permission = new Permission($permission); + if (is_string($name)) { + $permission = new Permission($name); } - $this->permissions[(string) $permission] = $permission; + $this->permissions[(string) $name] = $permission; } /** - * @param string $permission + * @param string $name * @return bool */ - public function hasPermission(string $permission): bool + public function hasPermission(string $name): bool { // This can be a performance problem if your role has a lot of permissions. Please refer // to the cookbook to an elegant way to solve this issue - return isset($this->permissions[$permission]); + return isset($this->permissions[$name]); } /** @@ -149,11 +149,18 @@ class HierarchicalRole implements RoleInterface return $this->children; } - /** - * @inheritDoc - */ public function hasChildren(): bool { return ! $this->children->isEmpty(); } + + public function addParent(RoleInterface $parent): void + { + // Do nothing + } + + public function getParents(): iterable + { + return []; + } } diff --git a/data/Permission.php.dist b/data/Permission.php.dist index d6fd224..2f986f9 100644 --- a/data/Permission.php.dist +++ b/data/Permission.php.dist @@ -41,12 +41,12 @@ class Permission protected ?int $id; /** - * @var string|null + * @var string * * @ORM\Column(type="string", length=128, unique=true) */ #[ORM\Column(type: 'string', length: 128, unique: true)] - protected ?string $name; + protected string $name = ''; /** * Constructor diff --git a/data/Role.php.dist b/data/Role.php.dist index 8f1d79b..d5e3c3b 100644 --- a/data/Role.php.dist +++ b/data/Role.php.dist @@ -21,8 +21,7 @@ declare(strict_types=1); use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping as ORM; -use Lmc\Rbac\Permission\PermissionInterface; -use Lmc\Rbac\Role\RoleInterface; +use Laminas\Permissions\Rbac\RoleInterface; /** * @ORM\Entity @@ -100,11 +99,11 @@ class Role implements RoleInterface * @param string $name * @return void */ - public function addPermission(string $permission): void + public function addPermission(string $name): void { - $permission = new \Permission($permission); + $permission = new \Permission($name); - $this->permissions[(string) $permission] = $permission; + $this->permissions[$name] = $permission; } public function getName(): string @@ -112,14 +111,14 @@ class Role implements RoleInterface return $this->name; } - public function hasPermission(string $permission): bool + public function hasPermission(string $name): bool { - return isset($this->permissions[$permission]); + return isset($this->permissions[$name]); } - public function addChild(RoleInterface $role): void + public function addChild(RoleInterface $child): void { - $this->children[] = $role; + $this->children[] = $child; } public function getChildren(): iterable @@ -131,4 +130,14 @@ class Role implements RoleInterface { return ! $this->children->isEmpty(); } + + public function addParent(RoleInterface $parent): void + { + // Do nothing + } + + public function getParents(): iterable + { + return []; + } } diff --git a/src/ConfigProvider.php b/src/ConfigProvider.php index 6d5ebc0..292a3da 100644 --- a/src/ConfigProvider.php +++ b/src/ConfigProvider.php @@ -21,13 +21,14 @@ namespace Lmc\Rbac; +use Laminas\Permissions\Rbac\Rbac; use Laminas\ServiceManager\Factory\InvokableFactory; use Lmc\Rbac\Assertion\AssertionPluginManager; use Lmc\Rbac\Assertion\AssertionPluginManagerFactory; use Lmc\Rbac\Assertion\AssertionPluginManagerInterface; use Lmc\Rbac\Options\ModuleOptions; use Lmc\Rbac\Options\ModuleOptionsFactory; -use Lmc\Rbac\Rbac; +//use Lmc\Rbac\Rbac; use Lmc\Rbac\Role\InMemoryRoleProvider; use Lmc\Rbac\Role\InMemoryRoleProviderFactory; use Lmc\Rbac\Role\ObjectRepositoryRoleProvider; diff --git a/src/Identity/IdentityInterface.php b/src/Identity/IdentityInterface.php index 831b6aa..32fc0be 100644 --- a/src/Identity/IdentityInterface.php +++ b/src/Identity/IdentityInterface.php @@ -21,11 +21,10 @@ namespace Lmc\Rbac\Identity; -use Lmc\Rbac\Role\RoleInterface; +use Laminas\Permissions\Rbac\RoleInterface; /** - * Interface for an identity - * + * Interface for an identity to provides roles */ interface IdentityInterface { diff --git a/src/Rbac.php b/src/Rbac.php deleted file mode 100644 index 282e575..0000000 --- a/src/Rbac.php +++ /dev/null @@ -1,68 +0,0 @@ -flattenRoles($roles) as $role) { - /** @var RoleInterface $role */ - if ($role->hasPermission($permission)) { - return true; - } - } - - return false; - } - - protected function flattenRoles(array $roles): Generator - { - foreach ($roles as $role) { - yield $role; - - if ($role->hasChildren()) { - yield from $this->flattenRoles($role->getChildren()); - } - } - } -} diff --git a/src/RbacInterface.php b/src/RbacInterface.php deleted file mode 100644 index 8b35b4c..0000000 --- a/src/RbacInterface.php +++ /dev/null @@ -1,15 +0,0 @@ -name = $name; - } - - public function getName(): string - { - return $this->name; - } - - public function getPermissions(): array - { - return $this->permissions; - } - - public function addPermission(string $permission): void - { - $this->permissions[$permission] = $permission; - } - - public function hasPermission(string $permission): bool - { - if (isset($this->permissions[$permission])) { - return true; - } - - foreach ($this->children as $child) { - if ($child->hasPermission($permission)) { - return true; - } - } - - return false; - } - - public function hasChildren(): bool - { - return ! empty($this->children); - } - - public function getChildren(): iterable - { - return $this->children; - } - - public function addChild(RoleInterface $role): void - { - $this->children[$role->getName()] = $role; - } -} diff --git a/src/Role/RoleInterface.php b/src/Role/RoleInterface.php deleted file mode 100644 index e90f885..0000000 --- a/src/Role/RoleInterface.php +++ /dev/null @@ -1,43 +0,0 @@ - */ - public function getChildren(): iterable; -} diff --git a/src/Role/RoleProviderInterface.php b/src/Role/RoleProviderInterface.php index d2e0e2e..754cbf4 100644 --- a/src/Role/RoleProviderInterface.php +++ b/src/Role/RoleProviderInterface.php @@ -21,12 +21,13 @@ namespace Lmc\Rbac\Role; +use Laminas\Permissions\Rbac\RoleInterface; + /** * A role provider is an object that collect roles from string and convert them to RoleInterface instances * * Data can come from anywhere. LmcRbac is bundled with two providers that allow to load roles from database * or from memory - * */ interface RoleProviderInterface { diff --git a/src/Service/AuthorizationService.php b/src/Service/AuthorizationService.php index ecae5ed..25e0ee8 100644 --- a/src/Service/AuthorizationService.php +++ b/src/Service/AuthorizationService.php @@ -21,6 +21,7 @@ namespace Lmc\Rbac\Service; +use Laminas\Permissions\Rbac\Rbac; use Lmc\Rbac\Assertion\AssertionInterface; use Lmc\Rbac\Assertion\AssertionPluginManagerInterface; use Lmc\Rbac\Assertion\AssertionSet; @@ -36,17 +37,20 @@ */ class AuthorizationService implements AuthorizationServiceInterface { - protected RbacInterface $rbac; + protected Rbac $rbac; protected RoleServiceInterface $roleService; private AssertionPluginManagerInterface $assertionPluginManager; - /** @var array */ + /** @var array */ private array $assertions; + /** + * @param array $assertions + */ public function __construct( - RbacInterface $rbac, + Rbac $rbac, RoleServiceInterface $roleService, AssertionPluginManagerInterface $assertionPluginManager, array $assertions = [] @@ -60,7 +64,7 @@ public function __construct( /** * Set assertions, either merging or replacing (default) * - * @param array $assertions + * @param array $assertions */ public function setAssertions(array $assertions, bool $merge = false): void { @@ -90,7 +94,7 @@ public function hasAssertion(string $permission): bool /** * Get the assertions * - * @return array + * @return array */ public function getAssertions(): array { @@ -105,33 +109,42 @@ public function getAssertion(string $permission): AssertionInterface|callable|st return $this->hasAssertion($permission) ? $this->assertions[$permission] : null; } - public function isGranted( - IdentityInterface|null $identity, - string $permission, - mixed $context = null - ): bool { + public function isGranted(IdentityInterface|null $identity, string $permission, mixed $context = null): bool + { $roles = $this->roleService->getIdentityRoles($identity); if (empty($roles)) { return false; } - if (! $this->rbac->isGranted($roles, $permission)) { - return false; - } + $this->injectRoles($this->rbac, $roles); - if (empty($this->assertions[$permission])) { - return true; - } + foreach ($roles as $role) { + if ($this->rbac->isGranted($role, $permission)) { + // Found one role with the permission + // Check for assertions + if (! isset($this->assertions[$permission])) { + return true; + } - if (is_array($this->assertions[$permission])) { - $permissionAssertions = $this->assertions[$permission]; - } else { - $permissionAssertions = [$this->assertions[$permission]]; - } + if (is_array($this->assertions[$permission])) { + $permissionAssertions = $this->assertions[$permission]; + } else { + $permissionAssertions = [$this->assertions[$permission]]; + } + + $assertionSet = new AssertionSet($this->assertionPluginManager, $permissionAssertions); - $assertionSet = new AssertionSet($this->assertionPluginManager, $permissionAssertions); + return $assertionSet->assert($permission, $identity, $context); + } + } + return false; + } - return $assertionSet->assert($permission, $identity, $context); + protected function injectRoles(Rbac $rbac, array $roles): void + { + foreach ($roles as $role) { + $rbac->addRole($role); + } } } diff --git a/src/Service/AuthorizationServiceFactory.php b/src/Service/AuthorizationServiceFactory.php index 536d799..d1d2326 100644 --- a/src/Service/AuthorizationServiceFactory.php +++ b/src/Service/AuthorizationServiceFactory.php @@ -20,9 +20,9 @@ namespace Lmc\Rbac\Service; +use Laminas\Permissions\Rbac\Rbac; use Lmc\Rbac\Assertion\AssertionPluginManagerInterface; use Lmc\Rbac\Options\ModuleOptions; -use Lmc\Rbac\Rbac; use Psr\Container\ContainerInterface; /** diff --git a/src/Service/AuthorizationServiceInterface.php b/src/Service/AuthorizationServiceInterface.php index c5e6d43..941ae36 100644 --- a/src/Service/AuthorizationServiceInterface.php +++ b/src/Service/AuthorizationServiceInterface.php @@ -40,6 +40,8 @@ public function isGranted( /** * Set assertions, either merging or replacing (default) + * + * @param array $assertions */ public function setAssertions(array $assertions, bool $merge = false): void; @@ -59,7 +61,7 @@ public function hasAssertion(string $permission): bool; /** * Get the assertions * - * @return array + * @return array */ public function getAssertions(): array; diff --git a/src/Service/RoleService.php b/src/Service/RoleService.php index a5ffa87..5a02f70 100644 --- a/src/Service/RoleService.php +++ b/src/Service/RoleService.php @@ -21,8 +21,8 @@ namespace Lmc\Rbac\Service; +use Laminas\Permissions\Rbac\RoleInterface; use Lmc\Rbac\Identity\IdentityInterface; -use Lmc\Rbac\Role\RoleInterface; use Lmc\Rbac\Role\RoleProviderInterface; use Traversable; @@ -62,7 +62,7 @@ public function getGuestRole(): string } /** - * Get the identity roles from the current identity, applying some more logic + * Get the roles from the current identity, applying some more logic * * @return RoleInterface[] */ @@ -79,22 +79,22 @@ public function getIdentityRoles(?IdentityInterface $identity = null): iterable /** * Convert the roles (potentially strings) to concrete RoleInterface objects using role provider * - * @param array|Traversable $roles + * @param string[]|RoleInterface[] $roles * @return RoleInterface[] */ - protected function convertRoles(iterable $roles): iterable + protected function convertRoles(array $roles): iterable { $collectedRoles = []; $toCollect = []; foreach ($roles as $role) { - // If it's already a RoleInterface, nothing to do as a RoleInterface contains everything already + // If it's already a RoleInterface, nothing to do as a RoleInterface contains everything already if ($role instanceof RoleInterface) { $collectedRoles[] = $role; continue; } // Otherwise, it's a string and hence we need to collect it - $toCollect[] = (string) $role; + $toCollect[] = $role; } // Nothing to collect, we don't even need to hit the (potentially) costly role provider diff --git a/src/Service/RoleServiceFactory.php b/src/Service/RoleServiceFactory.php index 41cf89d..06da13c 100644 --- a/src/Service/RoleServiceFactory.php +++ b/src/Service/RoleServiceFactory.php @@ -1,4 +1,5 @@ get(ModuleOptions::class); // Get the role provider from the options @@ -42,14 +46,14 @@ public function __invoke(ContainerInterface $container): RoleService } $roleProviderName = key($roleProvider); + /** @var RoleProviderInterface $roleProvider */ $roleProvider = $container->get($roleProviderName); if (! $roleProvider instanceof RoleProviderInterface) { - throw new ServiceNotCreatedException(sprintf('Class %s does not implement LmcRbac\Role\RoleProviderInterface', $roleProviderName)); + throw new ServiceNotCreatedException( + sprintf('Class %s does not implement LmcRbac\Role\RoleProviderInterface', $roleProviderName) + ); } - return new RoleService( - $roleProvider, - $moduleOptions->getGuestRole() - ); + return new RoleService($roleProvider, $moduleOptions->getGuestRole()); } } diff --git a/src/Service/RoleServiceInterface.php b/src/Service/RoleServiceInterface.php index ccd973d..a1f2857 100644 --- a/src/Service/RoleServiceInterface.php +++ b/src/Service/RoleServiceInterface.php @@ -21,8 +21,8 @@ namespace Lmc\Rbac\Service; +use Laminas\Permissions\Rbac\RoleInterface; use Lmc\Rbac\Identity\IdentityInterface; -use Lmc\Rbac\Role\RoleInterface; /** * Role service diff --git a/test/Asset/Role.php b/test/Asset/Role.php index 745c175..3e0a489 100644 --- a/test/Asset/Role.php +++ b/test/Asset/Role.php @@ -24,7 +24,7 @@ use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping as ORM; -use Lmc\Rbac\Role\RoleInterface; +use Laminas\Permissions\Rbac\RoleInterface; /** * @ORM\Entity @@ -85,9 +85,9 @@ public function getId(): ?int /** * Add a permission */ - public function addPermission(string $permission): void + public function addPermission(string $name): void { - $permission = new Permission($permission); + $permission = new Permission($name); $this->permissions[(string) $permission] = $permission; } @@ -97,14 +97,14 @@ public function getName(): string return $this->name; } - public function hasPermission(string $permission): bool + public function hasPermission(string $name): bool { - return isset($this->permissions[$permission]); + return isset($this->permissions[$name]); } - public function addChild(RoleInterface $role): void + public function addChild(RoleInterface $child): void { - $this->children[] = $role; + $this->children[] = $child; } public function getChildren(): iterable @@ -116,4 +116,14 @@ public function hasChildren(): bool { return ! $this->children->isEmpty(); } + + public function addParent(RoleInterface $parent): void + { + // TODO: Implement addParent() method. + } + + public function getParents(): iterable + { + return []; + } } diff --git a/test/ConfigProviderTest.php b/test/ConfigProviderTest.php index 09df2d3..25aa13d 100644 --- a/test/ConfigProviderTest.php +++ b/test/ConfigProviderTest.php @@ -21,6 +21,7 @@ namespace LmcTest\Rbac; +use Laminas\Permissions\Rbac\Rbac; use Laminas\ServiceManager\Factory\InvokableFactory; use Lmc\Rbac\Assertion\AssertionPluginManager; use Lmc\Rbac\Assertion\AssertionPluginManagerFactory; @@ -28,7 +29,6 @@ use Lmc\Rbac\ConfigProvider; use Lmc\Rbac\Options\ModuleOptions; use Lmc\Rbac\Options\ModuleOptionsFactory; -use Lmc\Rbac\Rbac; use Lmc\Rbac\Role\InMemoryRoleProvider; use Lmc\Rbac\Role\InMemoryRoleProviderFactory; use Lmc\Rbac\Role\ObjectRepositoryRoleProvider; diff --git a/test/Rbac/RbacTest.php b/test/Rbac/RbacTest.php.dist similarity index 100% rename from test/Rbac/RbacTest.php rename to test/Rbac/RbacTest.php.dist diff --git a/test/Role/InMemoryRoleProviderTest.php b/test/Role/InMemoryRoleProviderTest.php index 9d889d8..2161e6e 100644 --- a/test/Role/InMemoryRoleProviderTest.php +++ b/test/Role/InMemoryRoleProviderTest.php @@ -21,8 +21,8 @@ namespace LmcTest\Rbac\Role; +use Laminas\Permissions\Rbac\RoleInterface; use Lmc\Rbac\Role\InMemoryRoleProvider; -use Lmc\Rbac\Role\RoleInterface; use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\TestCase; diff --git a/test/Role/ObjectRepositoryRoleProviderTest.php b/test/Role/ObjectRepositoryRoleProviderTest.php index 470e2c5..4aee526 100644 --- a/test/Role/ObjectRepositoryRoleProviderTest.php +++ b/test/Role/ObjectRepositoryRoleProviderTest.php @@ -29,10 +29,10 @@ use Doctrine\Persistence\ObjectManager; use Doctrine\Persistence\ObjectRepository; use Generator; +use Laminas\Permissions\Rbac\Role; +use Laminas\Permissions\Rbac\RoleInterface; use Lmc\Rbac\Exception\RoleNotFoundException; use Lmc\Rbac\Role\ObjectRepositoryRoleProvider; -use Lmc\Rbac\Role\Role; -use Lmc\Rbac\Role\RoleInterface; use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; diff --git a/test/Role/RoleTest.php b/test/Role/RoleTest.php.dist similarity index 100% rename from test/Role/RoleTest.php rename to test/Role/RoleTest.php.dist diff --git a/test/Service/AuthorizationServiceFactoryTest.php b/test/Service/AuthorizationServiceFactoryTest.php index 0934766..177a0d7 100644 --- a/test/Service/AuthorizationServiceFactoryTest.php +++ b/test/Service/AuthorizationServiceFactoryTest.php @@ -21,10 +21,10 @@ namespace LmcTest\Rbac\Service; +use Laminas\Permissions\Rbac\Rbac; use Lmc\Rbac\Assertion\AssertionPluginManager; use Lmc\Rbac\Assertion\AssertionPluginManagerInterface; use Lmc\Rbac\Options\ModuleOptions; -use Lmc\Rbac\Rbac; use Lmc\Rbac\Service\AuthorizationService; use Lmc\Rbac\Service\AuthorizationServiceFactory; use Lmc\Rbac\Service\RoleServiceInterface; diff --git a/test/Service/AuthorizationServiceTest.php b/test/Service/AuthorizationServiceTest.php index 8cfa12d..3bb7014 100644 --- a/test/Service/AuthorizationServiceTest.php +++ b/test/Service/AuthorizationServiceTest.php @@ -21,17 +21,16 @@ namespace LmcTest\Rbac\Service; +use Laminas\Permissions\Rbac\Rbac; +use Laminas\Permissions\Rbac\Role; +use Laminas\Permissions\Rbac\RoleInterface; use Laminas\ServiceManager\ServiceManager; use Lmc\Rbac\Assertion\AssertionPluginManager; use Lmc\Rbac\Assertion\AssertionPluginManagerInterface; use Lmc\Rbac\Assertion\AssertionSet; use Lmc\Rbac\Exception\InvalidArgumentException; use Lmc\Rbac\Identity\IdentityInterface; -use Lmc\Rbac\Rbac; -use Lmc\Rbac\RbacInterface; use Lmc\Rbac\Role\InMemoryRoleProvider; -use Lmc\Rbac\Role\Role; -use Lmc\Rbac\Role\RoleInterface; use Lmc\Rbac\Service\AuthorizationService; use Lmc\Rbac\Service\RoleService; use Lmc\Rbac\Service\RoleServiceInterface; @@ -383,12 +382,12 @@ public function testContextIsPassedToRoleService(): void $identity = new Identity([]); $context = 'context'; - $rbac = $this->getMockBuilder(RbacInterface::class)->disableOriginalConstructor()->getMock(); - $roleService = $this->getMockBuilder(RoleServiceInterface::class)->getMock(); + $rbac = $this->getMockBuilder(Rbac::class)->disableOriginalConstructor()->getMock(); + $roleService = $this->createMock(RoleServiceInterface::class); $assertionPluginManager = $this->getMockBuilder(AssertionPluginManagerInterface::class)->getMock(); $authorizationService = new AuthorizationService($rbac, $roleService, $assertionPluginManager); - $roleService->expects($this->once())->method('getIdentityRoles')->with($identity, $context)->willReturn([]); + $roleService->expects($this->once())->method('getIdentityRoles')->with($identity)->willReturn([]); $authorizationService->isGranted($identity, 'foo', $context); } diff --git a/test/Service/RoleServiceTest.php b/test/Service/RoleServiceTest.php index 96bd620..6f78989 100644 --- a/test/Service/RoleServiceTest.php +++ b/test/Service/RoleServiceTest.php @@ -21,11 +21,9 @@ namespace LmcTest\Rbac\Service; -use ArrayIterator; -use Lmc\Rbac\Identity\IdentityInterface; +use Laminas\Permissions\Rbac\Role; +use Laminas\Permissions\Rbac\RoleInterface; use Lmc\Rbac\Role\InMemoryRoleProvider; -use Lmc\Rbac\Role\Role; -use Lmc\Rbac\Role\RoleInterface; use Lmc\Rbac\Role\RoleProviderInterface; use Lmc\Rbac\Service\RoleService; use LmcTest\Rbac\Asset\Identity; @@ -61,21 +59,6 @@ public function testReturnGuestRoleIfNullIdentityIsGiven(): void $this->assertEquals('guest', $result[0]->getName()); } - public function testReturnTraversableRolesFromIdentityGiven(): void - { - $roleService = new RoleService(new InMemoryRoleProvider([]), 'guest'); - $identity = $this->prophesize(IdentityInterface::class); - $identity->getRoles()->willReturn($roles = new ArrayIterator(['first', 'second', 'third'])); - - $result = $roleService->getIdentityRoles($identity->reveal()); - - $this->assertCount(3, $result); - $this->assertInstanceOf(RoleInterface::class, $result[0]); - $this->assertEquals($roles[0], $result[0]->getName()); - $this->assertEquals($roles[1], $result[1]->getName()); - $this->assertEquals($roles[2], $result[2]->getName()); - } - public function testWillNotInvokeRoleProviderIfAllRolesCollected(): void { $roleProvider = $this->prophesize(RoleProviderInterface::class); @@ -116,7 +99,7 @@ public function testGuestRoleSetterGetter(): void $roleService = new RoleService(new InMemoryRoleProvider([]), 'guest'); $this->assertEquals('guest', $roleService->getGuestRole()); - $roleService->setGuestRole('foo'); - $this->assertEquals('foo', $roleService->getGuestRole()); + //$roleService->setGuestRole('foo'); + //$this->assertEquals('foo', $roleService->getGuestRole()); } } From e553e9de5d7da702fd3af02921d1754f9c9da9b9 Mon Sep 17 00:00:00 2001 From: "Eric Richer eric.richer@vistoconsulting.com" Date: Tue, 27 Aug 2024 15:02:37 -0400 Subject: [PATCH 7/9] Updated docs Signed-off-by: Eric Richer eric.richer@vistoconsulting.com --- docs/docs/Guides/integrating.md | 158 +++++++++++++++++++++++++++++ docs/docs/Guides/laminas-mvc.md | 6 -- docs/docs/Guides/mezzio.md | 7 -- docs/docs/Upgrading/migration.md | 14 +-- docs/docs/Upgrading/to-v2.md | 31 ++++-- docs/docs/assertions.md | 12 ++- docs/docs/authorization-service.md | 152 +++++++++++++++++++++++---- docs/docs/gettingstarted.md | 125 ++++++++++++++++++++++- docs/docs/quickstart.md | 129 ----------------------- docs/docs/role-providers.md | 40 ++++++-- docs/docs/welcome.md | 13 +++ docs/docusaurus.config.js | 2 +- 12 files changed, 495 insertions(+), 194 deletions(-) create mode 100644 docs/docs/Guides/integrating.md delete mode 100644 docs/docs/Guides/laminas-mvc.md delete mode 100644 docs/docs/Guides/mezzio.md delete mode 100644 docs/docs/quickstart.md create mode 100644 docs/docs/welcome.md diff --git a/docs/docs/Guides/integrating.md b/docs/docs/Guides/integrating.md new file mode 100644 index 0000000..9f86439 --- /dev/null +++ b/docs/docs/Guides/integrating.md @@ -0,0 +1,158 @@ +--- +title: Integrating into applications +--- + +LmcRbac can be used in your application to implement role-based access control. + +However, it is important to note that Authorization service `isGranted()` method expects +an identity to be provided. The identity must also implement the `Lmc\Rbac\Identity\IdentityInterface`. + +User authentication is not in the scope of LmcRbac and must be implemented by your application. + +## Laminas MVC applications + +In a Laminas MVC application, you can use the ['laminas-authentication'](https://docs.laminas.dev/laminas-authentication) +component with an appropriate adapter to provide the identity. + +The `Laminas\Authentication\AuthenticationService` service provides the identity using the `getIdentity()` method. +However, it is not prescriptive on the signature of the returned identity object. It is up to the +authentication adapter to return a authentication result that contains an identity object that implements the +`IdentityInterface`. + +For example: + +```php +authenticationService = $authenticationService; + $this->authorizationService = $authorizationService; + } + + public function doSomething() + { + $identity = $this->authenticationService->hasIdentity() ? $this->authenticationService->getIdentity() : null; + + // Check for permission + if ($this->getAuthorizationService()->isGranted($identity, 'somepermssion')) { + // authorized + } else { + // not authorized + } + } + +} + +``` +### Other Laminas MVC components to use +To facilitate integration in an MVC application, you can use [LmcUser](https://lm-commons.github.io/LmcUser/) for +authentication. + +You can also use [LmcRbacMvc](https://lm-commons.github.io/LmcRbacMvc/) which extends LmcRbac by handling identities. +It also provides additional functionalities like route guards and strategies for handling unauthorized access. For example, +an unauthorized strategy could be to redirect to a login page. + +## Mezzio and PSR-7 applications + +In a Mezzio application, you can use the [`mezzio/mezzio-authentication`](https://docs.mezzio.dev/mezzio-authentication/) +component to provide the identity. `mezzio/mezzio-authentication` will add a `UserInterface` object to the request attributes. + +Although the `UserInterface` interface has a `getRoles` method, LmcRbac's `AuthorizationService` still expects the identity +to implement the `IdentityInterface`. + +This can be overcome by providing `mezzio/mezzio-authentication` with a custom factory to instantiate a user object that +implements the `IdentityInterface` as explained in this [section](https://docs.mezzio.dev/mezzio-authentication/v1/intro/) +of the `mezzio/mezzio-authentication` documentation. + +For example: + +```php +identity = $identity; + $this->roles = $roles; + $this->details = $details; + } + + public function getIdentity(): string + { + return $this->identity; + } + + public function getRoles(): array + { + return $this->roles; + } + + public function getDetails(): array + { + return $this->details; + } + + public function getDetail(string $name, $default = null) + { + return $this->details[$name] ?? $default; + } +} +``` +Then provide a factory for creating the user class somewhere in a config provider: +```php + [ + UserInterface => function (string $identity, array $roles = [], array $details = []): UserInterface { + return new MyUser($identity, $roles, $details); + }; + ], + ]; + +``` + +From this point, assuming that you have configured your application to use the `Mezzio\Authentication\AuthenticationMiddleware`, +you can use `MyUser` in your handler by retrieving it from the request: + +```php +// Retrieve the UserInterface object from the request. +$user = $request->getAttribute(UserInterface::class); + +// Check for permission, this works because $user implements IdentityInterface +if ($this->getAuthorizationService()->isGranted($user, 'somepermssion')) { + // authorized +} else { + // not authorized +} +``` + +How you define roles and permissions in your application is up to you. One way would be to use the route name as +a permission such that authorization can be set up based on routes and optionally on route+verb. + + +### Other Mezzio components to use + +A LmcRbac Mezzio component is under development to provide factories and middleware to facilitate integration of LmcRbac +in Mezzio applications. diff --git a/docs/docs/Guides/laminas-mvc.md b/docs/docs/Guides/laminas-mvc.md deleted file mode 100644 index ec602df..0000000 --- a/docs/docs/Guides/laminas-mvc.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -sidebar_label: Laminas MVC -sidebar_position: 1 -title: Integrating in Laminas MVC applications ---- - diff --git a/docs/docs/Guides/mezzio.md b/docs/docs/Guides/mezzio.md deleted file mode 100644 index 66c8e2b..0000000 --- a/docs/docs/Guides/mezzio.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -sidebar_label: Mezzio -sidebar_position: 2 -title: Integrating in Mezzio applications ---- - - diff --git a/docs/docs/Upgrading/migration.md b/docs/docs/Upgrading/migration.md index 29f5ac6..b582938 100644 --- a/docs/docs/Upgrading/migration.md +++ b/docs/docs/Upgrading/migration.md @@ -1,5 +1,5 @@ --- -sidebar_label: Migration from ZF-Commons Rbac +sidebar_label: From ZF-Commons Rbac v3 sidebar_position: 2 title: Migrating from ZF-Commons RBAC v3 --- @@ -12,9 +12,11 @@ When ZfcRbac was moved to LM-Commons, it was split into two repositories: - [LmcRbacMvc](https://github.com/LM-Commons/LmcRbacMvc) contains the old version 2 of ZfcRbac. - LmcRbac contains the version 3 of ZfcRbac, which was only released as v3.alpha.1. -To upgrade +To upgrade to LmcRbac v2, it is suggested to do it in two steps: -- Uninstall `zf-commons/zfc-rbac:3.0.0-alpha.1`. -- Install `lm-commons/lmc-rbac:~1.0` -- Change `zfc-rbac.global.php` to `lmcrbac.global.php` and update the key `zfc_rbac` to `lmc_rbac`. -- Review your code for usages of the `ZfcRbac/*` namespace to `LmcRbac/*` namespace. +1. Upgrade to LmcRbac v1 with the following steps: + * Uninstall `zf-commons/zfc-rbac:3.0.0-alpha.1`. + * Install `lm-commons/lmc-rbac:~1.0` + * Change `zfc-rbac.global.php` to `lmcrbac.global.php` and update the key `zfc_rbac` to `lmc_rbac`. + * Review your code for usages of the `ZfcRbac/*` namespace to `LmcRbac/*` namespace. +2. Upgrade to LmcRbac v2 using the instructions in this [section](to-v2.md). diff --git a/docs/docs/Upgrading/to-v2.md b/docs/docs/Upgrading/to-v2.md index 2d18db0..c43c345 100644 --- a/docs/docs/Upgrading/to-v2.md +++ b/docs/docs/Upgrading/to-v2.md @@ -1,7 +1,7 @@ --- -sidebar_label: To LmcRbac v2 +sidebar_label: From v1 to v2 sidebar_position: 1 -title: Upgrading to LmcRbac v2 +title: Upgrading from v1 to v2 --- LmcRbac v2 is a major version upgrade with many breaking changes that prevent @@ -14,11 +14,24 @@ The namespace has been changed from LmcRbac to Lmc\Rbac. Please review your code to replace references to the `LmcRbac` namespace by the `Lmc\Rbac` namespace. -### Deprecations +### LmcRbac is based on laminas-permissions-rbac -- `Lmc\Rbac\HierarchicalRole` has been deprecated since `Lmc\Rbac\Role` now supports hierarchical roles. Flat roles - are just a simplified version of a hierarchical roles. Use `Lmc\Rbac\Role` instead. -- The factories for services have been refactored from the `Lmc\Rbac\Container` namespace - to be colocated with the service that a factory is creating. All factories in the `Lmc\Rbac\Container` namespace have -been deprecated. -- The `AssertionContainer` class, interface and factory have been deprecated and replaced by `AssertionPluginManager` class, interface and factory. +LmcRbac is now based on the role class and interface provided by laminas-permissions-rbac which +provides a hierarchical role model only. + +Therefore the `Role`, `HierarchicalRole` classes and the `RoleInterface` and `HierarchicalRoleInterface` have been removed +in version 2. + +The `PermissionInterface` interface has been removed as permissions in `laminas-permissions-rbac` as just strings or any +objects that can be casted to a string. If you use objects to hold permissions, just make sure that the object can be +casted to a string by, for example, implementing a `__toString()` method. + +### Refactoring the factories + +The factories for services have been refactored from the `LmcRbac\Container` namespace +to be colocated with the service that a factory is creating. All factories in the `LmcRbac\Container` namespace have +been removed. + +### Refactoring the Assertion Plugin Manager + +The `AssertionContainer` class, interface and factory have been replaced by `AssertionPluginManager` class, interface and factory. diff --git a/docs/docs/assertions.md b/docs/docs/assertions.md index f106c21..dc86f1a 100644 --- a/docs/docs/assertions.md +++ b/docs/docs/assertions.md @@ -18,7 +18,7 @@ A dynamic assertion must implement the `Lmc\Rbac\Assertion\AssertionInterace` wh ```php public function assert( - PermissionInterface|string $permission, + string $permission, ?IdentityInterface $identity = null, mixed $context = null ): bool @@ -33,7 +33,7 @@ represented by `$permission` owns the resource represented by `$context`. class MyAssertion implements \Lmc\Rbac\Assertion\AssertionInterface { - public function assert(PermissionInterface|string $permission, ?IdentityInterface $identity = null, $context = null): bool + public function assert(string $permission, ?IdentityInterface $identity = null, $context = null): bool { // for 'edit' permission if ('edit' === $permission) { @@ -84,7 +84,7 @@ return [ 'lmc_rbac' => [ /* the rest of the file */ 'assertion_map' => [ - 'edit' => function assert(PermissionInterface|string $permission, ?IdentityInterface $identity = null, $context = null): bool + 'edit' => function assert(string $permission, ?IdentityInterface $identity = null, $context = null): bool { // for 'edit' permission if ('edit' === $permission) { @@ -145,3 +145,9 @@ associated with the `'delete'` permission above. The default logic is to combine assertions using 'and' logic but this can be explicitly set as shown above for `'delete'` permission. +## Defining dynamic assertions at run-time + +Although dynamic assertions are typically defined in the application's configuration, it is possible to set +dynamic assertions at run-time by using the Authorization Service utility methods for adding/getting assertions. + +These methods are described in the Authorization Service [reference](authorization-service.md#reference). diff --git a/docs/docs/authorization-service.md b/docs/docs/authorization-service.md index 358e516..ee68543 100644 --- a/docs/docs/authorization-service.md +++ b/docs/docs/authorization-service.md @@ -17,40 +17,150 @@ The Authorization service can be retrieved from the service manager using the na ``` ### Reference -`Lmc\Rbac\Service\AuthorizationServiceInterface` defines the following method: +`Lmc\Rbac\Service\AuthorizationServiceInterface` defines the following methods: -- `isGranted(?IdentityInterface $identity, PermissionInterface|string $permission, $context = null): bool` - | Parameter | Description | +#### `isGranted(?IdentityInterface $identity, string $permission, $context = null): bool` + +Checks that the identity has is granted the permission for the (optional) context. + + | Parameter | Description | |----------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------| - | `$identity` | The identity whose roles to checks.
If `$identity` is null, then the `guest` is used.
The `guest` role is definable via configuration and defaults to `'guest'`. | - | `$permission` | The permission to check against | - | `$context` | A context that will be passed to dynamic assertions that are defined for the permission | + | `$identity` | The identity whose roles to checks.
If `$identity` is null, then the `guest` is used.
The `guest` role is definable via configuration and defaults to `'guest'`. | + | `$permission` | The permission to check against | + | `$context` | A context that will be passed to dynamic assertions that are defined for the permission | + +#### `setAssertions(array $assertions, bool $merge = false): void` + +Allows to define dynamic assertions at run-time. -- `setAssertions(array $assertions, bool $merge = false): void` | Parameter | Description | |---------------|-----------------------------------------------------------------------------------------| | `$assertions` | An array of assertions to merge or to replace | | `$merge` | if `true` the content of `$assertions` will be merged with existing assertions. | -- `setAssertion(PermissionInterface|string $permission, AssertionInterface|callable|string $assertion): void` - | Parameter | Description | - |---------------|----------------------------------------| - | `$permission` | Permission or a permission name | - | `$assertion` | The assertion to set for `$permission` | -- `hasAssertion(PermissionInterface|string $permission): bool` - | Parameter | Description | - |---------------|----------------------------------------| - | `$permission` | Permission or a permission name | +#### `setAssertion(string $permission, AssertionInterface|callable|string $assertion): void` +Allows to define a dynamic assertion at run-time. + + | Parameter | Description | + |---------------|-----------------------------------------| + | `$permission` | Permission name | + | `$assertion` | The assertion to set for `$permission` | + +#### `hasAssertion(string $permission): bool` +Checks if the authorization has a dynamic assertion for a given permission. + + | Parameter | Description | + |---------------|--------------------------| + | `$permission` | Permission name | + + +#### `getAssertions(): array` -- `getAssertions(): array` +Returns all the dynamic assertions defined. -- `getAssertion(PermissionInterface|string $permission): AssertionInterface|callable|string|null` - | Parameter | Description | - |---------------|----------------------------------------| - | `$permission` | Permission or a permission name | +#### `getAssertion(string $permission): AssertionInterface|callable|string|null` + +Returns the dynamic assertion for the give permission + + | Parameter | Description | + |---------------|-----------------------------| + | `$permission` | Permission permission name | More on dynamic assertions can be found in the [Assertions](assertions.md) section. More on the `guest` role can be found in the [Configuration](configuration.md) section. +## Injecting the Authorization Service + +There are a few methods to inject the Authorization Service into your service. + +### Using a factory + +You can inject the AuthorizationService into your own objects using a factory. The Authorization Service +can be retrieved from the container using `'Lmc\Rbac\Service\AuthorizationServiceInterface'`. + +Here is a classic example for injecting the Authorization Service into your own service + +*in your app's Module* + +```php +use Lmc\Rbac\Service\AuthorizationServiceInterface; +class Module +{ + public function getConfig() + { + return [ + 'service_manager' => [ + 'factories' => [ + 'MyService' => function($sm) { + $authService = $sm->get('AuthorizationServiceInterface'); + return new MyService($authService); + } + ], + ], + ]; + } +} +```` + +### Using traits + +For convenience, LmcRbac provides a `AuthorizationServiceAwareTrait` that adds the `$authorizationService` property and +setter/getter methods. + +### Using delegators + +LmcRbac ships with a `Lmc\Rbac\Service\AuthorizationServiceDelegatorFactory` [delegator factory](https://docs.laminas.dev/laminas-servicemanager/delegators/) +to automatically inject the authorization service into your classes. + +Your class must implement the `Lmc\Rbac\Service\AuthorizationServiceAwareInterface` and use the above trait, as shown below: + +```php +namespace MyModule; + +use Lmc\Rbac\Service\AuthorizationServiceAwareInterface; +use Lmc\Rbac\Service\AuthorizationServiceAwareTrait; + +class MyClass implements AuthorizationServiceAwareInterface +{ + use AuthorizationServiceAwareTrait; + + public function doSomethingThatRequiresAuth() + { + if (! $this->getAuthorizationService()->isGranted($identity, 'deletePost')) { + throw new \Exception('You are not allowed !'); + } + return true; + } +} +``` + +And add your class to the right delegator: + +```php +namespace MyModule; +use Lmc\Rbac\Service\AuthorizationServiceDelegatorFactory; +class Module +{ + // ... + + public function getConfig() + { + return [ + 'service_manager' => [ + 'factories' => [ + MyClass::class => InvokableFactory::class, + ], + 'delegators' => [ + MyClass::class => [ + AuthorizationServiceDelegatorFactory::class, + ], + ], + ], + ]; + } +} +``` + + diff --git a/docs/docs/gettingstarted.md b/docs/docs/gettingstarted.md index e86e40e..3bb2503 100644 --- a/docs/docs/gettingstarted.md +++ b/docs/docs/gettingstarted.md @@ -1,7 +1,7 @@ --- sidebar_label: Getting Started -sidebar_position: 1 -title: Get started +sidebar_position: 2 +title: Getting started --- ## Requirements @@ -28,3 +28,124 @@ applications, or to the `config/config.php` file for Mezzio applications. Customize the module by copy-pasting the `lmcrbac.global.php` file to your `config/autoload` folder. + +:::note +On older versions of `LmcRbac`, the configuration file is named `config/config.global.php`. +::: + +## Defining roles + +By default, no roles and no permissions are defined. + +Roles and permissions are defined by a Role Provider. `LmcRbac` ships with two roles providers: +- a simple `InMemoryRoleProvider` that uses an associative array to define roles and their permission. This is the default. +- a `ObjectRepositoyRoleProvider` that is based on Doctrine ORM. + +To quickly get started, let's use the `InMemoryRoleProvider` role provider. + +In the `config/autoload/lmcrbac.global.php`, add the following: + +```php + [ + 'role_provider' => [ + Lmc\Rbac\Role\InMemoryRoleProvider::class => [ + 'guest', + 'user' => [ + 'permissions' => ['create', 'edit'], + ], + 'admin' => [ + 'children' => ['user'], + 'permissions' => ['delete'], + ], + ], + ], + ], +]; +``` + +This defines 3 roles: a `guest` role, a `user` role having 2 permissions, and a `admin` role which has the `user` role as +a child and with its own permission. If the hierarchy is flattened: + +- `guest` has no permission +- `user` has permissions `create` and `edit` +- `admin` has permissions `create`, `edit` and `delete` + +## Basic authorization + +The authorization service can get retrieved from the service manager container and used to check if a permission +is granted to an identity: + +```php +get('\Lmc\Rbac\Service\AuthorizationServiceInterface'); + + /** @var \Lmc\Rbac\Identity\IdentityInterface $identity */ + if ($authorizationService->isGranted($identity, 'create')) { + /** do something */ + } +``` + +If `$identity` has the role `user` and/or `admin` then the authorization is granted. If the identity has the role `guest`, then authorization +is denied. + +:::info +If `$identity` is null (no identity), then the guest role is assumed which is set to `'guest'` by default. The guest role +can be configured in the `lmcrbac.config.php` file. More on this in the [Configuration](configuration.md) section. +::: + +:::warning +`LmcRbac` does not provide any logic to instantiate an identity entity. It is assumed that +the application will instantiate an entity that implements `\Lmc\Rbac\Identity\IdentityInterface` which defines the `getRoles()` +method. +::: + +## Using assertions + +Even if an identity has the `user` role granting it the `edit` permission, it should not have the authorization to edit another identity's resource. + +This can be achieved using dynamic assertion. + +An assertion is a function that implements the `\Lmc\Rbac\Assertion\AssertionInterface` and is configured in the configuration +file. + +Let's modify the `lmcrbac.config.php` file as follows: + +```php + [ + 'role_provider' => [ + /* roles and permissions + ], + 'assertion_map' => [ + 'edit' => function ($permission, IdentityInterface $identity = null, $resource = null) { + if ($resource->getOwnerId() === $identity->getId() { + return true; + } else { + return false; + } + ], + ], +]; +``` + +Then use the authorization service passing the resource (called a 'context') in addition to the permission: + +```php +get('\Lmc\Rbac\Service\AuthorizationServiceInterface'); + + /** @var \Lmc\Rbac\Identity\IdentityInterface $identity */ + if ($authorizationService->isGranted($identity, 'edit', $resource)) { + /** do something */ + } +``` + +Dynanmic assertions are further discussed in the [Dynamic Assertions](assertions) section. diff --git a/docs/docs/quickstart.md b/docs/docs/quickstart.md deleted file mode 100644 index 35b7a66..0000000 --- a/docs/docs/quickstart.md +++ /dev/null @@ -1,129 +0,0 @@ ---- -sidebar_label: Quick start -sidebar_position: 3 -title: Quick Start ---- - -Once the library has been installed by Composer, you will need to copy the -`config/lmcrbac.global.php` file from `LmcRbac` to the `config/autoload` folder. - -:::note -On older versions of `LmcRbac`, the configuration file is named `config/config.global.php`. -::: - -## Defining roles - -By default, no roles and no permissions are defined. - -Roles and permissions are defined by a Role Provider. `LmcRbac` ships with two roles providers: -- a simple `InMemoryRoleProvider` that uses an associative array to define roles and their permission. This is the default. -- a `ObjectRepositoyRoleProvider` that is based on Doctrine ORM. - -To quickly get started, let's use the `InMemoryRoleProvider` role provider. - -In the `config/autoload/lmcrbac.global.php`, add the following: - -```php - [ - 'role_provider' => [ - Lmc\Rbac\Role\InMemoryRoleProvider::class => [ - 'guest', - 'user' => [ - 'permissions' => ['create', 'edit'], - ], - 'admin' => [ - 'children' => ['user'], - 'permissions' => ['delete'], - ], - ], - ], - ], -]; -``` - -This defines 3 roles: a `guest` role, a `user` role having 2 permissions, and a `admin` role which has the `user` role as -a child and with its own permission. If the hierarchy is flattened: - -- `guest` has no permission -- `user` has permissions `create` and `edit` -- `admin` has permissions `create`, `edit` and `delete` - -## Basic authorization - -The authorization service can get retrieved from service manager container and used to check if a permission -is granted to an identity: - -```php -get('\Lmc\Rbac\Service\AuthorizationServiceInterface'); - - /** @var \Lmc\Rbac\Identity\IdentityInterface $identity */ - if ($authorizationService->isGranted($identity, 'create')) { - /** do something */ - } -``` - -If `$identity` has the role `user` and/or `admin` then the authorization is granted. If the identity has the role `guest`, then authorization -is denied. - -:::info -If `$identity` is null (no identity), then the guest role is assumed which is set to `'guest'` by default. The guest role -can be configured in the `lmcrbac.config.php` file. More on this in the [Configuration](configuration.md) section. -::: - -:::warning -`LmcRbac` does not provide any logic to instantiate an identity entity. It is assumed that -the application will instantiate an entity that implements `\Lmc\Rbac\Identity\IdentityInterface` which defines the `getRoles()` -method. -::: - -## Using assertions - -Even if an identity has the `user` role granting it the `edit` permission, it should not have the authorization to edit another identity's resource. - -This can be achieved using dynamic assertion. - -An assertion is a function that implements the `\Lmc\Rbac\Assertion\AssertionInterface` and is configured in the configuration -file. - -Let's modify the `lmcrbac.config.php` file as follows: - -```php - [ - 'role_provider' => [ - /* roles and permissions - ], - 'assertion_map' => [ - 'edit' => function ($permission, IdentityInterface $identity = null, $resource = null) { - if ($resource->getOwnerId() === $identity->getId() { - return true; - } else { - return false; - } - ], - ], -]; -``` - -Then use the authorization service passing the resource (called a 'context') in addition to the permission: - -```php -get('\Lmc\Rbac\Service\AuthorizationServiceInterface'); - - /** @var \Lmc\Rbac\Identity\IdentityInterface $identity */ - if ($authorizationService->isGranted($identity, 'edit', $resource)) { - /** do something */ - } -``` - -Dynanmic assertions are further discussed in the [Dynamic Assertions](assertions) section. diff --git a/docs/docs/role-providers.md b/docs/docs/role-providers.md index d961d7a..a011763 100644 --- a/docs/docs/role-providers.md +++ b/docs/docs/role-providers.md @@ -8,8 +8,10 @@ sidebar_position: 4 A role is an object that returns a list of permissions that the role has. -Roles are defined using by the `\Lmc\Rbac\Role\Role` class or by a class -implementing `Lmc\Rbac\Role\RoleInterface`. +LmcRbac uses the Role class defined by [laminas-permissions-rbac](https://github.com/laminas/laminas-permissions-rbac). + +Roles are defined using by the `\Laminas\Permissions\Rbac\Role` class or by a class +implementing `\Laminas\Permissions\Rbac\RoleInterface`. Roles can have child roles and therefore provides a hierarchy of roles where a role inherit the permissions of all its child roles. @@ -19,27 +21,29 @@ may inherit the permissions of the 'user' role plus an additional 'delete' role. the 'admin' role will have 'user' as its child role. -:::info -#### Flat roles - +:::tip[Flat roles] Previous version of LmcRbac used to make a distinction between flat roles and hierarchical roles. -A flat role is just a simplification of a hierarchical role, i.e. a hierarchical role without children. Therefore -`Lmc\Rbac\Role\Role` is now hierarchical by default and `Lmc\Rbac\Role\HierarchicalRole` has been deprecated. +A flat role is just a simplification of a hierarchical role, i.e. a hierarchical role without children. +In `laminas-permissions-rbac`, roles are hierarchical. ::: ## Permissions -A permission in LmcRbac is simply a string that represents the permission such as 'read', 'write' or 'delete'. +A permission in `laminas-permissions-rbac` is simply a string that represents the permission such as 'read', 'write' or 'delete'. But it can also be more precise like 'article.read' or 'article.write'. -Permissions can also be objects as long as they implement the `Lmc\Rbac\Permission\PermissionInterface`. This could be the +A permission can also be an object as long as it can be casted to a string. This could be the case, for example, when permissions are stored in a database where they could also have a identified and a description. +:::tip +An object can be casted to a string by implementing the `__toString()` method. +::: + ## Role Providers A role provider is an object that returns a list of roles. A role provider must implement the `Lmc\Rbac\Role\RoleProviderInterface` interface. The only required method is `getRoles`, and must return an array -of `Lmc\Rbac\Role\RoleInterface` objects. +of `Laminas\Permissions\Rbac\RoleInterface` objects. Roles can come from one of many sources: in memory, from a file, from a database, etc. However, you can specify only one role provider per application. @@ -197,3 +201,19 @@ return [ ]; ``` +## Role Service + +LmcRbac provides a role service that will use the Role Providers to provide the roles +associated with a given identity. + +It can be retrieved from the container be requesting the `Lmc\Rbac\Service\RoleServiceIntgeface`. + +`Lmc\Rbac\Service\RoleServiceInterface` defines the following method: + +- `getIdentityRoles(?IdentityInterface $identity = null): iterable` + + | Parameter | Description | + |----------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| + | `$identity` | The identity whose roles to retrieve.
If `$identity` is null, then the `guest` is used.
The `guest` role is definable via configuration and defaults to `'guest'`. | + + diff --git a/docs/docs/welcome.md b/docs/docs/welcome.md new file mode 100644 index 0000000..824714f --- /dev/null +++ b/docs/docs/welcome.md @@ -0,0 +1,13 @@ +--- +title: Welcome +sidebar_position: 1 +--- + +LmcRbac offers components and services to implement role-based access control (RBAC) in your application. +LmcRbac extends the components provided by [laminas-permissions-rbac](https://github.com/laminas/laminas-permissions-rbac). + +LmcRbac can be used in Laminas MVC and in Mezzio applications. + +:::tip +If you are upgrading from LmcRbac v1 or from zfc-rbac v3, please read the [Upgrading section](Upgrading/to-v2.md) +::: diff --git a/docs/docusaurus.config.js b/docs/docusaurus.config.js index 71ae911..d508041 100644 --- a/docs/docusaurus.config.js +++ b/docs/docusaurus.config.js @@ -78,7 +78,7 @@ themeConfig: type: 'docSidebar', sidebarId: 'documentationSidebar', position: 'left', - label: 'Documentation', + label: 'Docs', }, { type: "docsVersionDropdown", From 4d4516544d7a56faf774a4f65f03ba07edf5b7f7 Mon Sep 17 00:00:00 2001 From: "Eric Richer eric.richer@vistoconsulting.com" Date: Tue, 27 Aug 2024 15:05:55 -0400 Subject: [PATCH 8/9] Deleting deprecated classes Signed-off-by: Eric Richer eric.richer@vistoconsulting.com --- src/Assertion/AssertionContainer.php | 45 ------------------- src/Assertion/AssertionContainerFactory.php | 42 ----------------- src/Assertion/AssertionContainerInterface.php | 32 ------------- src/Container/AssertionContainerFactory.php | 36 --------------- src/Container/AuthorizationServiceFactory.php | 40 ----------------- src/Container/InMemoryRoleProviderFactory.php | 37 --------------- src/Container/ModuleOptionsFactory.php | 36 --------------- .../ObjectRepositoryRoleProviderFactory.php | 38 ---------------- src/Container/RoleServiceFactory.php | 38 ---------------- 9 files changed, 344 deletions(-) delete mode 100644 src/Assertion/AssertionContainer.php delete mode 100644 src/Assertion/AssertionContainerFactory.php delete mode 100644 src/Assertion/AssertionContainerInterface.php delete mode 100644 src/Container/AssertionContainerFactory.php delete mode 100644 src/Container/AuthorizationServiceFactory.php delete mode 100644 src/Container/InMemoryRoleProviderFactory.php delete mode 100644 src/Container/ModuleOptionsFactory.php delete mode 100644 src/Container/ObjectRepositoryRoleProviderFactory.php delete mode 100644 src/Container/RoleServiceFactory.php diff --git a/src/Assertion/AssertionContainer.php b/src/Assertion/AssertionContainer.php deleted file mode 100644 index 6f04d44..0000000 --- a/src/Assertion/AssertionContainer.php +++ /dev/null @@ -1,45 +0,0 @@ -get('config')['lmc_rbac']['assertion_manager']; - - return new AssertionContainer($container, $config); - } -} diff --git a/src/Assertion/AssertionContainerInterface.php b/src/Assertion/AssertionContainerInterface.php deleted file mode 100644 index ad7a161..0000000 --- a/src/Assertion/AssertionContainerInterface.php +++ /dev/null @@ -1,32 +0,0 @@ - - * @licence MIT - * @deprecated Replaced by Lmc\Rbac\Service\AuthorizationServiceFactory - */ -final class AuthorizationServiceFactory extends \Lmc\Rbac\Service\AuthorizationServiceFactory -{ -} diff --git a/src/Container/InMemoryRoleProviderFactory.php b/src/Container/InMemoryRoleProviderFactory.php deleted file mode 100644 index 582f261..0000000 --- a/src/Container/InMemoryRoleProviderFactory.php +++ /dev/null @@ -1,37 +0,0 @@ - - * @licence MIT - * @deprecated Replaced by Lmc\Rbac\Options\ModuleOptionsFactory - */ -final class ModuleOptionsFactory extends \Lmc\Rbac\Options\ModuleOptionsFactory -{ -} diff --git a/src/Container/ObjectRepositoryRoleProviderFactory.php b/src/Container/ObjectRepositoryRoleProviderFactory.php deleted file mode 100644 index 543182f..0000000 --- a/src/Container/ObjectRepositoryRoleProviderFactory.php +++ /dev/null @@ -1,38 +0,0 @@ - - * @licence MIT - * @deprecated Replaced by Lmc\Rbac\Role\ObjectRepositoryRoleProviderFactory - */ -final class ObjectRepositoryRoleProviderFactory extends \Lmc\Rbac\Role\ObjectRepositoryRoleProviderFactory -{ -} diff --git a/src/Container/RoleServiceFactory.php b/src/Container/RoleServiceFactory.php deleted file mode 100644 index d6bbdb0..0000000 --- a/src/Container/RoleServiceFactory.php +++ /dev/null @@ -1,38 +0,0 @@ - - * @licence MIT - * @deprecated Replaced by Lmc\Rbac\Service\RoleServiceFactory - */ -final class RoleServiceFactory extends \Lmc\Rbac\Service\RoleServiceFactory -{ -} From c6986b96011ac81520ff426c6696d34802463e33 Mon Sep 17 00:00:00 2001 From: "Eric Richer eric.richer@vistoconsulting.com" Date: Tue, 27 Aug 2024 15:55:30 -0400 Subject: [PATCH 9/9] Updated docs Signed-off-by: Eric Richer eric.richer@vistoconsulting.com --- docs/docs/concepts.md | 44 --------------- .../{gettingstarted.md => quick-start.md} | 54 +++++++++++++++++-- docs/docs/welcome.md | 13 ----- docs/docusaurus.config.js | 8 ++- docs/src/components/HomepageFeatures/index.js | 10 +--- docs/src/components/HomepageHeader/index.js | 26 +++++++++ docs/src/pages/index.js | 27 +--------- phpcs.xml | 1 + 8 files changed, 88 insertions(+), 95 deletions(-) delete mode 100644 docs/docs/concepts.md rename docs/docs/{gettingstarted.md => quick-start.md} (66%) delete mode 100644 docs/docs/welcome.md create mode 100644 docs/src/components/HomepageHeader/index.js diff --git a/docs/docs/concepts.md b/docs/docs/concepts.md deleted file mode 100644 index 583fb5f..0000000 --- a/docs/docs/concepts.md +++ /dev/null @@ -1,44 +0,0 @@ ---- -sidebar_label: Concepts -sidebar_position: 2 -title: Concepts ---- - -[Role-Based Access Control (RBAC)](https://en.wikipedia.org/wiki/Role-based_access_control) -is an approach to restricting system access to authorized users by putting emphasis -on roles and their permissions. - -In the RBAC model: - -- an **identity** has one of more roles. -- a **role** has one of more permissions. -- a **permission** is typically an action like "read", "write", "delete". -- a **role** can have **child roles** thus providing a hierarchy of roles where a role will inherit the permissions of all its child roles. - -## Authorization - -An identity will be authorized to perform an action, such as accessing a resource, if it is granted -the permission that controls the execution of the action. - -For example, deleting an item could be restricted to identities that have at least one role that has the -`item.delete` permission. This could be implemented by defining a `member` role that has the `item.delete` and assigning -this role of an authenticated user. - -## Dynamic Assertions - -In some cases, just checking if the identity has the `item.delete` permission is not enough. -It would also be necessary to check, for example, that the `item` belongs to the identity. Dynamic assertion allow -to specify some extra checks before granting access to perform an action such as, in this case, being the owner of the -resource. - -## Identities - -An identity is typically provided by an authentication process within the application. - -Authentication is not in the scope of `LmcRbac` and it is assumed that an identity entity that can provide the assigned -roles is available when using the authorization service. If no identity is available, as it would be the case when no -user is "logged in", then a guest role is assumed. - - - - diff --git a/docs/docs/gettingstarted.md b/docs/docs/quick-start.md similarity index 66% rename from docs/docs/gettingstarted.md rename to docs/docs/quick-start.md index 3bb2503..e0f57bb 100644 --- a/docs/docs/gettingstarted.md +++ b/docs/docs/quick-start.md @@ -1,8 +1,54 @@ --- -sidebar_label: Getting Started -sidebar_position: 2 -title: Getting started +title: Quick Start +sidebar_position: 1 --- + +LmcRbac offers components and services to implement role-based access control (RBAC) in your application. +LmcRbac extends the components provided by [laminas-permissions-rbac](https://github.com/laminas/laminas-permissions-rbac). + +LmcRbac can be used in Laminas MVC and in Mezzio applications. + +:::tip +If you are upgrading from LmcRbac v1 or from zfc-rbac v3, please read the [Upgrading section](Upgrading/to-v2.md) +::: + +## Concepts + +[Role-Based Access Control (RBAC)](https://en.wikipedia.org/wiki/Role-based_access_control) +is an approach to restricting system access to authorized users by putting emphasis +on roles and their permissions. + +In the RBAC model: + +- an **identity** has one of more roles. +- a **role** has one of more permissions. +- a **permission** is typically an action like "read", "write", "delete". +- a **role** can have **child roles** thus providing a hierarchy of roles where a role will inherit the permissions of all its child roles. + +### Authorization + +An identity will be authorized to perform an action, such as accessing a resource, if it is granted +the permission that controls the execution of the action. + +For example, deleting an item could be restricted to identities that have at least one role that has the +`item.delete` permission. This could be implemented by defining a `member` role that has the `item.delete` and assigning +this role of an authenticated user. + +### Dynamic Assertions + +In some cases, just checking if the identity has the `item.delete` permission is not enough. +It would also be necessary to check, for example, that the `item` belongs to the identity. Dynamic assertion allow +to specify some extra checks before granting access to perform an action such as, in this case, being the owner of the +resource. + +### Identities + +An identity is typically provided by an authentication process within the application. + +Authentication is not in the scope of `LmcRbac` and it is assumed that an identity entity that can provide the assigned +roles is available when using the authorization service. If no identity is available, as it would be the case when no +user is "logged in", then a guest role is assumed. + ## Requirements - PHP 8.1 or higher @@ -22,7 +68,7 @@ You will be prompted by the `laminas-component-installer` plugin to inject LM-Co :::note **Manual installation:** -Enable the module by adding `LmcRbac` key to your `application.config.php` or `modules.config.php` file for Laminas MVC +Enable the module by adding `LmcRbac` key to your `application.config.php` or `modules.config.php` file for Laminas MVC applications, or to the `config/config.php` file for Mezzio applications. ::: diff --git a/docs/docs/welcome.md b/docs/docs/welcome.md deleted file mode 100644 index 824714f..0000000 --- a/docs/docs/welcome.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -title: Welcome -sidebar_position: 1 ---- - -LmcRbac offers components and services to implement role-based access control (RBAC) in your application. -LmcRbac extends the components provided by [laminas-permissions-rbac](https://github.com/laminas/laminas-permissions-rbac). - -LmcRbac can be used in Laminas MVC and in Mezzio applications. - -:::tip -If you are upgrading from LmcRbac v1 or from zfc-rbac v3, please read the [Upgrading section](Upgrading/to-v2.md) -::: diff --git a/docs/docusaurus.config.js b/docs/docusaurus.config.js index d508041..205bf61 100644 --- a/docs/docusaurus.config.js +++ b/docs/docusaurus.config.js @@ -9,7 +9,7 @@ import {themes as prismThemes} from 'prism-react-renderer'; /** @type {import('@docusaurus/types').Config} */ const config = { title: 'LmcRbac', - tagline: 'Role-based access control components for your Laminas or Mezzio application', + tagline: 'Role-based access control components for your Laminas MVC and Mezzio application', favicon: 'img/favicon.ico', // Set the production url of your site here @@ -102,6 +102,7 @@ themeConfig: footer: { style: 'dark', links: [ +/* { title: 'Docs', items: [ @@ -111,6 +112,8 @@ themeConfig: }, ], }, + + */ { title: 'Community', items: [ @@ -123,10 +126,13 @@ themeConfig: { title: 'More', items: [ +/* { label: 'Blog', to: '/blog', }, + + */ { label: 'GitHub', href: 'https://github.com/lm-commons/lmcrbac', diff --git a/docs/src/components/HomepageFeatures/index.js b/docs/src/components/HomepageFeatures/index.js index ab6064e..8392231 100644 --- a/docs/src/components/HomepageFeatures/index.js +++ b/docs/src/components/HomepageFeatures/index.js @@ -57,15 +57,9 @@ export default function HomepageFeatures() {
Introduction -

Components and services to provide role-based access control (RBAC) to your application.

+

LmcRbac offers components and services to implement role-based access control (RBAC) in your + application. LmcRbac extends the components provided by laminas-permissions-rbac.

LmcRbac can be used in Laminas MVC and in Mezzio applications.

-

Based on the original work of ZF-Commons/zfc-rbac v3.x.

-

If you are looking for the Laminas version of zfc-rbac v2, please use LM-Commons/LmcRbacMvc.

-
- Get started -
Support