---
url: /guide.md
---
[](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).