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/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/config/lmcrbac.global.php.dist b/config/lmcrbac.global.php.dist index e873f22..6bbf080 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/data/FlatRole.php.dist b/data/FlatRole.php.dist index 9bd49ea..67d97ac 100644 --- a/data/FlatRole.php.dist +++ b/data/FlatRole.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 @@ -45,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 @@ -101,28 +100,21 @@ class FlatRole implements RoleInterface /** * @inheritDoc */ - public function addPermission(PermissionInterface|string $permission): void + public function addPermission(string $name): void { - if (is_string($permission)) { - $permission = new Permission($permission); - } + $permission = new Permission($name); - $this->permissions[(string) $permission] = $permission; + $this->permissions[$name] = $permission; } /** * @inheritDoc */ - public function hasPermission(PermissionInterface|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[(string) $permission]); + return isset($this->permissions[$name]); } - - public function getChildren(): iterable { return []; @@ -133,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 ea4e54c..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|\Lmc\Rbac\Permission\PermissionInterface $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 $permission + * @param string $name * @return bool */ - public function hasPermission(string|\Lmc\Rbac\Permission\PermissionInterface $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[(string) $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 841b383..2f986f9 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 @@ -42,12 +41,12 @@ class Permission implements PermissionInterface 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 5cc5202..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 @@ -97,14 +96,14 @@ 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 $name): void { - $permission = new \Permission($permission); + $permission = new \Permission($name); - $this->permissions[(string) $permission] = $permission; + $this->permissions[$name] = $permission; } public function getName(): string @@ -112,9 +111,9 @@ class Role implements RoleInterface return $this->name; } - public function hasPermission(string|PermissionInterface $permission): bool + public function hasPermission(string $name): bool { - return isset($this->permissions[(string) $permission]); + return isset($this->permissions[$name]); } public function addChild(RoleInterface $child): void @@ -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/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/Upgrading/migration.md b/docs/docs/Upgrading/migration.md new file mode 100644 index 0000000..b582938 --- /dev/null +++ b/docs/docs/Upgrading/migration.md @@ -0,0 +1,22 @@ +--- +sidebar_label: From ZF-Commons Rbac v3 +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 to LmcRbac v2, it is suggested to do it in two steps: + +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 new file mode 100644 index 0000000..c43c345 --- /dev/null +++ b/docs/docs/Upgrading/to-v2.md @@ -0,0 +1,37 @@ +--- +sidebar_label: From v1 to v2 +sidebar_position: 1 +title: Upgrading from v1 to 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. + +### LmcRbac is based on laminas-permissions-rbac + +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 552a549..dc86f1a 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 + ?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(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, ], @@ -142,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 a8914e9..ee68543 100644 --- a/docs/docs/authorization-service.md +++ b/docs/docs/authorization-service.md @@ -7,27 +7,160 @@ 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: +`Lmc\Rbac\Service\AuthorizationServiceInterface` defines the following methods: -`isGranted(?IdentityInterface $identity, string $permission, $context = null): bool` +#### `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 | +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 | + +#### `setAssertions(array $assertions, bool $merge = false): void` + +Allows to define dynamic assertions at run-time. + + | 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(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` + +Returns all the dynamic assertions defined. + +#### `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/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/quick-start.md b/docs/docs/quick-start.md new file mode 100644 index 0000000..e0f57bb --- /dev/null +++ b/docs/docs/quick-start.md @@ -0,0 +1,197 @@ +--- +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 + +## 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. + +:::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/role-providers.md b/docs/docs/role-providers.md index a65654e..a011763 100644 --- a/docs/docs/role-providers.md +++ b/docs/docs/role-providers.md @@ -1,60 +1,71 @@ --- -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. +LmcRbac uses the Role class defined by [laminas-permissions-rbac](https://github.com/laminas/laminas-permissions-rbac). -### Flat roles +Roles are defined using by the `\Laminas\Permissions\Rbac\Role` class or by a class +implementing `\Laminas\Permissions\Rbac\RoleInterface`. -A flat role is the simplest role object. It contains the list of permissions that -the role has. +Roles can have child roles and therefore provides a hierarchy of roles where a role inherit the permissions of all its +child roles. -Flat roles are defined using by the `LmcRbac\Role\Role` class or by classes -implementing the `LmcRbac\Role\RoleInterface`. +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 -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. +:::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. -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. +In `laminas-permissions-rbac`, roles are hierarchical. +::: -Hierarchical roles may have flat roles or hierarchical roles as children. +## Permissions -Hierarchical roles are defined using by the `LmcRbac\Role\HierarchicalRole` class or by classes -implementing the `LmcRbac\Role\HierarchicalRoleInterface`. +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'. + +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 -`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 `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. ### 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 +76,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 +94,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 +103,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 +128,7 @@ return [ ]; ``` -### `LmcRbac\Role\ObjectRepositoryRoleProvider` +### `Lmc\Rbac\Role\ObjectRepositoryRoleProvider` This provider fetches roles from a database using `Doctrine\Common\Persistence\ObjectRepository` interface. @@ -129,7 +139,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 +154,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 +168,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 +183,7 @@ Then, you need to add it to the role provider manager: return [ 'lmc_rbac' => [ 'role_provider' => [ - 'Application\Role\CustomRoleProvider' => [ + MyCustomRoleProvider::class => [ // Options ], ], @@ -185,9 +195,25 @@ And the role provider is created using the service manager: return [ 'service_manager' => [ 'factories' => [ - 'Application\Role\CustomRoleProvider' => 'Application\Factory\CustomRoleProviderFactory' + MyCustomRoleProvider::class => MyCustomRoleProviderFactory::class, ], ], ]; ``` +## 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/docusaurus.config.js b/docs/docusaurus.config.js index 83083dc..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 @@ -71,15 +71,21 @@ themeConfig: logo: { alt: 'LM-Commons Logo', src: 'img/LMC-logo.png', +// href: 'https://lm-commons.github.io' }, items: [ { type: 'docSidebar', sidebarId: 'documentationSidebar', position: 'left', - label: 'Documentation', + label: 'Docs', }, - {to: '/blog', label: 'Blog', position: 'left'}, + { + type: "docsVersionDropdown", + position: "right", + }, + +// {to: '/blog', label: 'Blog', position: 'right'}, { href: 'https://lm-commons.github.io', label: 'LM-Commons', @@ -87,14 +93,16 @@ themeConfig: }, { href: 'https://github.com/lm-commons/lmcrbac', - label: 'GitHub', +// label: 'GitHub', position: 'right', + className: 'header-github-link', }, ], }, footer: { style: 'dark', links: [ +/* { title: 'Docs', items: [ @@ -104,6 +112,8 @@ themeConfig: }, ], }, + + */ { title: 'Community', items: [ @@ -116,10 +126,13 @@ themeConfig: { title: 'More', items: [ +/* { label: 'Blog', to: '/blog', }, + + */ { label: 'GitHub', href: 'https://github.com/lm-commons/lmcrbac', 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/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