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.
To specify additional contexts, you should use the authorize
method:
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
:
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 `user` 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:
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
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)
.
See also: action_policy#36 and action_policy#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 (event if you configure all the required keys in the controller).
For example:
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 subsets of the nested policies contexts).
You can override the implicit authorization context (generated with authorize
method) in-place
by passing the context
option:
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).