--- url: /guide.md --- [![Gem Version](https://badge.fury.io/rb/action_policy.svg)](https://badge.fury.io/rb/action_policy) # Action Policy ## What is it? *Authorization* is an act of giving **someone** official permission to **do something** (to not be confused with [*authentication*](https://en.wikipedia.org/wiki/Authentication)). Action Policy provides flexible tools to build an *authorization layer* for your application. **NOTE:** Action Policy does not force you to use a specific authorization model (i.e., roles, permissions, etc.) and does not provide one. It only answers a single question: **How to verify access?** ## Where to go from here? * [Quick start](./quick_start.md) * [Using with Rails](./rails.md) * [Using with other Ruby frameworks](./non_rails.md) * [Using with GraphQL Ruby](./graphql.md) ## Project State The project is being used in production since mid 2018. Major features have been implemented, API has been stabilized. Check out our [development board](https://github.com/palkan/action_policy/projects/1) to see what's coming next. ## History Action Policy gem is an *extraction*-kind of a library. Most of the code has been used in production for several years in different [Evil Martians][] projects. We have decided to collect all our authorization techniques and pack them into a standalone gem–and that is how Action Policy was born! ## What about the existing solutions? Why did we decide to build our own authorization gem instead of using the existing solutions, such as [Pundit][] and [CanCanCan][]? **TL;DR they didn't solve all of our problems.** [Pundit][] has been our framework of choice for a long time. Being too *dead-simple*, it required a lot of hacking to fulfill business logic requirements. These *hacks* later became Action Policy (initially, we even called it "Pundit, re-visited"). We also took a few ideas from [CanCanCan][]—such as [default rules and rule aliases](./aliases.md). It is also worth noting that Action Policy (despite having a *Railsy* name) is designed to be **Rails-free**. On the other hand, it contains some Rails-specific extensions and seamlessly integrates into the framework. So, what are the main reasons to consider Action Policy as your authorization tool? * **Performance**: multiple [caching strategies](./caching.md) out-of-the-box make authorization overhead as small as possible–especially useful when your rules involve DB queries; you can also monitor the performance and detect the bottlenecks using the built-in [instrumentation](./instrumentation.md) features. * **Composition & Customization**: use [only the features you need](./custom_policy.md) or easily extend the functionality–it's just Ruby classes and modules, (almost) zero magic! And you can add authorization [anywhere in your code](./non_rails.md), not only in controllers. * **Code Organization**: use [namespaces](./namespaces.md) to organize your policies (for example, when you have multiple authorization strategies); add [pre-checks](./pre_checks.md) to make rules more readable and better express your business-logic. * **...and more**: [testability](./testing.md), [i18n](./i18n.md) integrations, [actionable errors](./reasons.md). Learn more about the motivation behind the Action Policy and its features by watching this [RailsConf talk](https://www.youtube.com/watch?v=NVwx0DARDis). ## Resources * RubyRussia, 2019 "Welcome, or access denied?" talk ([video](https://www.youtube.com/watch?v=y15a2g7v8i0) \[RU], [slides](https://speakerdeck.com/palkan/rubyrussia-2019-welcome-or-access-denied)) * [Exposing permissions in GraphQL APIs with Action Policy](https://evilmartians.com/chronicles/exposing-permissions-in-graphql-apis-with-action-policy) * Seattle.rb, 2019 "A Denial!" talk \[[slides](https://speakerdeck.com/palkan/seattle-dot-rb-2019-a-denial)] * RailsConf, 2018 "Access Denied" talk \[[video](https://www.youtube.com/watch?v=NVwx0DARDis), [slides](https://speakerdeck.com/palkan/railsconf-2018-access-denied-the-missing-guide-to-authorization-in-rails)] [CanCanCan]: https://github.com/CanCanCommunity/cancancan [Pundit]: https://github.com/varvet/pundit [Evil Martians]: https://evilmartians.com --- --- url: /guide/behaviour.md --- # Action Policy Behaviour Action Policy provides a mixin called `ActionPolicy::Behaviour` which adds authorization methods to your classes. ## Usage Let's make our custom *service* object aware of authorization: ```ruby class PostUpdateAction # First, we should include the behaviour include ActionPolicy::Behaviour # Secondly, provide authorization subject (performer) authorize :user attr_reader :user def initialize(user) @user = user end def call(post, params) # Now we can use authorization methods authorize! post, to: :update? post.update!(params) end end ``` `ActionPolicy::Behaviour` provides `authorize` class-level method to configure [authorization context](authorization_context.md) and the instance-level methods: `authorize!`, `allowed_to?`, `allowance_to`, and `authorized`: ### `authorize!` This is a *guard-method* which raises an `ActionPolicy::Unauthorized` exception if authorization failed (i.e. policy rule returns false): ```ruby # `to` is a name of the policy rule to apply authorize! post, to: :update? ``` ### `allowed_to?` This is a *predicate* version of `authorize!`: it returns true if authorization succeed and false otherwise: ```ruby # the first argument is the rule to apply # the second one is the target if allowed_to?(:edit?, post) # ... end ``` ### `allowance_to` This method is similar to `allowed_to?` but returns an authorization result instead. It's especially useful for APIs when you want to return not only true or false but also, for example, [failure reasons](./reasons.md): ```ruby result = allowance_to(:edit?, post) {value: result.value, fullMessages: result.reasons.full_messages, details: result.reasons.details}.to_json ``` ### `authorized` See [scoping](./scoping.md) docs. ## Policy lookup All three instance methods (`authorize!`, `allowed_to?`, `authorized`) uses the same `policy_for` to lookup a policy class for authorization target. So, you can provide additional options to control the policy lookup process: * Explicitly specify policy class using `with` option: ```ruby allowed_to?(:edit?, post, with: SpecialPostPolicy) ``` * Provide a [namespace](./namespaces.md): ```ruby # Would try to lookup Admin::PostPolicy first authorize! post, to: :destroy?, namespace: Admin ``` * Provide a [strict\_namespace lookup option](./lookup_chain.md): ```ruby # Would not fallback lookup PostPolicy if Admin::PostPolicy doesn't exist authorize! post, to: :destroy?, namespace: Admin, strict_namespace: true # or by overriding a specific behavior method def authorization_strict_namespace true end ``` * Define a default policy to use in case lookup finds nothing: ```ruby # either explicitly authorize! post, to: :destroy?, default: GuestPolicy # or by overriding a specific behavior method def default_authorization_policy_class logged_in? ? DefaultUserPolicy : GuestPolicy end ``` ## Implicit authorization target You can omit the authorization target for all the methods by defining an *implicit authorization target*: ```ruby class PostActions include ActionPolicy::Behaviour authorize :user attr_reader :user, :post def initialize(user, post) @user = user @post = post end def update(params) # post is used here implicitly as a target authorize! to: :update post.update!(params) end def destroy # post is used here implicitly as a target authorize! to: :destroy post.destroy! end def implicit_authorization_target post end end ``` --- --- url: /guide/authorization_context.md --- # Authorization Context *Authorization context* contains all contextual information required to apply a policy rule. In most cases, it only contains a *user*. However, if needed, Action Policy allows extending a policy's authorization context to include any additional information required to contextualize the authorization of a system or a particular resource. You must configure authorization context in **two places**: in the policy itself and in the place where you perform the authorization (e.g., controllers). By default, `ActionPolicy::Base` includes `user` as authorization context. If you don't need it, you have to [build your own base policy](custom_policy.md). To specify additional contexts, you should use the `authorize` method: ```ruby class ApplicationPolicy < ActionPolicy::Base authorize :account end ``` Now you must provide `account` during policy initialization. When authorization key is missing or equals to `nil`, `ActionPolicy::AuthorizationContextMissing` error is raised. If you want to allow passing `nil` as `account` value, you must add `allow_nil: true` option to `authorize`. If you want to be able not to pass `account` at all, you must add `optional: true`: ```ruby class GuestPolicy < ApplicationPolicy # With allow_nil: true, the `user` key is still required to be present # in the authorization context authorize :user, allow_nil: true end class ProjectPolicy < ApplicationPolicy # With optional: true, authorization context may not include the `team` key at all authorize :team, optional: true end GuestPolicy.new(user: nil) #=> OK GuestPolicy.new #=> raises ActionPolicy::AuthorizationContextMissing ProjectPolicy.new(user: user) #=> OK ``` To do that automatically in your `authorize!` and `allowed_to?` calls, you must also configure authorization context. For example, in your controller: ```ruby class ApplicationController < ActionController::Base # First argument should be the same as in the policy. # `through` specifies the method name to be called to # get the required context object # (equals to the context name itself by default, i.e. `account`) authorize :account, through: :current_account # `through` can also be passed a proc that will be executed against self: authorize :user, through: -> { @user || Current.user } end ``` **NOTE:** To un-register a context (e.g., if you want to remove `:user` from the Base policy class), you can manipulate the contexts map directly: `authorization_targets.delete(:user)`. ## Nested Policies vs Contexts See also: [action\_policy#36](https://github.com/palkan/action_policy/issues/36) and [action\_policy#37](https://github.com/palkan/action_policy/pull/37) When you call another policy from the policy object (e.g. via `allowed_to?` method), the context of the current policy is passed to the *nested* policy. That means that if the nested policy has a different authorization context, we won't be able to build it (even if you configure all the required keys in the controller). For example: ```ruby class UserPolicy < ActionPolicy::Base authorize :user def show? allowed_to?(:show?, record.profile) end end class ProfilePolicy < ActionPolicy::Base authorize :user, :account end class ApplicationController < ActionController::Base authorize :user, through: :current_user authorize :account, through: :current_account end class UsersController < ApplicationController def show user = User.find(params[:id]) authorize! user #=> raises "Missing policy authorization context: account" end end ``` That means that **all the policies that could be used together MUST share the same set of authorization contexts** (or at least the *parent* policies' contexts must be supersets of the nested policies' contexts). ## Explicit context You can override the *implicit* authorization context (generated with `authorize` method) in-place by passing the `context` option: ```ruby def show user = User.find(params[:id]) authorize! user, context: {account: user.account} end ``` **NOTE:** the explicitly provided context is merged with the implicit one (i.e. you can specify only the keys you want to override). --- --- url: /guide/caching.md --- # Caching Action Policy aims to be as performant as possible. One of the ways to accomplish that is to include a comprehensive caching system. There are several cache layers available: rule-level memoization, local (instance-level) memoization, and *external* cache (through cache stores). ## Policy memoization ### Per-instance There could be a situation when you need to apply the same policy to the same record multiple times during the action (e.g., request). For example: ```ruby # app/controllers/posts_controller.rb class PostsController < ApplicationController def show @post = Post.find(params[:id]) authorize! @post render :show end end ``` ```erb # app/views/posts/show.html.erb

<%= @post.title %> <% if allowed_to?(:edit?, @post) %> <%= link_to "Edit", @post %> <% end %> <% if allowed_to?(:destroy?, @post) %> <%= link_to "Delete", @post, method: :delete %> <% end %> ``` In the above example, we need to use the same policy three times. Action Policy re-uses the policy instance to avoid unnecessary object allocation. We rely on the following assumptions: * parent object (e.g., a controller instance) is *ephemeral*, i.e., it is a short-lived object * all authorizations use the same [authorization context](authorization_context.md). We use `record.policy_cache_key` with fallback to `record.cache_key` or `record.object_id` as a part of policy identifier in the local store. **NOTE**: policies memoization is an extension for `ActionPolicy::Behaviour` and could be included with `ActionPolicy::Behaviours::Memoized`. **NOTE**: memoization is automatically included into Rails controllers integration, but not included into channels integration, since channels are long-lived objects. ### Per-thread Consider a more complex situation: ```ruby # app/controllers/comments_controller.rb class CommentsController < ApplicationController def index # all comments for all posts @comments = Comment.all end end ``` ```erb # app/views/comments/index.html.erb <% @comments.each do |comment| %>
  • <%= comment.text %> <% if allowed_to?(:edit?, comment) %> <%= link_to comment, "Edit" %> <% end %>
  • <% end %> ``` ```ruby # app/policies/comment_policy.rb class CommentPolicy < ApplicationPolicy def edit? user.admin? || (user.id == record.id) || allowed_to?(:manage?, record.post) end end ``` In some cases, we have to initialize **two** policies for each comment: one for the comment itself and one for the comment's post (in the `allowed_to?` call). That is an example of a *N+1 authorization* problem, which in its turn could easily cause a *N+1 query* problem (if `PostPolicy#manage?` makes database queries). Sounds terrible, doesn't it? It is likely that many comments belong to the same post. If so, we can move our memoization one level up and use local thread store. Action Policy provides `ActionPolicy::Behaviours::ThreadMemoized` module with this functionality (included into Rails controllers integration by default). If you want to add this behavior to your custom authorization-aware class, you should care about cleaning up the thread store manually (by calling `ActionPolicy::PerThreadCache.clear_all`). **NOTE:** per-thread cache is disabled by default in test environment (when either `RACK_ENV` or `RAILS_ENV` environment variable is equal to "test"). You can turn it on (or off) by setting: ```ruby ActionPolicy::PerThreadCache.enabled = true # or false to disable ``` ## Rule cache ### Per-instance There could be a situation when the same rule is called multiple times for the same policy instance (for example, when using [aliases](aliases.md)). In that case, Action Policy invokes the rule method only once, remembers the result, and returns it immediately for the subsequent calls. **NOTE**: rule results memoization is available only if you inherit from `ActionPolicy::Base` or include `ActionPolicy::Policy::CachedApply` into your `ApplicationPolicy`. ### Using the cache store Some policy rules might be *performance-heavy*, e.g., make complex database queries. In that case, it makes sense to cache the rule application result for a long time (not just for the duration of a request). Action Policy provides a way to use *cache stores* for that. You have to explicitly define which rules you want to cache in your policy class. For example: ```ruby class StagePolicy < ApplicationPolicy # mark show? rule to be cached cache :show? # you can also provide store-specific options # cache :show?, expires_in: 1.hour def show? full_access? || user.stage_permissions.where( stage_id: record.id ).exists? end private def full_access? !record.funnel.is_private? || user.permissions .where( funnel_id: record.funnel_id, full_access: true ).exists? end end ``` You must configure a cache store to use this feature: ```ruby ActionPolicy.cache_store = MyCacheStore.new ``` Or, in Rails: ```ruby # config/application.rb (or config/environments/.rb) Rails.application.configure do |config| config.action_policy.cache_store = :redis_cache_store end ``` Cache store must provide at least a `#read(key)` and `#write(key, value, **options)` methods. **NOTE:** cache store also should take care of serialiation/deserialization since the `value` is `ExecutionResult` instance (which contains also some additional information, e.g. failure reasons). Rails cache store supports serialization/deserialization out-of-the-box. By default, Action Policy builds a cache key using the following scheme (defined in `#rule_cache_key(rule)` method): ```ruby "#{cache_namespace}/#{context_cache_key}" \ "/#{record.policy_cache_key}/#{policy.class.name}/#{rule}" ``` Where `cache_namespace` is equal to `"acp:#{MAJOR_GEM_VERSION}.#{MINOR_GEM_VERSION}"`, and `context_cache_key` is a concatenation of all authorization contexts cache keys (in the same order as they are defined in the policy class). If any object does not respond to `#policy_cache_key`, we fallback to `#cache_key` (or `#cache_key_with_version` for modern Rails versions). If `#cache_key` is not defined, an `ArgumentError` is raised. **NOTE:** if your `#cache_key` method is performance-heavy (e.g. like the `ActiveRecord::Relation`'s one), we recommend to explicitly define the `#policy_cache_key` method on the corresponding class to avoid unnecessary load. See also [action\_policy#55](https://github.com/palkan/action_policy/issues/55). You can define your own `rule_cache_key` / `cache_namespace` / `context_cache_key` methods for policy class to override this logic. You can also use the `#cache` instance method to cache arbitrary values in you policies: ```ruby class ApplicationPolicy < ActionPolicy::Base # Suppose that a user has many roles each having an array of permissions def permissions cache(user) { user.roles.pluck(:permissions).flatten.uniq } end # You can pass multiple cache key "parts" def account_permissions(account) cache(user, account) { user.account_roles.where(account: account).pluck(:permissions).flatten.uniq } end end ``` **NOTE:** `#cache` method uses the same cache key generation logic as rules caching (described above). #### Invalidation There no one-size-fits-all solution for invalidation. It highly depends on your business logic. **Case #1**: no invalidation required. First of all, you should try to avoid manual invalidation at all. That could be achieved by using elaborate cache keys. Let's consider an example. Suppose that your users have *roles* (i.e. `User.belongs_to :role`) and you give access to resources through the `Access` model (i.e. `Resource.has_many :accesses`). Then you can do the following: * Keep tracking the last `Access` added/updated/deleted for resource (e.g. `Access.belongs_to :accessessable, touch: :access_updated_at`) * Use the following cache keys: ```ruby class User def policy_cache_key "user::#{id}::#{role_id}" end end class Resource def policy_cache_key "#{resource.class.name}::#{id}::#{access_updated_at}" end end ``` **Case #2**: discarding all cache at once. That's pretty easy: just override `cache_namespace` method in your `ApplicationPolicy` with the new value: ```ruby class ApplicationPolicy < ActionPolicy::Base # It's a good idea to store the changing part in the constant CACHE_VERSION = "v2".freeze # or even from the env variable # CACHE_VERSION = ENV.fetch("POLICY_CACHE_VERSION", "v2").freeze def cache_namespace "action_policy::#{CACHE_VERSION}" end end ``` **Case #3**: discarding some keys. That is an alternative approach to *crafting* cache keys. If you have a limited number of places in your application where you update access control, you can invalidate policies cache manually. If your cache store supports `delete_matched` command (deleting keys using a wildcard), you can try the following: ```ruby class ApplicationPolicy < ActionPolicy::Base # Define custom cache key generator def cache_key(rule) "policy_cache/#{user.id}/#{self.class.name}/#{record.id}/#{rule}" end end class Access < ApplicationRecord belongs_to :resource belongs_to :user after_commit :cleanup_policy_cache, on: [:create, :destroy] def cleanup_policy_cache # Clear cache for the corresponding user-record pair ActionPolicy.cache_store.delete_matched( "policy_cache/#{user_id}/#{ResourcePolicy.name}/#{resource_id}/*" ) end end class User < ApplicationRecord belongs_to :role after_commit :cleanup_policy_cache, on: [:update], if: :role_id_changed? def cleanup_policy_cache # Clear all policies cache for user ActionPolicy.cache_store.delete_matched( "policy_cache/#{user_id}/*" ) end end ``` --- --- url: /guide/custom_policy.md --- # Custom Base Policy `ActionPolicy::Base` is a combination of all available policy extensions with the default configuration. It looks like this: ```ruby 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 # ActionPolicy::Policy::Defaults module adds the following authorize :user default_rule :manage? alias_rule :new?, to: :create? def index? false end def create? false end def manage? false end end ``` You can write your `ApplicationPolicy` from scratch instead of inheriting from `ActionPolicy::Base` if the defaults above do not fit your needs. The only required component is `ActionPolicy::Policy::Core`: ```ruby # minimal ApplicationPolicy class ApplicationPolicy include ActionPolicy::Policy::Core end ``` The `Core` module provides `apply` and `allowed_to?` methods. **NOTE:** When using with Rails, custom [scope matchers](./scoping.md) are also registered within the `Base` class. If you plan to use them, you need to copy setup code from the [corresponding files](https://github.com/palkan/action_policy/tree/master/lib/action_policy/rails/scope_matchers). --- --- url: /guide/custom_lookup_chain.md --- # Custom Lookup Chain Action Policy's lookup chain is just an array of *probes* (lambdas with a specific interface). The lookup process itself is pretty simple: * Call the first probe; * Return the result if it is not `nil`; * Go to the next probe. You can override the default chain with your own. For example: ```ruby ActionPolicy::LookupChain.chain = [ # Probe accepts record as the first argument # and arbitrary options (passed to `authorize!` / `allowed_to?` call) lambda do |record, **options| # your custom lookup logic end ] ``` ## NullPolicy example Let's consider a simple example of extending the existing lookup chain with one more probe. Suppose that we want to have a fallback policy (policy used when none found for the resource) instead of raising an `ActionPolicy::NotFound` error. Let's call this policy a `NullPolicy`: ```ruby class NullPolicy < ActionPolicy::Base default_rule :any? def any? false end end ``` Here we use the [default rule](aliases.md#default-rule) to handle any rule applied. Now we need to add a simple probe to the end of our lookup chain: ```ruby ActionPolicy::LookupChain.chain << ->(_, _) { NullPolicy } ``` That's it! --- --- url: /guide/decorators.md --- # Dealing with Decorators Ref: [action\_policy#7](https://github.com/palkan/action_policy/issues/7). Since Action Policy [lookup mechanism](./lookup_chain.md) relies on the target record's class properties (names, methods) it could break when using with *decorators*. To make `authorize!` and other [behaviour](./behaviour.md) methods work seamlessly with decorated objects, you might want to *enhance* the `policy_for` method. For example, when using the [Draper](https://github.com/drapergem/draper) gem: ```ruby module ActionPolicy module Draper def policy_for(record:, **opts) # From https://github.com/GoodMeasuresLLC/draper-cancancan/blob/master/lib/draper/cancancan.rb record = record.model while record.is_a?(::Draper::Decorator) super end end end class ApplicationController < ActionController::Base prepend ActionPolicy::Draper end ``` --- --- url: /guide/debugging.md --- # Debug Helpers **NOTE:** this functionality requires two additional gems to be available in the app: * [prism](https://github.com/ruby/prism) (comes with Ruby 3.3+) * [method\_source](https://github.com/banister/method_source). We usually describe policy rules using *boolean expressions* (e.g. `A or (B and C)` where each of `A`, `B` and `C` is a simple boolean expression or predicate method). When dealing with complex policies, it could be hard to figure out which predicate/check made policy to fail. The `Policy#pp(rule)` method aims to help debug such situations. Consider a (synthetic) example: ```ruby def feed? (admin? || allowed_to?(:access_feed?)) && (user.name == "Jack" || user.name == "Kate") end ``` Suppose that you want to debug this rule ("Why does it return false?"). You can drop a [`binding.pry`](https://github.com/deivid-rodriguez/pry-byebug) (or `binding.irb`) right at the beginning of the method: ```ruby def feed? binding.pry # rubocop:disable Lint/Debugger #... end ``` Now, run your code and trigger the breakpoint (i.e., run the method): ``` # now you can preview the execution of the rule using the `pp` method (defined on the policy instance) pry> pp :feed? MyPolicy#feed? ↳ ( admin? #=> false OR allowed_to?(:access_feed?) #=> true ) AND ( user.name == "Jack" #=> false OR user.name == "Kate" #=> true ) # you can also check other rules or methods as well pry> pp :admin? MyPolicy#admin? ↳ user.admin? #=> false ``` ## Compatibility with debugging tools Since we partially evaluate code within a policy rule, pretty printing could get stuck if you have debugging breakpoints. By default, we ignore `binding.pry` and `binding.irb` expressions (you will see a `` comment in the output). If you're using an alternative debugging technique, you can add a regexp to ignore the corresponding expression: ```ruby ActionPolicy::PrettyPrint.ignore_expressions << /\bdebug\b/ ``` --- --- url: /guide/reasons.md --- # Failure Reasons When you have complex policy rules, it could be helpful to have an ability to define an exact reason for why a specific authorization was rejected. It is especially helpful when you compose policies (i.e., use one policy within another) or want to expose permissions to client applications (see [GraphQL](./graphql.md)). Action Policy allows you to track failed `allowed_to?` checks in your rules. Consider an example: ```ruby class ApplicantPolicy < ApplicationPolicy def show? user.has_permission?(:view_applicants) && allowed_to?(:show?, record.stage) end end ``` When `ApplicantPolicy#show?` check fails, the exception has the `result` object, which in its turn contains additional information about the failure (`reasons`): ```ruby class ApplicationController < ActionController::Base rescue_from ActionPolicy::Unauthorized do |ex| p ex.result.reasons.to_h #=> { stage: [:show?] } # or with i18n support p ex.result.reasons.full_messages #=> ["You do not have access to the stage"] end end ``` The reason key is the corresponding policy [identifier](writing_policies.md#identifiers). You can also wrap *local* rules into `allowed_to?` to populate reasons: ```ruby class ApplicantPolicy < ApplicationPolicy def show? allowed_to?(:view_applicants?) && allowed_to?(:show?, record.stage) end def view_applicants? user.has_permission?(:view_applicants) end end # then the reasons object could be p ex.result.reasons.to_h #=> { applicant: [:view_applicants?] } # or p ex.result.reasons.to_h #=> { stage: [:show?] } ``` Reason could also be specified for `deny!` calls: ```ruby class TeamPolicy < ApplicationPolicy def show? deny!(:no_user) if user.anonymous? user.has_permission?(:view_teams) end end p ex.result.reasons.to_h #=> { applicant: [:no_user] } ``` In some cases it might be useful to propagate reasons from the nested policy calls instead of adding a top-level reason. You can achieve this by adding the `inline_reasons: true` option: ```ruby class ApplicantPolicy < ApplicationPolicy def show? allowed_to?(:show?, record.stage, inline_reasons: true) end end class StagePolicy < ApplicationPolicy def show? deny!(:archived) if record.archived? end end # When applying ApplicationPolicy and the stage is archived p ex.result.reasons.details #=> { stage: [:archived] } # Without inline_reasons we would get { stage: [:show?] } instead ``` See also [#186](https://github.com/palkan/action_policy/issues/186) for discussion. ## Detailed Reasons You can provide additional details to your failure reasons by using a `details: { ... }` option: ```ruby class ApplicantPolicy < ApplicationPolicy def show? allowed_to?(:show?, record.stage) end end class StagePolicy < ApplicationPolicy def show? # Add stage title to the failure reason (if any) # (could be used by client to show more descriptive message) details[:title] = record.title # then perform the checks user.stages.where(id: record.id).exists? end end # when accessing the reasons p ex.result.reasons.to_h #=> { stage: [{show?: {title: "Onboarding"}] } ``` **NOTE**: when using detailed reasons, the `details` array contains as the last element a hash with ALL details reasons for the policy (in a form of ` =>
    `). The additional details are especially helpful when combined with localization, 'cause you can you them as interpolation data source for your translations. For example, for the above policy: ```yml en: action_policy: policy: stage: show?: "The %{title} stage is not accessible" ``` And then when you call `full_messages`: ```ruby p ex.result.reasons.full_messages #=> The Onboarding stage is not accessible ``` ### `result.all_details` Sometimes details could be useful not only to provide more context to the user, but to handle failures differently. In this cases, digging through the `ex.result.reasons.details` could be cumbersome (see [this PR](https://github.com/palkan/action_policy/pull/130) for discussion). We provide a helper method, `result.all_details`, which could be used to get all details merged into a single Hash: ```ruby class PostPolicy < ApplicationPolicy def edit? check?(:published?) end def published? details[:not_found] = true record.published? end end ``` ```ruby p ex.result.all_details #=> {not_found: true} ``` ```ruby rescue_from ActionPolicy::Unauthorized do |ex| if ex.result.all_details[:not_found] head :not_found else head :unauthorized end end ``` *** **P.S. What is the point of failure reasons?** Failure reasons helps you to write *actionable* error messages, i.e. to provide a user with helpful feedback. For example, in the above scenario, when the reason is `ApplicantPolicy#view_applicants?`, you could show the following message: ``` You don't have enough permissions to view applicants. Please, ask your manager to update your role. ``` And when the reason is `StagePolicy#show?`: ``` You don't have access to the stage XYZ. Please, ask your manager to grant access to this stage. ``` Much more useful than just showing "You are not authorized to perform this action," isn't it? --- --- url: /guide/graphql.md --- # GraphQL integration You can use Action Policy as an authorization library for your [GraphQL Ruby](https://graphql-ruby.org/) application via the [`action_policy-graphql` gem](https://github.com/palkan/action_policy-graphql). This integration provides the following features: * Fields & mutations authorization * List and connections scoping * [**Exposing permissions/authorization rules in the API**](https://evilmartians.com/chronicles/exposing-permissions-in-graphql-apis-with-action-policy). ## Getting Started First, add the `action_policy-graphql` gem to your Gemfile (see [installation instructions](https://github.com/palkan/action_policy-graphql#installation)). Then, include `ActionPolicy::GraphQL::Behaviour` to your base type (or any other type/mutation where you want to use authorization features): ```ruby # For fields authorization, lists scoping and rules exposing class Types::BaseObject < GraphQL::Schema::Object include ActionPolicy::GraphQL::Behaviour end # For using authorization helpers in mutations class Types::BaseMutation < GraphQL::Schema::Mutation include ActionPolicy::GraphQL::Behaviour end # For using authorization helpers in resolvers class Types::BaseResolver < GraphQL::Schema::Resolver include ActionPolicy::GraphQL::Behaviour end ``` ## Authorization Context By default, Action Policy uses `context[:current_user]` as the `user` [authorization context](./authorization_context.md). **NOTE:** see below for more information on what's included into `ActionPolicy::GraphQL::Behaviour`. ## Authorizing Fields You can add `authorize: true` option to any field (=underlying object) to protect the access (it's equal to calling `authorize! object, to: :show?`): ```ruby # authorization could be useful for find-like methods, # where the object is resolved from the provided params (e.g., ID) field :home, Home, null: false, authorize: true do argument :id, ID, required: true end def home(id:) Home.find(id) end # Without `authorize: true` the code would look like this def home(id:) Home.find(id).tap { |home| authorize! home, to: :show? } end ``` You can use authorization options to customize the behaviour, e.g. `authorize: {to: :preview?, with: CustomPolicy}`. By default, if a user is not authorized to access the field, an `ActionPolicy::Unauthorized` exception is raised. If you want to return a `nil` instead, you should add `raise: false` to the options: ```ruby # NOTE: don't forget to mark your field as nullable field :home, Home, null: true, authorize: {raise: false} ``` You can make non-raising behaviour a default by setting a configuration option: ```ruby ActionPolicy::GraphQL.authorize_raise_exception = false ``` You can also change the default `show?` rule globally: ```ruby ActionPolicy::GraphQL.default_authorize_rule = :show_graphql_field? ``` If you want to perform authorization before resolving the field value, you can use `preauthorize: *` option: ```ruby field :homes, [Home], null: false, preauthorize: {with: HomePolicy} def homes Home.all end ``` The code above is equal to: ```ruby field :homes, [Home], null: false def homes authorize! "homes", to: :index?, with: HomePolicy Home.all end ``` You can specify the default *raising* behaviour for `preauthorize:` by setting a configuration option: ```ruby # By default, it fallbacks to .authorize_raise_exception ActionPolicy::GraphQL.preauthorize_raise_exception = false ``` **NOTE:** we pass the field's name as the `record` to the policy rule. We assume that pre-authorization rules do not depend on the record itself and pass the field's name for debugging purposes only. You can customize the authorization options, e.g. `authorize: {to: :preview?, with: CustomPolicy}`. **NOTE:** unlike `authorize: *` you MUST specify the `with: SomePolicy` option. The default authorization rule depends on the type of the field: * for lists we use `index?` (configured by `ActionPolicy::GraphQL.default_preauthorize_list_rule` parameter) * for *singleton* fields we use `show?` (configured by `ActionPolicy::GraphQL.default_preauthorize_node_rule` parameter) ### Class-level authorization You can use Action Policy in the class-level [authorization hooks](https://graphql-ruby.org/authorization/authorization.html) (`self.authorized?`) like this: ```ruby class Types::Friendship < Types::BaseObject def self.authorized?(object, context) super && allowed_to?( :show?, object, # NOTE: you must provide context explicitly context: {user: context[:current_user]} ) end end ``` ## Authorizing Mutations A mutation is just a Ruby class with a single API method. There is nothing specific in authorizing mutations: from the Action Policy point of view, they are just [*behaviours*](./behaviour.md). If you want to authorize the mutation, you call `authorize!` method. For example: ```ruby class Mutations::DestroyUser < Types::BaseMutation argument :id, ID, required: true def resolve(id:) user = User.find(id) # Raise an exception if the user has not enough permissions authorize! user, to: :destroy? # Or check without raising and do what you want # # if allowed_to?(:destroy?, user) user.destroy! {deleted_id: user.id} end end ``` Check out this issue on how you can implement a `verify_authorized` callback for your mutations: [#28](https://github.com/palkan/action_policy-graphql/issues/28). ### Using `preauthorize: *` with mutations Since mutation is also a GraphQL field, we can also use our custom `authorize: *` and `preauthorize: *` options. However, using `authorize: *` for mutations is deprecated and will raise an error in the future versions: it doesn't make any sense because it's called after the field has been resolved (i.e., mutation has been executed). It is possible to override the default *raising* behaviour for mutation only via the following configuration option: ```ruby # By default, it fallbacks to .preauthorize_raise_exception ActionPolicy::GraphQL.preauthorize_mutation_raise_exception = true ``` ## Handling exceptions The query would fail with `ActionPolicy::Unauthorized` exception when using `authorize: true` (in raising mode) or calling `authorize!` explicitly. That could be useful to handle this exception and send a more detailed error message to the client, for example: Please make sure you have added error handling to your schema with `use GraphQL::Execution::Errors`. ```ruby # in your schema file rescue_from(ActionPolicy::Unauthorized) do |exp| raise GraphQL::ExecutionError.new( # use result.message (backed by i18n) as an error message exp.result.message, # use GraphQL error extensions to provide more context extensions: { code: :unauthorized, fullMessages: exp.result.reasons.full_messages, details: exp.result.reasons.details } ) end ``` ## Scoping Data You can add `authorized_scope: true` option to a field (list or [*connection*](https://graphql-ruby.org/pagination/connection_concepts.html)) to apply the corresponding policy rules to the data: ```ruby class CityType < ::Common::Graphql::Type # It would automatically apply the relation scope from the EventPolicy to # the relation (city.events) field :events, EventType.connection_type, null: false, authorized_scope: true # you can specify the policy explicitly field :events, EventType.connection_type, null: false, authorized_scope: {with: CustomEventPolicy} # without the option you would write the following code def events authorized_scope object.events # or if `with` option specified authorized_scope object.events, with: CustomEventPolicy end end ``` **NOTE:** you cannot use `authorize: *` and `authorized_scope: *` at the same time but you can combine `preauthorize: *` with `authorized_scope: *`. See the documentation on [scoping](./scoping.md). ## Exposing Authorization Rules With `action_policy-graphql` gem, you can easily expose your authorization logic to the client in a standardized way. For example, if you want to "tell" the client which actions could be performed against the object you can use the `expose_authorization_rules` macro to add authorization-related fields to your type: ```ruby class ProfileType < Types::BaseType # Adds can_edit, can_destroy fields with # AuthorizationResult type. # NOTE: prefix "can_" is used by default, no need to specify it explicitly expose_authorization_rules :edit?, :destroy?, prefix: "can_" end ``` **NOTE:** you can use [aliases](./aliases.md) here as well as defined rules. **NOTE:** This feature relies the [*failure reasons*](./reasons.md) and the [i18n integration](./i18n.md) extensions. If your policies don't include any of these, you won't be able to use it. Then the client could perform the following query: ```gql { post(id: $id) { canEdit { # (bool) true|false; not null value # top-level decline message ("Not authorized" by default); null if value is true message # detailed information about the decline reasons; null if value is true or you don't have "failure reasons" extension enabled reasons { details # JSON-encoded hash of the form { "event" => [:privacy_off?] } fullMessages # Array of human-readable reasons } } canDestroy { # ... } } } ``` You can override a custom authorization field prefix (`can_`): ```ruby ActionPolicy::GraphQL.default_authorization_field_prefix = "allowed_to_" ``` You can specify a custom field name as well (only for a single rule): ```ruby class ProfileType < ::Common::Graphql::Type # Adds can_create_post field. expose_authorization_rules :create?, with: PostPolicy, field_name: "can_create_post" end ``` ## Custom Behaviour Including the default `ActionPolicy::GraphQL::Behaviour` is equal to adding the following to your base class: ```ruby class Types::BaseObject < GraphQL::Schema::Object # include Action Policy behaviour and its extensions include ActionPolicy::Behaviour include ActionPolicy::Behaviours::ThreadMemoized include ActionPolicy::Behaviours::Memoized include ActionPolicy::Behaviours::Namespaced # define authorization context authorize :user, through: :current_user # add a method helper to get the current_user from the context def current_user context[:current_user] end # extend the field class to add `authorize` and `authorized_scope` options field_class.prepend(ActionPolicy::GraphQL::AuthorizedField) # add `expose_authorization_rules` macro include ActionPolicy::GraphQL::Fields end ``` Feel free to create your own behaviour by adding only the functionality you need. --- --- url: /guide/i18n.md --- # I18n Support `ActionPolicy` integrates with [`i18n`][] to support localizable `full_messages` for [reasons](./reasons.md) and the execution result's `message`: ```ruby class ApplicationController < ActionController::Base rescue_from ActionPolicy::Unauthorized do |ex| p ex.result.message #=> "You do not have access to the stage" p ex.result.reasons.full_messages #=> ["You do not have access to the stage"] end end ``` The message contains a string for the *rule* that was called, while `full_messages` contains the list of reasons, why `ActionPolicy::Unauthorized` has been raised. You can find more information about tracking failure reasons [here](./reasons.md). ## Configuration `ActionPolicy` doesn't provide any localization out-of-the-box and uses "You are not authorized to perform this action" as the default message. You can add your app-level default fallback by providing the `action_policy.unauthorized` key value. When using **Rails**, all you need is to add translations to any file under the `config/locales` folder (or create a new file, e.g. `config/locales/policies.yml`). Non-Rails projects should configure [`i18n`][] gem manually: ```ruby I18n.load_path << Dir[File.expand_path("config/locales") + "/*.yml"] ``` ## Translations lookup `ActionPolicy` uses the `action_policy` scope. Specific policies translations must be stored inside the `policy` sub-scope. The following algorithm is used to find out the translation for a policy with a class `klass` and rule `rule`: 1. Translation for `"#{klass.identifier}.#{rule}"` key, when `self.identifier =` is not specified then underscored class name without the *Policy* suffix would be used (e.g. `GuestUserPolicy` turns into `guest_user:` scope) 2. Repeat step 1 for each ancestor which looks like a policy (`.respond_to?(:identifier)?`) up to `ActionPolicy::Base` 3. Use `#{rule}` key 4. Use `en.action_policy.unauthorized` key 5. Use a default message provided by the gem For example, given a `GuestUserPolicy` class which is inherited from `DefaultUserPolicy` and a rule `feed?`, the following list of possible translation keys would be used: `[:"action_policy.policy.guest_user.feed?", :"action_policy.policy.default_user.feed?", :"action_policy.policy.feed?", :"action_policy.unauthorized"]` [`i18n`]: https://github.com/svenfuchs/i18n --- --- url: /guide/instrumentation.md --- # Instrumentation Action Policy integrates with [Rails instrumentation system](https://guides.rubyonrails.org/active_support_instrumentation.html), `ActiveSupport::Notifications`. ## Events ### `action_policy.apply_rule` This event is triggered every time a policy rule is applied: * when `authorize!` is called * when `allowed_to?` is called within the policy or the [behaviour](behaviour.md) * when `apply_rule` is called explicitly (i.e. `SomePolicy.new(record, context).apply_rule(record)`). The event contains the following information: * `:policy` – policy class name * `:rule` – applied rule (String) * `:result` — the authorization result object. The following fields are deprecated and will be removed in v1.0: * `:value` – the result of the rule application (true of false) (use `event[:result].value` instead) * `:cached` – whether we hit the [cache](caching.md)\* (use `even[:result].cached?` instead). \* This parameter tracks only the cache store usage, not memoization. You can use this event to track your policy cache usage and also detect *slow* checks. Here is an example code for sending policy stats to [Librato](https://librato.com/) using [`librato-rack`](https://github.com/librato/librato-rack): ```ruby ActiveSupport::Notifications.subscribe("action_policy.apply_rule") do |event, started, finished, _, data| # Track hit and miss events separately (to display two measurements) measurement = "#{event}.#{data[:cached] ? "hit" : "miss"}" # show ms times timing = ((finished - started) * 1000).to_i Librato.tracker.check_worker Librato.timing measurement, timing, percentile: [95, 99] end ``` ### `action_policy.authorize` This event is identical to `action_policy.apply_rule` with the one difference: **it's only triggered when `authorize!` method is called**. The motivation behind having a separate event for this method is to monitor the number of failed authorizations: the high number of failed authorizations usually means that we do not take into account authorization rules in the application UI (e.g., we show a "Delete" button to the user not permitted to do that). The `action_policy.apply_rule` might have a large number of failures, 'cause it also tracks the usage of non-raising applications (i.e. `allowed_to?`). ### `action_policy.init` This event is triggered every time a new policy object is initialized. The event contains the following information: * `:policy` – policy class name. This event is useful if you want to track the number of initialized policies per *action* (for example, when you want to ensure that the [memoization](caching.md) works as expected). ## Turn off instrumentation Instrumentation is enabled by default. To turn it off add to your configuration: ```ruby config.action_policy.instrumentation_enabled = false ``` **NOTE:** changing this setting after the application has been initialized doesn't take any effect. ## Non-Rails usage If you don't use Rails itself but have `ActiveSupport::Notifications` available in your application, you can use the instrumentation feature with some additional configuration: ```ruby # Enable `apply_rule` event by extending the base policy class require "action_policy/rails/policy/instrumentation" ActionPolicy::Base.include ActionPolicy::Policy::Rails::Instrumentation # Enabled `authorize` event by extending the authorizer class require "action_policy/rails/authorizer" ActionPolicy::Authorizer.singleton_class.prepend ActionPolicy::Rails::Authorizer ``` --- --- url: /guide/pundit_migration.md --- # Migrate from Pundit to Action Policy Migration from Pundit to Action Policy could be done in a progressive way: first, we make Pundit polices and authorization helpers use Action Policy under the hood, then you can rewrite policies in the Action Policy way. ## Phase 1. Quacking like a Pundit ### Step 1. Prepare controllers * Remove `include Pundit` from ApplicationController * Add `authorize` method: ```ruby def authorize(record, rule = nil) options = {} options[:to] = rule unless rule.nil? authorize! record, **options end ``` * Configure [authorization context](authorization_context.md) if necessary, e.g. add `authorize :current_user, as: :user` to `ApplicationController` (**NOTE:** added automatically in Rails apps) * Add `policy` and `policy_scope` helpers: ```ruby helper_method :policy helper_method :policy_scope def policy(record) policy_for(record: record) end def policy_scope(scope) authorized scope end ``` **NOTE**: `policy` defined above is not equal to `allowed_to?` since it doesn't take into account pre-checks. ### Step 2. Prepare policies We assume that you have a base class for all your policies, e.g. `ApplicationPolicy`. Then do the following: * Add `include ActionPolicy::Policy::Core` to `ApplicationPolicy` * Update `ApplicationPolicy#initialize`: ```ruby def initialize(target, user:) # ... end ``` * [Rewrite scopes](scoping.md). Unfortunately, there is no easy way to migrate Pundit class-based scope to Action Policies scopes. ### Step 3. Replace RSpec helper We provide a Pundit-compatible syntax for RSpec tests: ``` # Remove DSL # require "pundit/rspec" # # Add Action Policy Pundit DSL require "action_policy/rspec/pundit_syntax" ``` ## Phase 2. No more Pundit When everything is green, it's time to fully migrate to ActionPolicy: * make ApplicationPolicy inherit from `ActionPolicy::Base` * migrate view helpers (from `policy(..)` to `allowed_to?`, from `policy_scope` to `authorized`) * re-write specs using simple non-DSL syntax (or [Action Policy RSpec syntax](testing.md#rspec-dsl)) * add [authorization tests](testing.md#testing-authorization) (add `require 'action_policy/rspec'`) * use [Reasons](reasons.md), [I18n integration](i18n.md), [cache](caching.md) and other Action Policy features! --- --- url: /guide/namespaces.md --- # Namespaces Action Policy can lookup policies with respect to the current execution *namespace* (i.e., authorization class module). Consider an example: ```ruby module Admin class UsersController < ApplicationController def index # uses Admin::UserPolicy if any, otherwise fallbacks to UserPolicy authorize! end end end ``` Module nesting is also supported: ```ruby module Admin module Client class UsersController < ApplicationController def index # lookup for Admin::Client::UserPolicy -> Admin::UserPolicy -> UserPolicy authorize! end end end end ``` **NOTE**: to support namespaced lookup for non-inferrable resources, you should specify `policy_name` at a class level (instead of `policy_class`, which doesn't take namespaces into account): ```ruby class Guest < User def self.policy_name "UserPolicy" end end ``` **NOTE**: by default, we use class's name as a policy name; so, for namespaced resources, the namespace part is also included: ```ruby class Admin class User end end # search for Admin::UserPolicy, but not for UserPolicy authorize! Admin::User.new ``` You can access the current authorization namespace through `authorization_namespace` method. You can also define your own namespacing logic by overriding `authorization_namespace`: ```ruby def authorization_namespace return ::Admin if current_user.admin? return ::Staff if current_user.staff? # fallback to current namespace super end ``` **NOTE**: namespace support is an extension for `ActionPolicy::Behaviour` and could be included with `ActionPolicy::Behaviours::Namespaced` (included into Rails controllers and channel integrations by default). ## Namespace resolution cache We cache namespaced policy resolution for better performance (it could affect performance when we look up a policy from a deeply nested module context, see the [benchmark](https://github.com/palkan/action_policy/blob/master/benchmarks/namespaced_lookup_cache.rb)). It could be disabled by setting `ActionPolicy::LookupChain.namespace_cache_enabled = false`. It's enabled by default unless `RACK_ENV` env var is specified and is not equal to `"production"` (e.g. when `RACK_ENV=test` the cache is disabled). When using Rails it's enabled only in production mode but could be configured through setting the `config.action_policy.namespace_cache_enabled` parameter. --- --- url: /guide/lookup_chain.md --- # Policy Lookup Action Policy tries to automatically infer policy class from the target using the following *probes*: 1. If the target is a `Symbol`: a) Try `"#{target.to_s.camelize}Policy"` as a `policy_name` (see below); b) If `String#classify` is available, e.g. when using Rails' ActiveSupport, try `"#{target.to_s.classify}Policy"`; 2. If the target responds to `policy_class`, then use it; 3. If the target's class responds to `policy_class`, then use it; 4. If the target or the target's class responds to `policy_name`, then use it (the `policy_name` should end with `Policy` as it's not appended automatically); 5. Otherwise, use `#{target.class.name}Policy` or `#{target.name}Policy` if target is a Module or Class. > \* [Namespaces](namespaces.md) could be also be considered when `namespace` option is set. You can also set the `strict_namespace` option to disable constant cascade lookup. You can call `ActionPolicy.lookup(record, options)` to infer policy class for the record. When no policy class is found, an `ActionPolicy::NotFound` error is raised. You can [customize lookup](custom_lookup_chain.md) logic if necessary. --- --- url: /guide/pre_checks.md --- # Pre-Checks Consider a typical situation when you start most—or even all—of your rules with the same predicates. For example, when you have a super-user role in the application: ```ruby class PostPolicy < ApplicationPolicy def show? user.super_admin? || record.published end def update? user.super_admin? || (user.id == record.user_id) end # more rules end ``` Action Policy allows you to extract the common parts from rules into *pre-checks*: ```ruby class PostPolicy < ApplicationPolicy pre_check :allow_admins def show? record.published end def update? user.id == record.user_id end private def allow_admins allow! if user.super_admin? end end ``` Pre-checks act like *callbacks*: you can add multiple pre-checks, specify `except` and `only` options, and skip already defined pre-checks if necessary: ```ruby class UserPolicy < ApplicationPolicy skip_pre_check :allow_admins, only: :destroy? def destroy? user.admin? && !record.admin? end end ``` To halt the authorization process within a pre-check, you must return either `allow!` or `deny!` call value. When any other value is returned, the pre-check is ignored, and the rule is called (or next pre-check). **NOTE**: pre-checks are available only if you inherit from `ActionPolicy::Base` or include `ActionPolicy::Policy::PreCheck` into your `ApplicationPolicy`. --- --- url: /guide/quick_start.md --- # Quick Start ## Installation Install Action Policy with RubyGems: ```ruby gem install action_policy ``` Or add `action_policy` to your application's `Gemfile`: ```ruby gem "action_policy" ``` And then execute: ```sh bundle install ``` ## Basic usage The core component of Action Policy is a *policy class*. Policy class describes how you control access to resources. We suggest having a separate policy class for each resource and encourage you to follow these conventions: * put policies into the `app/policies` folder (when using with Rails); * name policies using the corresponding singular resource name (model name) with a `Policy` suffix, e.g. `Post -> PostPolicy`; * name rules using a predicate form of the corresponding activity (typically, a controller's action), e.g. `PostsController#update -> PostPolicy#update?`. We also recommend to use an application-specific `ApplicationPolicy` with a global configuration to inherit from: ```ruby class ApplicationPolicy < ActionPolicy::Base end ``` You could use the following command to generate it when using Rails: ```sh rails generate action_policy:install ``` **NOTE:** it is not necessary to inherit from `ActionPolicy::Base`; instead, you can [construct basic policy](custom_policy.md) choosing only the components you need. Rules must be public methods on the class. Using private methods as rules will raise an error. Consider a simple example: ```ruby class PostPolicy < ApplicationPolicy # everyone can see any post def show? true end def update? # `user` is a performing subject, # `record` is a target object (post we want to update) user.admin? || (user.id == record.user_id) end end ``` Now you can easily add authorization to your Rails\* controller: ```ruby class PostsController < ApplicationController def update @post = Post.find(params[:id]) authorize! @post if @post.update(post_params) redirect_to @post else render :edit end end end ``` \* See [Non-Rails Usage](non_rails.md) on how to add `authorize!` to any Ruby project. In the above case, Action Policy automatically infers a policy class and a rule to verify access: `@post -> Post -> PostPolicy`, rule is inferred from the action name (`update -> update?`), and `current_user` is used as `user` within the policy by default (read more about [authorization context](authorization_context.md)). When authorization is successful (i.e., the corresponding rule returns `true`), nothing happens, but in case of an authorization failure `ActionPolicy::Unauthorized` error is raised: ```ruby rescue_from ActionPolicy::Unauthorized do |ex| # Exception object contains the following information ex.policy #=> policy class, e.g. UserPolicy ex.rule #=> applied rule, e.g. :show? end ``` There is also an `allowed_to?` method which returns `true` or `false` and could be used, for example, in views: ```erb <% @posts.each do |post| %>
  • <%= post.title %> <% if allowed_to?(:edit?, post) %> <%= link_to "Edit", post %> <% end %>
  • <% end %> ``` Although Action Policy tries to [infer the corresponding policy class](lookup_chain.md) and rule itself, there could be a situation when you want to specify those values explicitly: ```ruby # specify the rule to verify access authorize! @post, to: :update? # specify policy class authorize! @post, with: CustomPostPolicy # or allowed_to? :edit?, @post, with: CustomPostPolicy ``` --- --- url: /guide/aliases.md --- # Rule Aliases Action Policy allows you to add rule aliases. It is useful when you rely on *implicit* rules in controllers. For example: ```ruby class PostsController < ApplicationController before_action :load_post, only: [:edit, :update, :destroy] private def load_post @post = Post.find(params[:id]) # depending on action, an `edit?`, `update?` or `destroy?` # rule would be applied authorize! @post end end ``` In your policy, you can create aliases to avoid duplication: ```ruby class PostPolicy < ApplicationPolicy alias_rule :edit?, :destroy?, to: :update? end ``` **NOTE**: `alias_rule` is available only if you inherit from `ActionPolicy::Base` or include `ActionPolicy::Policy::Aliases` into your `ApplicationPolicy`. **Why not just use Ruby's `alias`?** An alias created with `alias_rule` is resolved at *authorization time* (during an `authorize!` or `allowed_to?` call), and it does not add an alias method to the class. That allows us to write tests easier, as we should only test the rule, not the alias–and to leverage [caching](caching.md) better. By default, `ActionPolicy::Base` adds one alias: `alias_rule :new?, to: :create?`. ## Default rule You can add a *default* rule–the rule that would be applied if the rule specified during authorization is missing (like a "wildcard" alias): ```ruby class PostPolicy < ApplicationPolicy # For an ApplicationPolicy, makes :manage? match anything that is # not :index?, :create? or :new? default_rule :manage? # If you want manage? to catch really everything, place this alias #alias_rule :index?, :create?, :new?, to: :manage? def manage? # ... end end ``` Now when you call `authorize! post` with any rule not defined in the policy class, the `manage?` rule is applied. Note that `index?` `create?` and `new?` are already defined in the [superclass by default](custom_policy.md) (returning `false`) - if you want the same behaviour for *all* actions, define aliases like in the example above (commented out). By default, `ActionPolicy::Base` sets `manage?` as a default rule. ## Mistypes & Default In case you forget to add `?` at the end of `:new` in a code like `allowed_to?(:new, User)`, a wrong rule will be called. Instead of the`:new?`, the `:manage?` rule will be applied. You can set `ActionPolicy.enforce_predicate_rules_naming = true` to raise an error when the called rule doesn't end with a question mark. ## Aliases and Private Methods Rules in `action_policy` can only be public methods. Trying to use a private method as a rule will raise an error. Thus, aliases can also only point to public methods. ## Rule resolution with subclasses Here's the order in which aliases and concrete rule methods are resolved in regards to subclasses: 1. If there is a concrete rule method on the subclass, this is called, else 2. If there is a matching alias then this is called, else * When aliases are defined on the subclass they will overwrite matching aliases on the superclass. 3. If there is a concrete rule method on the superclass, then this is called, else 4. If there is a default rule defined, then this is called, else 5. If the default rule is unaltered, then the `manage?` rule is called 6. If the default rule is set to `nil`, `ActionPolicy::UnknownRule` is raised. Here's an example with the expected results: ```ruby class SuperPolicy < ApplicationPolicy alias_rule :update?, :destroy?, :create?, to: :edit? def manage? end def edit? end def index? end end class SubPolicy < AbstractPolicy default_rule nil alias_rule :index?, :update?, to: :manage? def create? end end ``` Authorizing against the SuperPolicy: * `update?` will resolve to `edit?` * `destroy?` will resolve to `edit?` * `create?` will resolve to `edit?` * `manage?` will resolve to `manage?` * `edit?` will resolve to `edit?` * `index?` will resolve to `index?` * `something?` will resolve to the `default_rule`: `manage?` Authorizing against the SubPolicy: * `index?` will resolve to `manage?` * `update?` will resolve to `manage?` * `create?` will resolve to `create?` * `destroy?` will resolve to `edit?` * `manage?` will resolve to `manage?` * `edit?` will resolve to `edit?` * `index?` will resolve to `manage?` * `something?` will raise `ActionPolicy::UnknownRule` --- --- url: /guide/scoping.md --- # Scoping By *scoping* we mean an ability to use policies to *scope data* (or *filter/modify/transform/choose-your-verb*). The most common situation is when you want to *scope* ActiveRecord relations depending on the current user permissions. Without policies it could look like this: ```ruby class PostsController < ApplicationController def index @posts = if current_user.admin? Post.all else Post.where(user: current_user) end end end ``` That's a very simplified example. In practice scoping rules might be more complex, and it's likely that we would use them in multiple places. Action Policy allows you to define scoping rules within a policy class and use them with the help of `authorized_scope` method (`authorized` alias is also available): ```ruby class PostsController < ApplicationController def index @posts = authorized_scope(Post.all) end end class PostPolicy < ApplicationPolicy relation_scope do |relation| next relation if user.admin? relation.where(user: user) end end ``` You can also use a callable object to encapsulate the scoping logic: ```ruby class PostsController < ApplicationController def index @posts = authorized_scope(Post.all) end end class PostPolicy < ApplicationPolicy relation_scope AuthorizedPosts end class AuthorizedPosts class << self def call(policy, relation, **_scope_options) user = policy.user user.admin? ? relation : relation.where(user: user) end end end ``` ## Define scopes To define scope you should use either `scope_for` or `smth_scope` methods in your policy: ```ruby class PostPolicy < ApplicationPolicy # define a scope of a `relation` type scope_for :relation do |relation| relation.where(user: user) end # define a scope of `data` type, # which acts on hashes scope_for :data do |data| next data if user.admin? data.delete_if { |k, _| SENSITIVE_KEYS.include?(k) } end end ``` Scopes have *types*: different types of scopes are meant to be applied to different data types. You can specify multiple scopes (*named scopes*) for the same type providing a scope name: ```ruby class EventPolicy < ApplictionPolicy scope_for :relation, :own do |relation| relation.where(owner: user) end end ``` When the second argument is not specified, the `:default` is implied as the scope name. Also, there are cases where it might be easier to add options to existing scope than create a new one. For example, if you use soft-deletion and your logic inside a scope depends on if deleted records are included, you can add `with_deleted` option: ```ruby class PostPolicy < ApplicationPolicy scope_for :relation do |relation, with_deleted: false| rel = some_logic(relation) with_deleted ? rel.with_deleted : rel end end ``` You can add as many options as you want: ```ruby class PostPolicy < ApplicationPolicy scope_for :relation do |relation, some_required_option:, with_deleted: false, magic_number: 42| # Your code end end ``` ### Scopes inheritance and chaining Even though we use blocks to declare scopes, under the hood they are transformed into class instance methods. Hence, inheritance works out-of-the-box as with pure Ruby classes. It is also possible to *chain* scopes by using the `super` keyword. For example: ```ruby class ApplicationPolicy < ActionPolicy::Base authorize :user, :account 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 ``` **NOTE:** Using `super` with implicit arguments is not supported, since we use `define_method` internally. ## Apply scopes Action Policy behaviour (`ActionPolicy::Behaviour`) provides an `authorized` method which allows you to use scoping: ```ruby class PostsController < ApplicationController def index # The first argument is the target, # which is passed to the scope block # # The second argument is the scope type @posts = authorized_scope(Post, type: :relation) # # For named scopes provide `as` option @events = authorized_scope(Event, type: :relation, as: :own) # # If you want to specify scope options provide `scope_options` option @events = authorized_scope(Event, type: :relation, scope_options: {with_deleted: true}) end end ``` You can also specify additional options for policy class inference (see [behaviour docs](behaviour.md)). For example, to explicitly specify the policy class use: ```ruby @posts = authorized_scope(Post, with: CustomPostPolicy) ``` ## Using scopes within policy You can also use scopes within policy classes using the same `authorized_scope` method. For example: ```ruby relation_scope(:edit) do |scope| teachers = authorized_scope(Teacher.all, as: :edit) scope .joins(:teachers) .where(teacher_id: teachers) end ``` ## Using scopes explicitly To use scopes without including Action Policy [behaviour](behaviour.md) do the following: ```ruby # initialize policy policy = ApplicantPolicy.new(user: user) # apply scope policy.apply_scope(User.all, type: :relation) ``` ## Scope type inference Action Policy could look up a scope type if it's not specified and if *scope matchers* were configured. Scope matcher is an object that implements `#===` (*case equality*) or a Proc. You can define it within a policy class: ```ruby class ApplicationPolicy < ActionPolicy::Base scope_matcher :relation, ActiveRecord::Relation # use Proc to handle AR models classes scope_matcher :relation, ->(target) { target < ActiveRecord::Base } scope_matcher :custom, MyCustomClass end ``` Adding a scope matcher also adds a DSL to define scope rules (just a syntax sugar): ```ruby class ApplicationPolicy < ActionPolicy::Base scope_matcher :relation, ActiveRecord::Relation # now you can define scope rules like this relation_scope { |relation| relation } end ``` When `authorized_scope` is called without the explicit scope type, Action Policy uses matchers (in the order they're defined) to infer the type. ## Rails integration Action Policy provides a couple of *scope matchers* out-of-the-box for Active Record relations and Action Controller parameters. ### Active Record scopes Scope type `:relation` is automatically applied to the object of `ActiveRecord::Relation` type. To define Active Record scopes you can use `relation_scope` macro (which is just an alias for `scope :relation`) in your policy: ```ruby class PostPolicy < ApplicationPolicy # Equals `scope_for :active_record_relation do ...` relation_scope do |scope| if super_user? || admin? scope else scope.joins(:accesses).where(accesses: {user_id: user.id}) end end # define named scope relation_scope(:own) do |scope| next scope.none if user.guest? scope.where(user: user) end end ``` **NOTE:** the `:active_record_relation` scoping is used if and only if an `ActiveRecord::Relation` is passed to `authorized`: ```ruby def index # BAD: Post is not a relation; raises an exception @posts = authorized_scope(Post) # GOOD: @posts = authorized_scope(Post.all) end ``` ### Action Controller parameters Use scopes of type `:params` if your strong parameters filtering depends on the current user: ```ruby class UserPolicy < ApplicationPolicy # Equals to `scope_for :action_controller_params do ...` params_filter do |params| if user.admin? params.permit(:name, :email, :role) else params.permit(:name) end end params_filter(:update) do |params| params.permit(:name) end end class UsersController < ApplicationController def create # Call `authorized_scope` on `params` object @user = User.create!(authorized_scope(params.require(:user))) # Or you can use `authorized` alias which fits this case better @user = User.create!(authorized(params.require(:user))) head :ok end def update @user.update!(authorized_scope(params.require(:user), as: :update)) head :ok end end ``` --- --- url: /guide/testing.md --- # Testing Authorization is one of the crucial parts of your application. Hence, it should be thoroughly tested (that is the place where 100% coverage makes sense). When you use policies for authorization, it is possible to split testing into the following parts: * Test the policy class itself * Test that **the required authorization is performed** within your authorization layer (controller, channel, etc.) * Test that **the required scoping has been applied**. ## Testing policies You can test policies as plain-old Ruby classes, no special tooling is required. Consider an RSpec example: ```ruby describe PostPolicy do let(:user) { build_stubbed(:user) } let(:post) { build_stubbed(:post) } let(:policy) { described_class.new(post, user: user) } describe "#update?" do subject { policy.apply(:update?) } it "returns false when the user is not admin nor author" do is_expected.to eq false end context "when the user is admin" do let(:user) { build_stubbed(:user, :admin) } it { is_expected.to eq true } end context "when the user is an author" do let(:post) { build_stubbed(:post, user: user) } it { is_expected.to eq true } end end end ``` And we provide a `#be_an_alias_of` RSpec matcher for testing authorization rule aliases: Add the following to your `rails_helper.rb` (or `spec_helper.rb`): ```ruby require "action_policy/rspec" ``` Now you can use `be_an_alias_of` matcher: ```ruby describe PostPolicy do let(:user) { build_stubbed(:user) } let(:post) { build_stubbed(:post) } let(:policy) { described_class.new(post, user: user) } describe "#show?" do it "is an alias of :index? authorization rule" do expect(:show?).to be_an_alias_of(policy, :index?) end end end ``` ### RSpec DSL We also provide a simple RSpec DSL which aims to reduce the boilerplate when writing policies specs. Example: ```ruby # Add this to your spec_helper.rb / rails_helper.rb require "action_policy/rspec/dsl" describe PostPolicy do let(:user) { build_stubbed :user } # `record` must be defined – it is the authorization target let(:record) { build_stubbed :post, draft: false } # `context` is the authorization context let(:context) { {user: user} } # `describe_rule` is a combination of # `describe` and `subject { ... }` (returns the result of # applying the rule to the record) describe_rule :show? do # `succeed` is `context` + `specify`, which checks # that the result of application is successful succeed "when post is published" # `failed` is `context` + `specify`, which checks # that the result of application wasn't successful failed "when post is draft" do before { post.draft = false } succeed "when user is a manager" do before { user.role = "manager" } end end end end ``` If test failed the exception message includes the result and [failure reasons](reasons.md) (if any): ```sh 1) PostPolicy#show? when post is draft Failure/Error: ... Expected to fail but succeed: ``` If you have [debugging utils](debugging.md) installed the message also includes the *annotated* source code of the policy rule: ```sh 1) UserPolicy#manage? when post is draft Failure/Error: ... Expected to fail but succeed: ↳ user.admin? #=> true OR !record.draft? #=> false ``` **NOTE:** DSL for focusing or skipping examples and groups is also available (e.g. `xdescribe_rule`, `fsucceed`, etc.). **NOTE:** the DSL is included only to example with the tag `type: :policy` or in the `spec/policies` folder. If you want to add this DSL to other examples, add `include ActionPolicy::RSpec::PolicyExampleGroup`. ### Testing scopes #### Active Record relation example There is no single rule on how to test scopes, 'cause it depends on the *nature* of the scope. Here's an example of RSpec tests for Active Record scoping rules: ```ruby describe PostPolicy do describe "relation scope" do let(:user) { build_stubbed :user } let(:context) { {user: user} } # Feel free to replace with `before_all` from `test-prof`: # https://test-prof.evilmartians.io/#/before_all before do create(:post, name: "A") create(:post, name: "B", draft: true) end let(:target) do # We want to make sure that only the records created # for this test are affected, and they have a deterministic order Post.where(name: %w[A B]).order(name: :asc) end subject { policy.apply_scope(target, type: :active_record_relation).pluck(:name) } context "as user" do it { is_expected.to eq(%w[A]) } end context "as manager" do before { user.role = :manager } it { is_expected.to eq(%w[A B]) } end context "as banned user" do before { user.banned = true } it { is_expected.to be_empty } end end end ``` #### Action Controller params example Here's an example of RSpec tests for Action Controller parameters scoping rules: ```ruby describe PostPolicy do describe "params scope" do let(:user) { build_stubbed :user } let(:context) { {user: user} } let(:params) { {name: "a", password: "b"} } let(:target) { ActionController::Parameters.new(params) } # it's easier to asses the hash representation, not the AC::Params object subject { policy.apply_scope(target, type: :action_controller_params).to_h } context "as user" do it { is_expected.to eq({name: "a"}) } end context "as manager" do before { user.update!(role: :manager) } it { is_expected.to eq({name: "a", password: "b"}) } end end end ``` ## Testing authorization To test the act of authorization you have to make sure that the `authorize!` method is called with the appropriate arguments. Action Policy provides tools for such kind of testing for Minitest and RSpec. ### Minitest Include `ActionPolicy::TestHelper` to your test class and you'll be able to use `assert_authorized_to` assertion: ```ruby # in your controller class PostsController < ApplicationController def update @post = Post.find(params[:id]) authorize! @post, context: {favorite: true} if @post.update(post_params) redirect_to @post else render :edit end end end # in your test require "action_policy/test_helper" class PostsControllerTest < ActionDispatch::IntegrationTest include ActionPolicy::TestHelper test "update is authorized" do sign_in users(:john) post = posts(:example) assert_authorized_to(:update?, post, with: PostPolicy, context: {favorite: true}) do patch :update, id: post.id, name: "Bob" end end end ``` You can omit the policy (then it would be inferred from the target): ```ruby assert_authorized_to(:update?, post) do patch :update, id: post.id, name: "Bob" end ``` ### RSpec Add the following to your `rails_helper.rb` (or `spec_helper.rb`): ```ruby require "action_policy/rspec" ``` Now you can use `be_authorized_to` matcher: ```ruby describe PostsController do subject { patch :update, id: post.id, params: params } it "is authorized" do expect { subject }.to be_authorized_to(:update?, post) .with(PostPolicy) end end ``` If you omit `.with(PostPolicy)` then the inferred policy for the target (`post`) would be used. When specifying contexts in the authorization, you can use the `with_context` modifier to ensure the context match. ```ruby class PostController def post authorize! post, context: {favorite: true} end end describe PostsController do subject { patch :update, id: post.id, params: params } it "is authorized" do expect { subject }.to be_authorized_to(:update?, post) .with_context(favorite: true) end end ``` RSpec composed matchers are available as target: ```ruby expect { subject }.to be_authorized_to(:show?, an_instance_of(Post)) ``` ## Testing scoping Action Policy provides a way to test that a correct scoping has been applied during the code execution. For example, you can test that in your `#index` action the correct scoping is used: ```ruby class UsersController < ApplicationController def index @user = authorized(User.all) end def for_user user = User.find(params[:id]) authorized_scope(User.all, context: {user:}) end end ``` ### Minitest Include `ActionPolicy::TestHelper` to your test class and you'll be able to use `assert_have_authorized_scope` assertion: ```ruby # in your test require "action_policy/test_helper" class UsersControllerTest < ActionDispatch::IntegrationTest include ActionPolicy::TestHelper test "index has authorized scope" do sign_in users(:john) assert_have_authorized_scope(type: :active_record_relation, with: UserPolicy) do get :index end end end ``` You can also specify `as` and `scope_options` options. **NOTE:** both `type` and `with` params are required. It's not possible to test that a scoped has been applied to a particular *target* but we provide a way to perform additional assertions against the matching target (if the assertion didn't fail): ```ruby test "index has authorized scope" do sign_in users(:john) assert_have_authorized_scope(type: :active_record_relation, with: UserPolicy) do get :index end.with_target do |target| # target is a object passed to `authorized` call assert_equal User.all, target end end ``` ### RSpec Add the following to your `rails_helper.rb` (or `spec_helper.rb`): ```ruby require "action_policy/rspec" ``` Now you can use `have_authorized_scope` matcher: ```ruby describe UsersController do subject { get :index } it "has authorized scope" do expect { subject }.to have_authorized_scope(:active_record_relation) .with(PostPolicy) end end ``` You can also add `.as(:named_scope)` and `with_scope_options(options_hash)` options. RSpec composed matchers are available as scope options: ```ruby expect { subject }.to have_authorized_scope(:scope) .with_scope_options(matching(with_deleted: a_falsey_value)) ``` You can use the `with_target` modifier to run additional expectations against the matching target (if the matcher didn't fail): ```ruby expect { subject }.to have_authorized_scope(:scope) .with_scope_options(matching(with_deleted: a_falsey_value)) .with_target { |target| expect(target).to eq(User.all) } ``` Also can use the `with_context` options: ```ruby expect { get :for_user, params: {id: user.id} }.to have_authorized_scope(:scope) .with_scope_options(matching(with_deleted: a_falsey_value)) .with_context(a_hash_including(user:)) ``` ## Testing views When you test views that call policies methods as `allowed_to?`, your may have `Missing policy authorization context: user` error. You may need to stub `current_user` to resolve the issue. Consider an RSpec example: ```ruby describe "users/index.html.slim" do let(:user) { build_stubbed :user } let(:users) { create_list(:user, 2) } before do allow(controller).to receive(:current_user).and_return(user) assign :users, users render end describe "displays user#index correctly" do it { expect(rendered).to have_link(users.first.email, href: edit_user_path(users.first)) } end end ``` ## Linting with RuboCop RSpec When you lint your RSpec spec files with `rubocop-rspec`, it will fail to detect RSpec aliases that Action Policy defines. Make sure to use `rubocop-rspec` 2.0 or newer and add the following to your `.rubocop.yml`: ```yaml inherit_gem: action_policy: config/rubocop-rspec.yml ``` --- --- url: /guide/rails.md --- # Using with Rails Action Policy seamlessly integrates with Ruby on Rails applications. In most cases, you do not have to do anything except writing policy files and adding `authorize!` calls. **NOTE:** both controllers and channels extensions are built on top of the Action Policy [behaviour](./behaviour.md) mixin. ## Generators Action Policy provides a couple of useful Rails generators: * `rails g action_policy:install` — adds `app/policies/application_policy.rb` file * `rails g action_policy:policy MODEL_NAME` — adds a policy file and a policy test file for a given model (also creates an `application_policy.rb` if it's missing) * `rails g action_policy:policy MODEL_NAME --parent=base_policy` — adds a policy file that inherits from `BasePolicy`, and a policy test file for a given model (also creates an `application_policy.rb` if it's missing) ## Controllers integration Action Policy assumes that you have a `current_user` method which specifies the current authenticated subject (`user`). You can turn off this behaviour by setting `config.action_policy.controller_authorize_current_user = false` in `application.rb`, or override it: ```ruby class ApplicationController < ActionController::Base authorize :user, through: :my_current_user end ``` You can also pass a proc that will be executed in action context `self` to `:through`. This is useful if you're using `ActiveSupport::CurrentAttributes` or want to refer to instance variables: ```ruby class ApplicationController < ActionController::Base authorize :user, through: -> { @user || Current.user } end ``` **NOTE:** The `controller_authorize_current_user` setting only affects the way authorization context is built in controllers but does not affect policy classes configuration. If you inherit from `ActionPolicy::Base`, you will still have the `user` required as an authorization context. Add `authorize :user, optional: true` to your base policy class to make it optional or use a [custom base class](custom_policy.md). > Read more about [authorization context](authorization_context.md). If you don't want to include Action Policy in your controllers at all, you can disable the integration by setting `config.action_policy.auto_inject_into_controller = false` in `application.rb`. ### `verify_authorized` hooks Usually, you need all of your actions to be authorized. Action Policy provides a controller hook which ensures that an `authorize!` call has been made during the action: ```ruby class ApplicationController < ActionController::Base # adds an after_action callback to verify # that `authorize!` has been called. verify_authorized # you can also pass additional options, # like with a usual callback verify_authorized except: :index end ``` You can skip this check when necessary: ```ruby class PostsController < ApplicationController skip_verify_authorized only: :show def index # or dynamically within an action skip_verify_authorized! if some_condition end end ``` When an unauthorized action is encountered, the `ActionPolicy::UnauthorizedAction` error is raised. ### `verify_authorized_scoped` hooks Similarly, you might want to ensure that `authorized_scope` has been called during certain actions. Action Policy provides a `verify_authorized_scoped` hook for this: ```ruby class ApplicationController < ActionController::Base # adds an after_action callback to verify # that `authorized_scope` has been called. verify_authorized_scoped # you can also pass additional options, # like with a usual callback verify_authorized_scoped only: :index end ``` You can skip this check when necessary: ```ruby class PostsController < ApplicationController skip_verify_authorized_scoped only: :show def index # or dynamically within an action skip_verify_authorized_scoped! if some_condition end end ``` When an unscoped action is encountered, the `ActionPolicy::UnscopedAction` error is raised. ### Resource-less `authorize!` You can also call `authorize!` without a resource specified. In that case, Action Policy tries to infer the resource class from the controller name: ```ruby class PostsController < ApplicationController def index # Uses Post class as a resource implicitly. # NOTE: it just calls `controller_name.classify.safe_constantize`, # you can override this by defining `implicit_authorization_target` method. authorize! end end ``` ### Usage with `API` and `Metal` controllers Action Policy is only included into `ActionController::Base`. If you want to use it with other base Rails controllers, you have to include it manually: ```ruby class ApiController < ActionController::API include ActionPolicy::Controller # NOTE: you have to provide authorization context manually as well authorize :user, through: :current_user end ``` ## Channels integration Action Policy also integrates with Action Cable to help you authorize your channels actions: ```ruby class ChatChannel < ApplicationCable::Channel def follow(data) chat = Chat.find(data["chat_id"]) # Verify against ChatPolicy#show? rule authorize! chat, to: :show? stream_from chat end end ``` Action Policy assumes that you have `current_user` as a connection identifier. You can turn off this behaviour by setting `config.action_policy.channel_authorize_current_user = false` in `application.rb`, or override it: ```ruby module ApplicationCable class Channel < ActionCable::Channel::Base # assuming that identifier is called `user` authorize :user end end ``` > Read more about [authorization context](authorization_context.md). In case you do not want to include Action Policy to channels at all, you can disable the integration by setting `config.action_policy.auto_inject_into_channel = false` in `application.rb`. --- --- url: /guide/non_rails.md --- # Using with Ruby applications Action Policy is designed to be independent of any framework and does not have specific dependencies on Ruby on Rails. You can [write your policies](writing_policies.md) for non-Rails applications the same way as you would do for Rails applications. In order to have `authorize!` / `allowed_to?` / `authorized` methods, you will have to include [`ActionPolicy::Behaviour`](./behaviour.md) into your class (where you want to perform authorization): ```ruby 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 ``` --- --- url: /guide/writing_policies.md --- # Writing Policies Policy class contains predicate methods (*rules*) which are used to authorize activities. A Policy is instantiated with the target `record` (authorization object) and the [authorization context](authorization_context.md) (by default equals to `user`): ```ruby class PostPolicy < ActionPolicy::Base def index? # allow everyone to perform "index" activity on posts true end def update? # here we can access our context and record user.admin? || (user.id == record.user_id) end end ``` ## Initializing policies **NOTE**: it is not recommended to manually initialize policy objects and use them directly (one exclusion–[tests](testing.md)). Use [`authorize!` / `allowed_to?` methods](./behaviour.md#authorize) instead. To initialize policy object, you should specify target record and context: ```ruby policy = PostPolicy.new(post, user: user) # simply call rule method policy.update? ``` You can omit the first argument (in that case `record` would be `nil`). Instead of calling rules directly, it is better to call the `apply` method (which wraps rule method with some useful functionality, such as [caching](caching.md), [pre-checks](pre_checks.md), and [failure reasons tracking](reasons.md)): ```ruby policy.apply(:update?) ``` ## Calling other policies Sometimes it is useful to call other resources policies from within a policy. Action Policy provides the `allowed_to?` method as a part of `ActionPolicy::Base`: ```ruby class CommentPolicy < ApplicationPolicy def update? user.admin? || (user.id == record.id) || allowed_to?(:update?, record.post) end end ``` You can also specify all the usual options (such as `with`). There is also a `check?` method which is just an "alias"\* for `allowed_to?` added for better readability: ```ruby class PostPolicy < ApplicationPolicy def show? user.admin? || check?(:publicly_visible?) end def publicly_visible? # ... end end ``` \* It's not a Ruby *alias* but a wrapper; we can't use `alias` or `alias_method`, 'cause `allowed_to?` could be extended by some extensions. ## Fail-fast or pass-fast For better readability, we provide an alternative API (originally introduced for [pre-checks](./pre_checks.md)) to allow or deny a particular action. There are two methods—`allow!` and `deny!`: ```ruby class PostPolicy < ApplicationPolicy def show? allow! if user.admin? check?(:publicly_visible?) end def destroy? deny! if record.subscribers.any? # some general logic end end ``` ## Identifiers Each policy class has an `identifier`, which is by default just an underscored class name: ```ruby class CommentPolicy < ApplicationPolicy end CommentPolicy.identifier #=> :comment ``` For namespaced policies it has a form of: ```ruby module ActiveAdmin class UserPolicy < ApplicationPolicy end end ActiveAdmin::UserPolicy.identifier # => :"active_admin/user" ``` You can specify your own identifier: ```ruby module MyVeryLong class LongLongNamePolicy < ApplicationPolicy self.identifier = :long_name end end MyVeryLong::LongLongNamePolicy.identifier #=> :long_name ``` Identifiers are required for some modules, such as [failure reasons tracking](reasons.md) and [i18n](i18n.md).