lib/pundit.rb in pundit-1.1.0 vs lib/pundit.rb in pundit-2.0.0.beta1
- old
+ new
@@ -1,24 +1,26 @@
+# frozen_string_literal: true
+
require "pundit/version"
require "pundit/policy_finder"
require "active_support/concern"
require "active_support/core_ext/string/inflections"
require "active_support/core_ext/object/blank"
require "active_support/core_ext/module/introspection"
require "active_support/dependencies/autoload"
# @api public
module Pundit
- SUFFIX = "Policy"
+ SUFFIX = "Policy".freeze
# @api private
module Generators; end
# @api private
class Error < StandardError; end
- # Error that will be raiser when authorization has failed
+ # Error that will be raised when authorization has failed
class NotAuthorizedError < Error
attr_reader :query, :record, :policy
def initialize(options = {})
if options.is_a? String
@@ -33,10 +35,13 @@
super(message)
end
end
+ # Error that will be raised if a policy or policy scope constructor is not called correctly.
+ class InvalidConstructorError < Error; end
+
# Error that will be raised if a controller action has not called the
# `authorize` or `skip_authorization` methods.
class AuthorizationNotPerformedError < Error; end
# Error that will be raised if a controller action has not called the
@@ -53,66 +58,85 @@
# 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
- # @param record [Symbol] the query method to check on the policy (e.g. `:show?`)
+ # @param query [Symbol, String] the predicate method to check on the policy (e.g. `:show?`)
+ # @param policy_class [Class] the policy class we want to force use of
# @raise [NotAuthorizedError] if the given query method returned false
- # @return [true] Always returns true
- def authorize(user, record, query)
- policy = policy!(user, record)
+ # @return [Object] Always returns the passed object record
+ def authorize(user, record, query, policy_class: nil)
+ policy = policy_class ? policy_class.new(user, record) : policy!(user, record)
- unless policy.public_send(query)
- raise NotAuthorizedError, query: query, record: record, policy: policy
- end
+ raise NotAuthorizedError, query: query, record: record, policy: policy unless policy.public_send(query)
- true
+ record
end
# Retrieves the policy scope for the given record.
#
- # @see https://github.com/elabs/pundit#scopes
+ # @see https://github.com/varvet/pundit#scopes
# @param user [Object] the user that initiated the action
- # @param record [Object] the object we're retrieving the policy scope for
+ # @param scope [Object] the object we're retrieving the policy scope for
+ # @raise [InvalidConstructorError] if the policy constructor called incorrectly
# @return [Scope{#resolve}, nil] instance of scope class which can resolve to a scope
def policy_scope(user, scope)
policy_scope = PolicyFinder.new(scope).scope
- policy_scope.new(user, scope).resolve if policy_scope
+ policy_scope.new(user, pundit_model(scope)).resolve if policy_scope
+ rescue ArgumentError
+ raise InvalidConstructorError, "Invalid #<#{policy_scope}> constructor is called"
end
# Retrieves the policy scope for the given record.
#
- # @see https://github.com/elabs/pundit#scopes
+ # @see https://github.com/varvet/pundit#scopes
# @param user [Object] the user that initiated the action
- # @param record [Object] the object we're retrieving the policy scope for
+ # @param scope [Object] the object we're retrieving the policy scope for
# @raise [NotDefinedError] if the policy scope cannot be found
+ # @raise [InvalidConstructorError] if the policy constructor called incorrectly
# @return [Scope{#resolve}] instance of scope class which can resolve to a scope
def policy_scope!(user, scope)
- PolicyFinder.new(scope).scope!.new(user, scope).resolve
+ policy_scope = PolicyFinder.new(scope).scope!
+ policy_scope.new(user, pundit_model(scope)).resolve
+ rescue ArgumentError
+ raise InvalidConstructorError, "Invalid #<#{policy_scope}> constructor is called"
end
# Retrieves the policy for the given record.
#
- # @see https://github.com/elabs/pundit#policies
+ # @see https://github.com/varvet/pundit#policies
# @param user [Object] the user that initiated the action
# @param record [Object] the object we're retrieving the policy for
+ # @raise [InvalidConstructorError] if the policy constructor called incorrectly
# @return [Object, nil] instance of policy class with query methods
def policy(user, record)
policy = PolicyFinder.new(record).policy
- policy.new(user, record) if policy
+ policy.new(user, pundit_model(record)) if policy
+ rescue ArgumentError
+ raise InvalidConstructorError, "Invalid #<#{policy}> constructor is called"
end
# Retrieves the policy for the given record.
#
- # @see https://github.com/elabs/pundit#policies
+ # @see https://github.com/varvet/pundit#policies
# @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
+ # @raise [InvalidConstructorError] if the policy constructor called incorrectly
# @return [Object] instance of policy class with query methods
def policy!(user, record)
- PolicyFinder.new(record).policy!.new(user, record)
+ policy = PolicyFinder.new(record).policy!
+ policy.new(user, pundit_model(record))
+ rescue ArgumentError
+ raise InvalidConstructorError, "Invalid #<#{policy}> constructor is called"
end
+
+ private
+
+ def pundit_model(record)
+ record.is_a?(Array) ? record.last : record
+ end
end
# @api private
module Helper
def policy_scope(scope)
@@ -125,27 +149,14 @@
if respond_to?(:helper_method)
helper_method :policy
helper_method :pundit_policy_scope
helper_method :pundit_user
end
- if respond_to?(:hide_action)
- hide_action :policy
- hide_action :policy_scope
- hide_action :policies
- hide_action :policy_scopes
- hide_action :authorize
- hide_action :verify_authorized
- hide_action :verify_policy_scoped
- hide_action :permitted_attributes
- hide_action :pundit_user
- hide_action :skip_authorization
- hide_action :skip_policy_scope
- hide_action :pundit_policy_authorized?
- hide_action :pundit_policy_scoped?
- end
end
+protected
+
# @return [Boolean] whether authorization has been performed, i.e. whether
# one {#authorize} or {#skip_authorization} has been called
def pundit_policy_authorized?
!!@_pundit_policy_authorized
end
@@ -158,22 +169,22 @@
# 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}.
#
- # @see https://github.com/elabs/pundit#ensuring-policies-are-used
+ # @see https://github.com/varvet/pundit#ensuring-policies-and-scopes-are-used
# @raise [AuthorizationNotPerformedError] if authorization has not been performed
# @return [void]
def verify_authorized
raise AuthorizationNotPerformedError, self.class unless pundit_policy_authorized?
end
# 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.
#
- # @see https://github.com/elabs/pundit#ensuring-policies-are-used
+ # @see https://github.com/varvet/pundit#ensuring-policies-and-scopes-are-used
# @raise [AuthorizationNotPerformedError] if policy scoping has not been performed
# @return [void]
def verify_policy_scoped
raise PolicyScopingNotPerformedError, self.class unless pundit_policy_scoped?
end
@@ -181,99 +192,113 @@
# 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
- # @param record [Symbol, nil] the query method to check on the policy (e.g. `:show?`)
+ # @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.
+ # @param policy_class [Class] the policy class we want to force use of
# @raise [NotAuthorizedError] if the given query method returned false
- # @return [true] Always returns true
- def authorize(record, query = nil)
- query ||= params[:action].to_s + "?"
+ # @return [Object] Always returns the passed object record
+ def authorize(record, query = nil, policy_class: nil)
+ query ||= "#{action_name}?"
@_pundit_policy_authorized = true
- policy = policy(record)
+ policy = policy_class ? policy_class.new(pundit_user, record) : policy(record)
- unless policy.public_send(query)
- raise NotAuthorizedError, query: query, record: record, policy: policy
- end
+ raise NotAuthorizedError, query: query, record: record, policy: policy unless policy.public_send(query)
- true
+ record
end
# Allow this action not to perform authorization.
#
- # @see https://github.com/elabs/pundit#ensuring-policies-are-used
+ # @see https://github.com/varvet/pundit#ensuring-policies-and-scopes-are-used
# @return [void]
def skip_authorization
@_pundit_policy_authorized = true
end
# Allow this action not to perform policy scoping.
#
- # @see https://github.com/elabs/pundit#ensuring-policies-are-used
+ # @see https://github.com/varvet/pundit#ensuring-policies-and-scopes-are-used
# @return [void]
def skip_policy_scope
@_pundit_policy_scoped = true
end
# Retrieves the policy scope for the given record.
#
- # @see https://github.com/elabs/pundit#scopes
- # @param record [Object] the object we're retrieving the policy scope for
+ # @see https://github.com/varvet/pundit#scopes
+ # @param scope [Object] the object we're retrieving the policy scope for
+ # @param policy_scope_class [Class] the policy scope class we want to force use of
# @return [Scope{#resolve}, nil] instance of scope class which can resolve to a scope
- def policy_scope(scope)
+ def policy_scope(scope, policy_scope_class: nil)
@_pundit_policy_scoped = true
- pundit_policy_scope(scope)
+ policy_scope_class ? policy_scope_class.new(pundit_user, scope).resolve : pundit_policy_scope(scope)
end
# Retrieves the policy for the given record.
#
- # @see https://github.com/elabs/pundit#policies
+ # @see https://github.com/varvet/pundit#policies
# @param record [Object] the object we're retrieving the policy for
# @return [Object, nil] instance of policy class with query methods
def policy(record)
policies[record] ||= Pundit.policy!(pundit_user, record)
end
# Retrieves a set of permitted attributes from the policy by instantiating
# the policy class for the given record and calling `permitted_attributes` on
- # it, or `permitted_attributes_for_{action}` if it is defined. It then infers
+ # it, or `permitted_attributes_for_{action}` if `action` is defined. It then infers
# what key the record should have in the params hash and retrieves the
# permitted attributes from the params hash under that key.
#
- # @see https://github.com/elabs/pundit#strong-parameters
+ # @see https://github.com/varvet/pundit#strong-parameters
# @param record [Object] the object we're retrieving permitted attributes for
+ # @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.
# @return [Hash{String => Object}] the permitted attributes
- def permitted_attributes(record, action = params[:action])
- param_key = PolicyFinder.new(record).param_key
+ def permitted_attributes(record, action = action_name)
policy = policy(record)
method_name = if policy.respond_to?("permitted_attributes_for_#{action}")
"permitted_attributes_for_#{action}"
else
"permitted_attributes"
end
- params.require(param_key).permit(policy.public_send(method_name))
+ pundit_params_for(record).permit(*policy.public_send(method_name))
end
+ # Retrieves the params for the given record.
+ #
+ # @param record [Object] the object we're retrieving params for
+ # @return [ActionController::Parameters] the params
+ def pundit_params_for(record)
+ params.require(PolicyFinder.new(record).param_key)
+ end
+
# Cache of policies. You should not rely on this method.
#
# @api private
+ # rubocop:disable Naming/MemoizedInstanceVariableName
def policies
@_pundit_policies ||= {}
end
+ # rubocop:enable Naming/MemoizedInstanceVariableName
# Cache of policy scope. You should not rely on this method.
#
# @api private
+ # rubocop:disable Naming/MemoizedInstanceVariableName
def policy_scopes
@_pundit_policy_scopes ||= {}
end
+ # rubocop:enable Naming/MemoizedInstanceVariableName
# Hook method which allows customizing which user is passed to policies and
# scopes initialized by {#authorize}, {#policy} and {#policy_scope}.
#
- # @see https://github.com/elabs/pundit#customize-pundit-user
+ # @see https://github.com/varvet/pundit#customize-pundit-user
# @return [Object] the user object to be used with pundit
def pundit_user
current_user
end