2016-01-16 21:06:44 -06:00
|
|
|
# frozen_string_literal: true
|
2017-05-10 22:05:06 +02:00
|
|
|
|
2012-11-04 10:20:45 +01:00
|
|
|
require "pundit/version"
|
2012-11-19 10:57:17 +01:00
|
|
|
require "pundit/policy_finder"
|
2012-11-19 13:02:42 +01:00
|
|
|
require "active_support/concern"
|
|
|
|
require "active_support/core_ext/string/inflections"
|
|
|
|
require "active_support/core_ext/object/blank"
|
2014-05-21 22:09:17 -04:00
|
|
|
require "active_support/core_ext/module/introspection"
|
2014-04-22 22:02:03 -06:00
|
|
|
require "active_support/dependencies/autoload"
|
2012-11-04 10:20:45 +01:00
|
|
|
|
2019-04-17 10:27:46 +02:00
|
|
|
# @api private
|
|
|
|
# To avoid name clashes with common Error naming when mixing in Pundit,
|
|
|
|
# keep it here with compact class style definition.
|
|
|
|
class Pundit::Error < StandardError; end # rubocop:disable Style/ClassAndModuleChildren
|
|
|
|
|
2016-01-14 16:01:56 +01:00
|
|
|
# @api public
|
2012-11-04 10:20:45 +01:00
|
|
|
module Pundit
|
2016-01-16 21:06:44 -06:00
|
|
|
SUFFIX = "Policy".freeze
|
2015-04-01 11:17:30 +02:00
|
|
|
|
2016-01-14 16:01:56 +01:00
|
|
|
# @api private
|
|
|
|
module Generators; end
|
|
|
|
|
2016-05-10 13:44:00 +08:00
|
|
|
# Error that will be raised when authorization has failed
|
2015-04-07 14:46:50 -04:00
|
|
|
class NotAuthorizedError < Error
|
2015-02-25 20:30:08 +01:00
|
|
|
attr_reader :query, :record, :policy
|
|
|
|
|
|
|
|
def initialize(options = {})
|
2015-05-05 08:07:50 +02:00
|
|
|
if options.is_a? String
|
|
|
|
message = options
|
|
|
|
else
|
|
|
|
@query = options[:query]
|
|
|
|
@record = options[:record]
|
|
|
|
@policy = options[:policy]
|
2015-02-25 20:30:08 +01:00
|
|
|
|
2019-03-04 16:38:26 -05:00
|
|
|
message = options.fetch(:message) { "not allowed to #{query} this #{record.class}" }
|
2015-05-05 08:07:50 +02:00
|
|
|
end
|
2015-02-25 20:30:08 +01:00
|
|
|
|
|
|
|
super(message)
|
|
|
|
end
|
2014-02-25 18:54:22 -08:00
|
|
|
end
|
2016-01-14 16:01:56 +01:00
|
|
|
|
2017-02-18 15:46:55 +00:00
|
|
|
# Error that will be raised if a policy or policy scope constructor is not called correctly.
|
|
|
|
class InvalidConstructorError < Error; end
|
|
|
|
|
2016-01-14 16:01:56 +01:00
|
|
|
# Error that will be raised if a controller action has not called the
|
|
|
|
# `authorize` or `skip_authorization` methods.
|
2015-04-07 14:46:50 -04:00
|
|
|
class AuthorizationNotPerformedError < Error; end
|
2016-01-14 16:01:56 +01:00
|
|
|
|
|
|
|
# Error that will be raised if a controller action has not called the
|
|
|
|
# `policy_scope` or `skip_policy_scope` methods.
|
2014-07-07 20:50:22 -05:00
|
|
|
class PolicyScopingNotPerformedError < AuthorizationNotPerformedError; end
|
2016-01-14 16:01:56 +01:00
|
|
|
|
|
|
|
# Error that will be raised if a policy or policy scope is not defined.
|
2015-04-07 14:46:50 -04:00
|
|
|
class NotDefinedError < Error; end
|
2012-11-19 10:57:17 +01:00
|
|
|
|
|
|
|
extend ActiveSupport::Concern
|
|
|
|
|
|
|
|
class << self
|
2016-01-14 16:01:56 +01:00
|
|
|
# Retrieves the policy for the given record, initializing it with the
|
|
|
|
# record and user and finally throwing an error if the user is not
|
|
|
|
# authorized to perform the given action.
|
|
|
|
#
|
|
|
|
# @param user [Object] the user that initiated the action
|
|
|
|
# @param record [Object] the object we're checking permissions of
|
2016-02-24 13:05:38 +00:00
|
|
|
# @param query [Symbol, String] the predicate method to check on the policy (e.g. `:show?`)
|
2018-07-01 20:04:25 +02:00
|
|
|
# @param policy_class [Class] the policy class we want to force use of
|
2016-01-14 16:01:56 +01:00
|
|
|
# @raise [NotAuthorizedError] if the given query method returned false
|
2016-05-10 13:44:00 +08:00
|
|
|
# @return [Object] Always returns the passed object record
|
2018-07-01 20:04:25 +02:00
|
|
|
def authorize(user, record, query, policy_class: nil)
|
|
|
|
policy = policy_class ? policy_class.new(user, record) : policy!(user, record)
|
2015-03-26 10:32:20 +01:00
|
|
|
|
2018-04-27 12:40:42 +02:00
|
|
|
raise NotAuthorizedError, query: query, record: record, policy: policy unless policy.public_send(query)
|
2015-03-26 10:32:20 +01:00
|
|
|
|
2016-05-10 13:44:00 +08:00
|
|
|
record
|
2015-03-26 10:32:20 +01:00
|
|
|
end
|
|
|
|
|
2016-01-14 16:01:56 +01:00
|
|
|
# Retrieves the policy scope for the given record.
|
|
|
|
#
|
2018-07-04 20:30:06 +02:00
|
|
|
# @see https://github.com/varvet/pundit#scopes
|
2016-01-14 16:01:56 +01:00
|
|
|
# @param user [Object] the user that initiated the action
|
2016-02-24 13:05:38 +00:00
|
|
|
# @param scope [Object] the object we're retrieving the policy scope for
|
2017-02-18 15:50:24 +00:00
|
|
|
# @raise [InvalidConstructorError] if the policy constructor called incorrectly
|
2016-01-14 16:01:56 +01:00
|
|
|
# @return [Scope{#resolve}, nil] instance of scope class which can resolve to a scope
|
2014-08-22 11:19:36 +02:00
|
|
|
def policy_scope(user, scope)
|
2018-09-20 13:18:41 +02:00
|
|
|
policy_scope_class = PolicyFinder.new(scope).scope
|
|
|
|
return unless policy_scope_class
|
|
|
|
|
|
|
|
begin
|
|
|
|
policy_scope = policy_scope_class.new(user, pundit_model(scope))
|
|
|
|
rescue ArgumentError
|
|
|
|
raise InvalidConstructorError, "Invalid #<#{policy_scope_class}> constructor is called"
|
|
|
|
end
|
|
|
|
|
|
|
|
policy_scope.resolve
|
2012-11-19 10:57:17 +01:00
|
|
|
end
|
|
|
|
|
2016-01-14 16:01:56 +01:00
|
|
|
# Retrieves the policy scope for the given record.
|
|
|
|
#
|
2018-07-04 20:30:06 +02:00
|
|
|
# @see https://github.com/varvet/pundit#scopes
|
2016-01-14 16:01:56 +01:00
|
|
|
# @param user [Object] the user that initiated the action
|
2016-02-24 13:05:38 +00:00
|
|
|
# @param scope [Object] the object we're retrieving the policy scope for
|
2016-01-14 16:01:56 +01:00
|
|
|
# @raise [NotDefinedError] if the policy scope cannot be found
|
2017-02-18 15:50:24 +00:00
|
|
|
# @raise [InvalidConstructorError] if the policy constructor called incorrectly
|
2016-01-14 16:01:56 +01:00
|
|
|
# @return [Scope{#resolve}] instance of scope class which can resolve to a scope
|
2014-08-22 11:19:36 +02:00
|
|
|
def policy_scope!(user, scope)
|
2018-09-20 13:18:41 +02:00
|
|
|
policy_scope_class = PolicyFinder.new(scope).scope!
|
|
|
|
return unless policy_scope_class
|
|
|
|
|
|
|
|
begin
|
|
|
|
policy_scope = policy_scope_class.new(user, pundit_model(scope))
|
|
|
|
rescue ArgumentError
|
|
|
|
raise InvalidConstructorError, "Invalid #<#{policy_scope_class}> constructor is called"
|
|
|
|
end
|
|
|
|
|
|
|
|
policy_scope.resolve
|
2012-11-19 10:57:17 +01:00
|
|
|
end
|
|
|
|
|
2016-01-14 16:01:56 +01:00
|
|
|
# Retrieves the policy for the given record.
|
|
|
|
#
|
2018-07-04 20:30:06 +02:00
|
|
|
# @see https://github.com/varvet/pundit#policies
|
2016-01-14 16:01:56 +01:00
|
|
|
# @param user [Object] the user that initiated the action
|
|
|
|
# @param record [Object] the object we're retrieving the policy for
|
2017-02-18 15:50:24 +00:00
|
|
|
# @raise [InvalidConstructorError] if the policy constructor called incorrectly
|
2016-01-14 16:01:56 +01:00
|
|
|
# @return [Object, nil] instance of policy class with query methods
|
2014-08-22 11:19:36 +02:00
|
|
|
def policy(user, record)
|
|
|
|
policy = PolicyFinder.new(record).policy
|
2018-06-17 09:18:41 +02:00
|
|
|
policy.new(user, pundit_model(record)) if policy
|
2017-02-18 15:49:37 +00:00
|
|
|
rescue ArgumentError
|
2017-02-18 16:35:34 +00:00
|
|
|
raise InvalidConstructorError, "Invalid #<#{policy}> constructor is called"
|
2012-11-19 10:57:17 +01:00
|
|
|
end
|
|
|
|
|
2016-01-14 16:01:56 +01:00
|
|
|
# Retrieves the policy for the given record.
|
|
|
|
#
|
2018-07-04 20:30:06 +02:00
|
|
|
# @see https://github.com/varvet/pundit#policies
|
2016-01-14 16:01:56 +01:00
|
|
|
# @param user [Object] the user that initiated the action
|
|
|
|
# @param record [Object] the object we're retrieving the policy for
|
|
|
|
# @raise [NotDefinedError] if the policy cannot be found
|
2017-02-18 15:50:24 +00:00
|
|
|
# @raise [InvalidConstructorError] if the policy constructor called incorrectly
|
2016-01-14 16:01:56 +01:00
|
|
|
# @return [Object] instance of policy class with query methods
|
2014-08-22 11:19:36 +02:00
|
|
|
def policy!(user, record)
|
2017-02-18 15:49:37 +00:00
|
|
|
policy = PolicyFinder.new(record).policy!
|
2018-06-17 09:26:10 +02:00
|
|
|
policy.new(user, pundit_model(record))
|
2017-02-18 15:49:37 +00:00
|
|
|
rescue ArgumentError
|
2017-02-18 16:35:34 +00:00
|
|
|
raise InvalidConstructorError, "Invalid #<#{policy}> constructor is called"
|
2012-11-19 10:57:17 +01:00
|
|
|
end
|
2018-06-17 09:18:41 +02:00
|
|
|
|
|
|
|
private
|
|
|
|
|
|
|
|
def pundit_model(record)
|
|
|
|
record.is_a?(Array) ? record.last : record
|
|
|
|
end
|
2012-11-19 10:57:17 +01:00
|
|
|
end
|
|
|
|
|
2016-01-14 16:01:56 +01:00
|
|
|
# @api private
|
2015-03-27 13:51:02 +01:00
|
|
|
module Helper
|
|
|
|
def policy_scope(scope)
|
|
|
|
pundit_policy_scope(scope)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2012-11-19 10:57:17 +01:00
|
|
|
included do
|
2015-03-27 13:51:02 +01:00
|
|
|
helper Helper if respond_to?(:helper)
|
2012-11-19 10:57:17 +01:00
|
|
|
if respond_to?(:helper_method)
|
|
|
|
helper_method :policy
|
2015-03-27 13:51:02 +01:00
|
|
|
helper_method :pundit_policy_scope
|
2013-07-14 00:50:39 +02:00
|
|
|
helper_method :pundit_user
|
2012-11-19 10:57:17 +01:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2016-02-02 13:31:43 -06:00
|
|
|
protected
|
|
|
|
|
2016-01-14 16:01:56 +01:00
|
|
|
# @return [Boolean] whether authorization has been performed, i.e. whether
|
|
|
|
# one {#authorize} or {#skip_authorization} has been called
|
2015-03-18 15:50:06 +01:00
|
|
|
def pundit_policy_authorized?
|
|
|
|
!!@_pundit_policy_authorized
|
|
|
|
end
|
|
|
|
|
2016-01-14 16:01:56 +01:00
|
|
|
# @return [Boolean] whether policy scoping has been performed, i.e. whether
|
|
|
|
# one {#policy_scope} or {#skip_policy_scope} has been called
|
2015-03-18 15:50:06 +01:00
|
|
|
def pundit_policy_scoped?
|
|
|
|
!!@_pundit_policy_scoped
|
|
|
|
end
|
|
|
|
|
2016-01-14 16:01:56 +01:00
|
|
|
# Raises an error if authorization has not been performed, usually used as an
|
|
|
|
# `after_action` filter to prevent programmer error in forgetting to call
|
|
|
|
# {#authorize} or {#skip_authorization}.
|
|
|
|
#
|
2018-07-04 20:30:06 +02:00
|
|
|
# @see https://github.com/varvet/pundit#ensuring-policies-and-scopes-are-used
|
2016-01-14 16:01:56 +01:00
|
|
|
# @raise [AuthorizationNotPerformedError] if authorization has not been performed
|
|
|
|
# @return [void]
|
2012-11-19 10:57:17 +01:00
|
|
|
def verify_authorized
|
2016-01-14 15:15:30 +01:00
|
|
|
raise AuthorizationNotPerformedError, self.class unless pundit_policy_authorized?
|
2012-11-19 10:57:17 +01:00
|
|
|
end
|
|
|
|
|
2016-01-14 16:01:56 +01:00
|
|
|
# Raises an error if policy scoping has not been performed, usually used as an
|
|
|
|
# `after_action` filter to prevent programmer error in forgetting to call
|
|
|
|
# {#policy_scope} or {#skip_policy_scope} in index actions.
|
|
|
|
#
|
2018-07-04 20:30:06 +02:00
|
|
|
# @see https://github.com/varvet/pundit#ensuring-policies-and-scopes-are-used
|
2016-01-14 16:01:56 +01:00
|
|
|
# @raise [AuthorizationNotPerformedError] if policy scoping has not been performed
|
|
|
|
# @return [void]
|
2013-04-17 22:05:24 -07:00
|
|
|
def verify_policy_scoped
|
2016-01-14 15:15:30 +01:00
|
|
|
raise PolicyScopingNotPerformedError, self.class unless pundit_policy_scoped?
|
2013-04-17 22:05:24 -07:00
|
|
|
end
|
|
|
|
|
2016-01-14 16:01:56 +01:00
|
|
|
# Retrieves the policy for the given record, initializing it with the record
|
|
|
|
# and current user and finally throwing an error if the user is not
|
|
|
|
# authorized to perform the given action.
|
|
|
|
#
|
|
|
|
# @param record [Object] the object we're checking permissions of
|
2016-02-24 13:05:38 +00:00
|
|
|
# @param query [Symbol, String] the predicate method to check on the policy (e.g. `:show?`).
|
|
|
|
# If omitted then this defaults to the Rails controller action name.
|
2018-07-01 20:04:25 +02:00
|
|
|
# @param policy_class [Class] the policy class we want to force use of
|
2016-01-14 16:01:56 +01:00
|
|
|
# @raise [NotAuthorizedError] if the given query method returned false
|
2016-05-10 13:44:00 +08:00
|
|
|
# @return [Object] Always returns the passed object record
|
2016-12-19 11:54:01 +01:00
|
|
|
def authorize(record, query = nil, policy_class: nil)
|
2016-09-27 11:29:43 -04:00
|
|
|
query ||= "#{action_name}?"
|
2015-02-25 20:30:08 +01:00
|
|
|
|
2014-08-26 08:57:27 -07:00
|
|
|
@_pundit_policy_authorized = true
|
2014-02-25 18:54:22 -08:00
|
|
|
|
2016-12-19 11:54:01 +01:00
|
|
|
policy = policy_class ? policy_class.new(pundit_user, record) : policy(record)
|
2016-01-14 15:15:30 +01:00
|
|
|
|
2018-04-27 12:40:42 +02:00
|
|
|
raise NotAuthorizedError, query: query, record: record, policy: policy unless policy.public_send(query)
|
2014-02-25 18:54:22 -08:00
|
|
|
|
2016-05-10 13:44:00 +08:00
|
|
|
record
|
2012-11-19 10:57:17 +01:00
|
|
|
end
|
|
|
|
|
2016-01-14 16:01:56 +01:00
|
|
|
# Allow this action not to perform authorization.
|
|
|
|
#
|
2018-07-04 20:30:06 +02:00
|
|
|
# @see https://github.com/varvet/pundit#ensuring-policies-and-scopes-are-used
|
2016-01-14 16:01:56 +01:00
|
|
|
# @return [void]
|
2015-02-20 14:38:08 -05:00
|
|
|
def skip_authorization
|
|
|
|
@_pundit_policy_authorized = true
|
|
|
|
end
|
|
|
|
|
2016-01-14 16:01:56 +01:00
|
|
|
# Allow this action not to perform policy scoping.
|
|
|
|
#
|
2018-07-04 20:30:06 +02:00
|
|
|
# @see https://github.com/varvet/pundit#ensuring-policies-and-scopes-are-used
|
2016-01-14 16:01:56 +01:00
|
|
|
# @return [void]
|
2015-04-08 09:17:32 +08:00
|
|
|
def skip_policy_scope
|
|
|
|
@_pundit_policy_scoped = true
|
|
|
|
end
|
|
|
|
|
2016-01-14 16:01:56 +01:00
|
|
|
# Retrieves the policy scope for the given record.
|
|
|
|
#
|
2018-07-04 20:30:06 +02:00
|
|
|
# @see https://github.com/varvet/pundit#scopes
|
2016-02-24 13:05:38 +00:00
|
|
|
# @param scope [Object] the object we're retrieving the policy scope for
|
2018-07-01 20:04:25 +02:00
|
|
|
# @param policy_scope_class [Class] the policy scope class we want to force use of
|
2016-01-14 16:01:56 +01:00
|
|
|
# @return [Scope{#resolve}, nil] instance of scope class which can resolve to a scope
|
2016-12-19 16:03:40 +01:00
|
|
|
def policy_scope(scope, policy_scope_class: nil)
|
2014-08-26 08:57:27 -07:00
|
|
|
@_pundit_policy_scoped = true
|
2016-12-19 16:03:40 +01:00
|
|
|
policy_scope_class ? policy_scope_class.new(pundit_user, scope).resolve : pundit_policy_scope(scope)
|
2012-11-19 10:57:17 +01:00
|
|
|
end
|
|
|
|
|
2016-01-14 16:01:56 +01:00
|
|
|
# Retrieves the policy for the given record.
|
|
|
|
#
|
2018-07-04 20:30:06 +02:00
|
|
|
# @see https://github.com/varvet/pundit#policies
|
2016-01-14 16:01:56 +01:00
|
|
|
# @param record [Object] the object we're retrieving the policy for
|
|
|
|
# @return [Object, nil] instance of policy class with query methods
|
2012-11-19 10:57:17 +01:00
|
|
|
def policy(record)
|
2014-08-22 13:20:32 +02:00
|
|
|
policies[record] ||= Pundit.policy!(pundit_user, record)
|
2014-05-19 15:05:09 +10:00
|
|
|
end
|
|
|
|
|
2016-01-14 16:01:56 +01:00
|
|
|
# Retrieves a set of permitted attributes from the policy by instantiating
|
|
|
|
# the policy class for the given record and calling `permitted_attributes` on
|
2016-02-24 13:05:38 +00:00
|
|
|
# it, or `permitted_attributes_for_{action}` if `action` is defined. It then infers
|
2016-01-14 16:01:56 +01:00
|
|
|
# what key the record should have in the params hash and retrieves the
|
|
|
|
# permitted attributes from the params hash under that key.
|
|
|
|
#
|
2018-07-04 20:30:06 +02:00
|
|
|
# @see https://github.com/varvet/pundit#strong-parameters
|
2016-01-14 16:01:56 +01:00
|
|
|
# @param record [Object] the object we're retrieving permitted attributes for
|
2016-02-24 13:05:38 +00:00
|
|
|
# @param action [Symbol, String] the name of the action being performed on the record (e.g. `:update`).
|
|
|
|
# If omitted then this defaults to the Rails controller action name.
|
2016-01-14 16:01:56 +01:00
|
|
|
# @return [Hash{String => Object}] the permitted attributes
|
2016-09-27 11:29:43 -04:00
|
|
|
def permitted_attributes(record, action = action_name)
|
2016-01-14 14:43:51 +01:00
|
|
|
policy = policy(record)
|
|
|
|
method_name = if policy.respond_to?("permitted_attributes_for_#{action}")
|
|
|
|
"permitted_attributes_for_#{action}"
|
2016-01-14 13:25:32 +01:00
|
|
|
else
|
2016-01-14 14:43:51 +01:00
|
|
|
"permitted_attributes"
|
2016-01-14 13:25:32 +01:00
|
|
|
end
|
2018-03-09 18:18:43 +01:00
|
|
|
pundit_params_for(record).permit(*policy.public_send(method_name))
|
|
|
|
end
|
|
|
|
|
2018-05-16 13:18:03 +02:00
|
|
|
# Retrieves the params for the given record.
|
|
|
|
#
|
|
|
|
# @param record [Object] the object we're retrieving params for
|
|
|
|
# @return [ActionController::Parameters] the params
|
2018-03-09 18:18:43 +01:00
|
|
|
def pundit_params_for(record)
|
|
|
|
params.require(PolicyFinder.new(record).param_key)
|
2015-03-27 10:14:09 +01:00
|
|
|
end
|
|
|
|
|
2016-01-14 16:01:56 +01:00
|
|
|
# Cache of policies. You should not rely on this method.
|
|
|
|
#
|
|
|
|
# @api private
|
2018-04-27 12:40:42 +02:00
|
|
|
# rubocop:disable Naming/MemoizedInstanceVariableName
|
2014-08-22 13:20:32 +02:00
|
|
|
def policies
|
2014-08-26 08:57:27 -07:00
|
|
|
@_pundit_policies ||= {}
|
2014-08-22 13:20:32 +02:00
|
|
|
end
|
2018-04-27 12:40:42 +02:00
|
|
|
# rubocop:enable Naming/MemoizedInstanceVariableName
|
2014-08-22 13:20:32 +02:00
|
|
|
|
2016-01-14 16:01:56 +01:00
|
|
|
# Cache of policy scope. You should not rely on this method.
|
|
|
|
#
|
|
|
|
# @api private
|
2018-04-27 12:40:42 +02:00
|
|
|
# rubocop:disable Naming/MemoizedInstanceVariableName
|
2014-08-22 13:20:32 +02:00
|
|
|
def policy_scopes
|
2014-08-26 08:57:27 -07:00
|
|
|
@_pundit_policy_scopes ||= {}
|
2013-07-13 05:42:34 +02:00
|
|
|
end
|
2018-04-27 12:40:42 +02:00
|
|
|
# rubocop:enable Naming/MemoizedInstanceVariableName
|
2013-07-13 05:42:34 +02:00
|
|
|
|
2016-01-14 16:01:56 +01:00
|
|
|
# Hook method which allows customizing which user is passed to policies and
|
|
|
|
# scopes initialized by {#authorize}, {#policy} and {#policy_scope}.
|
|
|
|
#
|
2018-07-04 20:30:06 +02:00
|
|
|
# @see https://github.com/varvet/pundit#customize-pundit-user
|
2016-01-14 16:01:56 +01:00
|
|
|
# @return [Object] the user object to be used with pundit
|
2013-07-13 05:42:34 +02:00
|
|
|
def pundit_user
|
2013-07-13 16:24:13 +02:00
|
|
|
current_user
|
2012-11-19 10:57:17 +01:00
|
|
|
end
|
2015-03-27 13:51:02 +01:00
|
|
|
|
|
|
|
private
|
|
|
|
|
|
|
|
def pundit_policy_scope(scope)
|
|
|
|
policy_scopes[scope] ||= Pundit.policy_scope!(pundit_user, scope)
|
|
|
|
end
|
2012-11-04 10:20:45 +01:00
|
|
|
end
|