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
return [
/** Named or grouped permissions. */
'permissions' => [
'system' => [
'description' => 'System permissions',
'permissions' => [
'access:admin' => ['description' => 'Access to the dashboard.'],
'manage:media' => ['description' => 'Manage media.'],
'manage:settings' => ['description' => 'Manage settings.'],
'change:settings' => ['description' => 'Update or delete settings.'],
'manage:options' => ['description' => 'Manage plugin or theme options.'],
'change:options' => ['description' => 'Update or delete plugin or theme options.'],
'manage:profile' => ['description' => 'User can manage their profile.'],
'core:updates' => ['description' => 'Manage system updates.'],
'flush:cache' => ['description' => 'Flush the cache.'],
'change:username' => ['description' => 'Update a user username.'],
'reset:password' => ['description' => 'Can reset a user password.'],
],
],
'content' => [
'description' => 'Content permissions',
'permissions' => [
'create:content' => ['description' => 'Create content.'],
'manage:content' => ['description' => 'Manage content.'],
'update:content' => ['description' => 'Update content.'],
'delete:content' => ['description' => 'Delete content.'],
'publish:content' => ['description' => 'Publish content.'],
'schedule:content' => ['description' => 'Schedule content for publishing.'],
'archive:content' => ['description' => 'Archive content.'],
'review:content' => ['description' => 'Review content for publishing.'],
'assign:content_reviewers' => ['description' => 'Assign content to reviewers.'],
'approve:content' => ['description' => 'Approve content for publishing.'],
'view:content_revisions' => ['description' => 'View content revisions.'],
'restore:content_revisions' => ['description' => 'Restore content revisions.'],
'view:content_activity' => ['description' => 'View content activity.'],
'manage:content_notifications' => ['description' => 'Manage content notifications.'],
'view:content_comments' => ['description' => 'View content comments.'],
'comment:content' => ['description' => 'Manage comments.'],
'edit:content_comments' => ['description' => 'Edit content comments.'],
'reply:content_comments' => ['description' => 'Reply to content comments.'],
'resolve:content_comments' => ['description' => 'Resolve content comments.'],
'delete:content_comments' => ['description' => 'Delete content comments.'],
],
],
'product' => [
'description' => 'Product permissions',
'permissions' => [
'create:product' => ['description' => 'Create product.'],
'manage:product' => ['description' => 'Manage product.'],
'manage:products' => ['description' => 'Manage product inventory.'],
'update:product' => ['description' => 'Update product.'],
'delete:product' => ['description' => 'Delete product.'],
'publish:product' => ['description' => 'Publish product.'],
'review:product' => ['description' => 'Review product for publishing.'],
'archive:product' => ['description' => 'Archive product.'],
],
],
'user' => [
'description' => 'User permissions',
'permissions' => [
'create:users' => ['description' => 'Create users.'],
'manage:users' => ['description' => 'Manage users.'],
'update:users' => ['description' => 'Update users.'],
'delete:users' => ['description' => 'Delete users.'],
'switch:user' => ['description' => 'Switch user'],
],
],
'extensions' => [
'description' => 'Extension permissions',
'permissions' => [
'install:extensions' => ['description' => 'Install any extension.'],
'uninstall:extensions' => ['description' => 'Uninstall any extension.'],
'manage:plugins' => ['description' => 'Manage plugins.'],
'manage:themes' => ['description' => 'Manage themes.'],
'install:themes' => ['description' => 'Install themes.'],
'activate:themes' => ['description' => 'Activate themes.'],
'deactivate:themes' => ['description' => 'Deactivate themes.'],
'install:plugins' => ['description' => 'Install plugins.'],
'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' => ['system','content','product','user','extensions','sites','webmaster'],
],
'admin' => [
'description' => 'Administrator',
'permissions' => [
'access:admin',
'manage:media',
'manage:options',
'change:options',
'manage:settings',
'change:settings',
'manage:profile',
'flush:cache',
'change:username',
'reset:password',
'content',
'product',
'user',
'extensions',
'webmaster',
],
],
'author' => [
'description' => 'Author who can create and edit drafts, but cannot publish.',
'permissions' => [
'access:admin',
'manage:content',
'create:content',
'update:content',
'review:content',
'view:content_activity',
'view:content_comments',
'view:content_revisions',
'comment:content',
'reply:content_comments',
'manage:media',
'manage:profile',
'flush:cache',
],
],
'editor' => [
'description' => 'Site editor',
'permissions' => [
'access:admin',
'manage:content',
'update:content',
'review:content',
'approve:content',
'schedule:content',
'assign:content_reviewers',
'view:content_activity',
'view:content_revisions',
'view:content_comments',
'comment:content',
'reply:content_comments',
'edit:content_comments',
'resolve:content_comments',
'manage:product',
'manage:products',
'update:product',
'review:product',
'manage:media',
'manage:profile',
'flush:cache',
],
],
'publisher' => [
'description' => 'Can publish approved content and archive published content.',
'permissions' => [
'access:admin',
'manage:content',
'update:content',
'schedule:content',
'publish:content',
'archive:content',
'view:content_activity',
'view:content_revisions',
'view:content_comments',
'comment:content',
'reply:content_comments',
'resolve:content_comments',
'manage:media',
'manage:profile',
'flush:cache',
],
],
'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.
- Alias:
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.
- Alias:
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.
- Alias: