Wednesday, October 16, 2013

Advanced Rails 4 Authorization with Pundit.

Here is a good illustration of the underlying power of Voidness...

Pundit is a realy simple Ruby Gem, that does nothing more that you could have done yourself. Here is the power !

When updating to Rails 4 in it's early days, you had no compliant authorization solutions.
Ryan Bates's Cancan was the most used due to it's flexibility and simplicity, but it is not Rails 4 compliant, and brings too much magic for me.
But is there something more flexible and simple than the Void ? ( OK, ... I stop with VoidMadness ... )

For one of my current job, I started to build a software that needs flexible right management. ( And also a People relations graph, but this is for another post ).
The requirement are :
* Fined grained activities( or action ) access control
* Fields read / write access control
* Flexible Role based system that allow role inheritance

In this article I'll address only the first requirement, the second will be explained in the next article, while the last one does not need to be since it is obvious.

Let introduce the base models


It is a basic pattern. I don't need to explain it. User can have many Roles and Role may be filled by many Users.
For each Role, I want to allow some activities. (IE: For accountant : create bill, update payment. For Team Manager : Plan Task for a member of his/her Team )

About Pundit


Pundit set some helpers to :
* Check Policy for a given user on a given object( it is not restricted to ActiveRecord::Base instances ).
* Describe Policy in Ruby Class
* Enforce use of Policy in your controllers
* Bring syntactic sugar in you Rspec tests.

I recommend you to read Pundit's README.md before going further, since I won't explain what is already explained there.

The Activities


If found an interesting post from Derick Bailey about authorization patterns, putting words on some of my thoughts. Reading this is not required to go further, but I recommend to.

The activities is not a finite collection. In fact, from my point of view, activities are defined by the following set :  All methods directly exposed to user except.

In the Ruby On Rails world as in many MVC frameworks, this mean all the methods available on controllers. (Authentication Activities is a Subset of this set, that has particularity to be allowed to all users.)

I do not want to have a collection of activities to maintain. I do not want to reify them through an Postgres table or a flat file.
I want that default behavior is to deny. This behavior should be overridden if you have the appropriate role.

Since I'am building a REST API, I want to scope each activity on his resource.

Example:
The Person resource expose through API  it's CRUD methods (We will not use HTTP verbs to authorized. I do not find clever to bind Authorization on the Transfer Protocol ).

I see Person activities as the following set :

person:create
person:update
person:delete
person:show
person:index
...

The pattern is dead simple and interpolate as #{resource}:#{activity}

With all those postulates, I decided to store activities on Role through the Postgres Array Type. It will be an array of strings.


Meta-programming Right Management


Then I just had to teach Pundit the way I wan't it to authorize activities :


This may need some explanations :
This is the ApplicationPolicy, it will be inherited by each resource, allowing to extend/ override Policies by resources.

I replaced the bulk Pundits question marked CRUD methods with some meta-programming to avoid code duplication, and tedious declarations.

Line 9 : It recovers allowed activities for the current user.

Line 13 : The current activity against which we authorize is inferred from the object's class name ( the word record is used but i plan to change this, since record mean database record for me, but it could be any object ).

Line 17 : The (in)famous Ruby's method missing !
Any unknown method ending with a question mark in the current scope will be interpreted as a check access right for this activity method.
Each time we want to check access right for an activity, it will lookup the allowed activities for the current user roles ...

This is the way the most of work is done, with a few ruby lines of code.
To manage rights, all we need is to add the activity to user role, what could be more simple ?

The person resource


Since all the work is done by the ApplicationPolicy class, the PersonPolicy class is just there to include and override behaviors.

The application Controller


Nothing extravagant here. Just read comments. Note that when the right are insufficient, It just return a 403 HTTP Header, it is enough for a REST API and is the way I understand the RFC 2616.

The Rspec Tests. ( Behaviour Driven ? )


Just to show how it works and as proof , this is a dumb spec/test for the dumb person example.


The test ouput the following :


Easy :)

In a further post i'll detail how to apply read/write mask on object.


13 comments:

  1. In reply to some readers
    I've made a more explicit example on how to manage role/user:

    https://gist.github.com/Electron-libre/d15581062a520943bb82

    ReplyDelete
  2. So, I have one question on this approach: How do you manage activities?

    Like say I have 3 roles: Admin, Manager, Guest and I have 10 resources. For each role, I need to manage around 60 activities (or more if I have more resources). This sounds to me like a lot of work... Is there a way to simplify that ?

    ReplyDelete
  3. I had in mind a more granular Role management. A user can have plenty of atomic roles. It is not intended to be used with large monolithic roles.
    I do not have an "Admin" role for example. But I have a "Role manager", "User manager", "Profile manager", "Person Manager", "Article Manager" ... And all of these roles compose the Admin profile.

    I use this approach to compose role into larger profiles. Having atomic roles allow to compose new profiles with ease.

    Then from the role defined I can have an Admin profile as said above , a Reviewer profile composed of Person manager and Article manager. A Community Director profile composed of User Manager and Person manger role ...

    I hope it helps.

    ReplyDelete
    Replies
    1. Even so, without a UI of sorts it's probably gonna become a nightmare to manage roles.

      Delete
    2. Yes, you need to build your own system upon this.
      This is not intended to be used out of box, or a full tutorial.
      UI is not required, you just need prototypes for you profiles.

      Feel free to give feedback on how you built uppon this.

      Delete
  4. Another issue that I'm concerned with is handling controller action that have no model associated (ie. HomeController). I don't know how this setup (or even Pundit's default) is supposed to work with that.

    ReplyDelete
    Replies
    1. I only managed right on resources. I do not have a non-resource related controller. Even authentication is resouce based.
      By resources I mean model or object or whatever things I expose *representation* for.

      What is the purpose of you HomeController, and the use cases for ?

      Delete
    2. HomeController might be your homepage but it can be any action that's not tied to a resource/model.

      Delete
    3. Not sure, but look like you're using Rails as a CMS.
      Even for a HomeController, I would have a resource exposed ( a homeCollection composed of articles , for example ).

      But this is more related to the way I build applications. Home has content, this content is either the HomeResource, a pointer to an Article or a HomeCollection, the collection of articles exposed on your front page.
      Hope it help

      Delete
    4. Check this out: https://github.com/elabs/pundit/issues/77

      Delete
    5. I'am wondering what do you expect from this comment, let me try to answer, and please forgive me if the answer does not fit your expectations.

      Pundit policies are defined against regular Ruby objects, you could then use Rails controller object to build your policy.

      This seem to be a good way, Pundit gives you freedom, be creative !

      Delete
    6. You are looking for headless policies, not sure when they added this though https://github.com/varvet/pundit#headless-policies

      Delete
  5. Very interesting blog Really excellent information and thank you for giving your valuable information ruby on rails online training hyderabad

    ReplyDelete