Load And Authorize Resource
Auto-loads and authorizes resources in your resource controllers so you can do this:
class NotesController < ApplicationController
load_and_authorize_resource
def show
# @note is already loaded and the current_user has authority to view it
end
end
This was inspired heavily by functionality in the CanCan gem, but built to work mostly independent of any authorization library.
Mascot
This is LAAR. He's a horse my daughter drew.
Assumptions
This library assumes your app follows some (fairly common) conventions:
- Your controller name matches your model name, e.g. "NotesController" for the "Note" model.
- You have a method on your (Application)Controller called
current_user
that returns your User model. - Your User model has methods like
can_read?
,can_update?
,can_delete?
, etc. (This works great with Authority gem, but naturally can work with any authorization library, given you/it defines those methods.) - You have a method on your controller that returns the resource parameters, e.g.
note_params
.
Installing
Add to your Gemfile:
gem 'load_and_authorize_resource'
...and run bundle install
.
Then add the following to your ApplicationController:
class ApplicationController < ActionController::Base
include LoadAndAuthorizeResource
end
Loading and Authorizing the Resource
class NotesController < ApplicationController
load_and_authorize_resource
def show
# @note is already loaded
end
def new
# @note is a new Note instance
end
def create
# @note is a new Note instance
# with attributes set from note_params
end
end
For each controller action, current_user.can_<action>?(@note)
is consulted. If false, then an LoadAndAuthorizeResource::AccessDenied
error is raised.
This works very nicely along with the Authority gem.
If you don't wish to authorize, or if you wish to do the loading yourself, you can just call load_resource
and/or authorize_resource
. Also, each macro accepts the normal before_action options such as :only
and :except
if you wish to only apply the filters to certain actions.
Loading and Authorizing the Parent Resource
Also loads and authorizes the parent resource(s)... given the following routes:
My::Application.routes.draw do
resources :people do
resources :notes
end
resources :groups do
resources :notes
end
end
... you can do this in your controller:
class NotesController < ApplicationController
load_and_authorize_parent :person, :group
load_and_authorize_resource
def show
# for /people/1/notes/2
# @parent = @person = Person.find(1)
# for /groups/1/notes/2
# @parent = @group = Group.find(1)
end
end
If you don't wish to authorize, or if you wish to do the loading yourself, you can just call load_parent
and/or authorize_parent
. Also, each macro accepts the normal before_action options such as :only
and :except
if you wish to only apply the filters to certain actions.
Accessing Children
When you setup to load a parent resoure, a private method is defined with the name of the child resource that returns an ActiveRecord::Relation scoped to the @parent
(if present). It basically looks like this:
class NotesController < ApplicationController
private
def notes
if @person
@person.notes
elsif !required(:parent)
Note.all
end
end
end
This allows you to easily access the set of notes that make sense given the URL, e.g.:
class NotesController < ApplicationController
def index
# notes is basically equivalent to @group.notes, @person.notes, or just Note,
# for the urls /groups/1/notes, /people/1/notes, or /notes (respectively).
@notes = notes.order(:created_at).page(params[:page])
end
end
For parent resources, current_user.can_read?(@parent)
is consulted. If false, then an LoadAndAuthorizeResource::AccessDenied
error is raised.
If none of the parent IDs are present, e.g. person_id
and group_id
are both absent in params
, then a LoadAndAuthorizeResource::ParameterMissing
exception is raised.
Specifying Type of Authorization Required
When authorizing a parent resource, you may wish to check a permission other than :read
. If so, specify the permit
option:
class NotesController < ApplicationController
load_and_authorize_parent :person, permit: :edit
end
Instead of asking current_user.can_read?(person)
, LAAR will ask current_user.can_edit?(person)
.
Shallow (Optional) Routes
You can make the parent loading and authorization optional:
class NotesController < ApplicationController
load_and_authorize_parent :person, :group, optional: true
end
...this will allow all of the following URLs to work:
-
/people/1/notes/2
-@person
will be set and authorized for reading -
/groups/1/notes/2
-@group
will be set and authorized for reading -
/notes/2
- no parent will be set
Rescuing Exceptions
You are encouraged to rescue the two possible exceptions in your ApplicationController, like so:
class ApplicationController < ActionController::Base
rescue_from 'LoadAndAuthorizeResource::AccessDenied', 'LoadAndAuthorizeResource::ParameterMissing' do |exception|
render text: 'not authorized', status: :forbidden
end
end
Author
Made with ❤ by Tim Morgan.
Licensed under MIT license. Please use it, fork it, make it more awesome.