TheRole - heme for the organization of role distribution on the RoR site (
with control panel )


')
tl; dr
Another (1001st) way to ensure the differentiation of rights in a web application. The concept of this solution was implemented for a long time in PHP, and was later rewritten in ruby. Due to the simplicity of implementation, the described approach can be applied in any MVC framework like
Rails ,
Laravel , etc.
In the text, I tried to disclose in detail, not only the technical integration of the solution into the application, but also the reasons for the proposed implementation.
In 2015, perhaps, only a madman can dare to write another gem for distributing user rights for Ruby on Rails. After all, everyone has been using
CanCan ,
Pundit, or eventually
Acl9 for a long time . However, not everything is so simple. First, I have several reasons to justify myself:
- That decision, which I will tell you today, appeared long before I became a rubist. The first working concept was written in 2006 in PHP for a school site.
- Fortunately for me, then I did not know other approaches, and therefore I was completely free in the flight of fancy. It is unlikely that they will accuse me of repeating someone else's idea, and it is always interesting to put my own inventions into practice
- Projects like this one, for me, are a platform for working with people I take for online training. And even if only by this TheRole introduces a little beaver to the rail world
- The development of any heme gives feedback that allows you to become a little more better (I lived in Ivanovo for too long)
- The approach implemented in TheRole can be easily, simply and quickly repeated in PHP, JS, Python. Suddenly, and you will like this solution of the authorization task for MVC, and you repeat it in some Laravel . How to know?
Secondly, below I will not only describe in detail how to organize the role distribution in your RoR project, but also touch on the causes and history of this decision. I will talk about the tasks that I tried to solve, about the features of the heme, limits of applicability, options for use and (not without pride) I will introduce you to one of my hardworking and talented
online students of partners, without whose release the 3rd version of the heme would not take place still very long.
What is TheRole?
Before you begin to spread the idea of ​​the tree, in an attempt to explain in detail how you can use TheRole in your project, I will try to formulate my vision of this tool.
TheRole is a role-based delineation system for RoR applications that:
- provides a predefined path for access control in the project
- does not require additional programming in most cases
- has a concise way to integrate into controllers for access control to actions
- Actively uses the principle of "Agreement vs. Configurations
- It has a GUI to manage the access list.
- Rather reliable and well supported, due to the simplicity of implementation, a small amount of code and decent test coverage for all major ruby ​​/ rails / database combinations
Undoubtedly, this decision is not a panacea, it has both a number of positive and negative sides. I will try to talk about what, in my opinion, is important and I would be interested to know your opinion on this matter.
A bit of history
In 2006-2007, when I was still a student, for the first time I had to meet using PHP in commercial projects. What I saw, frankly, shocked me so much that I could not help looking for a way to bring the PHP code in order. Short searches led me to MVC architecture. Since I did not find an acceptable solution for myself in PHP at that moment, then the passion for programming and the availability of free time pushed me to selflessly write my MVC PHP framework.
I successfully started the run-up MVC bicycle on the website of the school where I worked as a teacher. Very soon I wondered about the role-based distribution of users on the site. In short, I did not find such a solution, which I would have liked. And my self-made MVC implementation with
controllers / actions , coupled with the desire to make admin panel to manage role-based access policies, pushed to write another PHP bike, which has now become TheRole.
In 2008, I was trapped in
Ruby on Rails and all my PHP experiments were a thing of the past. However, the desire to repeat one of my decisions in ruby ​​code haunted me. When in 2011 I began to implement my late PHP concept in ruby, my colleagues smiled for a reason, because
CanCan at the time was a de facto production-solution. And there was simply no need to re-invent the wheel. But I was not used to giving up my dreams. So, I began to work slowly on writing this gem.
Why created TheRole?
The main goal that I was pursuing was to create a solution for differentiating access rights
with a graphical interface .
I am not satisfied with the “programmer approach” of solving the problem, when for access control you first need to program something (such as the
Ability class), and then to change the operation of access policies (an existing and functioning system) you need to invite a programmer who reprograms something (read make a mistake) and redistribute the code to the server.
I am satisfied with the “user approach” of solving the problem, when the site administrator, with minimal training, can independently change the access policy to some of the site’s capabilities through the provided interface. (Although I do not deny that the probability of making a mistake is no less here, but at least the consequences of the error that occurred are not on the programmer’s shoulders)
What are the access criteria?
Exploring the principles of access control in various systems, you somehow come up with approximately the following list of criteria for accessing the execution of an operation:
- Ownership - usually we want the user to be able to perform important operations only on those objects that belong to him. Those. there is some attribute of ownership (possession) between the user and the object. To get to the hotel room you need to have a key.
- Availability of operation - as a rule is determined by the ACL . Those. in a certain repository there is information about whether a given user can, in principle, perform some action. The dean's office has a list of students who can take the exam.
- Time available operations - The action itself may be available, but not now. A student can take exams only during the session, although in principle the exam is available to him.
- The availability of an operation on an object is analogous to the ACL , but here each operation is tied to a specific object, and the ownership criterion here does not play a big role. The dean's office has a list of students who can take specific subjects.
- Availability of an operation on an object with a time limit - Just an example, the dean's office has a list of students who can retake certain subjects (each for their own), but only on specific days (for each their own)
I’ll stop here, but I’ll point out that this list can be easily multiplied by 2, if you take not personal criteria, but group ones. But this is really quite deep. You can drown.
And now attention! TheRole provides only the first 2 accessibility criteria:
Operation Ownership and
Accessibility (ACL) . The rest boldly throwing out.
1.5 access criteria. Determining ownership of an object
TheRole provides work only with the 2nd access criteria:
Object ownership and
Operation availability . However, it must be admitted that the criterion of
Possession of an object , strictly speaking, is not the task of TheRole or any other heme for Authorization. No one knows who and how the relationships between objects are organized, and what are your signs of ownership of an object. A universal solution is simply impossible to create here.
This means that the method of checking ownership of the
owner object
? , available in TheRole out of the box, is based on the simplest case of the relationship between objects and is probably not suitable for all cases of your application.
What does the
owner method do
? , so it tries to match the ID of this user, with the field USER_ID of the specified object. Those. in the next call:
@user.owner?(@page)
will actually be verified
@user.id == @page.user_id
That's all.
At the entrance, in most projects and in most cases, this method will work. However, be prepared to take additional measures, what would be the
owner? returned the required result. How to do this is
described in the documentation for the gem .
Why only 1.5 access test criteria?
If you think that it would be great to create a system for distributing rights to such a level that it covers at least those 5 cases that I designated in the section “What access criteria do you have?”, Then I’m afraid to upset you. An attempt to create such systems for widespread use is as heroic as a meaningless act.
- Firstly, such things are practically unnecessary to anyone, and such a combine is unlikely to ever receive a real publication.
- Secondly, such rights distribution systems are associated with a huge number of cases that need to be covered with tests. And it is quite laborious.
- Thirdly, it is extremely difficult for such a system to come up with an accessible interface that would allow the end user to consciously control the system.
- Fourth, I am convinced that the end user will not use all the features that this solution will provide, even if it is completed.
That is why TheRole solves only the task of ensuring the
availability criterion of an
operation and tries to give the first sketch to check the criterion of
ownership of the object . Surely in 99% of cases this will be enough.
What is an ACL?
No, there will be no dry explanation. You will find it
on Wikipedia . It will be even more dry. An ACL is simply a repository of access rules, over which the boolean
acl_check function is
applied :
acl_check(@user, @action_name)
which all it can do is return true or false,
red or blue , good or evil, zero or one.
Like other ACL systems, TheRole simply provides
acl_check over the rule store (I store the access rules as a JSON string in the database). Nothing special. But perhaps you will be interested to know how TheRole organizes the storage of the rules and why this is so.
Flexible data structure for ACL storage
From the very beginning, I came to the conclusion that if I want to store an access control list in a database and want to provide a flexible means of managing this list, then storing data in the form of a table drain is not very convenient. Obtaining an access list, its line-by-line update and all other operations will be very expensive (at least even from the point of view of the graphical interface).
At one time in PHP, I paid attention to associative arrays that could be easily converted from an object into a string and back. It was easy to form such arrays on the client, and on the server, after submitting the form, the array of rules was actually ready. All I had to do was just turn it into a string and save it in the database. It turned out to be extremely easy to draw arrays of rules on the client and work with them on the server.
In PHP, I used
serialize / unserialize to work with associative arrays. In ruby ​​now I use JSON and hashes.
It all started with very simple access lists. For example, a user can create a post, but does not have access to the comments control panel (explicitly defined), and cannot edit photo albums (not explicitly defined, the rule does not exist, it means false).
{ post_create: true, post_delete: true, comments_panel: false }
but the moderator can create and delete posts, get access to the comments control panel, but cannot edit photo albums either
{ post_create: true, post_delete: true, comments_panel: true }
MVC & ACL. Everyone sees what he wants to see.
Using the MVC implementation of ROR, and seeing the two-level
controller / action structure every day (although my controller’s code was similar to the ROR), it’s very difficult not to shift the 2-level controller / action structure to the access control list. The temptation is so great that I could not resist him. So the ACL in the first implementations of TheRole, in addition to the flexible data format for storage, also received a 2-level structure.
This is how the role of a user who can create a pilgrim might look like, but for some reason access to editing pages has been restricted.
pages: { index: true, show: true, new: true, create: true, edit: false, update: false, destroy: false }
As soon as TheRole received a 2-level ACL structure, it became extremely easy to control access to controller actions in the application. And this is usually one of the most useful and effective checks in the application. Now, for such a check, it is enough to call the access check method in
before_filter , to which the name of the controller and the name of the action should be passed.
return page_404 if not @user.has_role?(controller_name, action_name)
Waterfall Access Controls in Controllers
So. TheRole allows you to check the user's access rights to a specific action. But if we consider the issue more closely, we note that this is just one of the access checks that the controller should be given.
The first check for access to the controller action is performed by your Authentication heme. For example, Devise or Sorcery. Gem Devise does it like this:
before_action :authenticate_user!, except: [:index, :show]
The second check for access to the controller action should be performed only if we are sure that the user for the rights check exists. So, for example, when trying to access an
update action, the first
before_action: authenticate_user! and if this filter is successful (ie, the user exists), then here you can already transfer the authority to the TheRole gem:
before_action :role_required, except: [:index, :show]
role_required is a method that internally invokes a type check of
current_user.has_role? (controller_name, action_name) and shows a page with an access error if the user does not have proper permissions.
The third check on the ownership of the object. Without owning the object, we cannot access the action of the controller that this object can change (delete or edit). However, we cannot perform this check until we have an object. This means that we must first find the object.
before_action :set_page, only: [:edit, :update, :destroy]
We see that the object search is performed on a limited number of controller actions. It is only for these actions that it makes sense to conduct a proficiency check through the TheRole gem.
before_action :owner_required, only: [:edit, :update, :destroy]
It should be noted that from the
set_page method
you need to transfer the found object to the
owner_required method of checking the ownership. This is done using the
for_ownership_check method
.As a result, we get the following controller template with a fairly reliable access restriction system:
class PagesController < ApplicationController before_action :authenticate_user!, except: [:index, :show] before_action :role_required, except: [:index, :show] before_action :set_page, only: [:edit, :update, :destroy] before_action :owner_required, only: [:edit, :update, :destroy]
Virtual sections and rules
Having presented the ACL in the form of a 2-level array, where the first level denotes
sections (groups) of rules , and the second -
rules with corresponding Boolean values, I managed to integrate the role system into the application controllers rather accurately. But only control access to the actions of the controllers can not be limited.
Despite the fact that the device ACL can very accurately reflect the real device controller application, it does not mean that all sections and rules in the ACL must clearly coincide with the device of our application. We can create absolutely any convenient group of rules in the ACL and use them at our discretion. I call such groups of rules
virtual , based on the fact that they do not reflect the real code device, but are endowed with only logical meaning.
Here is an example of a user role that can be used to control access to controller actions and to control the display of social buttons on a page.
pages: { index: true, show: true, new: true, create: true, edit: true, update: true, destroy: true }, social_buttons: { vk: false, twitter: true, facebook: true },
Reading such an access list is quite easy if you take care of giving the sections and rules distinct names. Here I see that a user with this role can perform any basic actions in the Pages controller and in addition, he can work with the social buttons of Twitter and Facebook. But for some reason, the user cannot work with the Vkontakte button.
If we figured out the integration of TheRole into the controller, then the integration into the View is still simpler:
- if current_user - if current_user.has_role?(:social_buttons, :vk) = link_to "Like with VK", "#" - if current_user.has_role?(:social_buttons, :twitter) = link_to "Like with TW", "#" - if current_user.has_role?(:social_buttons, :facebook) = link_to "Like with Fb", "#"
Special virtual sections: system and moderator
I could not be puzzled by the question of how to quickly and easily enter into the role system of the
superuser and
moderators . I entered in the TheRole 2 virtual sections that are of particular importance. In some ways, this decision can be perceived as a crutch, but I do not see anything wrong with it. It does not violate the unity of the general idea.
A user with the
system section and the
administrator rule in his list of access rules is considered the owner of any objects and always gets true for an access request.
system: { administrator: true }
A user with a
moderator section will receive true in response to all requests to section rules that are specified in his rule set and are equal to true.
moderator: { pages: true, blogs: false, twitter: true }
those. for any requests of the form
user.has_role? (: pages,: blabla) and
user.has_role? (: twitter,: blabla) this user will always receive true. But requests of the form
user.has_role? (: Blogs,: blabla) will give such permissions that are given to this user in the
blogs section. Those. This user has no privileges when working with blogs.
ACL control panel
Now, the essence of the functioning of the heme as a whole is disclosed, you can look at the control panel.
The control panel is implemented by a separate gem and, with a strong desire, it may not be installed in your application.
But in general, I think it makes sense to install it.The control panel provides:- Create new empty roles
- Creating new roles based on existing ones
- Editing information about this role
- Creating and deleting new sections within a given role
- Creating and deleting new rules within a given role section
- Uploading one or all system roles to a JSON file
- Load JSON file with roles
Import / Export roles can be useful if you need to make a backup ACL. Or, for example, to move customized roles between multiple projects using TheRole.Limited flexibility
On the one hand, TheRole allows you to create any kind of rules for use in your access checks and, if you are consistent, these rules will rather semantically reflect what is happening in your application. On the other hand, TheRole still has many limitations that you should be aware of:- You can only use the predefined access check path, through a limited API (and I believe that this is good)
- The user has only one role (this is my principal position and unchangeable technical implementation)
- TheRole only works with the User model . (A matter of time and active distributor)
- TheRole only works with the current_user helper . (A matter of time and active distributor)
- Does not support mongo. (A matter of time and active distributor)
- Supports only 3 SQL-like databases (sqlite, mysql, psql)
- It does not assume the use of native json database columns and stores JSON in the database only as text. (although yes, there is a patch for psql, but I will not include it in the standard delivery of the heme, due to the desire not to clog the code with specific behavior)
Well, OK. But at least to do so, what would the user have multiple roles at the same time possible? Sorry but no. This is a vicious approach. It is associated with a large number of logical expectations, which may be completely different for different people.If you need a system that provides multiple roles for a single user, then this means that either you are seriously mistaken, or that TheRole is catastrophically inappropriate.Thanks
The past 2014 was extremely successful for me - under the pretext of online learning, I met and became friends with several talented people passionate about programming. The geography is vast - from Vladivostok to Kiev. And I am sincerely happy that people consciously choose ruby ​​technologies for solving their problems and translating ideas. Particularly pleased that we manage to do something together in the framework of open source projects.1) I want to thank one of these people who put more effort and effort in order for the 3rd release of the heme to take place. This man’s name is Ilya Bondarenko , he lives in Permand, as far as I know, works as a tester. Frankly, I was pleasantly surprised at how Ilya was able to quickly accomplish the tasks and the degree of enthusiasm he showed. As part of our collaboration, Ilya helped to completely rework tests, perform a number of important changes in the structure of the code, fix a couple of important bugs, improve documenting, and even add a number of suggestions to the roadmap. Ilya , I’m not sure that this review will be useful to you from a career point of view, but still, I can safely recommend you to the public, as a person who knows how to properly perform the tasks and achieve excellent results. Thanks again!
2) In addition, I want to thank Sergei Fuchsman . Apparently Sergey became one of the first users who migrated from TheRole 2 to TheRole 3 and ran into minor problems. Sergey, thank you for your valuable feedback and trust in TheRole.Completion
At this point I seem to have told everything I wanted. Conclusions about the feasibility and usefulness of the decision to do to you. But at least you now know another (1001st) version of the solution of the Authorization problem in projects with MVC structure.Successes in development!