Action Based Authorization in Rails

1. Users

Devise

  • https://github.com/plataformatec/devise

2. Authorisation

Why?

  • different users can do different things

How?

  • roles
  • User has many Roles

Limitations

  • high level
  • fine grained => a lot of roles
  • can do action?
    • if user has role...
    • what if logic is more complex?

Case Study: Library App

  • a library
  • has books
  • has librarians, customers

John the librarian

  • can he administer books?
    • yes
  • all the books?
    • yes

Multiple libraries

  • multiple libraries
  • each with books
  • each with librarians and customers

John the librarian

  • can he administer books?
    • yes
  • all the books?
    • no!
  • easy to implement?
    • no

Access Control Lists

(ACLs)

REST route based

  • GET /:library/books
  • POST /:library/books
  • GET /:library/books/:id
  • POST /:library/books/:id

Things get complicated

  • POST /:library/books/an_action
    • can John do this?
    • what if you can't figure it out only by looking at the route?

Manage multiple entities

  • POST /:library/an_action
    • can the librarian access this?
    • maybe he can't manage any entity...
    • next month, we add the Nth resource there => gotta update the ACL!

Action Based Authorisation

  • a given user can do something with something
    • I can update this book
  • very fine grained access control
  • target:
    • instance
    • class (all instances)
    • :any
  • standard actions
    • :create, :read, :update, :destroy
    • :manage
  • custom actions
    • anything you want :)

Checking permission

# book with id 1
@book = Book.find(1)

# boolean answer
can? :manage, @book
can? :read, @book

# authorisation exception
authorize! :manage @book
authorize! :read @book

Ability file

class Ability
  include CanCan::Ability

  def initialize(user)
    user ||= User.new # guest user (not logged in)
    if user.librarian?
      can :manage, Book, library_id: user.library_id
    else
      can :read, Book, library_id: user.library_id
    end
  end
end

Load only what you are allowed to see!

class BooksController < ApplicationController

  load_and_authorize_resource

end

Ability file with blocks

class Ability
  include CanCan::Ability

  def initialize(user)
    user ||= User.new # guest user (not logged in)

    if user.librarian?
      can :manage, Book do |book|
        book.library_id == user.library_id 
        # and it's raining outside, and some other condition
      end
    end
  end
end

Code

Tips & Tricks

  • restrictions:
    • because permissions & because business logic
  • for API
    • loader should be route based not only cancan based
    • ex: /users/:id/libraries
  • generated queries
    • if complex, load resources on your own

Thanks

rails-authorization

By Horia Radu

rails-authorization

  • 308