A Post Entitled I am the Keymaster, are you the Gatekeeper?

posted 2 years ago in rails authorization

Rick Moranis in Ghostbusters

Several weeks ago I started writing an authorization gem on the way home from a weeks’ vacation. The problem with the set of gems and plugins that I’d used in the past were several. One gem was much too tightly coupled to an authentication gem that I no longer used. Others required too much wiring. So, off I went to create the next great gem. At least until I got home and ran into a stray question in a ruby forum about the DeclarativeAuthorization gem.

DeclarativeAuthorization is the gem that I wished I’d written for a number of reasons. First, DeclarativeAuthorization is almost entirely self-contained from the rest of your application. It’s only requirement is that your user/account model support a collection of role_names. How you support that collection is entirely up to you. That’s the kind of separation that I really wanted and that ultimately had triggered my ideas for writing a gem in the first place.

DeclarativeAuthorization uses a ruby script to define roles and to assign privileges to those roles. The approach is simple and clean… even when the rules for roles get murky. There are two big advantages to this. One is that the rules do not depend on any “Rails magic” or golden values in your database. The other is that the roles and privileges can grow with your project regardless of the eventual complexity.

Composing Privileges

Out of the box DeclarativeAuthorization has a simple set of privilges for CRUD… conveniently named Create, Read, Update, and Delete. The privileges are declared as follows.


privileges do
  privilege :manage, :includes => [:create, :read, :update, :delete]
  privilege :read, :includes => [:index, :show]
  privilege :create, :includes => :new
  privilege :update, :includes => :edit
  privilege :delete, :includes => :destroy
end

What’s been really well thought out here is how privileges should be composed together. For example, it seems fairly obvious at the UI level that in order to be able to ‘update’ something you must first be able to ‘edit’ it (at least in the Rails view of REST). Similarly, DeclarativeAuth sensibly defines ‘read’ as ‘show’ and ‘index’.

But what if you wanted to go a level higher? For example, what if you have an admin area and you know that only admins should be able to perform any CRUD? Won’t it get annoying repeating tests for each aspect of CRUD? Nope. Because you can compose the privileges. That’s the essence of the first line of the block. In that line we have a “manage” privilege that is defined to be the same as granting each individual C/R/U/D privilege.

Roles Compose, Too!

DeclarativeAuth also supports composing roles as well.


authorization do
  role :guest do
    has_permission_on :conferences, :to => :read do
      if_attribute :published => true
    end
    has_permission_on :talks, :to => :read do
      if_permitted_to :read, :conference
    end
    has_permission_on :users, :to => :create
    has_permission_on :authorization_rules, :to => :read
    has_permission_on :authorization_usages, :to => :read
  end
  
  role :user do
    includes :guest
    has_permission_on :conference_attendees, :to => :create, :join_by => :and do
      if_attribute :user => is {user}
      if_permitted_to :read, :conference
    end
    has_permission_on :conference_attendees, :to => :delete do
      if_attribute :user => is {user}
    end
    has_permission_on :talk_attendees, :to => :create do
      if_attribute :talk => { :conference => { :attendees => contains {user} }},
          :user => is {user}
    end
    has_permission_on :talk_attendees, :to => :delete do
      if_attribute :user => is {user}
    end
  end

  ...
end

Obviously there’s a *lot* going on in that code snippet. The point here is found in the first line of the block for ‘role :user do’ — ‘include :guest’. Simply put, the user gets to do everything that a guest can do plus what’s defined in the rest of the block. That makes for a very powerful and concise way to keep track of roles and privileges.

Without getting into the details too much, the other thing to note in the definition of the :user role is the if_attribute calls. if_attribute allows the role definition to move beyond global scope for the class and validate privileges on an instance by instance basis. The if_attribute calls in the :user definition essentially say “if the current user is the user associated with the instance (the one that created it) then the user can…”.

Integration across the Rails MVC stack

With the ability to set privileges based on attributes of a particular instance you may be thinking, “Wow, I could use that to secure data at the instance level…” You’d be right. And, of course, DeclarativeAuth already does that.

DeclarativeAuth has a simple API that allows you to specify privileges in the models to secure access at the (data) class level in addition to the broader access control at the controller level. There are even nice helpers available in the views to show/hide UI based on the privileges of the current user.

ConferenceAttendee data class


class ConferenceAttendee < ActiveRecord::Base
  using_access_control
  
  belongs_to :user
  belongs_to :conference
  validates_presence_of :user, :conference
  validates_associated :user, :conference
end

ConferencesController#index


def index
    # Only show conferences that the current user may read:
    @conferences = Conference.with_permissions_to(:read)
 
    respond_to do |format|
      format.html # index.html.erb
      format.xml  { render :xml => @conferences }
    end
  end

Note: All the code snippets presented here come from the sample app for DeclarativeAuthorization

Comments

About this site and its Author


The Tumblrs to Follow

  • cdmwebs
  • staff
  • paulsullivanjr
  • dawnvanasse
  • bitbltr