-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Signed-off-by: Eric Richer [email protected] <[email protected]>
- Loading branch information
Showing
12 changed files
with
989 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
<?php | ||
namespace MyApp; | ||
|
||
use \Laminas\Authentication\AuthenticationService; | ||
use \Lmc\Rbac\Service\AuthorizationServiceAwareTrait; | ||
|
||
class MyClass | ||
{ | ||
use AuthorizationServiceAwareTrait; | ||
protected AuthenticationService $authenticationService; | ||
|
||
public function __construct($authenticationService, $authorizationService) | ||
{ | ||
$this->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 | ||
<?php | ||
namespace MyApp; | ||
|
||
use \Lmc\Rbac\Identity\IdentityInterface; | ||
use \Mezzio\Authentication\UserInterface; | ||
|
||
class MyUser implements UserInterface, IdentityInterface | ||
{ | ||
private string $identity; | ||
private $roles; | ||
private $details; | ||
|
||
public function __construct(string $identity, array $roles = [], array $details = []) | ||
{ | ||
$this->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 | ||
<?php | ||
use \Mezzio\Authentication\UserInterface; | ||
use MyUser; | ||
// ... | ||
return [ | ||
'factories' => [ | ||
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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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). |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,153 @@ | ||
--- | ||
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 `Lmc\Rbac\Assertion\AssertionInterace` which defines only one method: | ||
|
||
```php | ||
public function assert( | ||
string $permission, | ||
?IdentityInterface $identity = null, | ||
mixed $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 | ||
<?php | ||
|
||
class MyAssertion implements \Lmc\Rbac\Assertion\AssertionInterface | ||
{ | ||
public 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; | ||
} | ||
} | ||
``` | ||
## 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 | ||
<?php | ||
use Laminas\ServiceManager\Factory\InvokableFactory | ||
|
||
return [ | ||
'lmc_rbac' => [ | ||
/* the rest of the file */ | ||
'assertion_map' => [ | ||
'edit' => \My\Namespace\MyAssertion::class, | ||
], | ||
'assertion_manager' => [ | ||
'factories' => [ | ||
\My\Namespace\MyAssertion::class => InvokableFactory::class | ||
], | ||
], | ||
], | ||
]; | ||
``` | ||
It is also possible to configure an assertion using a callable instead of a class: | ||
|
||
```php | ||
<?php | ||
|
||
use Lmc\Rbac\Permission\PermissionInterface; | ||
|
||
return [ | ||
'lmc_rbac' => [ | ||
/* 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 | ||
<?php | ||
|
||
return [ | ||
'lmc_rbac' => [ | ||
/* the rest of the file */ | ||
'assertion_map' => [ | ||
'edit' => [ | ||
\My\Namespace\AssertionA::class, | ||
\My\Namespace\AssertionB::class, | ||
], | ||
'read' => [ | ||
'condition' => \Lmc\Rbac\Assertion\AssertionSet::CONDITION_OR, | ||
\My\Namespace\AssertionC::class, | ||
\My\Namespace\AssertionD::class, | ||
], | ||
'delete' => [ | ||
'condition' => \Lmc\Rbac\Assertion\AssertionSet::CONDITION_OR, | ||
\My\Namespace\AssertionE::class, | ||
[ | ||
'condition' => \Lmc\Rbac\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. | ||
|
||
## 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). |
Oops, something went wrong.