TheRole 3. Authorization for Ruby on Rails

  • Tutorial
TheRole - gem for the organization of role distribution on the RoR site ( with a control panel )


Gem version Build status 

tl; dr

Another (1001st) way to provide 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 the 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 the distribution of user rights for Ruby on Rails. After all, everyone has been using CanCan , Pundit, or in the end Acl9. However, not everything is so simple. Firstly, I have several reasons to justify myself:

  • The decision that I will tell you about today appeared long before I became a ruby. 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 was completely free to fly imagination. It’s unlikely that they will accuse me of repeating someone else’s idea, and it’s always interesting to put my inventions into practice
  • Projects like this, for me, are a platform for working with people whom I take for online training. And even with this, TheRole brings a little beaver to the rail world
  • The development of any gem gives feedback, which allows you to become a little 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, you will like this option of solving the authorization problem for MVC, and you will repeat it in some Laravel . How to know ?!

Secondly, below I will not only explain in detail how to organize role distribution in your RoR project, but also touch on the reasons and history of this solution. I will talk about the tasks that I was trying to solve, about the features of the gem, the limits of applicability, use cases and (not without pride) I will present you one of my hardworking and talented online student companions, without whose help the release of the 3rd version of the gem would not have taken place for a very long time.

What is TheRole?

Before you start spreading your thoughts on 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 demarcation system for RoR applications, which:

  1. provides a predefined path for access control in a project
  2. does not require additional programming in most cases
  3. has a concise way to integrate into controllers to limit access to actions
  4. Actively uses the principle of “Agreement vs. Configurations »
  5. Has a GUI for managing access lists
  6. Reliable enough and well supported, due to the simplicity of implementation, a small amount of code and decent coverage with tests for all the main combinations of ruby ​​/ rails / database

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 will be interested to know your opinion on this matter.

A bit of history

In 2006-2007, when I was still a student, I first had to use PHP in commercial projects. What I saw, frankly, shocked me so much that I could not help but start looking for a way to put PHP code in order. A short search led me to MVC architecture. Since I did not find an acceptable solution in PHP at that moment, the passion for programming and the availability of free time prompted me to selflessly write my MVC framework in PHP.

I successfully started running the newly minted MVC bike on the website of the school where I worked as a teacher. Very soon I wondered about the role distribution of users on the site. In short, I did not find a solution that I would like. And my self-made MVC implementation with controllers / actionscoupled with a desire to make an admin panel for managing role-based access policies, they pushed to write another PHP bike, which has now turned into TheRole.

In 2008, I fell into the trap of 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 deceased PHP concept in ruby, my colleagues smiled for a reason, because CanCan at that time was a de facto production solution. And there was simply no need to reinvent the wheel. But I’m not used to giving up my dreams. So, I started a leisurely job writing this gem.

Why is TheRole created?

The main goal that I pursued was to create a solution for differentiating access rights with a graphical interface .

I am not satisfied with the “programmer's approach” to solving the problem when, for access control, you first need to program something (like the Ability class ), and then to change the access policies (an existing and functioning system), you need to invite a programmer who reprograms something (read, make a mistake) and re-install the code on the server.

I'm comfortable with the “user approach”solutions to the problem when the site administrator, with minimal preparation, can independently change the access policy to some site features through the provided interface. (Although I do not deny that the probability of making an error here is not less, but at least the consequences of the error do not lie on the shoulders of the programmer)

What access criteria exist?

Exploring the principles of access control in various systems, one way or another you will come to approximately the following list of criteria for access to the operation:

  1. Ownership - usually we want the user to be able to perform important operations only on those objects that belong to him. Those. between the user and the object there is some sign of ownership (ownership). To get to the hotel room you need to have a key
  2. Operational Availability - This is typically determined through an ACL . Those. in a certain storage 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. Operation Availability Time - The action itself may be available, but not now. The student can take exams only during the session, although in principle the operation of passing the exam is available to him.
  4. The availability of an operation on an object is an analogue of the ACL , but here each operation is tied to a specific object, and the ownership criterion does not play a big role here. The dean’s office has a list of students who can take specific subjects
  5. Availability of operations on an object with a time limit - Just an example, the dean’s office has a list of students who can retake some subjects (to each his own), but only on specific days (for each his own)

Here I will stop, but I’ll indicate that this list can be easily multiplied by 2, if we take not personal criteria, but group ones. But this is very deep. You can drown.

Now attention! TheRole provides only the first 2 accessibility criteria: Ownership and Operational Availability (ACL) . We boldly throw out the rest.

1.5 access criteria. Property Ownership Determination

TheRole provides work with only 2 access criteria: Object ownership and Operation availability . However, we must admit that the criterion of Owning an object , strictly speaking, is not the task of TheRole or any other gem for Authorization. Nobody knows who and how the relationships between the objects are organized, and what your signs of ownership of the object are. It is simply impossible to create a universal solution here.

Does this mean that the owner ownership test method ? , available in TheRole out of the box, is based on the simplest case of the relationship between objects and 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 USER_ID field of the specified object. Those. in the following call:


will actually be checked == @page.user_id

That's all.

At the input, in most projects and in most cases, this method will work. However, be prepared to take additional steps to get the owner? returned the desired result. How to do this is described in the documentation for the gem .

Why only 1.5 access control criteria?

If you think it would be great to create a system of distribution of rights of such a level that it would cover at least those 5 cases that I have indicated in the section “What access criteria exist?”, 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 release.
  • Secondly, such systems of distribution of rights are associated with a huge number of cases that need to be covered with tests. And it’s quite laborious.
  • Thirdly, for such a system it is extremely difficult 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 problem of ensuring the criterion of Availability of the 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 drier. An ACL is simply a repository of access rules, over which the boolean function acl_check of the form is applicable :

acl_check(@user, @action_name)

which all that 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, you might be interested to know how TheRole organizes rules storage and why.

Flexible data structure for storing ACLs

From the very beginning, I came to the conclusion that if I want to store an access control list in the database and want to provide a flexible means of managing this list, then it is not very convenient to store data in the form of a stock table. Obtaining an access list, updating it line by line, and all other operations will be very expensive (even if only from the point of view of the graphical interface).

At one time in PHP I drew attention to associative arrays, which could be easily turned from an object to a string and vice versa. 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. Drawing arrays of rules on the client and working with them on the server turned out to be extremely simple.

In PHP, I used to use serialize / unserialize to work with associative arrays. In ruby, I now 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 comment control panel (explicitly defined), and cannot edit photo albums (not defined explicitly, the rule does not exist, then false).

  post_create: true,
  post_delete: true,
  comments_panel: false

but the moderator can create and delete posts, access the control panel comments, but also can not edit photo albums

  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 daily seeing the two-level structure of the controller / action (although the code of my controllers was arranged similarly before ROR), it is very difficult not to transfer the 2-level structure of controller / action to the access control list. The temptation is so great that I could not resist it. So ACL in the first implementations of TheRole, in addition to a flexible data format for storage, also got a 2-tier structure.

This may look like the role of a user who can create a page, but for some reason access to editing pages was restricted to him.

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, as a rule, is one of the most useful and effective checks in the application. Now for such verification it is enough to call the access verification method in before_filter , to which the name of the controller and the name of the action are passed.

return page_404 if not @user.has_role?(controller_name, action_name)

Access Checks Waterfall in Controllers

So. TheRole allows you to check the user's access to a specific action. But if we consider the issue more carefully, we will notice that this is just one of the access checks that the controller should be endowed with.

The first check for access to the controller action is performed by your Authentication gem. For example, Devise or Sorcery. The Devise gem does it like this:

before_action :authenticate_user!, except: [:index, :show]

The second check for access to the action of the controller should be performed only if we are sure that the user exists to check the rights. So, for example, when trying to access the update action, before_action: authenticate_user will be the first to work ! and if this filter succeeds (that is, the user exists), then here you can already transfer the authority to TheRole gem:

before_action :role_required,  except: [:index, :show]

role_required is a method that internally invokes a check of the form current_user.has_role? (controller_name, action_name) and displays a page with an access error if the user does not have the necessary rights.

The third check on the ownership of the object. Without owning an object, we cannot access a controller action that can change this object (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 ownership check through TheRole gem

before_action :owner_required, only: [:edit, :update, :destroy]

It should be noted that from the set_page method, you need to pass the found object to the owner_required method of checking 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 ...
  def set_page
    @page = Page.find params[:id]

Virtual Sections and Rules

By presenting the ACL as a 2-level array, where the first level denotes the rule sections (groups) and the second denotes the rules with the corresponding Boolean values, I managed to integrate the role system into the application controllers rather accurately. But only by controlling access to the actions of controllers can not be limited.

Despite the fact that the ACL device can very accurately reflect the real device of the application controller, this does not mean that all sections and rules in the ACL must exactly match the device of our application. We can create absolutely any rule groups in the ACL that are convenient for us and use them at our discretion. I call these rule groups virtual, based on the fact that they do not reflect the real structure of the code, but are endowed only with a 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 this access list is fairly easy if you take care to give 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 use 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 integration with View is still easier:

- 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 help but be puzzled by the question of how to quickly and easily introduce superuser and moderators into the role system . I introduced virtual sections in TheRole 2 that are of particular importance. In a way, this decision can be perceived as a crutch, but I do not see anything bad in it. It does not violate the unity of the general idea.

A user who has a system section in his list of access rules and an administrator: true rule is considered the owner of any objects and always receives true on an access request.

system: {
  administrator: true

A user with a moderator section will receive true in response to all requests for section rules that are specified in his rule set and are true.

moderator: {
  pages: true,
  blogs: false,
  twitter: true

those. for any queries of the form user.has_role? (: pages,: blabla) and user.has_role? (: twitter,: blabla) this user will always get true. But requests like user.has_role? (: Blogs,: blabla) will give the permissions that this user has in the blogs section . Those. when working with blogs, this user does not have any privileges.

ACL Control Panel

Now, the essence of the functioning of the gem as a whole is revealed, you can look at the control panel.

TheRole Management Panel

The control panel is implemented by a separate gem and, if desired, may not be installed in your application. But in the general case, 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
  • Create and delete new sections within a given role
  • Create and delete new rules inside a given role section
  • Unloading one or all system roles into a JSON file
  • Loading JSON file with roles

Import / Export roles can be useful if you need to backup ACL. Or, for example, to move customized roles between several projects using TheRole.

Limited flexibility

On the one hand, TheRole allows you to create any rules you want to use in your access checks, and if you are consistent, these rules will rather semantically reflect what is happening in your application. TheRole, on the other hand, still has many limitations that you should be aware of:

  • Вы можете использовать только предопределенный путь проверки доступа, через ограниченный API (и я верю в то, что это хорошо)
  • Пользователь обладает только одной ролью (это моя принципиальная позиция и неизменяемая техническая реализация)
  • TheRole работает только с моделью User. (Вопрос времени и активного контрибьютора)
  • TheRole работает только с хелпером current_user. (Вопрос времени и активного контрибьютора)
  • Не поддерживает mongo. (Вопрос времени и активного контрибьютора)
  • Поддерживает только 3 SQL-like БД (sqlite, mysql, psql)
  • Не предполагает использование native json столбцов БД и хранит JSON в базе исключительно как текст. (хотя да, есть патч для PSQL, но в стандартную поставку гема я включать его не буду, ввиду стремления не засорять код specific поведением)

Well, OK. But at least it is possible to make the user possess several roles at the same time? Sorry but no. This is a vicious approach. It is associated with a large number of logical expectations, which can be completely different for different people.

If you need a system that provides many roles for one user, then this means that either you are seriously mistaken, or that TheRole is disastrous for you.


Last year 2014 was extremely successful for me - under the pretext of online learning, I met and made friends with several talented people who are passionate about programming. The geography is vast - from Vladivostok to Kiev. And I am sincerely glad that people consciously choose ruby ​​technologies to solve their problems and implement ideas. It is especially pleasant that we manage to do something together as part of open source projects.

1) I want to thank one of these people, who put in more effort and effort so that the 3rd release of the gem would take place. This person’s name is Ilya Bondarenko , he lives in Permand, as far as I know, works as a tester. Frankly, I was pleasantly surprised how Ilya was able to quickly solve the tasks and the degree of enthusiasm that he showed. As part of our collaboration, Ilya helped completely redesign the tests, perform a number of important transformations in the code structure, fix a couple of important bugs, improve the documentation, and even make a number of suggestions in the roadmap gem. Ilya , I’m not sure that this review will be useful to you from the point of view of your career, but nevertheless, I can safely recommend you to the public as a person who knows how to perform the tasks in a high-quality manner and achieves excellent results. Thanks again!

2) In addition, I want to thank Sergey Fuchsman. Apparently, Sergey was one of the first users who migrated from TheRole 2 to TheRole 3 and ran into small problems. Sergey, thanks for the valuable feedback and trust in TheRole.


On this, I, it seems, talked about everything I wanted. Conclusions about the appropriateness and usefulness of the decision to make you. But, at least you now know one more (1001st) option for solving the Authorization problem in projects with an MVC structure.

Good luck in the development!

Also popular now: