Action Policy Documentation

repository·master·Indexed 23 days ago

https://github.com/palkan/action_policy

A composable, extensible, and performant authorization framework for Ruby and Rails applications. It provides tools for defining resource-specific policies, enforcing authorization in controllers via `authorize!`, and checking permissions in views with `allowed_to?`. Key features include rule aliasing with `alias_rule`, default rule configuration, and customizable authorization contexts.

Tokens
38.3K
Snippets
115
Records
179
Agent score
76%

What's inside Action Policy

  1. What is Action Policy?

    master

    Action Policy is a flexible authorization layer for Ruby applications. It focuses on answering the single question: How to verify access?

    Unlike some other authorization libraries, Action Policy does not force a specific model (like roles or permissions) on you. It provides the tools to implement your own authorization logic. It is designed to be Rails-free but offers seamless integration and specific extensions for Rails applications.

  2. Overview of Action Policy capabilities

    master

    Action Policy is an authorization framework for Ruby and Rails designed to manage access control in a structured and maintainable way. Key capabilities include:

    • Policy Classes: Defining authorization logic using rule methods.
    • Pre-checks and Aliases: Using pre-checks to keep policy logic DRY (Don't Repeat Yourself) and creating aliases for common rules.
    • Scoping: Restricting record visibility so users only see the data they are authorized to access.
    • Testing: Utilizing built-in test helpers to verify policies and authorization logic.
    • Failure Reasons: Providing specific feedback to users explaining why an action was denied.
  3. Explore the HelpDesk Demo App

    master

    The HelpDesk application is a support ticket management system used in this tutorial to demonstrate Action Policy. It features three distinct user roles:

    • Alice (Customer): Submits and tracks support tickets.
    • Bob (Agent): Handles and responds to tickets.
    • Charlie (Admin): Full access to manage everything.

    In its initial state, the application lacks access controls, meaning any user can perform any action (edit, delete, read internal comments, etc.). This serves as the baseline for implementing authorization with Action Policy.

  4. Explore advanced Action Policy features

    master

    Beyond the core tutorial, Action Policy provides several advanced features for production-grade applications:

    • Caching: Cache policy results to avoid redundant checks and improve performance.
    • Namespaces: Organize policies into namespaces (e.g., Admin::TicketPolicy) to manage complexity in large applications.
    • Instrumentation: Hook into the policy evaluation lifecycle for monitoring and observability.
    • GraphQL integration: Use Action Policy to enforce authorization within GraphQL APIs.
  5. Key features of Action Policy

    master

    Action Policy is designed to address common limitations in other authorization frameworks through the following features:

    • Performance: Includes multiple out-of-the-box caching strategies to minimize overhead (especially for rules involving database queries) and built-in instrumentation to monitor and detect bottlenecks.
    • Composition & Customization: Built using standard Ruby classes and modules with minimal magic. You can use only the features you need or extend functionality easily. Authorization can be added anywhere in your code, not just in controllers.
    • Code Organization: Supports namespaces to organize different authorization strategies and pre_checks to make business logic rules more readable.
    • Advanced Capabilities: Provides built-in support for testability, i18n integrations, and actionable errors.
  6. Inherit and chain scopes using `super`

    master

    Since scopes are transformed into class instance methods, they support standard Ruby inheritance. You can chain scopes from a parent policy by using the super keyword within the scope block.

    Important: You must explicitly pass the argument to super (e.g., super(relation)); using super with implicit arguments is not supported because scopes are defined using define_method internally.

    class ApplicationPolicy < ActionPolicy::Base
      scope_for :relation do |relation|
        relation.where(account_id: account.id)
      end
    end
    
    class PostPolicy < ApplicationPolicy
      scope_for :relation do |relation|
        super(relation).published
      end
    end
  7. Enable rule-level memoization

    master

    Rule-level memoization ensures that if the same rule method is called multiple times on the same policy instance, the result is returned immediately after the first execution.

    This is only available if your policy inherits from ActionPolicy::Base or includes ActionPolicy::Policy::CachedApply in your ApplicationPolicy.

  8. Understand the composition of ActionPolicy::Base

    master

    The ActionPolicy::Base class is composed of several modules that provide different capabilities. Understanding these helps you decide if you should inherit from Base or build a custom minimal policy.

    Included Modules

    • ActionPolicy::Policy::Core: Provides apply and allowed_to?.
    • ActionPolicy::Policy::Authorization: Core authorization logic.
    • ActionPolicy::Policy::PreCheck: Support for pre-checks.
    • ActionPolicy::Policy::Reasons: Support for authorization reasons.
    • ActionPolicy::Policy::Aliases: Support for rule aliasing.
    • ActionPolicy::Policy::Scoping: Support for record scoping.
    • ActionPolicy::Policy::Cache & ActionPolicy::Policy::CachedApply: Caching capabilities.
    • ActionPolicy::Policy::Defaults: Provides default configuration like authorize :user, default_rule, and rule aliases.

    Rails Extensions in Base

    If you inherit from Base in a Rails environment, the following are also included:

    • ActionPolicy::ScopeMatchers::ActiveRecord: Adds active_record_relation scope matcher.
    • ActionPolicy::ScopeMatchers::ActionControllerParams: Adds action_controller_params scope matcher.
    • ActionPolicy::Policy::Rails::Instrumentation: Adds Active Support notifications.
    class ActionPolicy::Base
      include ActionPolicy::Policy::Core
      include ActionPolicy::Policy::Authorization
      include ActionPolicy::Policy::PreCheck
      include ActionPolicy::Policy::Reasons
      include ActionPolicy::Policy::Aliases
      include ActionPolicy::Policy::Scoping
      include ActionPolicy::Policy::Cache
      include ActionPolicy::Policy::CachedApply
      include ActionPolicy::Policy::Defaults
    
      # Rails-specific scoping extensions
      extend ActionPolicy::ScopeMatchers::ActiveRecord
      scope_matcher :active_record_relation, ActiveRecord::Relation
    
      extend ActionPolicy::ScopeMatchers::ActionControllerParams
      scope_matcher :action_controller_params, ActionController::Parameters
    
      # Active Support notifications
      prepend ActionPolicy::Policy::Rails::Instrumentation
    end
  9. How ActionPolicy looks up translations

    master

    ActionPolicy uses the action_policy scope and looks for policy-specific translations within a policy sub-scope.

    When a rule named rule fails in a policy class klass, ActionPolicy follows this lookup algorithm to find a translation key:

    1. Specific Policy Rule: action_policy.policy.<underscored_class_without_policy_suffix>.<rule>
    2. Ancestor Policies: It repeats step 1 for each ancestor that responds to :identifier (up to ActionPolicy::Base).
    3. Generic Rule: action_policy.policy.<rule>
    4. Global Unauthorized Fallback: action_policy.unauthorized
    5. Gem Default: The hardcoded default message.

    Example Lookup Path: For a GuestUserPolicy (inheriting from DefaultUserPolicy) failing the feed? rule, the lookup order is:

    • action_policy.policy.guest_user.feed?
    • action_policy.policy.default_user.feed?
    • action_policy.policy.feed?
    • action_policy.unauthorized
  10. How policy namespacing works

    master

    Action Policy supports looking up policies based on the current execution namespace (the module/class where authorization is being called). This allows you to define specialized policies for specific contexts (like an Admin module) while falling back to a default policy if a namespaced one is not found.

    Lookup Logic

    When authorize! is called within a module, Action Policy performs a hierarchical search:

    1. It looks for a policy matching the current namespace (e.g., Admin::UserPolicy).
    2. If not found, it traverses up the module nesting (e.g., Admin::Client::UserPolicy -> Admin::UserPolicy -> UserPolicy).
    3. If no namespaced policy is found, it falls back to the base policy.

    Requirements

    Namespace support is an extension of ActionPolicy::Behaviour. It is included by default in Rails controllers and channel integrations via ActionPolicy::Behaviours::Namespaced.

    module Admin
      module Client
        class UsersController < ApplicationController
          def index
            # lookup for Admin::Client::UserPolicy -> Admin::UserPolicy -> UserPolicy
            authorize!
          end
        end
      end
    end
  11. How the lookup chain works in Action Policy

    master

    The lookup chain is an array of probes (lambdas) used to find the appropriate policy for a resource. The lookup process follows a simple sequential logic:

    1. Call the first probe in the array.
    2. If the probe returns a non-nil value, that value is used as the policy and the process stops.
    3. If the probe returns nil, the process moves to the next probe in the chain.

    If the entire chain is exhausted without a non-nil return, Action Policy typically raises an ActionPolicy::NotFound error.