how i cancan - andrew brieningandrew.briening.com/presentations/how-i-cancan.pdf · not...
TRANSCRIPT
![Page 1: HOW I CANCAN - Andrew Brieningandrew.briening.com/presentations/how-i-cancan.pdf · NOT AUTHENTICATION • HTTP 1.1 Spec • Authorization Header and "401 Unauthorized" are inaccurate](https://reader033.vdocuments.us/reader033/viewer/2022043014/5fb1efc54f8fdd31bc7797a0/html5/thumbnails/1.jpg)
HOW I CANCANAndrew Briening (abriening)
![Page 2: HOW I CANCAN - Andrew Brieningandrew.briening.com/presentations/how-i-cancan.pdf · NOT AUTHENTICATION • HTTP 1.1 Spec • Authorization Header and "401 Unauthorized" are inaccurate](https://reader033.vdocuments.us/reader033/viewer/2022043014/5fb1efc54f8fdd31bc7797a0/html5/thumbnails/2.jpg)
NOT AUTHENTICATION
• HTTP 1.1 Spec
• Authorization Header and "401 Unauthorized" are inaccurate
• Should be Authentication
•Devise, Authlogic, OmniAuth, Sorcery, Clearance, or ActiveModel::SecurePassword with ActionController ::HttpAuthentication
![Page 3: HOW I CANCAN - Andrew Brieningandrew.briening.com/presentations/how-i-cancan.pdf · NOT AUTHENTICATION • HTTP 1.1 Spec • Authorization Header and "401 Unauthorized" are inaccurate](https://reader033.vdocuments.us/reader033/viewer/2022043014/5fb1efc54f8fdd31bc7797a0/html5/thumbnails/3.jpg)
AUTHORIZATION
• 403 Forbidden
• CanCan, Declarative Authorization, and a sharp drop-off to other solutions
• https://www.ruby-toolbox.com/categories/rails_authorization
![Page 4: HOW I CANCAN - Andrew Brieningandrew.briening.com/presentations/how-i-cancan.pdf · NOT AUTHENTICATION • HTTP 1.1 Spec • Authorization Header and "401 Unauthorized" are inaccurate](https://reader033.vdocuments.us/reader033/viewer/2022043014/5fb1efc54f8fdd31bc7797a0/html5/thumbnails/4.jpg)
CANCAN
• https://github.com/ryanb/cancan
•No runtime dependencies
•Decoupled from Rails, but includes plenty of helpers
• http://railscasts.com/episodes/192-authorization-with-cancan
![Page 5: HOW I CANCAN - Andrew Brieningandrew.briening.com/presentations/how-i-cancan.pdf · NOT AUTHENTICATION • HTTP 1.1 Spec • Authorization Header and "401 Unauthorized" are inaccurate](https://reader033.vdocuments.us/reader033/viewer/2022043014/5fb1efc54f8fdd31bc7797a0/html5/thumbnails/5.jpg)
INSTALL CANCAN
• Authentication & current_user should already exist
• gem ‘cancan’
• bundle install
• rails g cancan:ability
![Page 6: HOW I CANCAN - Andrew Brieningandrew.briening.com/presentations/how-i-cancan.pdf · NOT AUTHENTICATION • HTTP 1.1 Spec • Authorization Header and "401 Unauthorized" are inaccurate](https://reader033.vdocuments.us/reader033/viewer/2022043014/5fb1efc54f8fdd31bc7797a0/html5/thumbnails/6.jpg)
ABILITY
class Ability include CanCan::Ability
def initialize(user) # ... endend
![Page 7: HOW I CANCAN - Andrew Brieningandrew.briening.com/presentations/how-i-cancan.pdf · NOT AUTHENTICATION • HTTP 1.1 Spec • Authorization Header and "401 Unauthorized" are inaccurate](https://reader033.vdocuments.us/reader033/viewer/2022043014/5fb1efc54f8fdd31bc7797a0/html5/thumbnails/7.jpg)
ABILITIES
• Role based?
• Type based?
• Anything-you-want based?
![Page 8: HOW I CANCAN - Andrew Brieningandrew.briening.com/presentations/how-i-cancan.pdf · NOT AUTHENTICATION • HTTP 1.1 Spec • Authorization Header and "401 Unauthorized" are inaccurate](https://reader033.vdocuments.us/reader033/viewer/2022043014/5fb1efc54f8fdd31bc7797a0/html5/thumbnails/8.jpg)
EXAMPLES
class Ability include CanCan::Ability
def initialize(user) user ||= User.new # guest user
if user.role? :admin can :manage, :all else can :read, :all can :create, Comment can :update, Comment do |comment| comment.user == user end end endend
![Page 9: HOW I CANCAN - Andrew Brieningandrew.briening.com/presentations/how-i-cancan.pdf · NOT AUTHENTICATION • HTTP 1.1 Spec • Authorization Header and "401 Unauthorized" are inaccurate](https://reader033.vdocuments.us/reader033/viewer/2022043014/5fb1efc54f8fdd31bc7797a0/html5/thumbnails/9.jpg)
EXAMPLESclass Ability include CanCan::Ability
def initialize(user) user ||= User.new # guest user
admin if user.role?(:admin) moderator if user.role?(:moderator) translator if user.role?(:translator) end
def admin can :manage, :all end
def moderator # ... end
def translator # ... endend
![Page 10: HOW I CANCAN - Andrew Brieningandrew.briening.com/presentations/how-i-cancan.pdf · NOT AUTHENTICATION • HTTP 1.1 Spec • Authorization Header and "401 Unauthorized" are inaccurate](https://reader033.vdocuments.us/reader033/viewer/2022043014/5fb1efc54f8fdd31bc7797a0/html5/thumbnails/10.jpg)
EXAMPLES
class Ability include CanCan::Ability
module Admin def apply_rules # ... end end
def initialize(user) user ||= User.new # guest user
extend Admin if user.role?(:admin) # ... apply_rules if respond_to? :apply_rules endend
![Page 11: HOW I CANCAN - Andrew Brieningandrew.briening.com/presentations/how-i-cancan.pdf · NOT AUTHENTICATION • HTTP 1.1 Spec • Authorization Header and "401 Unauthorized" are inaccurate](https://reader033.vdocuments.us/reader033/viewer/2022043014/5fb1efc54f8fdd31bc7797a0/html5/thumbnails/11.jpg)
CHECKING ABILITIES
Ability.new(@user).can?(:destroy, @project)Ability.new(@user).cannot?(:destroy, @project)
# controller instance methodsauthorize! :read, @article
def authorize!(*args) @_authorized = true current_ability.authorize!(*args)end
# controller class methodsload_and_authorize_resource
load_resource
authorize_resource
check_authorization
![Page 12: HOW I CANCAN - Andrew Brieningandrew.briening.com/presentations/how-i-cancan.pdf · NOT AUTHENTICATION • HTTP 1.1 Spec • Authorization Header and "401 Unauthorized" are inaccurate](https://reader033.vdocuments.us/reader033/viewer/2022043014/5fb1efc54f8fdd31bc7797a0/html5/thumbnails/12.jpg)
CHECKING ABILITIES# before.form-actions = link_to t('.back'), worlds_path = link_to t('.edit'), edit_world_path(@world) = link_to t('.destroy'), world_path(@world), :method => "delete" ...
# after.form-actions - if can? :read, World = link_to t('.back'), worlds_path - if can? :update, @world = link_to t('.edit'), edit_world_path(@world) - if can? :destroy, @world = link_to t('.destroy'), world_path(@world), :method => "delete" ...
![Page 13: HOW I CANCAN - Andrew Brieningandrew.briening.com/presentations/how-i-cancan.pdf · NOT AUTHENTICATION • HTTP 1.1 Spec • Authorization Header and "401 Unauthorized" are inaccurate](https://reader033.vdocuments.us/reader033/viewer/2022043014/5fb1efc54f8fdd31bc7797a0/html5/thumbnails/13.jpg)
ALIASES & SPECIAL CASES
:manage, :all, :read, :create, :update
def matches_action?(action) @expanded_actions.include?(:manage) || ...end
def matches_subject?(subject) @subjects.include?(:all) || ... || ...end
def default_alias_actions { :read => [:index, :show], :create => [:new], :update => [:edit], }end
![Page 14: HOW I CANCAN - Andrew Brieningandrew.briening.com/presentations/how-i-cancan.pdf · NOT AUTHENTICATION • HTTP 1.1 Spec • Authorization Header and "401 Unauthorized" are inaccurate](https://reader033.vdocuments.us/reader033/viewer/2022043014/5fb1efc54f8fdd31bc7797a0/html5/thumbnails/14.jpg)
BEWARE MANAGE ALL
# in ability.rb can :manage, World, :owner => { :id => user.id }
# in controllerdef explode authorize! :explode, @world @world.explode # ;)end
Can inadvertently allow actions
![Page 15: HOW I CANCAN - Andrew Brieningandrew.briening.com/presentations/how-i-cancan.pdf · NOT AUTHENTICATION • HTTP 1.1 Spec • Authorization Header and "401 Unauthorized" are inaccurate](https://reader033.vdocuments.us/reader033/viewer/2022043014/5fb1efc54f8fdd31bc7797a0/html5/thumbnails/15.jpg)
BEWARE MANAGE ALL
# in ability.rb can [:create, :read, :update], World, :owner => { :id => user.id }# in controller authorize! :explode, @world # the world is safe again
“Deny, Allow” by explicitly defining each action
Acceptance testing is important
![Page 16: HOW I CANCAN - Andrew Brieningandrew.briening.com/presentations/how-i-cancan.pdf · NOT AUTHENTICATION • HTTP 1.1 Spec • Authorization Header and "401 Unauthorized" are inaccurate](https://reader033.vdocuments.us/reader033/viewer/2022043014/5fb1efc54f8fdd31bc7797a0/html5/thumbnails/16.jpg)
BEWARE MANAGE ALL
# in ability.rb can [:create, :read, :update, :destroy], World# in controller authorize! :manage, @world # nope!
authorize!(:manage) and can?(:manage, ...) only work if the ability was explicitly defined as can(:manage, ...)
![Page 17: HOW I CANCAN - Andrew Brieningandrew.briening.com/presentations/how-i-cancan.pdf · NOT AUTHENTICATION • HTTP 1.1 Spec • Authorization Header and "401 Unauthorized" are inaccurate](https://reader033.vdocuments.us/reader033/viewer/2022043014/5fb1efc54f8fdd31bc7797a0/html5/thumbnails/17.jpg)
USE INSTANCES WHEN POSSIBLE
# rulecan :create, World, owner: { id: @user }
# This doesn't work, can? usage is not reciprocal with can rules# :owner is silently ignoredcan? :create, World, owner: { id: @user } # yepcan? :create, World, owner: { id: @other_user } # that's ok
# Pass in an instantiated recordcan? :create, World # yes, surprisinglycan? :create, World.new(owner: @user) # yepcan? :create, World.new(owner: @other_user) # nope!
![Page 18: HOW I CANCAN - Andrew Brieningandrew.briening.com/presentations/how-i-cancan.pdf · NOT AUTHENTICATION • HTTP 1.1 Spec • Authorization Header and "401 Unauthorized" are inaccurate](https://reader033.vdocuments.us/reader033/viewer/2022043014/5fb1efc54f8fdd31bc7797a0/html5/thumbnails/18.jpg)
KEEP IT RESTFUL
# in ability.rb can [:create, :read, :update, :destroy], World
Stick to CRUD methods, :create, :read, :update, :destroy
![Page 19: HOW I CANCAN - Andrew Brieningandrew.briening.com/presentations/how-i-cancan.pdf · NOT AUTHENTICATION • HTTP 1.1 Spec • Authorization Header and "401 Unauthorized" are inaccurate](https://reader033.vdocuments.us/reader033/viewer/2022043014/5fb1efc54f8fdd31bc7797a0/html5/thumbnails/19.jpg)
# seems intuitive# in ability.rb can :play, World
# check in worlds controller def play can? :play, @world # ... end # but gets awkward; how do I "unplay"? can :stop_server, World, server: { world: { owner: {id: user} } } can? :stop_server, @world
# better can [:create, :destroy], Server, world: {owner: {id: user}}
# check can? :create, Server.new(@world) can? :destroy, @server
KEEP IT RESTFUL
![Page 20: HOW I CANCAN - Andrew Brieningandrew.briening.com/presentations/how-i-cancan.pdf · NOT AUTHENTICATION • HTTP 1.1 Spec • Authorization Header and "401 Unauthorized" are inaccurate](https://reader033.vdocuments.us/reader033/viewer/2022043014/5fb1efc54f8fdd31bc7797a0/html5/thumbnails/20.jpg)
CONTROLLER HELPERS# class methodsload_and_authorize_resource
load_resource
authorize_resource
check_authorization
• Helpful in getting things setup fast, DRY, but ...
• Breaks single responsibility
• Adds complexity when need to override method of loading
![Page 21: HOW I CANCAN - Andrew Brieningandrew.briening.com/presentations/how-i-cancan.pdf · NOT AUTHENTICATION • HTTP 1.1 Spec • Authorization Header and "401 Unauthorized" are inaccurate](https://reader033.vdocuments.us/reader033/viewer/2022043014/5fb1efc54f8fdd31bc7797a0/html5/thumbnails/21.jpg)
CONTROLLER HELPERS# world_controller, before def index authorize! :read, World @worlds = worlds.all respond_with @worlds end
def show @world = worlds.find(params[:id]) authorize! :read, @world respond_with @world end
# world_controller, after load_and_authorize_resource def index respond_with @worlds end
def show respond_with @world end
![Page 22: HOW I CANCAN - Andrew Brieningandrew.briening.com/presentations/how-i-cancan.pdf · NOT AUTHENTICATION • HTTP 1.1 Spec • Authorization Header and "401 Unauthorized" are inaccurate](https://reader033.vdocuments.us/reader033/viewer/2022043014/5fb1efc54f8fdd31bc7797a0/html5/thumbnails/22.jpg)
ACCESSIBLE BY
World.accessible_by current_ability
# chain with scopesWorld.active.accessible_by current_ability
# chain with collection [email protected]_by(current_ability)
# converted to scopecan :update, World, owner: user
# Boom! Can’t use block syntax and accessible_bycan :update, World do |world| world.owner == userend
![Page 23: HOW I CANCAN - Andrew Brieningandrew.briening.com/presentations/how-i-cancan.pdf · NOT AUTHENTICATION • HTTP 1.1 Spec • Authorization Header and "401 Unauthorized" are inaccurate](https://reader033.vdocuments.us/reader033/viewer/2022043014/5fb1efc54f8fdd31bc7797a0/html5/thumbnails/23.jpg)
CANCAN::ACCESSDENIED
class ApplicationController < ActionController::Base
def authorization_error # 403 Forbidden response respond_to do |format| format.html{ render '/rescues/access_denied', :status => 403 } format.xml{ render :xml => 'Access Denied', :status => 403 } format.json{ render :json => 'Access Denied', :status => 403 } end end
rescue_from CanCan::AccessDenied, :authorization_errorend
![Page 24: HOW I CANCAN - Andrew Brieningandrew.briening.com/presentations/how-i-cancan.pdf · NOT AUTHENTICATION • HTTP 1.1 Spec • Authorization Header and "401 Unauthorized" are inaccurate](https://reader033.vdocuments.us/reader033/viewer/2022043014/5fb1efc54f8fdd31bc7797a0/html5/thumbnails/24.jpg)
410 GONE