Skip to content

Commit

Permalink
Prepping docs for v2.0
Browse files Browse the repository at this point in the history
Signed-off-by: Eric Richer [email protected] <[email protected]>
  • Loading branch information
visto9259 committed Sep 10, 2024
1 parent 3bace88 commit 2f9a5a7
Show file tree
Hide file tree
Showing 12 changed files with 989 additions and 1 deletion.
8 changes: 8 additions & 0 deletions docs/docusaurus.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,14 @@ const config = {
// Remove this to remove the "edit this page" links.
editUrl:
'https://github.com/lm-commons/lmcrbac/tree/master/docs/',
includeCurrentVersion: false,
lastVersion: '2.0',
versions: {
"2.0": {
label: '2.0',
path: '2.0',
}
}
},
blog: {
showReadingTime: true,
Expand Down
2 changes: 1 addition & 1 deletion docs/src/components/HomepageHeader/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export function HomepageHeader() {
<div className={styles.buttons}>
<Link
className="button button--secondary button--lg"
to="/docs/next/quick-start">
to="/docs/2.0/quick-start">
Quick start
</Link>
</div>
Expand Down
158 changes: 158 additions & 0 deletions docs/versioned_docs/version-2.0/Guides/integrating.md
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.
22 changes: 22 additions & 0 deletions docs/versioned_docs/version-2.0/Upgrading/migration.md
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).
37 changes: 37 additions & 0 deletions docs/versioned_docs/version-2.0/Upgrading/to-v2.md
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.
153 changes: 153 additions & 0 deletions docs/versioned_docs/version-2.0/assertions.md
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).
Loading

0 comments on commit 2f9a5a7

Please sign in to comment.