As you obviously know, RBAC is role-based access control . Everyone who created the systems a little larger than the home page and a little smaller than the state services, thought about how to differentiate the rights of users.
In this article I will not talk about what RBAC is and why it’s good (although I’ll tell you a little, of course), and I’ll introduce you to my modest development ( h-rbac ) and try to explain why it is better in some aspects than famous "monsters".
The two pillars on which RBAC holds are roles and operations . I like the word “operation” more than “resolution”, and the meaning reflects more correctly. It looks like this:
Code → Operation → Role → User
Thus, in the code, we check the possibility of performing the operation :
if ($request->user()->can('add-to-favorites')) { // - }
In turn, the RBAC module determines whether the specified operation is allowed for a given user (that is, whether it is contained in his role) and, if so, the block content is executed.
In no case should you check in the code that the user has a role, since Today, the "manager" can add to favorites, but tomorrow can not. Searching by text and excluding individual roles from checks is not a thankful business; in fact, there are “operations” for this.
The operation is always directly connected with the program block (see the example above) and if you change roles here, you don’t have to change anything. But operations are included and excluded easily, simply and centrally.
Usually a list of roles and operations is stored in a database. Users and roles are most often associated with the many-to-many relationship, and roles with operations are associated with the same attitude.
Everyone who created the system a little big ... I repeat something. In short, you all know the main players of the market access restrictions for Laravel. It:
These are really serious products that have many different "buns" like redefining operations for a specific user, etc., but I have one simple question for them ( goodbye karma, we were good together ):
How to allow the user to edit only their articles?
Hmm ... and there are no built-in mechanisms for this! And it is very strange, because In most systems, users generate some kind of content (for example, articles) and should be able to edit it without having access to editing someone else’s.
And also: what if my project is not so big? What if all this hemorrhoids with storing roles and operations in the database, linking tables for many-to-many and creating a whole UI to manage the entire farm is redundant?
- Ta-dam!
So, it's time to talk about your own R clothes The title of the article does not accidentally sound "... RBAC for the smallest." This, of course, is not about the performance characteristics of programmers, but rather about the size of projects (and, of course, this estimate is very conditional).
If your project does not have a lot of operations and not a lot of roles, and you do not mind dreamed of storing them as an array, then I’m happy to introduce the h-rbac module (hierarchical RBAC with callbacks).
There are no contradictions in order to add different providers to the module and store roles and operations even in the database, at least ingp ...any other place of interest.
I just want to admit that the principle used in this module was spied by me in Yii. It was spared from an unnecessary (in my opinion) Task entity and a real perversion in the form of bizRule, computed using eval()
(of course, I'm talking about Yii 1.1, in 2.0 it was different, but, in my opinion, also quite confusing ).
To work with the module requires Laravel 5.1 or higher.
I want to start with the fact that the standard system of permissions (operations) appeared in Laravel since version 5.1. It has a good infrastructure, many useful methods, is supported in the blade and even allows you to allow the user to edit his articles (ie, pass arguments to the check), but (!) There are no roles at all ...
... all users are equal - just some kind of democracy. But we all know that there must be someone "more equal" than the rest. No other way!
So, the h-rbac module , in fact, is a superstructure over the standard authorization mechanism (not to be confused with authentication!), Which adds roles and a hierarchy of operations. Therefore, you will continue to use absolutely all the standard "buns", but in the context of the presence of roles.
Using Composer
$ composer require dlnsk/h-rbac
We register the provider in config / app.php
Dlnsk\HierarchicalRBAC\HRBACServiceProvider::class,
Publish the items we need
$ php artisan vendor:publish --provider="Dlnsk\HierarchicalRBAC\HRBACServiceProvider"
namely:
users
table)Yes, dear friends, as the title says, this is a “for the little ones” module, so the user can have only one role! But on the other hand, because the role - it's just an array, add another easy.
Take the following roles:
and a set of operations:
Suppose we want to divide users into those who can edit all articles, those who will edit articles only in certain categories and those who can only edit their own articles.
To do this, we actually have to create three operations and combine them into a chain from the most open to the most limited:
update-post → update-post-in-category → update-own-post
class AuthorizationClass extends Authorization { public function getPermissions() { return [ 'update-post' => [ // "" 'description' => ' ', // () 'next' => 'update-post-in-category', ], 'update-post-in-category' => [ 'description' => ' ', 'next' => 'update-own-post', ], 'update-own-post' => [ 'description' => ' ', // ], // 'add-to-favorites' => [ 'description' => ' ', ], ]; } }
Assigning operations to roles is very simple. Who can do what is obvious:
class AuthorizationClass extends Authorization { public function getRoles() { return [ 'admin' => [ 'update-post', ], 'manager' => [ 'update-post-in-category', ], 'user' => [ 'update-own-post', 'add-to-favorites', ], ]; } }
Please note that in essence we only have two update-post
and add-to-favorites
operations, and we should check the possibility of their implementation. update-post-in-category
and update-own-post
support operations will be checked automatically, since they are the contents of the one with the update-post
chain.
// PostController.php class PostController extends Controller { public function update(Post $post) { $this->authorize('update-post', $post); // , } }
<!-- post-view.php --> <h1>{{ $post->title }}</h1> <ul class="post-tools"> <li class="post author">{{ $post->author->username }}</li> @can('update-post', $post) <li class="post update"><button></button></li> @endcan @can('add-to-favorites') <li class="post add-favorite"><button></button></li> @endcan </ul>
The add-to-favorites
operation implies the ability to add any article to favorites, i.e. no additional checks are needed, it is enough that this operation is contained in the user role. For this reason, you can not transfer the $post
object to the scan (but I would pass it in any case, since there are no additional conditions today, but they may appear tomorrow).
It remains to figure out how to do the checks for those additional conditions for update-post-in-category
and update-own-post
. To do this, it is enough to add two methods (the names of the methods are obtained by camelization (o-pa!) The name of the operation):
class AuthorizationClass extends Authorization { public function updatePostInCategory($user, $post, $permission) { // , $post id $post = $this->getModel(\App\Post::class, $post); return $user->category_id === $post->category_id; } public function updateOwnPost($user, $post, $permission) { $post = $this->getModel(\App\Post::class, $post); return $user->id === $post->user_id; } }
The $permission
parameter in both of these methods will contain the name of the originally requested update-post
operation.
All operations in the chain are checked one by one in the order selected by the user and the operation:
Let's add a delete-post
operation. Logic dictates that the user can delete articles that he can edit. A similar situation occurs quite often and in order not to create an additional chain of operations and absolutely identical argument checking functions, we use one trick - the equal
parameter:
class AuthorizationClass extends Authorization { public function getPermissions() { return [ 'update-post' => [ // "" 'description' => ' ', // () 'next' => 'update-post-in-category', ], 'update-post-in-category' => [ 'description' => ' ', 'next' => 'update-own-post', ], 'update-own-post' => [ 'description' => ' ', // ], // 'add-to-favorites' => [ 'description' => ' ', ], // 'delete-post' => [ 'description' => ' ', 'equal' => 'update-post', // ], ]; } public function getRoles() { return [ 'admin' => [ 'update-post', 'delete-post', ], 'manager' => [ 'update-post-in-category', ], 'user' => [ 'update-own-post', 'add-to-favorites', 'delete-post', ], ]; } }
Based on this example, the admin will be able to delete any posts, the user only his own, but the manager will not be able to delete anything at all, because in its role there is no delete-post
operation, and therefore nothing is required to be checked.
As mentioned earlier , the module is an add-on above the standard authorization system for Laravel 5.1 and above, so you need to use it as it is written in the documentation , and if it is short, here are some examples:
if (\Gate::allows('update-post', $post)) { // -, } ... if (\Gate::denies('update-post', $post)) { abort(403); } ... if (\Gate::forUser($user)->allows('update-post', $post)) { // -, }
From the User model:
if ($request->user()->can('update-post', $post)) { // - } ... if ($request->user()->cannot('update-post', $post)) { abort(403); }
In the controller:
$this->authorize('update-post', $post);
Using Blade
@can('update-post', $post) <!-- --> @else <!-- --> @endcan @cannot('update-post', $post) <!-- --> @endcannot
In addition, especially for bad boys and girls, an additional @role
directive has been @role
that can be used with @else
@role('user|manager') <!-- --> @endrole
This is a module that combines the power of standard features, really necessary functionality and ease of configuration. Thanks for attention!
// app/Classes/Authorization/AuthorizationClass.php <?php namespace App\Classes\Authorization; use Dlnsk\HierarchicalRBAC\Authorization; class AuthorizationClass extends Authorization { public function getPermissions() { return [ 'update-post' => [ // "" 'description' => ' ', // () 'next' => 'update-post-in-category', ], 'update-post-in-category' => [ 'description' => ' ', 'next' => 'update-own-post', ], 'update-own-post' => [ 'description' => ' ', // ], // 'add-to-favorites' => [ 'description' => ' ', ], // 'delete-post' => [ 'description' => ' ', 'equal' => 'update-post', // ], ]; } public function getRoles() { return [ 'admin' => [ 'update-post', 'delete-post', ], 'manager' => [ 'update-post-in-category', ], 'user' => [ 'update-own-post', 'add-to-favorites', 'delete-post', ], ]; } ////////////// Callbacks /////////////// public function updatePostInCategory($user, $post) { // , $post id $post = $this->getModel(\App\Post::class, $post); return $user->category_id === $post->category_id; } public function updateOwnPost($user, $post) { $post = $this->getModel(\App\Post::class, $post); return $user->id === $post->user_id; } }
PS:
I almost forgot ... The names of the roles "admin", "manager" and "user" are taken in this article just as an example. In fact, the "admin" role is built into the module and does not need to be defined. It makes the user a SUPER USER. No checks are applied to him, he is allowed absolutely everything. Cheers, comrades!
References:
Source: https://habr.com/ru/post/321678/
All Articles