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
endLoad only what you are allowed to see!
class BooksController < ApplicationController
load_and_authorize_resource
endAbility 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
endCode
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