📜 ⬆️ ⬇️

TheRole 3. Authorization for Ruby on Rails

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

TheRole

Gem version Build status
')

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:


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:

  1. provides a predefined path for access control in the project
  2. does not require additional programming in most cases
  3. has a concise way to integrate into controllers for access control to actions
  4. Actively uses the principle of "Agreement vs. Configurations
  5. It has a GUI to manage the access list.
  6. 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:

  1. 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.
  2. 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.
  3. 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.
  4. 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.
  5. 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.


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] # ... code ... private def set_page @page = Page.find params[:id] for_ownership_check(@page) end end 

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.

TheRole Management Panel
GUI

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:


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:


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!

Source: https://habr.com/ru/post/249951/


All Articles