diff --git a/composer.json b/composer.json index 79f3fea8..dc11c5eb 100644 --- a/composer.json +++ b/composer.json @@ -37,14 +37,15 @@ "require": { "php": "^7.2", "laminas/laminas-servicemanager": "^3.3", - "laminas/laminas-stdlib": "^3.1" + "laminas/laminas-stdlib": "^3.1", + "doctrine/persistence": "^2.0" }, "require-dev": { "malukenho/docheader": "^0.1.7", "phpunit/phpunit": "^8.5.2", "phpspec/prophecy": "^1.10", "friendsofphp/php-cs-fixer": "^2.9.3", - "doctrine/common": "^2.4", + "doctrine/doctrine-orm-module": "^3.1", "satooshi/php-coveralls": "^2.0" }, "autoload": { diff --git a/docs/01. Introduction.md b/docs/01. Introduction.md deleted file mode 100644 index 97f00093..00000000 --- a/docs/01. Introduction.md +++ /dev/null @@ -1,59 +0,0 @@ -# Introduction - -Welcome to the official documentation of LmcRbac! - -In this part, the following questions will be answered: - -* Why should I use an authorization module? -* What is the Rbac model? -* How can I integrate LmcRbac into my application? - -## Why should I use an authorization module? - -The authorization part of an application is an essential aspect to secure your application. While the authentication -part tells you who is using your website, the authorization answers if the given identity has the permission to -perform specific actions. - -## What is the Rbac model? - -Rbac stands for **role-based access control**. We use a very simple (albeit powerful) implementation of this model -through the use of [this PHP library](https://github.com/zf-fr/rbac). - -> We are not using the official ZF2 Rbac component since ZfcRbac 2.0 as it has some design flaws. The library we are -using here is actually a prototype for ZF3 Rbac component I've made specifically for ZfcRbac. - -The basic idea of Rbac is to use roles and permissions: - -* **Users** can have one or many **Roles** -* **Roles** request access to **Permissions** -* **Permissions** are granted to **Roles** - -By default, LmcRbac can be used for two kinds of Rbac model: - -* Flat RBAC model: in this model, roles cannot have children. This is ideal for smaller application, as it is easier -to understand, and the database design is simpler (no need for a join table). -* Hierarchical RBAC model: in this model, roles can have children roles. When evaluating if a given role has a -permission, this model also checks recursively if any of its child roles also have the permission. - - -## How can I integrate LmcRbac into my application? - -LmcRbac offers multiple ways to protect your application: - -* Using **Guards**: those classes act as "firewalls" that block access to routes and/or controllers. Guards are usually - configured using PHP arrays, and are executed early in the MVC dispatch process. Typically this happens right after - the route has been matched. -* Using **AuthorizationService**: a complementary method is to use the `AuthorizationService` and inject them into your - service classes to protect them from unwanted access. - -While it is advised to use both methods to make your application even more secure, this is completely optional and you -can choose either of them independently. - -To find out about how you can easily make your existing application more secure, please refer to the following section: - -* [Cookbook: A real world example](/docs/07. Cookbook.md#a-real-world-application) - -### Navigation - -* Continue to [the **Quick Start**](/docs/02. Quick Start.md) -* Back to [the Index](/docs/README.md) diff --git a/docs/02. Quick Start.md b/docs/02. Quick Start.md deleted file mode 100644 index 545b970d..00000000 --- a/docs/02. Quick Start.md +++ /dev/null @@ -1,136 +0,0 @@ -# Quick Start - -In this section, you will learn: - -* How to setup the module -* How to specify an identity provider -* How to add simple role provider - -Before starting the quick start, make sure you have properly installed the module by following the instructions in -the README file. - -## Specifying an identity provider - -By default, LmcRbac internally uses the `Laminas\Authentication\AuthenticationService` service key to retrieve the user (logged or -not). Therefore, you must implement and register this service in your application by adding those lines in your `module.config.php` file: - -```php -return [ - 'service_manager' => [ - 'factories' => [ - 'Laminas\Authentication\AuthenticationService' => function($sm) { - // Create your authentication service! - } - ] - ] -]; -``` -The identity given by `Laminas\Authentication\AuthenticationService` must implement `LmcRbac\Identity\IdentityInterface`. Note that the default identity provided with ZF2 does not implement this interface, neither does the ZfcUser suite. - -LmcRbac is flexible enough to use something else than the built-in `AuthenticationService`, by specifying custom -identity providers. For more information, refer [to this section](/docs/03. Role providers.md#identity-providers). - -## Adding a guard - -A guard allows to block access to routes and/or controllers using a simple syntax. For instance, this configuration -grants access to any route that begins with `admin` (or is exactly `admin`) to the `admin` role only: - -```php -return [ - 'lmc_rbac' => [ - 'guards' => [ - 'LmcRbac\Guard\RouteGuard' => [ - 'admin*' => ['admin'] - ] - ] - ] -]; -``` - -LmcRbac have several built-in guards, and you can also register your own guards. For more information, refer -[to this section](/docs/04. Guards.md#built-in-guards). - -## Adding a role provider - -RBAC model is based on roles. Therefore, for LmcRbac to work properly, it must be aware of all the roles that are -used inside your application. - -This configuration creates an *admin* role that has a children role called *member*. The *admin* role automatically -inherits the *member* permissions. - -```php -return [ - 'lmc_rbac' => [ - 'role_provider' => [ - \LmcRbac\Role\InMemoryRoleProvider::class => [ - 'admin' => [ - 'children' => ['member'], - 'permissions' => ['delete'] - ], - 'member' => [ - 'permissions' => ['edit'] - ] - ] - ] - ] -]; -``` - -In this example, the *admin* role have two permissions: `delete` and `edit` (because it inherits the permissions from -its child), while the *member* role only has the permission `edit`. - -LmcRbac have several built-in role providers, and you can also register your own role providers. For more information, -refer [to this section](/docs/03. Role providers.md#built-in-role-providers). - -## Registering a strategy - -When a guard blocks access to a route/controller, or if you throw the `LmcRbac\Exception\UnauthorizedException` -exception in your service, LmcRbac automatically performs some logic for you depending on the view strategy used. - -For instance, if you want LmcRbac to automatically redirect all unauthorized requests to the "login" route, add -the following code in the `onBootstrap` method of your `Module.php` class: - -```php -public function onBootstrap(EventInterface $e) -{ - $t = $e->getTarget(); - - $t->getEventManager()->attach( - $t->getServiceManager()->get('LmcRbac\View\Strategy\RedirectStrategy') - ); -} -``` - -By default, `RedirectStrategy` redirects all unauthorized requests to a route named "login" when user is not connected -and to a route named "home" when user is connected. This is, of course, entirely configurable. - -> For flexibility purpose, LmcRbac **does not** register any strategy for you by default! - -For more information about built-in strategies, refer [to this section](/docs/05. Strategies.md#built-in-strategies). - -## Using the authorization service - -Now that LmcRbac is properly configured, you can inject the authorization service in any class and use it to check -if the current identity is granted to do something. - -The authorization service is registered inside the service manager using the following key: `LmcRbac\Service\AuthorizationService`. -Once injected, you can use it as follow: - -```php -use LmcRbac\Exception\UnauthorizedException; - -public function delete() -{ - if (!$this->authorizationService->isGranted('delete')) { - throw new UnauthorizedException(); - } - - // Delete the post -} -``` - -### Navigation - -* Continue to [the **Role providers**](/docs/03. Role providers.md) -* Back to [the Introduction](/docs/01. Introduction.md) -* Back to [the Index](/docs/README.md) diff --git a/docs/03. Role providers.md b/docs/03. Role providers.md deleted file mode 100644 index 833682c4..00000000 --- a/docs/03. Role providers.md +++ /dev/null @@ -1,200 +0,0 @@ -# Role providers - -In this section, you will learn: - -* What are role providers -* What are identity providers -* How to use and configure built-in providers -* How to create custom role providers - -## What are role providers? - -A role provider is an object that returns a list of roles. Each role provider must implement the -`LmcRbac\Role\RoleProviderInterface` interface. The only required method is `getRoles`, and must return an array -of `Rbac\Role\RoleInterface` objects. - -Roles can come from any sources: in memory, from a file, from a database... However, please note that since LmcRbac -2.0, you can specify only one role provider per application. The reason is that having multiple role providers make -the workflow harder and can lead to security problems that are very hard to spot. - -## Identity providers? - -Identity providers return the current identity. Most of the time, this means the logged user. LmcRbac comes with a -default identity provider (`LmcRbac\Identity\AuthenticationIdentityProvider`) that uses the -`Laminas\Authentication\AuthenticationService` service. - -### Create your own identity provider - -If you want to implement your own identity provider, create a new class that implements -`LmcRbac\Identity\IdentityProviderInterface` class. Then, change the `identity_provider` option in LmcRbac config, -as shown below: - -```php -return [ - 'lmc_rbac' => [ - 'identity_provider' => 'MyCustomIdentityProvider' - ] -]; -``` - -The identity provider is automatically pulled from the service manager. - -## Built-in role providers - -LmcRbac comes with two built-in role providers: `InMemoryRoleProvider` and `ObjectRepositoryRoleProvider`. A role -provider must be added to the `role_provider` subkey: - -```php -return [ - 'lmc_rbac' => [ - 'role_provider' => [ - // Role provider config here! - ] - ] -]; -``` - -### InMemoryRoleProvider - -This provider is ideal for small/medium sites with few roles/permissions. All the data is specified in a simple -PHP file, so you never hit a database. - -Here is an example of the format you need to use: - -```php -return [ - 'lmc_rbac' => [ - 'role_provider' => [ - \LmcRbac\Role\InMemoryRoleProvider::class => [ - '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 optionals. Internally, the `InMemoryRoleProvider` creates -either a `Rbac\Role\Role` object if the role does not have any children, or a `Rbac\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::class => [ - 'admin' => [ - 'permissions' => [ - 'article.delete', - 'article.edit', - 'article.archive', - 'article.read' - ] - ], - 'member' => [ - 'permissions' => [ - 'article.edit', - 'article.archive', - 'article.read' - ] - ], - 'guest' => [ - 'permissions' => ['article.read'] - ] - ] - ] - ] -]; -``` - -### ObjectRepositoryRoleProvider - -This provider fetches roles from the 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::class => [ - '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::class => [ - '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 `Rbac\Role\RoleInterface` interface. - -## Creating custom role providers - -To create a custom role providers, 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_manager' => [ - 'factories' => [ - 'Application\Role\CustomRoleProvider' => 'Application\Factory\CustomRoleProviderFactory' - ] - ] - ] -]; -``` - -You can now use it like any other role provider: - -```php -return [ - 'lmc_rbac' => [ - 'role_provider' => [ - 'Application\Role\CustomRoleProvider' => [ - // Options - ] - ] - ] -]; -``` - -### Navigation - -* Continue to [the **Guards**](/docs/04. Guards.md) -* Back to [the Quick Start](/docs/02. Quick Start.md) -* Back to [the Index](/docs/README.md) diff --git a/docs/04. Guards.md b/docs/04. Guards.md deleted file mode 100644 index 602bd133..00000000 --- a/docs/04. Guards.md +++ /dev/null @@ -1,496 +0,0 @@ -# Guards - -In this section, you will learn: - -* What are guards -* How to use and configure built-in guards -* How to create custom guards - -## What are guards and when to use them? - -Guards (called firewalls in older versions of LmcRbac) are listeners that are registered on a specific event of -the MVC workflow. They allow to quickly unauthorized requests. - -Here is a simple workflow without guards: - -![Laminas workflow without guards](/docs/images/workflow-without-guards.png?raw=true) - -And here is a simple workflow with a route guard: - -![Laminas workflow with guards](/docs/images/workflow-with-guards.png?raw=true) - -RouteGuard and ControllerGuard are not aware of permissions but rather only think about "roles". For -instance, you may want to refuse access to each routes that begin by "admin/*" to all users that do not have the -"admin" role. - -If you want to protect a route for a set of permissions, you must use RoutePermissionsGuard. For instance, -you may want to grant access to a route "post/delete" only to roles having the "delete" permission. -Note that in a RBAC system, a permission is linked to a role, not to a user. - -Albeit simple to use, guards should not be the only protection in your application, and you should always also -protect your service. The reason is that your business logic should be handled by your service. Protecting a given -route or controller does not mean that the service cannot be access from elsewhere (another action for instance). - -### Protection policy - -By default, when a guard is added, it will perform check only on the specified guard rules. Any route or controller -that are not specified in the rules will be "granted" by default. Therefore, the default is a "blacklist" -mechanism. - -However, you may want a more restrictive approach (also called "whitelist"). In this mode, once a guard is added, -anything that is not explicitely added will be refused by default. - -For instance, let's say you have two routes: "index" and "login". If you specify a route guard rule to allow "index" -route to "member" role, your "login" route will become defacto unauthorized to anyone, unless you add a new rule for -allowing the route "login" to "member" role. - -You can change it in LmcRbac config, as follows: - -```php -use LmcRbac\Guard\GuardInterface; - -return [ - 'lmc_rbac' => [ - 'protection_policy' => GuardInterface::POLICY_DENY - ] -]; -``` - -> NOTE: this policy will block ANY route/controller (so it will also block any console routes or controllers). The -deny policy is much more secure, but it needs much more configuration to work with. - -## Built-in guards - -LmcRbac comes with four guards, in order of priority : - -* RouteGuard : protect a set of routes based on the identity roles -* RoutePermissionsGuard : protect a set of routes based on roles permissions -* ControllerGuard : protect a controllers and/or actions based on the identity roles -* ControllerPermissionsGuard : protect a controllers and/or actions based on roles permissions - -All guards must be added in the `guards` subkey: - -```php -return [ - 'lmc_rbac' => [ - 'guards' => [ - // Guards config here! - ] - ] -]; -``` - -Because of the way Laminas handles config, you can without problem define some rules in one module, and -more rules in another module. All the rules will be automatically merged. - -> For your mental health, I recommend you to use either the route guard OR the controller guard, but not both. If -you decide to use both conjointly, I recommend you to set the protection policy to "allow" (otherwise, you will -need to define rules for every routes AND every controller, which can become quite frustrating!). - -Please note that if your application use both route and controller guards, route guards are always executed -**before** controller guards (they have a higher priority). - -### RouteGuard - -> The RouteGuard listens to the `MvcEvent::EVENT_ROUTE` event with a priority of -5. - -The RouteGuard allows to protect a route or a hierarchy of route. You must provide an array of "key" => "value", -where the key is a route pattern, and value an array of role names: - -```php -return [ - 'lmc_rbac' => [ - 'guards' => [ - 'LmcRbac\Guard\RouteGuard' => [ - 'admin*' => ['admin'], - 'login' => ['guest'] - ] - ] - ] -]; -``` - -> Only one role in a rule need to be matched (it is an OR condition). - -Those rules grant access to all admin routes to users that have the "admin" role, and grant access to the "login" -route to users that have the "guest" role (eg.: most likely unauthenticated users). - -> The route pattern is not a regex. It only supports the wildcard (*) character, that replaces any segment. - -You can also use the wildcard character * for roles: - -```php -return [ - 'lmc_rbac' => [ - 'guards' => [ - 'LmcRbac\Guard\RouteGuard' => [ - 'home' => ['*'] - ] - ] - ] -]; -``` - -This rule grants access to the "home" route to anyone. - -Finally, you can also omit the roles array to completly block a route, for maintenance purpose for example : - -```php -return [ - 'lmc_rbac' => [ - 'guards' => [ - 'LmcRbac\Guard\RouteGuard' => [ - 'route_under_construction' - ] - ] - ] -]; -``` - -This rule will be inaccessible. - -Note : this last example could be (and should be) written in a more explicit way : - -```php -return [ - 'lmc_rbac' => [ - 'guards' => [ - 'LmcRbac\Guard\RouteGuard' => [ - 'route_under_construction' => [] - ] - ] - ] -]; -``` - - -### RoutePermissionsGuard - -> The RoutePermissionsGuard listens to the `MvcEvent::EVENT_ROUTE` event with a priority of -8. - -The RoutePermissionsGuard allows to protect a route or a hierarchy of route. You must provide an array of "key" => "value", -where the key is a route pattern, and value an array of permissions names: - -```php -return [ - 'lmc_rbac' => [ - 'guards' => [ - 'LmcRbac\Guard\RoutePermissionsGuard' => [ - 'admin*' => ['admin'], - 'post/manage' => ['post.update', 'post.delete'] - ] - ] - ] -]; -``` - -> By default, all permissions in a rule must be matched (an AND condition). - -In the previous example, one must have ```post.update``` **AND** ```post.delete``` permissions -to access the ```post/manage``` route. You can also specify an OR condition like so: - -```php -use LmcRbac\Guard\GuardInterface; - -return [ - 'lmc_rbac' => [ - 'guards' => [ - 'LmcRbac\Guard\RoutePermissionsGuard' => [ - 'post/manage' => [ - 'permissions' => ['post.update', 'post.delete'], - 'condition' => GuardInterface::CONDITION_OR - ] - ] - ] - ] -]; -``` - -> Permissions are linked to roles, not to users - -Those rules grant access to all admin routes to roles that have the "admin" permission, and grant access to the -"post/delete" route to roles that have the "post.delete" or "admin" permissions. - -> The route pattern is not a regex. It only supports the wildcard (*) character, that replaces any segment. - -You can also use the wildcard character * for permissions: - -```php -return [ - 'lmc_rbac' => [ - 'guards' => [ - 'LmcRbac\Guard\RoutePermissionsGuard' => [ - 'home' => ['*'] - ] - ] - ] -]; -``` - -This rule grants access to the "home" route to anyone. - -Finally, you can also use an empty array to completly block a route, for maintenance purpose for example : - -```php -return [ - 'lmc_rbac' => [ - 'guards' => [ - 'LmcRbac\Guard\RoutePermissionsGuard' => [ - 'route_under_construction' => [] - ] - ] - ] -]; -``` - -This rule will be inaccessible. - - -### ControllerGuard - -> The ControllerGuard listens to the `MvcEvent::EVENT_ROUTE` event with a priority of -10. - -The ControllerGuard allows to protect a controller. You must provide an array of array: - -```php -return [ - 'lmc_rbac' => [ - 'guards' => [ - 'LmcRbac\Guard\ControllerGuard' => [ - [ - 'controller' => 'MyController', - 'roles' => ['guest', 'member'] - ] - ] - ] - ] -]; -``` - -> Only one role in a rule need to be matched (it is an OR condition). - -Those rules grant access to each actions of the MyController controller to users that have either the "guest" or -"member" roles. - -As for RouteGuard, you can use a wildcard (*) character for roles. - -You can also specify optional actions, so that the rule only apply to one or several actions: - -```php -return [ - 'lmc_rbac' => [ - 'guards' => [ - 'LmcRbac\Guard\ControllerGuard' => [ - [ - 'controller' => 'MyController', - 'actions' => ['read', 'edit'], - 'roles' => ['guest', 'member'] - ] - ] - ] - ] -]; -``` - -You can combine a generic rule and a specific action rule for the same controller, as follow: - -```php -return [ - 'lmc_rbac' => [ - 'guards' => [ - 'LmcRbac\Guard\ControllerGuard' => [ - [ - 'controller' => 'PostController', - 'roles' => ['member'] - ], - [ - 'controller' => 'PostController', - 'actions' => ['delete'], - 'roles' => ['admin'] - ] - ] - ] - ] -]; -``` - -Those rules grant access to each actions of the controller to users that have the "member" role, but restrict the -"delete" action to "admin" only. - -### ControllerPermissionsGuard - -> The ControllerPermissionsGuard listens to the `MvcEvent::EVENT_ROUTE` event with a priority of -13. - -The ControllerPermissionsGuard allows to protect a controller using permissions. You must provide an array of array: - -```php -return [ - 'lmc_rbac' => [ - 'guards' => [ - 'LmcRbac\Guard\ControllerPermissionsGuard' => [ - [ - 'controller' => 'MyController', - 'permissions' => ['post.update', 'post.delete'] - ] - ] - ] - ] -]; -``` - -> All permissions in a rule must be matched (it is an AND condition). - -In the previous example, the user must have ```post.update``` **AND** ```post.delete``` permissions -to access each actions of the MyController controller. - -As for all other guards, you can use a wildcard (*) character for permissions. - -The configuration rules are the same as for ControllerGuard. - -### Security notice - -RouteGuard and ControllerGuard listen to the `MvcEvent::EVENT_ROUTE` event. Therefore, if you use the -`forward` method into your controller, those guards will not intercept and check requests (because internally -ZF2 does not trigger again a new MVC loop). - -Most of the time, this is not an issue, but you must be aware of it, and this is an additional reason why you -should always protect your services too. - -## Creating custom guards - -LmcRbac is flexible enough to allow you to create custom guard. Let's say we want to create a guard that will -refuse access based on an IP addresses blacklist. - -First create the guard: - -```php -namespace Application\Guard; - -use Laminas\Http\Request as HttpRequest; -use Laminas\Mvc\MvcEvent; -use LmcRbac\Guard\AbstractGuard; - -class IpGuard extends AbstractGuard -{ - const EVENT_PRIORITY = 100; - - /** - * List of IPs to blacklist - */ - protected $ipAddresses = []; - - /** - * @param array $ipAddresses - */ - public function __construct(array $ipAddresses) - { - $this->ipAddresses = $ipAddresses; - } - - /** - * @param MvcEvent $event - * @return bool - */ - public function isGranted(MvcEvent $event) - { - $request = $event->getRequest(); - - if (!$request instanceof HttpRequest) { - return true; - } - - if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) { - $clientIp = $_SERVER['HTTP_X_FORWARDED_FOR']; - } else { - $clientIp = $_SERVER['REMOTE_ADDR']; - } - - return !in_array($clientIp, $this->ipAddresses); - } -} -``` - -> Guards must implement `LmcRbac\Guard\GuardInterface`. - -By default, guards are listening to the event `MvcEvent::EVENT_ROUTE` with a priority of -5 (you can change the default -event to listen by overriding the `EVENT_NAME` constant in your guard subclass). However, in this case, we don't -even need to wait for the route to be matched, so we overload the `EVENT_PRIORITY` constant to be executed earlier. - -The `isGranted` method simply retrieves the client IP address, and check it against the blacklist. - -However, for this to work, we must register the newly created guard to the guard plugin manager. To do so, add the -following code in your config: - -```php -return [ - 'lmc_rbac' => [ - 'guard_manager' => [ - 'factories' => [ - 'Application\Guard\IpGuard' => 'Application\Factory\IpGuardFactory' - ] - ] - ] -]; -``` - -The `guard_manager` config follows a conventional service manager config format. - -Now, let's create the factory: - -```php -namespace Application\Factory; - -use Application\Guard\IpGuard; -use Laminas\ServiceManager\FactoryInterface; -use Laminas\ServiceManager\MutableCreationOptionsInterface; -use Laminas\ServiceManager\ServiceLocatorInterface; - -class IpGuardFactory implements FactoryInterface, MutableCreationOptionsInterface -{ - /** - * @var array - */ - protected $options; - - /** - * {@inheritDoc} - */ - public function setCreationOptions(array $options) - { - $this->options = $options; - } - - /** - * {@inheritDoc} - */ - public function createService(ServiceLocatorInterface $serviceLocator) - { - return new IpGuard($this->options); - } -} -``` - -The `MutableCreationOptionsInterface` was introduced in Laminas 2.2, and allows your factories to accept -options. In fact, in a real use case, you would likely fetched the blacklist from database. - -Now, we just need to add the guard to the `guards` option, so that LmcRbac execute the logic behind this guard. In -your config, add the following code: - -```php -return [ - 'lmc_rbac' => [ - 'guards' => [ - 'Application\Guard\IpGuard' => [ - '87.45.66.46', - '65.87.35.43' - ] - ] - ] -]; -``` - -### Navigation - -* Continue to [the **Strategies**](/docs/05. Strategies.md) -* Back to [the Role providers](/docs/03. Role providers.md) -* Back to [the Index](/docs/README.md) diff --git a/docs/05. Strategies.md b/docs/05. Strategies.md deleted file mode 100644 index b70675c6..00000000 --- a/docs/05. Strategies.md +++ /dev/null @@ -1,153 +0,0 @@ -# Strategies - -In this section, you will learn: - -* What are strategies -* How to use built-in strategies -* Creating custom strategies - -## What are strategies? - -A strategy is an object that listens to the `MvcEvent::EVENT_DISPATCH_ERROR` event. It is used to describe what -happens when an access to a resource is unauthorized by LmcRbac. - -LmcRbac strategies all check if an `LmcRbac\Exception\UnauthorizedExceptionInterface` has been thrown. - -By default, LmcRbac does not register any strategy for you. The best place to register it is in your `onBootstrap` -method of the `Module.php` class: - -```php -public function onBootstrap(MvcEvent $event) -{ - $app = $event->getApplication(); - $sm = $app->getServiceManager(); - $em = $app->getEventManager(); - - $listener = $sm->get(\LmcRbac\View\Strategy\RedirectStrategy::class); - $listener->attach($em); -} -``` - -## Built-in strategies - -LmcRbac comes with two built-in strategies: `RedirectStrategy` and `UnauthorizedStrategy`. - -### RedirectStrategy - -This strategy allows to redirect any unauthorized request to another route, by optionally appending the previous -URL as a query param. - -To register it, copy-paste this code in your Module.php class: - -```php -public function onBootstrap(EventInterface $e) -{ - $t = $e->getTarget(); - - $t->getEventManager()->attach( - $t->getServiceManager()->get('LmcRbac\View\Strategy\RedirectStrategy') - ); -} -``` - -You can configure the strategy using the `redirect_strategy` subkey: - -```php -return [ - 'lmc_rbac' => [ - 'redirect_strategy' => [ - 'redirect_when_connected' => true, - 'redirect_to_route_connected' => 'home', - 'redirect_to_route_disconnected' => 'login', - 'append_previous_uri' => true, - 'previous_uri_query_key' => 'redirectTo' - ], - ] -]; -``` - -If a user tries to access to an unauthorized resource (eg.: http://www.example.com/delete), he/she will be -redirect to the "login" route if is not connected and to the "home" route otherwise (it must exists in your route config, -of course) with the previous URL appended : http://www.example.com/login?redirectTo=http://www.example.com/delete - -You can prevent redirection when a user is connected (i.e. so that the user gets a 403 page) by setting `redirect_when_connected` to `false`. - -### UnauthorizedStrategy - -This strategy allows to render a template on any unauthorized request. - -To register it, copy-paste this code in your Module.php class: - -```php -public function onBootstrap(EventInterface $e) -{ - $t = $e->getTarget(); - - $t->getEventManager()->attach( - $t->getServiceManager()->get('LmcRbac\View\Strategy\UnauthorizedStrategy') - ); -} -``` - -You can configure the strategy using the `unauthorized_strategy` subkey: - -```php -return [ - 'lmc_rbac' => [ - 'unauthorized_strategy' => [ - 'template' => 'error/custom-403' - ], - ] -]; -``` - -> By default, LmcRbac uses a template called `error/403`. - -## Creating custom strategies - -Creating a custom strategy is rather easy. Let's say we want to create a strategy that integrates with -the [ApiProblem](https://github.com/laminas-api-tools/api-tools-api-problem) Laminas module: - -```php -namespace Application\View\Strategy; - -use Laminas\Http\Response as HttpResponse; -use Laminas\Mvc\MvcEvent; -use Laminas\ApiTools\ApiProblem\ApiProblem; -use Laminas\ApiTools\ApiProblem\ApiProblemResponse; -use LmcRbac\View\Strategy\AbstractStrategy; -use LmcRbac\Exception\UnauthorizedExceptionInterface; - -class ApiProblemStrategy extends AbstractStrategy -{ - public function onError(MvcEvent $event) - { - // Do nothing if no error or if response is not HTTP response - if (!($exception = $event->getParam('exception') instanceof UnauthorizedExceptionInterface) - || ($result = $event->getResult() instanceof HttpResponse) - || !($response = $event->getResponse() instanceof HttpResponse) - ) { - return; - } - - return new ApiProblemResponse(new ApiProblem($exception->getMessage())); - } -} -``` - -Register your strategy: - -```php -public function onBootstrap(EventInterface $e) -{ - $e->getTarget() - ->getEventManager() - ->attach(new ApiProblemStrategy()); -} -``` - -### Navigation - -* Continue to [the **Authorization Service**](/docs/06. Using the Authorization Service.md) -* Back to [the Guards](/docs/04. Guards.md) -* Back to [the Index](/docs/README.md) diff --git a/docs/06. Using the Authorization Service.md b/docs/06. Using the Authorization Service.md deleted file mode 100644 index af295d58..00000000 --- a/docs/06. Using the Authorization Service.md +++ /dev/null @@ -1,276 +0,0 @@ -# Using the Authorization Service - -This section will teach you how to use the AuthorizationService to its full extend. - -## Injecting the Authorization Service - -### Using initializers - -To automatically inject the authorization service into your classes, you can implement the -`AuthorizationServiceAwareInterface` and use the trait, as shown below: - -```php -namespace YourModule; - -use LmcRbac\Service\AuthorizationServiceAwareInterface; -use LmcRbac\Service\AuthorizationServiceAwareTrait; - -class MyClass implements AuthorizationServiceAwareInterface -{ - use AuthorizationServiceAwareTrait; - - public function doSomethingThatRequiresAuth() - { - if (! $this->getAuthorizationService()->isGranted('deletePost')) { - throw new UnauthorizedException('You are not allowed !'); - } - - return true; - } -} -``` - -Then, registers the initializer in your config (it is not by default): - -```php -class Module -{ - // ... - - public function getServiceConfig() - { - return [ - 'initializers' => [ - 'LmcRbac\Initializer\AuthorizationServiceInitializer' - ] - ]; - } -} -``` - -> While initializers allow rapid prototyping, it can leads to more fragile code. We'd suggest using factories. - -### Using delegator factory - -LmcRbac is shipped with a `LmcRbac\Factory\AuthorizationServiceDelegatorFactory` [delegator factory] -(http://framework.zend.com/manual/2.3/en/modules/zend.service-manager.delegator-factories.html) -to automatically inject the authorization service into your classes. - -As for the initializer, the class must implement the `AuthorizationServiceAwareInterface`. - -You just have to add your classes to the right delegator : - -```php -class Module -{ - // ... - - public function getServiceConfig() - { - return [ - 'invokables' => [ - 'Application\Service\MyClass' => 'Application\Service\MyClassService', - ], - 'delegators' => [ - 'Application\Service\MyClass' => [ - 'LmcRbac\Factory\AuthorizationServiceDelegatorFactory', - // eventually add more delegators here - ], - ], - ]; - } -} -``` - -> While they need a little more configuration, delegators factories have better performances than initializers. - -### Using Factories - -You can inject the AuthorizationService in your factories by using Zend's ServiceManager. The AuthorizationService -is known to the ServiceManager as `\LmcRbac\Service\AuthorizationService::class`. Here is a classic example for injecting -the AuthorizationService: - -*YourModule/Module.php* - -```php -class Module -{ - // getAutoloaderConfig(), etc... - - public function getServiceConfig() - { - return [ - 'factories' => [ - 'MyService' => function($sm) { - $authService = $sm->get(\LmcRbac\Service\AuthorizationService::class); - return new MyService($authService); - } - ] - ]; - } -} -``` - -### Using Zend\DI - -DI is a great way for prototyping, getting results *fast* and maintaining a flexible structure. However it adds overhead and can get very slow. Unless you are using a compiler it is **not** recommended for production. -Here's how you enable Zend\DI to inject the AuthorizationService in MyClass: - -*YourModule/Module.php* - -```php -namespace YourModule; - -class Module -{ - // getAutoloaderConfig(), etc... - - public function getConfig() - { - return [ - 'di' => [ - 'definition' => [ - 'class' => [ - __NAMESPACE__ . '\MyClass' => [ - 'setAuthorizationService' => [ - 'required' => true - ] - ] - ] - ] - ] - ]; - } -} -``` - -## Permissions and Assertions - -Since you now know how to inject the AuthorizationService, let's use it! - -One of the great things the AuthorizationService brings are **assertions**. Assertions get executed *if the identity -in fact holds the permission you are requesting*. A common example is a blog post, which only the author can edit. In -this case, you have a `post.edit` permission and run an assertion checking the author afterwards. - -### Defining assertions - -The AssertionPluginManager is a great way for you to use assertions and IOC. You can add new assertions quite easily -by adding this to your `module.config.php` file: - -```php -return [ - 'lmc_rbac' => [ - 'assertion_manager' => [ - 'factories' => [ - 'MyAssertion' => 'MyAssertionFactory' - ] - ] - ] -]; -``` - -### Defining the assertion map - -The assertion map can automatically map permissions to assertions. This means that every times you check for a -permission with an assertion map, you'll include the assertion in your check. You can define the assertion map by -adding this to your `module.config.php` file: - -```php -return [ - 'lmc_rbac' => [ - 'assertion_map' => [ - 'myPermission' => 'myAssertion' - ] - ] -]; -``` - -Now, everytime you check for `myPermission`, `myAssertion` will be checked as well. - -### Checking permissions in a service - -So let's check for a permission, shall we? - -```php -$authorizationService->isGranted('myPermission'); -``` - -That was easy, wasn't it? - -`isGranted` checks if the current identity is granted the permission and additionally runs the assertion that is -provided by the assertion map. - -### Checking permissions in a controller or in a view - -LmcRbac comes with both a controller plugin and a view helper to check permissions. - -In a controller : - -```php - public function doSomethingAction() - { - if (!$this->isGranted('myPermission')) { - // redirect if not granted for example - } - } -``` - -In a view : - -```php - isGranted('myPermission')): ?> -
Display only if granted
-