%PDF- %PDF-
Mini Shell

Mini Shell

Direktori : /var/www/html/bbw/farmaci/kritik-portale/vendor/zf-commons/zfc-rbac/docs/
Upload File :
Create Path :
Current File : /var/www/html/bbw/farmaci/kritik-portale/vendor/zf-commons/zfc-rbac/docs/07. Cookbook.md

# Cookbook

This section will help you to further understand how ZfcRbac works by providing more concrete examples. If you have
any other recipe you'd like to add, please open an issue!

- [A Real World Application](/docs/07.%20Cookbook.md#a-real-world-application)
    - [Best Practices](https://github.com/manuakasam/zfc-rbac/blob/master/docs/07.%20Cookbook.md#best-practices)
    - [When to use Guards](/docs/07.%20Cookbook.md#when-using-guards-then)
- [A Real World Application Part 2 - Only delete your own Posts](/docs/07.%20Cookbook.md#a-real-world-application-part-2---only-delete-your-own-posts)
- [A Real World Application Part 3 - But Admins can delete everything](/docs/07.%20Cookbook.md#a-real-world-application-part-3---admins-can-delete-everything)
- [A Real World Application Part 4 - Only admins have the menu item for managing the posts]
    (/docs/07.%20Cookbook.md#a-real-world-application-part-4---checking-permissions-in-the-view)
- [Using ZfcRbac with Doctrine ORM](/docs/07.%20Cookbook.md#using-zfcrbac-with-doctrine-orm)
    - [How to deal with roles with lot of permissions?](/docs/07.%20Cookbook.md#how-to-deal-with-roles-with-lot-of-permissions)
- [Using ZfcRbac and ZF2 Assetic](/docs/07.%20Cookbook.md#using-zfcrbac-and-zf2-assetic)
- [Using ZfcRbac and ZfcUser](/docs/07.%20Cookbook.md#using-zfcrbac-and-zfcuser)

## A Real World Application

In this example we are going to create a very little real world application. We will create a controller
`PostController` that interacts with a service called `PostService`. For the sake of simplicity we will only
cover the `delete`-methods of both parts.

Let's start by creating a controller that has the `PostService` as dependency:

```php
class PostController
{
    protected $postService;

    public function __construct(PostService $postService)
    {
        $this->postService = $postService;
    }

    // addAction(), editAction(), etc...

    public function deleteAction()
    {
        $id = $this->params()->fromQuery('id');

        $this->postService->deletePost($id);

        return $this->redirect()->toRoute('posts');
    }
}
```

Since we have a dependency, let's inject it using the `ControllerManager`, we will do this inside our `Module`-Class

```php
class Module
{
    public function getConfig()
    {
        return [
            'controllers' => [
                'factories' => [
                    'PostController' => function ($cpm) {
                        // We assume a Service key 'PostService' here that returns the PostService Class
                        return new PostController(
                            $cpm->getServiceLocator()->get('PostService')
                        );
                    },
                ],
            ],
        ];
    }
}
```

Now that we got this in place let us quickly define our `PostService`. We will be using a Service that makes use
of Doctrine, so we require a `Doctrine\Common\Persistence\ObjectManager` as dependency.

```php
use Doctrine\Common\Persistence\ObjectManager;

class PostService
{
    protected $objectManager;

    public function __construct(ObjectManager $objectManager)
    {
        $this->objectManager = $objectManager;
    }

    public function deletePost($id)
    {
        $post = $this->objectManager->find('Post', $id);
        $this->objectManager->remove($post);
        $this->objectManager->flush();
    }
}
```

And for this one, too, let's quickly create the factory, again within our `Module` class.

```php
class Module
{
    // getAutoloaderConfig(), getConfig(), etc...

    public function getServiceConfig()
    {
        return [
            'factories' => [
                 'PostService' => function($sm) {
                     return new PostService(
                         $sm->get('doctrine.entitymanager.orm_default')
                     );
                 }
            ]
        ];
    }
}
```

With this set up we can now cover some best practices.

## Best practices

Ideally, you should not protect your applications using only guards (Route or Controller guards).
This leaves your application open for some undesired side-effects.
As a best practice you should protect all your services or controllers by injecting the authorization service.
But let's go step by step:

Assuming the application example above we can easily use ZfcRbac to protect our route using the following guard:

```php
return [
    'zfc_rbac' => [
        'guards' => [
            'ZfcRbac\Guard\RouteGuard' => [
                'post/delete' => ['admin']
            ]
        ]
    ]
];
```

Now, any users that do not have the "admin" role will receive a 403 error (unauthorized) when trying to access
the "post/delete" route. However, this does not prevent the service (which should contain the actual logic in a properly
design application) to be injected and used elsewhere in your code. For instance:

```php
class PostController
{
    protected $postService;

    public function createAction()
    {
        // this action may have been reached through the "forward" method, hence bypassing guards
        $this->postService->deletePost('2');
    }
}
```

You see the issue!

The solution is to inject the `AuthorizationService` into your services, and checking for the
permissions before doing anything wrong. So let's modify our previously created `PostService`-class

```php
use Doctrine\Common\Persistence\ObjectManager;
use ZfcRbac\Service\AuthorizationService;

class PostService
{
    protected $objectManager;

    protected $authorizationService;

    public function __construct(
        ObjectManager        $objectManager,
        AuthorizationService $autorizationService
    ) {
        $this->objectManager        = $objectManager;
        $this->authorizationService = $autorizationService;
    }

    public function deletePost($id)
    {
        // First check permission
        if (!$this->authorizationService->isGranted('deletePost')) {
            throw new UnauthorizedException('You are not allowed !');
        }

        $post = $this->objectManager->find('Post', $id);
        $this->objectManager->remove($post);
        $this->objectManager->flush();
    }
}
```

Since we now have an additional dependency we should inject it through our factory, again within our `Module` class.

```php
class Module
{
    // getAutoloaderConfig(), getConfig(), etc...

    public function getServiceConfig()
    {
        return [
            'factories' => [
                 'PostService' => function($sm) {
                     return new PostService(
                         $sm->get('doctrine.entitymanager.orm_default'),
                         $sm->get('ZfcRbac\Service\AuthorizationService') // This is new!
                     );
                 }
            ]
        ];
    }
}
```

Alternatively, you can also protect your controllers using the `isGranted` helper (you do not need to inject
the AuthorizationService then):

```php
class PostController
{
    protected $postService;

    public function createAction()
    {
        if (!$this->isGranted('deletePost')) {
            throw new UnauthorizedException('You are not allowed !');
        }

        $this->postService->deletePost('2');
    }
}
```

While protecting services is the more defensive way (because services are usually the last part of the logic flow),
it is often complicated to deal with.
If your application is architectured correctly, it is often simpler to protect your controllers.

### When using guards then?

In fact, you should see guards as a very efficient way to quickly reject access to a hierarchy of routes or a
whole controller. For instance, assuming you have the following route config:

```php
return [
    'router' => [
        'routes' => [
            'admin' => [
                'type'    => 'Literal',
                'options' => [
                    'route' => '/admin'
                ],
                'may_terminate' => true,
                'child_routes' => [
                    'users' => [
                        'type' => 'Literal',
                        'options' => [
                            'route' => '/users'
                        ]
                    ],
                    'invoices' => [
                        'type' => 'Literal',
                        'options' => [
                            'route' => '/invoices'
                        ]
                    ]
                ]
            ]
        ]
    ]
};
```

You can quickly unauthorized access to all admin routes using the following guard:

```php
return [
    'zfc_rbac' => [
        'guards' => [
            'ZfcRbac\Guard\RouteGuard' => [
                'admin*' => ['admin']
            ]
        ]
    ]
];
```

## A Real World Application Part 2 - Only delete your own Posts

If you jumped straight to this section please notice, that we assume you have the knowledge that we presented in the
previous example. In here we will cover a very common use-case. Users of our Application should only have delete
permissions to their own content. So let's quickly refresh our `PostService` class:

```php
use Doctrine\Common\Persistence\ObjectManager;

class PostService
{
    protected $objectManager;

    protected $authorizationService;

    public function __construct(
        ObjectManager        $objectManager,
        AuthorizationService $autorizationService
    ) {
        $this->objectManager        = $objectManager;
        $this->authorizationService = $autorizationService;
    }

    public function deletePost($id)
    {
        // First check permission
        if (!$this->authorizationService->isGranted('deletePost')) {
            throw new UnauthorizedException('You are not allowed !');
        }

        $post = $this->objectManager->find('Post', $id);
        $this->objectManager->remove($post);
        $this->objectManager->flush();
    }
}
```

As we can see, we check within our Service if the User of our Application is allowed to delete the post with a check
against the `deletePost` permission. Now how can we achieve that only a user who is the owner of the Post to be able to
delete his own post, but other users can't? We do not want to change our Service with more complex logic because this
is not the task of such service. The Permission-System should handle this. And we can, for this we have the
 [`AssertionPluginManager`](/src/ZfcRbac/Assertion/AssertionPluginManager.php) and here is how to do it:

First of all things we need to write an Assertion. The Assertion will return a boolean statement about the current
identity being the owner of the post.

```php
namespace Your\Namespace;

use ZfcRbac\Assertion\AssertionInterface;
use ZfcRbac\Service\AuthorizationService;

class MustBeAuthorAssertion implements AssertionInterface
{
    /**
     * Check if this assertion is true
     *
     * @param  AuthorizationService $authorization
     * @param  mixed                $post
     *
     * @return bool
     */
    public function assert(AuthorizationService $authorization, $post = null)
    {
        return $authorization->getIdentity() === $post->getAuthor();
    }
}
```

This simple `MustBeAuthorAssertion` will check against the current `$authorization` if it equals the identity of the
current context Author. The second parameter is called the "context". A context can be anything (an object, a scalar,
an array...) and makes only sense in the context of the assertion.

Imagine a user calls `http://my.dom/post/delete/42`, so obviously he wants to delete the Post-Entity with ID#42. In
this case Entity#42 is our Context! If you're wondering of how the context get there, bare with me, we will get to
this later.

Now that we have written the Assertion, we want to make sure that this assertion will always be called, whenever we
check for the `deletePost` permission. We don't want others to delete our previous content! For this we have the so-
called `assertion_map`. In this map we glue `assertions` and `permissions` together.

```php
// module.config.php or wherever you configure your RBAC stuff
return [
    'zfc_rbac' => [
        'assertion_map' => [
            'deletePost' => 'Your\Namespace\MustBeAuthorAssertion'
        ]
    ]
];
```

Now, whenever some test the `deletePost` permission, it will automatically call the `MustBeAuthorAssertion` from
the `AssertionPluginManager`. This plugin manager is configured to automatically add unknown classes to an invokable.
However, some assertions may need dependencies. You can manually configure the assertion plugin manager as
shown below:

```php
// module.config.php or wherever you configure your RBAC stuff
return [
    'zfc_rbac' => [
        // ... other rbac stuff
        'assertion_manager' => [
            'factories' => [
                'AssertionWithDependency' => 'Your\Namespace\AssertionWithDependencyFactory'
            ]
        ]
    ]
];
```

Now we need to remember about the **context**. Somehow we need to let the `AssertionPluginManager` know about our
context. This is done as simple as to passing it to the `isGranted()` method. For this we need to modify our Service
one last time.

```php
use Doctrine\Common\Persistence\ObjectManager;

class PostService
{
    protected $objectManager;

    protected $authorizationService;

    public function __construct(
        ObjectManager        $objectManager,
        AuthorizationService $autorizationService
    ) {
        $this->objectManager        = $objectManager;
        $this->authorizationService = $autorizationService;
    }

    public function deletePost($id)
    {
        // Note, we now need to query for the post of interest first!
        $post = $this->objectManager->find('Post', $id);

        // Check the permission now with a given context
        if (!$this->authorizationService->isGranted('deletePost', $post)) {
            throw new UnauthorizedException('You are not allowed !');
        }

        $this->objectManager->remove($post);
        $this->objectManager->flush();
    }
}
```

And there you have it. The context is injected into the `isGranted()` method and now the `AssertionPluginManager` knows
about it and can do its thing. Note that in reality, after you have queried for the `$post` you would check if `$post`
is actually a real post. Because if it is an empty return value then you should throw an exception earlier without
needing to check against the permission.

## A Real World Application Part 3 - Admins can delete everything

Often, you want users with a specific role to be able to have full access to everything. For instance, admins could
delete all the posts, even if they don't own it.

However, with the previous assertion, even if the admin has the permission `deletePost`, it won't work because
the assertion will evaluate to false.

Actually, the answer is quite simple: deleting my own posts and deleting others' posts should be treated like
two different permissions (it makes sense if you think about it). Therefore, admins will have the permission
`deleteOthersPost` (as well as the permission `deletePost`, because admin could write posts, too).

The assertion must therefore be modified like this:

```php
namespace Your\Namespace;

use ZfcRbac\Assertion\AssertionInterface;
use ZfcRbac\Service\AuthorizationService;

class MustBeAuthorAssertion implements AssertionInterface
{
    /**
     * Check if this assertion is true
     *
     * @param  AuthorizationService $authorization
     * @param  mixed                $context
     *
     * @return bool
     */
    public function assert(AuthorizationService $authorization, $context = null)
    {
        if ($authorization->getIdentity() === $context->getAuthor()) {
            return true;
        }

        return $authorization->isGranted('deleteOthersPost');
    }
}
```

## A Real World Application Part 4 - Checking permissions in the view

If some part of the view needs to be protected, you can use the shipped ```isGranted``` view helper.

For example, lets's say that only users with the permissions ```post.manage``` will have a menu item to acces
the adminsitration panel :

In your template post-index.phtml

```php
<ul class="nav">
    <li><a href="/">Home</a></li>
    <li><a href="/posts/list">View posts</a></li>
    <?php if ($this->isGranted('post.manage'): ?>
    <li><a href="/posts/admin">Manage posts</a></li>
    <?php endif ?>
</ul>
```

You can even protect your menu item regarding a role, by using the ```hasRole``` view helper :

```php
<ul class="nav">
    <li><a href="/">Home</a></li>
    <li><a href="/posts/list">View posts</a></li>
    <?php if ($this->hasRole('admin'): ?>
    <li><a href="/posts/admin">Manage posts</a></li>
    <?php endif ?>
</ul>
```

In this last example, the menu item will be hidden for users who don't have the ```admin``` role.

## Using ZfcRbac with Doctrine ORM

First your User entity class must implement `ZfcRbac\Identity\IdentityInterface` :

```php
use ZfcUser\Entity\User as ZfcUserEntity;
use ZfcRbac\Identity\IdentityInterface;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;

/**
 * @ORM\Entity
 * @ORM\Table(name="user")
 */
class User extends ZfcUserEntity implements IdentityInterface
{
    /**
     * @var Collection
     * @ORM\ManyToMany(targetEntity="HierarchicalRole")
     */
    private $roles;

    public function __construct()
    {
        $this->roles = new ArrayCollection();
    }

    /**
     * {@inheritDoc}
     */
    public function getRoles()
    {
        return $this->roles->toArray();
    }

    /**
     * Set the list of roles
     * @param Collection $roles
     */
    public function setRoles(Collection $roles)
    {
        $this->roles->clear();
        foreach ($roles as $role) {
            $this->roles[] = $role;
        }
    }

    /**
     * Add one role to roles list
     * @param \Rbac\Role\RoleInterface $role
     */
    public function addRole(RoleInterface $role)
    {
        $this->roles[] = $role;
    }
}
```
For this example we will use the more complex situation by using `Rbac\Role\HierarchicalRoleInterface` so the second step is to create HierarchicalRole entity class

```php
class HierarchicalRole implements HierarchicalRoleInterface
{
    /**
     * @var HierarchicalRoleInterface[]|\Doctrine\Common\Collections\Collection
     *
     * @ORM\ManyToMany(targetEntity="HierarchicalRole")
     */
    protected $children;

    /**
     * @var PermissionInterface[]|\Doctrine\Common\Collections\Collection
     *
     * @ORM\ManyToMany(targetEntity="Permission", indexBy="name", fetch="EAGER", cascade={"persist"})
     */
    protected $permissions;

    /**
     * Init the Doctrine collection
     */
    public function __construct()
    {
        $this->children    = new ArrayCollection();
        $this->permissions = new ArrayCollection();
    }

    /**
     * {@inheritDoc}
     */
    public function addChild(HierarchicalRoleInterface $child)
    {
        $this->children[] = $child;
    }

    /*
     * Set the list of permission
     * @param Collection $permissions
     */
    public function setPermissions(Collection $permissions)
    {
        $this->permissions->clear();
        foreach ($permissions as $permission) {
            $this->permissions[] = $permission;
        }
    }

    /**
     * {@inheritDoc}
     */
    public function addPermission($permission)
    {
        if (is_string($permission)) {
            $permission = new Permission($permission);
        }

        $this->permissions[(string) $permission] = $permission;
    }

    /**
     * {@inheritDoc}
     */
    public function hasPermission($permission)
    {
        // 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]);
    }

    /**
     * {@inheritDoc}
     */
    public function getChildren()
    {
        return $this->children->toArray();
    }

    /**
     * {@inheritDoc}
     */
    public function hasChildren()
    {
        return !$this->children->isEmpty();
    }
}
```

And the last step is to create Permission entity class which is a very simple entity class, you don't have to do specific things !

You can find all entity example in this folder : [Example](/data)

You need one more configuration step. Indeed, how can the RoleProvider retrieve your role and permissions ? For this you need to configure `ZfcRbac\Role\ObjectRepositoryRoleProvider` in your `zfc_rbac.global.php` file :
```php
        /**
         * Configuration for role provider
         */
        'role_provider' => [
            'ZfcRbac\Role\ObjectRepositoryRoleProvider' => [
                'object_manager'     => 'doctrine.entitymanager.orm_default', // alias for doctrine ObjectManager
                'class_name'         => 'User\Entity\HierarchicalRole', // FQCN for your role entity class
                'role_name_property' => 'name', // Name to show
            ],
        ],
```

Using DoctrineORM with ZfcRbac is very simple. You need to be aware from performances where there is a lot of permissions for roles.

## How to deal with roles with lot of permissions?

In very complex applications, your roles may have dozens of permissions. In the [/data/FlatRole.php.dist] entity
we provide, we configure the permissions association so that whenever a role is loaded, all its permissions are also
loaded in one query (notice the `fetch="EAGER"`):

```php
/**
  * @ORM\ManyToMany(targetEntity="Permission", indexBy="name", fetch="EAGER")
  */
protected $permissions;
```

The `hasPermission` method is therefore really simple:

```php
public function hasPermission($permission)
{
    return isset($this->permissions[(string) $permission]);
}
```

However, with a lot of permissions, this method will quickly kill your database. What you can do is modfiy the Doctrine
mapping so that the collection is not actually loaded:

```php
/**
  * @ORM\ManyToMany(targetEntity="Permission", indexBy="name", fetch="LAZY")
  */
protected $permissions;
```

Then, modify the `hasPermission` method to use the Criteria API. The Criteria API is a Doctrine 2.2+ API that allows
to efficiently filter a collection without loading the whole collection:

```php
use Doctrine\Common\Collections\Criteria;

public function hasPermission($permission)
{
    $criteria = Criteria::create()->where(Criteria::expr()->eq('name', (string) $permission));
    $result   = $this->permissions->matching($criteria);

    return count($result) > 0;
}
```

> NOTE: This is only supported starting from Doctrine ORM 2.5!

## Using ZfcRbac and ZF2 Assetic

To use [Assetic](https://github.com/widmogrod/zf2-assetic-module) with ZfcRbac guards, you should modify your
`module.config.php` using the following configuration:

```php
return [
    'assetic_configuration' => [
        'acceptableErrors' => [
            \ZfcRbac\Guard\GuardInterface::GUARD_UNAUTHORIZED
        ]
    ]
];
```

## Using ZfcRbac and ZfcUser

To use the authentication service from ZfcUser, just add the following alias in your application.config.php:

```php
return [
    'service_manager' => [
        'aliases' => [
            'Zend\Authentication\AuthenticationService' => 'zfcuser_auth_service'
        ]
    ]
];
```

Finally add the ZfcUser routes to your `guards`:

```php
return [
    'zfc_rbac' => [
        'guards' => [
            'ZfcRbac\Guard\RouteGuard' => [
                'zfcuser/login'    => ['guest'],
                'zfcuser/register' => ['guest'], // required if registration is enabled
                'zfcuser*'         => ['user'] // includes logout, changepassword and changeemail
            ]
        ]
    ]
];
```

### Navigation

* Back to [the Using the Authorization Service](/docs/06. Using the Authorization Service.md)
* Back to [the Index](/docs/README.md)

Zerion Mini Shell 1.0