Upgrade to Pro — share decks privately, control downloads, hide ads and more …

[RailsConf 2018] Access Denied: the missing guide to authorization in Rails

[RailsConf 2018] Access Denied: the missing guide to authorization in Rails

Video: https://www.youtube.com/watch?v=NVwx0DARDis

http://actionpolicy.evilmartians.io
https://github.com/palkan/action_policy
https://twitter.com/palkan_tula

Rails brings us a lot of useful tools out-of-the-box, but there are missing parts too. For example, for such essential tasks as authorization we are on our own. Even if we choose a trending OSS solution, we still have to care about the way to keep our code maintainable, efficient, and, of course, bug-less.

Working on Rails projects, I've noticed some common patterns in designing access systems as well as useful code techniques I'd like to share with you in this talk.

Vladimir Dementyev

April 17, 2018
Tweet

More Decks by Vladimir Dementyev

Other Decks in Programming

Transcript

  1. ACCESS DENIED
    Vladimir
    Dementyev
    The missing guide to
    authorization in Rails

    View full-size slide

  2. @palkan
    @palkan_tula
    Vladimir Dementyev

    View full-size slide

  3. ! Moscow

    # New York

    Pittsburgh

    View full-size slide

  4. https://evilmartians.com

    View full-size slide

  5. https://evilmartians.com

    View full-size slide

  6. Brooklyn, NY
    https://evilmartians.com

    View full-size slide

  7. Part 1
    THEORY…

    View full-size slide

  8. 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

    View full-size slide

  9. Authentication
    AUTHORIZATION

    View full-size slide

  10. AUTHENTICATION
    Who are you?

    View full-size slide

  11. AUTHORIZATION
    Am I allowed to do that?

    View full-size slide

  12. 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

    View full-size slide

  13. 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

    View full-size slide

  14. AUTHENTICATION
    rodauth
    warden authlogic
    clearance
    devise
    doorkeeper
    sorcery

    View full-size slide

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

    View full-size slide

  16. Authentication
    System constraints
    AUTHORIZATION

    View full-size slide

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

    View full-size slide

  18. Authentication
    System constraints
    Data model constraints
    (validations)
    AUTHORIZATION

    View full-size slide

  19. LINES
    OF
    DEFENCE
    authentication
    authorization
    constraints
    validations

    View full-size slide

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

    View full-size slide

  21. FORMAL
    MODELS
    DAC
    MAC
    RBAC
    ABAC

    View full-size slide

  22. 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)

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  25. 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

    View full-size slide

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

    View full-size slide

  27. 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)

    View full-size slide

  28. … AND
    PRACTICE
    FOR ALL
    Part 2

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  31. TOOLS
    CanCan(-Can)
    Pundit

    View full-size slide

  32. 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

    View full-size slide

  33. 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

    View full-size slide

  34. 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

    View full-size slide

  35. 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

    View full-size slide

  36. 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

    View full-size slide

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

    View full-size slide

  38. FRAMEWORK
    OF A DREAM
    Part 3

    View full-size slide

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

    View full-size slide

  40. THE ANSWER?
    gem "action_policy"

    View full-size slide

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

    View full-size slide

  42. ACTION POLICY

    View full-size slide

  43. 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

    View full-size slide

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

    View full-size slide

  45. Let’s talk about
    BOILERplate

    View full-size slide

  46. 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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  52. 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

    View full-size slide

  53. 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

    View full-size slide

  54. PERFORMANCE
    © https://www.railsspeed.com

    View full-size slide

  55. PERFORMANCE
    How to measure?
    What to measure?
    How to fix?

    View full-size slide

  56. 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”

    View full-size slide

  57. 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

    View full-size slide

  58. 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

    View full-size slide

  59. 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

    View full-size slide

  60. 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

    View full-size slide

  61. TESTABILITY
    How to test authorization?

    View full-size slide

  62. 100%
    COVERAGE

    View full-size slide

  63. 100%
    CODE
    COVERAGE

    View full-size slide

  64. 100%
    BUSINESS LOGIC
    COVERAGE

    View full-size slide

  65. 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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  68. 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

    View full-size slide

  69. # 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

    View full-size slide

  70. MORE
    FEATURES

    View full-size slide

  71. MORE FEATURES
    Scopes

    View full-size slide

  72. 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

    View full-size slide

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

    View full-size slide

  74. MORE FEATURES
    Scopes
    Namespaces

    View full-size slide

  75. 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

    View full-size slide

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

    View full-size slide

  77. MORE FEATURES
    Scopes
    Namespaces
    i18n

    View full-size slide

  78. 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

    View full-size slide

  79. MORE FEATURES
    Scopes
    Namespaces
    i18n
    Failure reasons

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  82. 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

    View full-size slide

  83. MORE FEATURES
    authorize! everywhere

    View full-size slide

  84. 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!

    View full-size slide

  85. MORE FEATURES
    authorize! everywhere
    Authorization contexts

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  88. http://actionpolicy.evilmartians.io

    View full-size slide

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

    View full-size slide