The Role-Based Access Control (RBAC) component provides role-based authorization abstraction for the Devflow CMF.

Introduction

Role-Based Access Control (RBAC) is based on the idea of roles rather than permissions as you may find in ACL. In a web application, users will typically have identities defined by username, email, token, etc.

RBAC System:

  • An Identity has one or more roles
  • A role requests access to a permission
  • A permission is given to a role

Thus, RBAC has:

  • Many-to-many relationship between identities and roles.
  • Many-to-many relationship between roles and permissions.
  • Roles can have a parent role.

Rules

Sometimes you need to perform an extra check. For example, what if you only want authors to edit, update or delete their own content, but not someone else's content? You can do that by setting a rule. You can do so by implementing the AssertionRule interface with the execute() method.

<?php

declare(strict_types=1);

namespace Infrastructure\Service;

use Codefy\Framework\Auth\Rbac\Entity\AssertionRule;

final class AuthorRule implements AssertionRule
{

    /**
     * @param array|null $params
     *
     * @return bool
     */
    public function execute(?array $params = null): bool
    {
        // @var Post $post
        if($post = $params['post'] ?? null) {
            return $post->authorId === ($params['userId'] ?? null);
        }
        return false;
    }
}

File: ./Cms/Infrastructure/Service/AuthorRule.php

Configure RBAC

<?php

use Infrastructure\Service\AuthorRule;

return [
    /** Named or grouped permissions. */
    'permissions' => [
        'admin' => [
            'description' => 'All system permissions',
            'permissions' => [
                'access:admin' => ['description' => 'Access to the dashboard.'],
                'create:content' => ['description' => 'Create content.'],
                'create:product' => ['description' => 'Create product.'],
                'create:users' => ['description' => 'Create users.'],
                'manage:content' => ['description' => 'Manage content.'],
                'manage:product' => ['description' => 'Manage product.'],
                'manage:users' => ['description' => 'Manage users.'],
                'manage:media' => ['description' => 'Manage media.'],
                'manage:options' => ['description' => 'Manage options.'],
                'manage:settings' => ['description' => 'Manage settings.'],
                'manage:products' => ['description' => 'Manage product inventory.'],
                'manage:plugins' => ['description' => 'Manage plugins.'],
                'manage:themes' => ['description' => 'Manage themes.'],
                'update:content' => ['description' => 'Update content.'],
                'update:product' => ['description' => 'Update product.'],
                'update:users' => ['description' => 'Update users.'],
                'delete:content' => ['description' => 'Delete content.', 'ruleClass' => AuthorRule::class],
                'delete:product' => ['description' => 'Delete product.'],
                'delete:users' => ['description' => 'Delete users.'],
                'manage:profile' => ['description' => 'User can manage their profile.'],
                'switch:user' => ['description' => 'Switch user'],
                'publish:content' => ['description' => 'Publish content.'],
                'publish:product' => ['description' => 'Publish product.'],
                'activate:plugins' => ['description' => 'Activate plugins.'],
                'deactivate:plugins' => ['description' => 'Deactivate plugins.'],
            ],
        ],
        'sites' => [
            'description' => 'All site permissions',
            'permissions' => [
                'manage:sites' => ['description' => 'Manage sites.'],
                'create:sites' => ['description' => 'Create sites.'],
                'update:sites' => ['description' => 'Update sites.'],
                'delete:sites' => ['description' => 'Delete sites.'],
            ]
        ],
        'webmaster' => [
            'description' => 'Website creator and builder.',
            'permissions' => [
                'vihzhuo:manage' => ['description' => 'Can use the Vihzhuo pagebuilder to build pages.'],
            ],
        ],
    ],

    'roles' => [
        'super' => [
            'description' => 'Super administrator',
            'permissions' => ['admin','sites','webmaster'],
        ],
        'admin' => [
            'description' => 'Administrator',
            'permissions' => [
                'access:admin',
                'create:content',
                'manage:content',
                'update:content',
                'delete:content',
                'publish:content',
                'create:product',
                'manage:product',
                'update:product',
                'delete:product',
                'publish:product',
                'create:users',
                'manage:users',
                'update:users',
                'delete:users',
                'switch:user',
                'manage:media',
                'manage:options',
                'manage:settings',
                'manage:plugins',
                'manage:themes',
                'manage:profile',
                'activate:plugins',
                'deactivate:plugins',
                'webmaster',
            ],
        ],
        'editor' => [
            'description' => 'Site editor',
            'permissions' => [
                'access:admin',
                'create:content',
                'manage:content',
                'update:content',
                'delete:content',
                'publish:content',
                'create:product',
                'manage:product',
                'update:product',
                'delete:product',
                'publish:product',
                'manage:media',
                'manage:profile',
                'webmaster',
            ],
        ],
        'user' => [],
    ],
];

File: ./config/rbac.php

Check Rights

<?php

use function App\Shared\Helpers\current_user_can;

if(current_user_can('delete:content', ['userId' => $userId, 'post' => $post]) {
    ... // The user is author of the post and can delete it
}

Authorizing Users

Devflow provides several middlewares for checking and validating user roles, permissions, and session handling.

If you need to check whether a user is authorized to view a certain page, you will need to add
Codefy\Framework\Http\Middleware\Auth\UserAuthorizationMiddleware or the Injector alias (user.authorization) to your route:

<?php

declare(strict_types=1);

return (function(\Qubus\Routing\Psr7Router $router) {
    $router->get('/protected/', function() {
        //
    })
    ->middleware('user.authorization')
    ->name('protected.page');
});

File: ./routes/web/web.php

When a user visits the /protected/ route, the middleware will check if the user is logged in. If the user is logged in, the user will continue on, otherwise, the user will be redirected to your login route via the redirect_guests_to setting in ./config/auth.php.

A different approach would be to check permissions via the controller. You can use the App\Shared\Helpers\current_user_can helper or the Gate Middleware:

<?php

declare(strict_types=1);

namespace Application\Http\Controller;

use App\Application\Devflow;
use Psr\Http\Message\ResponseInterface;
use Qubus\Routing\Psr7Router;

use function App\Shared\Helpers\current_user_can;
use function Codefy\Framework\Helpers\trans;
use function Codefy\Framework\Helpers\view;
use function Qubus\Routing\Helpers\redirect;

final class ProtectedController
{
    public function __construct(protected Psr7Router $router)
    {
    }

    public function securePage(): ResponseInterface
    {
        if (false === current_user_can('manage:content')) {
            Devflow::$PHP->flash->error(
                message: 'Access denied.'
            );

            return redirect($this->router->url(name: 'protected.page'));
        }

        return view(
            template: 'cmf::backend/protected',
            data: ['title' => trans('Protected Page')]
        );
    }
}

File: ./Cms/Application/Http/Controller/ProtectedController.php

If you use the gate middleware, then your controller becomes cleaner by adding the gate middleware to your route:

<?php

declare(strict_types=1);

use Application\Http\Controller\ProtectedController;

return (function(\Qubus\Routing\Psr7Router $router) {
    $router->get('/protected/', function(ProtectedController $controller) {
        return $controller->securePage();
    })
    ->middleware(['gate:manage:content,/admin/login/']);
});

File: ./routes/web/admin.php

<?php

declare(strict_types=1);

namespace Application\Http\Controller;

use Psr\Http\Message\ResponseInterface;

use function Codefy\Framework\Helpers\trans;
use function Codefy\Framework\Helpers\view;

final class ProtectedController
{
    public function securePage(): ResponseInterface
    {
        return view(
            template: 'cmf::backend/protected',
            data: ['title' => trans('Protected Page')]
        );
    }
}

File: ./Cms/Application/Http/Controller/AdminController.php

current_user_can('manage:content') is what's used to check if a logged-in user has a certain permission to continue.

Here is a list of other authentication middlewares along with their aliases:

  • Codefy\Framework\Http\Middleware\Auth\ExpireUserSessionMiddleware
    • Alias: user.session.expire
    • Description: This middleware can be used for a logout route to clear the user's session and cookie.
  • Codefy\Framework\Http\Middleware\Auth\AuthenticationMiddleware
    • Alias: user.authenticate
    • Description: This middleware can be used for a login route which checks the submitted login credentials against the database.
  • Codefy\Framework\Http\Middleware\Auth\UserSessionMiddleware
    • Alias: user.session
    • Description: This middleware should be used with the previous middleware. If authentication is successful, the user session and cookie will be created.