# 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 ZfcRbac) 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:

![Zend Framework workflow without guards](/docs/images/workflow-without-guards.png?raw=true)

And here is a simple workflow with a route guard:

![Zend Framework 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 ZfcRbac config, as follows:

```php
use ZfcRbac\Guard\GuardInterface;

return [
    'zfc_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

ZfcRbac 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 [
    'zfc_rbac' => [
        'guards' => [
            // Guards config here!
        ]
    ]
];
```

Because of the way Zend Framework 2 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 [
    'zfc_rbac' => [
        'guards' => [
            'ZfcRbac\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 [
    'zfc_rbac' => [
        'guards' => [
            'ZfcRbac\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 [
    'zfc_rbac' => [
        'guards' => [
            'ZfcRbac\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 [
    'zfc_rbac' => [
        'guards' => [
            'ZfcRbac\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 [
    'zfc_rbac' => [
        'guards' => [
            'ZfcRbac\Guard\RoutePermissionsGuard' => [
                'admin*' => ['admin'],
                'post/manage'   => ['post.update', 'post.delete']
            ]
        ]
    ]
];
```

> All permissions in a rule must be matched (it is an AND condition).

In the previous example, one must have ```post.update``` **AND** ```post.delete``` permissions
to access the ```post/manage``` route.

> 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 [
    'zfc_rbac' => [
        'guards' => [
            'ZfcRbac\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 [
    'zfc_rbac' => [
        'guards' => [
            'ZfcRbac\Guard\RoutePermissionsGuard' => [
                'route_under_construction' => []
            ]
        ]
    ]
];
```

This rule will be inaccessible.


### ControllerGuard

> The RoutePermissionsGuard 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 [
    'zfc_rbac' => [
        'guards' => [
            'ZfcRbac\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 [
    'zfc_rbac' => [
        'guards' => [
            'ZfcRbac\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 [
    'zfc_rbac' => [
        'guards' => [
            'ZfcRbac\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 RoutePermissionsGuard 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 [
    'zfc_rbac' => [
        'guards' => [
            'ZfcRbac\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

ZfcRbac 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 Zend\Http\Request as HttpRequest;
use Zend\Mvc\MvcEvent;
use ZfcRbac\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 `ZfcRbac\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 [
    'zfc_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 Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\MutableCreationOptionsInterface;
use Zend\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 Zend Framework 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 ZfcRbac execute the logic behind this guard. In
your config, add the following code:

```php
return [
    'zfc_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)
