Slide 1

Slide 1 text

ACCESS DENIED Vladimir Dementyev The missing guide to authorization in Rails

Slide 2

Slide 2 text

@palkan @palkan_tula Vladimir Dementyev

Slide 3

Slide 3 text

! Moscow ✈ # New York Pittsburgh

Slide 4

Slide 4 text

No content

Slide 5

Slide 5 text

https://evilmartians.com

Slide 6

Slide 6 text

https://evilmartians.com

Slide 7

Slide 7 text

No content

Slide 8

Slide 8 text

Brooklyn, NY https://evilmartians.com

Slide 9

Slide 9 text

Part 1 THEORY…

Slide 10

Slide 10 text

AUTHORIZATION Official permission for something to happen, or the act of giving someone official permission to do something https://dictionary.cambridge.org/dictionary/english/authorization

Slide 11

Slide 11 text

Authentication AUTHORIZATION

Slide 12

Slide 12 text

AUTHENTICATION Who are you?

Slide 13

Slide 13 text

AUTHORIZATION Am I allowed to do that?

Slide 14

Slide 14 text

EXAMPLE class AppController < ActionController ::Base before_action :authenticate_user! def authenticate_user! @current_user = User.find_by( token: params[:token] ) || head :unauthorized end end authentication

Slide 15

Slide 15 text

EXAMPLE class UsersController < ApplicationController def destroy item = Item.find(params[:id]) if item.owner_id == current_user.id item.destroy! head :ok end else head :forbidden end end authorization

Slide 16

Slide 16 text

AUTHENTICATION rodauth warden authlogic clearance devise doorkeeper sorcery

Slide 17

Slide 17 text

AUTHORIZATION cancancan rolify consul allowy walruz action_access pundit the_role trust eaco declarative_aithorization SimonSays canable kan

Slide 18

Slide 18 text

No content

Slide 19

Slide 19 text

Authentication System constraints AUTHORIZATION

Slide 20

Slide 20 text

EXAMPLE class ReposController < AppController def create if current_account.available_repos.zero? head :payment_required end # ... end end constraint check

Slide 21

Slide 21 text

Authentication System constraints Data model constraints (validations) AUTHORIZATION

Slide 22

Slide 22 text

LINES OF DEFENCE authentication authorization constraints validations

Slide 23

Slide 23 text

How to grant/revoke access? Authorization model (roles, permission, accesses) How to verify access? Authorization layer (policies, rules) AUTHORIZATION

Slide 24

Slide 24 text

FORMAL MODELS DAC MAC RBAC ABAC

Slide 25

Slide 25 text

User Permission Resource 1 0,* 0,* 1 @resource.permissions.create!(user: user, activity: :read) user.can?(:read, @resource) # => is transformed to @resource.permissions.exists?(user: user, activity: :read) DISCRETIONARY (DAC)

Slide 26

Slide 26 text

user.can?(:read, @resource) # => is transformed to @resource.security_level <= user.security_clearance MANDATORY (MAC) Security clearance vs. security level

Slide 27

Slide 27 text

ROLE-BASED (RBAC) https://github.com/the-teacher/the_role

Slide 28

Slide 28 text

ATTRIBUTE-BASED [ { "resource": "TestApp ::Book", "action": ["read"], "description": "Allow owners to read their books", "effect": "allow", "conditions": [ { "equal": { "resource ::owner ::id": ["user ::id"] } } ] } ] https://github.com/TheClimateCorporation/iron_hide

Slide 29

Slide 29 text

ABAC https://csrc.nist.gov/projects/abac/

Slide 30

Slide 30 text

How to grant/revoke access? Authorization model (roles, permission, accesses) How to verify access? Authorization layer (policies, rules) AUTHORIZATION How to verify access? Authorization layer (policies, rules)

Slide 31

Slide 31 text

… AND PRACTICE FOR ALL Part 2

Slide 32

Slide 32 text

IN RAILS

Slide 33

Slide 33 text

IN RAILS Controllers Views (templates, serializers) Channels (Action Cable)

Slide 34

Slide 34 text

IN RAILS Controllers Views (templates, serializers) Channels (Action Cable) GraphQL resolvers

Slide 35

Slide 35 text

TOOLS CanCan(-Can) Pundit

Slide 36

Slide 36 text

WHAT PEOPLE SAY ✅ Easy to use ✅ Simple, readable config ✅ Great community & docs CanCanCan Pundit ✅ Pure OOP ✅ No magic ✅ Easy to test * “Authorization in Rails” survey bit.ly/rails-auth

Slide 37

Slide 37 text

CANCAN class Ability include CanCan ::Ability def user_abilities can :create, [Question, Answer] can :update, [Question, Answer], user_id: user.id can :destroy, [Question, Answer], user_id: user.id can :destroy, Attachment, attachable: { user_id: user.id } can [:vote_up, :vote_down], [Question, Answer] do |resource| resource.user_id != user.id end end end

Slide 38

Slide 38 text

Ability

Slide 39

Slide 39 text

PUNDIT class QuestionPolicy < ApplicationPolicy def index? true end def create? true end def update? user.admin? || (user.id == target.user_id) end alias edit? update? alias destroy? update? end

Slide 40

Slide 40 text

WHAT PEOPLE SAY ❌ Framework-specific ❌ A lot of magic CanCanCan Pundit ❌ A lot of duplication ❌ No namespaces support * “Authorization in Rails” survey bit.ly/rails-auth

Slide 41

Slide 41 text

THE EVOLUTION Start with CanCan Realize that it becomes more difficult to maintain abilities as project grows Migrate to Pundit Customize it again and again

Slide 42

Slide 42 text

WHY CUSTOMIZE? http://gemcheck.evilmartians.io Boilerplate Performance Testing Flexibility

Slide 43

Slide 43 text

FRAMEWORK OF A DREAM Part 3

Slide 44

Slide 44 text

THE QUESTION Why does Rails not provide an authorization solution out-of-the-box?

Slide 45

Slide 45 text

THE ANSWER? gem "action_policy"

Slide 46

Slide 46 text

Pundit, re-visited Born in Hell in production Pre-release published today ACTION POLICY https://github.com/palkan/action_policy

Slide 47

Slide 47 text

ACTION POLICY

Slide 48

Slide 48 text

class ProductsController < ApplicationController before_action :load_product, except: [:index, :new, :create] def load_product @product = current_account.products.find(params[:id]) # auto-infer policy and rule authorize! @product # explicit rule and policy authorize! @product, to: :manage?, with: SpecialProductPolicy end end ACTION POLICY

Slide 49

Slide 49 text

class ProductsController < ApplicationController def index # non-raising predicate method if allowed_to?(:create?) @tags = current_account.tags end end end ACTION POLICY

Slide 50

Slide 50 text

Let’s talk about BOILERplate

Slide 51

Slide 51 text

BOILERPLATE # With Pundit class CoursePolicy < ApplicationPolicy def show? admin? || manager? || owner? || assigned? end def update? admin? || assigned? end def destroy? admin? || manager? || owner? end end

Slide 52

Slide 52 text

BOILERPLATE # With ActionPolicy class ApplicationPolicy < ActionPolicy ::Base pre_check :allow_admins private def allow_admins allow! if user.admin? end end

Slide 53

Slide 53 text

BOILERPLATE # With ActionPolicy class CoursePolicy < ApplicationPolicy def show? manager? || owner? || assigned? end def update? assigned? end def destroy? manager? || owner? end end

Slide 54

Slide 54 text

BOILERPLATE # With ActionPolicy class UserPolicy < ApplicationPolicy skip_pre_check :allow_admins, only: :destroy? def destroy? (admin? || !record.admin?) && manage? end end

Slide 55

Slide 55 text

BOILERPLATE # With Pundit class ProductsController < ApplicationController def create authorize Product end end

Slide 56

Slide 56 text

BOILERPLATE # With ActionPolicy class ProductsController < ApplicationController def create # target class is inferred from controller authorize! end end CONVENTION OVER CONFIGURATION

Slide 57

Slide 57 text

BOILERPLATE # With ActionPolicy class RepoPolicy < ApplicationPolicy # manage? is a default (fallback) rule default_rule :manage? def manage? user.admin? || (user.id == record.user_id) end end

Slide 58

Slide 58 text

BOILERPLATE # With ActionPolicy class RepoPolicy < ApplicationPolicy # or specify explicit aliases alias_rule :update?, :destroy?, :edit?, to: :manage? def manage? user.admin? || (user.id == record.user_id) end end

Slide 59

Slide 59 text

PERFORMANCE © https://www.railsspeed.com

Slide 60

Slide 60 text

PERFORMANCE How to measure? What to measure? How to fix?

Slide 61

Slide 61 text

ActiveSupport ::Notifications “action_policy.authorize” # fires “action_policy.authorize" and “action_policy.apply” events authorize! @product # fires “action_policy.apply” event allowed_to?(:edit?, @product) “action_policy.apply”

Slide 62

Slide 62 text

ActiveSupport ::Notifications “action_policy.apply” ActiveSupport ::Notifications.subscribe(“action_policy.apply") do |event, started, finished, _, data| timing = ((finished - started) * 1000).to_i $stdout.puts "authorize ##{event[:policy]} ##{event[:rule]}={timing}ms" end

Slide 63

Slide 63 text

N+1 AUTHORIZATION 14 Mar 2018 14:08:30.722241 <190>1 2018-03-14T11:08:30.349156+00:00 app web.1 - - [1bb8d912-308a-4966-b436-a407ef34bac2] Started GET "/test_account/openings/public-funnel/dashboard" for 185.13.112.107 at 2018-03-14 11:08:30 +0000 14 Mar 2018 14:08:30.722186 <190>1 2018-03-14T11:08:30.358367+00:00 app web.1 - - [1bb8d912-308a-4966-b436-a407ef34bac2] [U#6] Processing by DashboardsController#show as HTML 14 Mar 2018 14:08:30.722216 <190>1 2018-03-14T11:08:30.358391+00:00 app web.1 - - [1bb8d912-308a-4966-b436-a407ef34bac2] [U#6] Parameters: {"account_slug"=>"test_account", "opening_id"=>"public-funnel"} 14 Mar 2018 14:08:30.722174 <190>1 2018-03-14T11:08:30.368142+00:00 app web.1 - - [1bb8d912-308a-4966-b436-a407ef34bac2] [U#6] measure#action_policy.scope.hit=0ms 14 Mar 2018 14:08:30.722175 <190>1 2018-03-14T11:08:30.399348+00:00 app web.1 - - [1bb8d912-308a-4966-b436-a407ef34bac2] [U#6] measure#action_policy.scope.miss=6ms 14 Mar 2018 14:08:30.722176 <190>1 2018-03-14T11:08:30.407044+00:00 app web.1 - - [1bb8d912-308a-4966-b436-a407ef34bac2] [U#6] measure#action_policy.scope.miss=21ms 14 Mar 2018 14:08:30.722174 <190>1 2018-03-14T11:08:30.441693+00:00 app web.1 - - [1bb8d912-308a-4966-b436-a407ef34bac2] [U#6] measure#action_policy.scope.hit=0ms 14 Mar 2018 14:08:30.722175 <190>1 2018-03-14T11:08:30.471756+00:00 app web.1 - - [1bb8d912-308a-4966-b436-a407ef34bac2] [U#6] measure#action_policy.check.miss=2ms 14 Mar 2018 14:08:30.722174 <190>1 2018-03-14T11:08:30.474080+00:00 app web.1 - - [1bb8d912-308a-4966-b436-a407ef34bac2] [U#6] measure#action_policy.check.hit=0ms 14 Mar 2018 14:08:30.722175 <190>1 2018-03-14T11:08:30.504566+00:00 app web.1 - - [1bb8d912-308a-4966-b436-a407ef34bac2] [U#6] measure#action_policy.check.miss=1ms 14 Mar 2018 14:08:30.722175 <190>1 2018-03-14T11:08:30.505893+00:00 app web.1 - - [1bb8d912-308a-4966-b436-a407ef34bac2] [U#6] measure#action_policy.check.miss=1ms 14 Mar 2018 14:08:30.722175 <190>1 2018-03-14T11:08:30.507222+00:00 app web.1 - - [1bb8d912-308a-4966-b436-a407ef34bac2] [U#6] measure#action_policy.check.miss=1ms 14 Mar 2018 14:08:30.722175 <190>1 2018-03-14T11:08:30.508522+00:00 app web.1 - - [1bb8d912-308a-4966-b436-a407ef34bac2] [U#6] measure#action_policy.check.miss=1ms 14 Mar 2018 14:08:30.722175 <190>1 2018-03-14T11:08:30.509846+00:00 app web.1 - - [1bb8d912-308a-4966-b436-a407ef34bac2] [U#6] measure#action_policy.check.miss=1ms 14 Mar 2018 14:08:30.722175 <190>1 2018-03-14T11:08:30.511189+00:00 app web.1 - - [1bb8d912-308a-4966-b436-a407ef34bac2] [U#6] measure#action_policy.check.miss=1ms 14 Mar 2018 14:08:30.796175 <190>1 2018-03-14T11:08:30.512857+00:00 app web.1 - - [1bb8d912-308a-4966-b436-a407ef34bac2] [U#6] measure#action_policy.check.miss=1ms 14 Mar 2018 14:08:30.796174 <190>1 2018-03-14T11:08:30.523828+00:00 app web.1 - - [1bb8d912-308a-4966-b436-a407ef34bac2] [U#6] measure#action_policy.check.hit=0ms 14 Mar 2018 14:08:30.796174 <190>1 2018-03-14T11:08:30.527719+00:00 app web.1 - - [1bb8d912-308a-4966-b436-a407ef34bac2] [U#6] measure#action_policy.check.hit=0ms 14 Mar 2018 14:08:30.796174 <190>1 2018-03-14T11:08:30.529970+00:00 app web.1 - - [1bb8d912-308a-4966-b436-a407ef34bac2] [U#6] measure#action_policy.check.hit=0ms 14 Mar 2018 14:08:31.295174 <190>1 2018-03-14T11:08:30.928734+00:00 app web.1 - - [1bb8d912-308a-4966-b436-a407ef34bac2] [U#6] measure#action_policy.check.hit=0ms 14 Mar 2018 14:08:31.295174 <190>1 2018-03-14T11:08:31.039388+00:00 app web.1 - - [1bb8d912-308a-4966-b436-a407ef34bac2] [U#6] measure#action_policy.check.hit=0ms 14 Mar 2018 14:08:31.295174 <190>1 2018-03-14T11:08:31.040101+00:00 app web.1 - - [1bb8d912-308a-4966-b436-a407ef34bac2] [U#6] measure#action_policy.check.hit=0ms 14 Mar 2018 14:08:31.295174 <190>1 2018-03-14T11:08:31.137807+00:00 app web.1 - - [1bb8d912-308a-4966-b436-a407ef34bac2] [U#6] measure#action_policy.check.hit=0ms 14 Mar 2018 14:08:31.295174 <190>1 2018-03-14T11:08:31.138677+00:00 app web.1 - - [1bb8d912-308a-4966-b436-a407ef34bac2] [U#6] measure#action_policy.check.hit=0ms 14 Mar 2018 14:08:31.295174 <190>1 2018-03-14T11:08:31.139952+00:00 app web.1 - - [1bb8d912-308a-4966-b436-a407ef34bac2] [U#6] measure#action_policy.check.hit=0ms 14 Mar 2018 14:08:31.295174 <190>1 2018-03-14T11:08:31.140577+00:00 app web.1 - - [1bb8d912-308a-4966-b436-a407ef34bac2] [U#6] measure#action_policy.check.hit=0ms 14 Mar 2018 14:08:31.295174 <190>1 2018-03-14T11:08:31.141658+00:00 app web.1 - - [1bb8d912-308a-4966-b436-a407ef34bac2] [U#6] measure#action_policy.check.hit=0ms 14 Mar 2018 14:08:31.295174 <190>1 2018-03-14T11:08:31.142342+00:00 app web.1 - - [1bb8d912-308a-4966-b436-a407ef34bac2] [U#6] measure#action_policy.check.hit=0ms 14 Mar 2018 14:08:31.295174 <190>1 2018-03-14T11:08:31.143228+00:00 app web.1 - - [1bb8d912-308a-4966-b436-a407ef34bac2] [U#6] measure#action_policy.check.hit=0ms 14 Mar 2018 14:08:31.295174 <190>1 2018-03-14T11:08:31.143837+00:00 app web.1 - - [1bb8d912-308a-4966-b436-a407ef34bac2] [U#6] measure#action_policy.check.hit=0ms 14 Mar 2018 14:08:31.295174 <190>1 2018-03-14T11:08:31.144657+00:00 app web.1 - - [1bb8d912-308a-4966-b436-a407ef34bac2] [U#6] measure#action_policy.check.hit=0ms 14 Mar 2018 14:08:31.295174 <190>1 2018-03-14T11:08:31.145339+00:00 app web.1 - - [1bb8d912-308a-4966-b436-a407ef34bac2] [U#6] measure#action_policy.check.hit=0ms 14 Mar 2018 14:08:31.295174 <190>1 2018-03-14T11:08:31.146206+00:00 app web.1 - - [1bb8d912-308a-4966-b436-a407ef34bac2] [U#6] measure#action_policy.check.hit=0ms 14 Mar 2018 14:08:31.295174 <190>1 2018-03-14T11:08:31.146852+00:00 app web.1 - - [1bb8d912-308a-4966-b436-a407ef34bac2] [U#6] measure#action_policy.check.hit=0ms 14 Mar 2018 14:08:31.295174 <190>1 2018-03-14T11:08:31.147671+00:00 app web.1 - - [1bb8d912-308a-4966-b436-a407ef34bac2] [U#6] measure#action_policy.check.hit=0ms 14 Mar 2018 14:08:31.295174 <190>1 2018-03-14T11:08:31.148303+00:00 app web.1 - - [1bb8d912-308a-4966-b436-a407ef34bac2] [U#6] measure#action_policy.check.hit=0ms 14 Mar 2018 14:08:31.295174 <190>1 2018-03-14T11:08:31.149119+00:00 app web.1 - - [1bb8d912-308a-4966-b436-a407ef34bac2] [U#6] measure#action_policy.check.hit=0ms 14 Mar 2018 14:08:31.295174 <190>1 2018-03-14T11:08:31.149849+00:00 app web.1 - - [1bb8d912-308a-4966-b436-a407ef34bac2] [U#6] measure#action_policy.check.hit=0ms 14 Mar 2018 14:08:31.295174 <190>1 2018-03-14T11:08:31.150702+00:00 app web.1 - - [1bb8d912-308a-4966-b436-a407ef34bac2] [U#6] measure#action_policy.check.hit=0ms 14 Mar 2018 14:08:31.295174 <190>1 2018-03-14T11:08:31.151359+00:00 app web.1 - - [1bb8d912-308a-4966-b436-a407ef34bac2] [U#6] measure#action_policy.check.hit=0ms 14 Mar 2018 14:08:31.295174 <190>1 2018-03-14T11:08:31.152254+00:00 app web.1 - - [1bb8d912-308a-4966-b436-a407ef34bac2] [U#6] measure#action_policy.check.hit=0ms 14 Mar 2018 14:08:31.295174 <190>1 2018-03-14T11:08:31.152879+00:00 app web.1 - - [1bb8d912-308a-4966-b436-a407ef34bac2] [U#6] measure#action_policy.check.hit=0ms 14 Mar 2018 14:08:31.295174 <190>1 2018-03-14T11:08:31.153761+00:00 app web.1 - - [1bb8d912-308a-4966-b436-a407ef34bac2] [U#6] measure#action_policy.check.hit=0ms 14 Mar 2018 14:08:31.295174 <190>1 2018-03-14T11:08:31.154353+00:00 app web.1 - - [1bb8d912-308a-4966-b436-a407ef34bac2] [U#6] measure#action_policy.check.hit=0ms 14 Mar 2018 14:08:31.370174 <190>1 2018-03-14T11:08:31.155193+00:00 app web.1 - - [1bb8d912-308a-4966-b436-a407ef34bac2] [U#6] measure#action_policy.check.hit=0ms 14 Mar 2018 14:08:31.370174 <190>1 2018-03-14T11:08:31.155826+00:00 app web.1 - - [1bb8d912-308a-4966-b436-a407ef34bac2] [U#6] measure#action_policy.check.hit=0ms 14 Mar 2018 14:08:31.370174 <190>1 2018-03-14T11:08:31.156625+00:00 app web.1 - - [1bb8d912-308a-4966-b436-a407ef34bac2] [U#6] measure#action_policy.check.hit=0ms 14 Mar 2018 14:08:31.370174 <190>1 2018-03-14T11:08:31.157286+00:00 app web.1 - - [1bb8d912-308a-4966-b436-a407ef34bac2] [U#6] measure#action_policy.check.hit=0ms 45 policy checks per request

Slide 64

Slide 64 text

No content

Slide 65

Slide 65 text

CACHE STORE # application.rb config.action_policy.cache_store = :redis_cache_store class StagePolicy < ApplicationPolicy cache :show? def show? full_access? || user.stage_permissions.where( stage_id: record.id ).exists? end def full_access? # complex SQL end end

Slide 66

Slide 66 text

ActiveSupport ::Notifications “action_policy.apply” ActiveSupport ::Notifications.subscribe("action_policy.apply") do |event, started, finished, _, data| measurement = " #{event}. #{(data[:cached] ? "hit" : "miss")}" timing = ((finished - started) * 1000).to_i $stdout.puts "measure ##{measurement}= #{timing}ms" end

Slide 67

Slide 67 text

No content

Slide 68

Slide 68 text

TESTABILITY How to test authorization?

Slide 69

Slide 69 text

100% COVERAGE

Slide 70

Slide 70 text

100% CODE COVERAGE

Slide 71

Slide 71 text

100% BUSINESS LOGIC COVERAGE

Slide 72

Slide 72 text

THE SURVEY Where do you test authorization logic? % of answers 0 17,5 35 52,5 70 requests/controllers tests unit tests (e.g. policy tests) system tests other * “Authorization in Rails” survey bit.ly/rails-auth

Slide 73

Slide 73 text

CASE: TESTING CONTROLLERS 0 15 30 45 60 % of tests Other Access

Slide 74

Slide 74 text

CASE: TESTING CONTROLLERS 0 1 2 3 4 5 time to run (minutes) Other Access

Slide 75

Slide 75 text

ACTION POLICY # in controller/request specs subject { patch :update, id: product.id } it "is authorized" do expect { subject } .to be_authorized_to(:manage?, product) .with(ProductPolicy) end

Slide 76

Slide 76 text

# PORO unit tests describe StagePolicy do let(:user) { build_stubbed :user } let(:record) { build_stubbed :stage } let(:policy) { described_class.new(record, user: user) } describe '#show?' do subject { policy.show? } it { is_expected.to be true} end end ACTION POLICY

Slide 77

Slide 77 text

MORE FEATURES

Slide 78

Slide 78 text

MORE FEATURES Scopes

Slide 79

Slide 79 text

SCOPES class CoursePolicy < ApplicationPolicy default_scope do if user.manager? scope.where(account: account) else scope.where(account: account).assigned(user) end end # Named scope scope :own do next target.none if user.listener? scope.where(account: account, user: user) end end

Slide 80

Slide 80 text

SCOPES class CoursesController < ApplicationController def index # default scope @courses = scoped(Course.all) # named scope @courses = scoped(Course.all, :own) end end

Slide 81

Slide 81 text

SCOPES

Slide 82

Slide 82 text

MORE FEATURES Scopes Namespaces

Slide 83

Slide 83 text

NAMESPACES module Admin class ProductsController < ApplicationController def show @product = Product.find(params[:id]) # Tries to use load Admin ::ProductPolicy first # and then fallbacks to ::ProductPolicy authorize! @product end end end

Slide 84

Slide 84 text

NAMESPACES class PittsburghersController < ApplicationController def authorization_namespace return ::Penguins if current_user.hockey_fan? return ::Pirates if current_user.baseball_fan? super end end

Slide 85

Slide 85 text

MORE FEATURES Scopes Namespaces i18n

Slide 86

Slide 86 text

I18N # in locales en: action_policy: course: take: "You are not allowed to view this course" # in controller rescue_from ActionPolicy ::Unauthorized do |ex| p ex.message # => "You are not allowed to view this course” end

Slide 87

Slide 87 text

MORE FEATURES Scopes Namespaces i18n Failure reasons

Slide 88

Slide 88 text

REASONS class ApplicantPolicy < ApplicationPolicy def show? allowed_to?(:view?) && allowed_to?(:show?, stage) end def view? user.has_permission?(:view_applicants) end end

Slide 89

Slide 89 text

REASONS # in controller rescue_from ActionPolicy ::Unauthorized do |exception| # either p exception.reasons.messages # => { stage: [:show] } # or p exception.reasons.messages # => { applicant: [:view] } end

Slide 90

Slide 90 text

REASONS & I18N # in locales en: action_policy: applicant: view: "You don’t have permission to view applicants" # in controller rescue_from ActionPolicy ::Unauthorized do |exception| p exception.reasons.full_messages # => ["You don’t have permission to view applicants"] end

Slide 91

Slide 91 text

MORE FEATURES authorize! everywhere

Slide 92

Slide 92 text

class PostUpdateAction include ActionPolicy ::Behaviour # provide authorization subject (performer) authorize :user attr_reader :user def initialize(user) @user = user end def call(post, params) authorize! post, to: :update? post.update!(params) end end authorize!

Slide 93

Slide 93 text

MORE FEATURES authorize! everywhere Authorization contexts

Slide 94

Slide 94 text

CONTEXTS class ApplicationPolicy < ActionPolicy ::Base authorize :account end class ApplicationController < ActionController ::Base authorize :account, through: :current_account end

Slide 95

Slide 95 text

github.com/palkan/action_policy http://actionpolicy.evilmartians.io ACTION POLICY

Slide 96

Slide 96 text

http://actionpolicy.evilmartians.io

Slide 97

Slide 97 text

THANK YOU! Vladimir Dementyev evilmartians.com @palkan @palkan_tula Brooklyn, NY