Edit this Page

Security/RBAC

The Spiral Framework includes Security component based on role-rule-permission structure.

Overview

The Security component work using set of roles associated with different application permissions (i.e. ability to edit post) based on set of context specific rules (how to allow specific permission).

Booting permission

In order to configure security component in your application use boot method of custom bootloader:

class AccessBootloader extends Bootloader
{
    const BOOT = true;

    const BINDINGS = [
        ActorInterface::class => [self::class, 'getActor']
    ];

    /**
     * @param \Spiral\Security\PermissionsInterface $permissions
     */
    public function boot(PermissionsInterface $permissions)
    {
        $permissions->addRole('admin');
        $permissions->addRole('manager');

        //Grant admin full assess to 3 levers of permissions
        $permissions->associate('admin', "*");
        $permissions->associate('admin', "*.*");
        $permissions->associate('admin', "*.*.*");

        //Grant manager only access to vault dashboard 
        $permissions->associate('manager', "vault");
        $permissions->associate('manager', "vault.dashboard");
    }

    //Use authorizer user as actor (see authorization)
    public function getActor(ContextInterface $context): ActorInterface
    {
        if ($context->isAuthenticated()) {
            return $context->getUser();
        }

        return new Guest();
    }
}

Roles and Permissions

Boot method in a given sample creates 2 roles "admin" and "manager" and associates such roles with application permissions.

Security Component provides ability to associate role and rule with multiple permissions using * patter, for example we can grant ability for admin for all post specific permissions:

$permissions->associate('admin', "posts.*");

Manager on other end only have associated with 2 direct permissions.

Actors

Security component requires active IoC scope for application actor (i.e. ActorInterface):

interface ActorInterface
{
    /**
     * Method must return list of roles associated with current actor is a form of array.
     *
     * @return array
     */
    public function getRoles(): array;
}

Actors usually represent by authenticated users which demonstrated in getActor factory method.

Guarding Code

To guard our code in services or controllers use GuardTrait or request GuardInterface dependency:

use GuardedTrait;

public function indexAction()
{
    if(!$this->getGuard()->allows('post.edit')) {
        throw new ForbiddenException();
    }
}

You are able to use shorter method in your controllers by using AuthorizesTrait:

use AuthorizesTrait;

public function indexAction()
{
   //Will throw ControllerException when failed
   $this->authorize('posts.edit');
}

Rules

Previous example used default role-permissions association without any of additional conditions.

Practically we can rewrite association using following code:

$permissions->associate('admin', "*", AllowRule::class);

When AllowRule looks like:

final class AllowRule implements RuleInterface, SingletonInterface
{
    /**
     * {@inheritdoc}
     */
    public function allows(ActorInterface $actor, string $permission, array $context): bool
    {
        return true;
    }
}

To create custom rules which can work based on a context, let's extend default class Rule to get access to method injections in our rules:

class AuthorRule extends Rule
{
    public function check(User $user, Post $post)
    {
        return $post->author->id == $actor->id;
    }
}

Check this file to see how Rule class works.

This rule grants access when user is post owner, we can register it like that:

$permissions->associate('user', "post.edit", AuthorRule::class);

In order to use this rule properly we have to supply additional context "post" (actor/user will be resolved automatically):

$this->authorize('posts.edit', ['post' => $post]);

Check RuleInterface class in order to understand how to create more rules, all rules are initiated by Container which allows __constructor injections:

interface RuleInterface
{
    /**
     * @param ActorInterface $actor
     * @param string         $permission
     * @param array          $context
     *
     * @return bool
     *
     * @throws RuleException
     */
    public function allows(ActorInterface $actor, string $permission, array $context): bool;
}

Included Rules

Security component ships with set of pre-defined rules:

Rule Description
AllowRule Always grant access.
ForbidRule Always forbid access
CallableRule Provides ability to wrap external functions and closures
CompositeRule Combines multiple rules together

Composite Rule

Example of composite rule definition:

class PostRule extends CompositeRule
{
    const BEHAVIOUR = self::AT_LEAST_ONE;

    const RULES = [AdminRule::class, AuthorRule::class];
}

This rule will grant permission access if user is Admin or Post author.

Vault Core

You can use pre-build implementation of HMVC core - Vault which includes automatic authorization for each of it nested controllers and actions.