Access controle model

Concepts

Every time an entity is created, viewed or updated, the software check if the user has the permission to make this action. The decision is made with three parameters :

  • the type of entity ;
  • the entity’s center ;
  • the entity’scope

The user must be granted access to the action on this particular entity, with this scope and center.

From an user point of view

The software is design to allow fine tuned access rights for complicated installation and team structure. The administrators may also decide that every user has the right to see all resources, where team have a more simple structure.

Here is an overview of the model.

Chill can be multi-center

Chill is designed to be installed once for social center who work with multiple teams separated, or for social services’s federation who would like to share the same installation of the software for all their members.

This was required for cost reduction, but also to ease the generation of statistics agregated across federation’s members, or from the central unit of the social center with multiple teams.

Otherwise, it is not required to create multiple center: Chill can also work for one center.

Obviously, users working in the different centers are not allowed to see the entities (_persons_, _reports_, _activities_) of other centers. But users may be attached to multiple centers: consequently they will be able to see the entities of the multiple centers they are attached to.

Inside center, scope divide team

Users are attached to one or more center and, inside to those center, there may exists differents scopes. The aim of those _scopes_ is to divide the whole team of social worker amongst different departement, for instance: the social team, the psychologist team, the nurse team, the administrative team, ... Each team is granted of different rights amongst scope. For instance, the social team may not see the _activities_ of the psychologist team. The administrative team may see the date & time’s activities, but is not allowed to see the detail of those entities (the personal notes, ...).

The administrator is responsible of creating those scopes and team. He may also decide to not define a division inside his team: he creates only one scope and all entities will belong to this scope, all users will be able to see all entities.

As entities have only one scopes, if some entities must be shared across two different teams, the administrator will have to create a scope shared by two different team, and add the ability to create, view, or update this scope to those team.

Example: if some activities must be seen and updated between nurses and psychologists, the administrator will create a scope “nurse and psy” and add the ability for both team “nurse” and “psychologist” to “create”, “see”, and “update” the activities belonging to scope “nurse and psy”.

The concepts translated into code

../_images/access_control_model.png

Schema of the access control model

Chill handle entities, like persons, reports (associated to persons), activities (also associated to _persons), ... On creation, those entities are linked to one center and, eventually, to one scope. They implements the interface HasCenterInterface.

Note

Somes entities are linked to a center through the entity they are associated with. For instance, activities or reports are associated to a person, and the person is associated to a center. The report‘s center is always the person‘s center.

Entities may be associated with a scope. In this case, they implement the HasScopeInterface.

Note

Currently, only the person entity is not associated with a scope.

At each step of his lifetime (creation, view of the entity and eventually of his details, update and, eventually, deletion), the right of the user are checked. To decide wether the user is granted right to execute the action, the software must decide with those elements :

  • the entity’s type;
  • the entity’s center ;
  • the entity’s scope, if it exists,
  • and, obviously, a string representing the action

All those action are executed through symfony voters and helpers.

How to check authorization ?

Just use the symfony way-of-doing, but do not forget to associate the entity you want to check access. For instance, in controller :

class MyController extends Controller
{

   public function viewAction($entity)
   {
      $this->denyAccessUnlessGranted('CHILL_ENTITY_SEE', $entity);

      //... go on with this action
   }
}

And in template :

{{ if is_granted('CHILL_ENTITY_SEE', entity) %}print something{% endif %}

Retrieving reachable scopes and centers

The class AuthorizationHelper helps you to get centers and scope reachable by a user.

Those methods are intentionnaly build to give information about user rights:

  • getReachableCenters: to get reachable centers for a user
  • getReachableScopes : to get reachable scopes for a user

Note

The service is reachable through the Depedency injection with the key chill.main.security.authorization.helper. Example :

$helper = $container->get('chill.main.security.authorization.helper');

Todo

Waiting for a link between our api and this doc, we invite you to read the method signatures here

Adding your own roles

Extending Chill will requires you to define your own roles and rules for your entities. You will have to define your own voter to do so.

To create your own roles, you should:

  • implement your own voter. This voter will have to extends the AbstractChillVoter. As defined by Symfony, this voter must be declared as a service and tagged with security.voter;
  • declare the role through implementing a service tagged with chill.role and implementing ProvideRoleInterface.

Note

Both operation may be done through a simple class: you can implements ProvideRoleInterface and AbstractChillVoter on the same class. See live example: ActivityVoter, and similar examples in the PersonBundle and ReportBundle.

See also

How to Use Voters to Check User Permissions

From the symfony cookbook

New in Symfony 2.6: Simpler Security Voters

From the symfony blog

Declare your role

To declare new role, implement the class ProvideRoleInterface.

interface ProvideRoleInterface
{
    /**
     * return an array of role provided by the object
     *
     * @return string[] array of roles (as string)
     */
    public function getRoles();

    /**
     * return roles which doesn't need
     *
     * @return string[] array of roles without scopes
     */
    public function getRolesWithoutScope();
}

Then declare your service with a tag chill.role. Example :

your_service:
    class: Chill\YourBundle\Security\Authorization\YourVoter
    tags:
        - { name: chill.role }

Example of an implementation of ProvideRoleInterface:

namespace Chill\PersonBundle\Security\Authorization;

use Chill\MainBundle\Security\ProvideRoleInterface;

class PersonVoter implements ProvideRoleInterface
{
    const CREATE = 'CHILL_PERSON_CREATE';
    const UPDATE = 'CHILL_PERSON_UPDATE';
    const SEE    = 'CHILL_PERSON_SEE';

    public function getRoles()
    {
        return array(self::CREATE, self::UPDATE, self::SEE);
    }

    public function getRolesWithoutScope()
    {
        return array(self::CREATE, self::UPDATE, self::SEE);
    }

}

Implement your voter

Inside this class, you might use the AuthorizationHelper to check permission (do not re-invent the wheel). This is a real-world example:

namespace Chill\ReportBundle\Security\Authorization;
use Chill\MainBundle\Security\Authorization\AbstractChillVoter;
use Chill\MainBundle\Security\Authorization\AuthorizationHelper;


class ReportVoter extends AbstractChillVoter
{
    const CREATE = 'CHILL_REPORT_CREATE';
    const SEE    = 'CHILL_REPORT_SEE';
    const UPDATE = 'CHILL_REPORT_UPDATE';

    /**
     *
     * @var AuthorizationHelper
     */
    protected $helper;

    public function __construct(AuthorizationHelper $helper)
    {
        $this->helper = $helper;
    }

    protected function getSupportedAttributes()
    {
        return array(self::CREATE, self::SEE, self::UPDATE);
    }
    protected function getSupportedClasses()
    {
        return array('Chill\ReportBundle\Entity\Report');
    }
    protected function isGranted($attribute, $report, $user = null)
    {
        if (! $user instanceof \Chill\MainBundle\Entity\User){

            return false;
        }

        return $this->helper->userHasAccess($user, $report, $attribute);
    }
}

Then, you will have to declare the service and tag it as a voter :

services:
    chill.report.security.authorization.report_voter:
        class: Chill\ReportBundle\Security\Authorization\ReportVoter
        arguments:
            - "@chill.main.security.authorization.helper"
        tags:
         - { name: security.voter }

Adding role hierarchy

You should prepend Symfony’s security component directly from your code.

namespace Chill\ReportBundle\DependencyInjection;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
use Symfony\Component\DependencyInjection\Loader;
use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface;
use Chill\MainBundle\DependencyInjection\MissingBundleException;

/**
 * This is the class that loads and manages your bundle configuration
 *
 * To learn more see {@link http://symfony.com/doc/current/cookbook/bundles/extension.html}
 */
class ChillReportExtension extends Extension implements PrependExtensionInterface
{
    public function prepend(ContainerBuilder $container)
    {
        $this->prependRoleHierarchy($container);
    }

    protected function prependRoleHierarchy(ContainerBuilder $container)
    {
        $container->prependExtensionConfig('security', array(
           'role_hierarchy' => array(
              'CHILL_REPORT_UPDATE' => array('CHILL_REPORT_SEE'),
              'CHILL_REPORT_CREATE' => array('CHILL_REPORT_SEE')
           )
        ));
    }
}