mirror of
https://github.com/varvet/pundit.git
synced 2022-11-09 12:30:11 -05:00
Extract Pundit controller authorization module
This commit is contained in:
parent
46af6620b0
commit
4d9b5849f4
7 changed files with 454 additions and 414 deletions
|
@ -30,6 +30,10 @@ changes.
|
|||
- Dropped support for Ruby end-of-life versions: 2.4, 2.5 and JRuby 9.1 (#676)
|
||||
- Dropped support for RSpec 2 (#615)
|
||||
|
||||
### Deprecated
|
||||
|
||||
- Deprecate `include Pundit` in favor of `include Pundit::Authorization` (#621)
|
||||
|
||||
## 2.1.0 (2019-08-14)
|
||||
|
||||
### Fixed
|
||||
|
|
12
README.md
12
README.md
|
@ -26,11 +26,11 @@ Sponsored by:
|
|||
gem "pundit"
|
||||
```
|
||||
|
||||
Include Pundit in your application controller:
|
||||
Include `Pundit::Authorization` in your application controller:
|
||||
|
||||
``` ruby
|
||||
class ApplicationController < ActionController::Base
|
||||
include Pundit
|
||||
include Pundit::Authorization
|
||||
end
|
||||
```
|
||||
|
||||
|
@ -334,7 +334,7 @@ that you haven't forgotten to authorize the action. For example:
|
|||
|
||||
``` ruby
|
||||
class ApplicationController < ActionController::Base
|
||||
include Pundit
|
||||
include Pundit::Authorization
|
||||
after_action :verify_authorized
|
||||
end
|
||||
```
|
||||
|
@ -347,7 +347,7 @@ authorize individual instances.
|
|||
|
||||
``` ruby
|
||||
class ApplicationController < ActionController::Base
|
||||
include Pundit
|
||||
include Pundit::Authorization
|
||||
after_action :verify_authorized, except: :index
|
||||
after_action :verify_policy_scoped, only: :index
|
||||
end
|
||||
|
@ -490,7 +490,7 @@ method in every controller.
|
|||
|
||||
```ruby
|
||||
class ApplicationController < ActionController::Base
|
||||
include Pundit
|
||||
include Pundit::Authorization
|
||||
|
||||
rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized
|
||||
|
||||
|
@ -643,7 +643,7 @@ class UserContext
|
|||
end
|
||||
|
||||
class ApplicationController
|
||||
include Pundit
|
||||
include Pundit::Authorization
|
||||
|
||||
def pundit_user
|
||||
UserContext.new(current_user, request.ip)
|
||||
|
|
167
lib/pundit.rb
167
lib/pundit.rb
|
@ -7,6 +7,7 @@ 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"
|
||||
require "pundit/authorization"
|
||||
|
||||
# @api private
|
||||
# To avoid name clashes with common Error naming when mixing in Pundit,
|
||||
|
@ -53,7 +54,10 @@ module Pundit
|
|||
# Error that will be raised if a policy or policy scope is not defined.
|
||||
class NotDefinedError < Error; end
|
||||
|
||||
extend ActiveSupport::Concern
|
||||
def self.included(base)
|
||||
warn "[DEPRECATION] 'include Pundit' is deprecated. Please use 'include Pundit::Authorization' instead."
|
||||
base.include Authorization
|
||||
end
|
||||
|
||||
class << self
|
||||
# Retrieves the policy for the given record, initializing it with the
|
||||
|
@ -163,165 +167,4 @@ module Pundit
|
|||
pundit_policy_scope(scope)
|
||||
end
|
||||
end
|
||||
|
||||
included do
|
||||
helper Helper if respond_to?(:helper)
|
||||
if respond_to?(:helper_method)
|
||||
helper_method :policy
|
||||
helper_method :pundit_policy_scope
|
||||
helper_method :pundit_user
|
||||
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
|
||||
|
||||
# @return [Boolean] whether policy scoping has been performed, i.e. whether
|
||||
# one {#policy_scope} or {#skip_policy_scope} has been called
|
||||
def pundit_policy_scoped?
|
||||
!!@_pundit_policy_scoped
|
||||
end
|
||||
|
||||
# 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/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/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
|
||||
|
||||
# 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, Array] the object we're checking permissions of
|
||||
# @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 [Object] Always returns the passed object record
|
||||
def authorize(record, query = nil, policy_class: nil)
|
||||
query ||= "#{action_name}?"
|
||||
|
||||
@_pundit_policy_authorized = true
|
||||
|
||||
Pundit.authorize(pundit_user, record, query, policy_class: policy_class, cache: policies)
|
||||
end
|
||||
|
||||
# Allow this action not to perform authorization.
|
||||
#
|
||||
# @see https://github.com/varvet/pundit#ensuring-policies-and-scopes-are-used
|
||||
# @return [void]
|
||||
def skip_authorization
|
||||
@_pundit_policy_authorized = :skipped
|
||||
end
|
||||
|
||||
# Allow this action not to perform policy scoping.
|
||||
#
|
||||
# @see https://github.com/varvet/pundit#ensuring-policies-and-scopes-are-used
|
||||
# @return [void]
|
||||
def skip_policy_scope
|
||||
@_pundit_policy_scoped = :skipped
|
||||
end
|
||||
|
||||
# Retrieves the policy scope for the given record.
|
||||
#
|
||||
# @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, policy_scope_class: nil)
|
||||
@_pundit_policy_scoped = true
|
||||
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/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 `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/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 = action_name)
|
||||
policy = policy(record)
|
||||
method_name = if policy.respond_to?("permitted_attributes_for_#{action}")
|
||||
"permitted_attributes_for_#{action}"
|
||||
else
|
||||
"permitted_attributes"
|
||||
end
|
||||
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/varvet/pundit#customize-pundit-user
|
||||
# @return [Object] the user object to be used with pundit
|
||||
def pundit_user
|
||||
current_user
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def pundit_policy_scope(scope)
|
||||
policy_scopes[scope] ||= Pundit.policy_scope!(pundit_user, scope)
|
||||
end
|
||||
end
|
||||
|
|
168
lib/pundit/authorization.rb
Normal file
168
lib/pundit/authorization.rb
Normal file
|
@ -0,0 +1,168 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Pundit
|
||||
module Authorization
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
helper Helper if respond_to?(:helper)
|
||||
if respond_to?(:helper_method)
|
||||
helper_method :policy
|
||||
helper_method :pundit_policy_scope
|
||||
helper_method :pundit_user
|
||||
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
|
||||
|
||||
# @return [Boolean] whether policy scoping has been performed, i.e. whether
|
||||
# one {#policy_scope} or {#skip_policy_scope} has been called
|
||||
def pundit_policy_scoped?
|
||||
!!@_pundit_policy_scoped
|
||||
end
|
||||
|
||||
# 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/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/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
|
||||
|
||||
# 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, Array] the object we're checking permissions of
|
||||
# @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 [Object] Always returns the passed object record
|
||||
def authorize(record, query = nil, policy_class: nil)
|
||||
query ||= "#{action_name}?"
|
||||
|
||||
@_pundit_policy_authorized = true
|
||||
|
||||
Pundit.authorize(pundit_user, record, query, policy_class: policy_class, cache: policies)
|
||||
end
|
||||
|
||||
# Allow this action not to perform authorization.
|
||||
#
|
||||
# @see https://github.com/varvet/pundit#ensuring-policies-and-scopes-are-used
|
||||
# @return [void]
|
||||
def skip_authorization
|
||||
@_pundit_policy_authorized = :skipped
|
||||
end
|
||||
|
||||
# Allow this action not to perform policy scoping.
|
||||
#
|
||||
# @see https://github.com/varvet/pundit#ensuring-policies-and-scopes-are-used
|
||||
# @return [void]
|
||||
def skip_policy_scope
|
||||
@_pundit_policy_scoped = :skipped
|
||||
end
|
||||
|
||||
# Retrieves the policy scope for the given record.
|
||||
#
|
||||
# @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, policy_scope_class: nil)
|
||||
@_pundit_policy_scoped = true
|
||||
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/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 `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/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 = action_name)
|
||||
policy = policy(record)
|
||||
method_name = if policy.respond_to?("permitted_attributes_for_#{action}")
|
||||
"permitted_attributes_for_#{action}"
|
||||
else
|
||||
"permitted_attributes"
|
||||
end
|
||||
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/varvet/pundit#customize-pundit-user
|
||||
# @return [Object] the user object to be used with pundit
|
||||
def pundit_user
|
||||
current_user
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def pundit_policy_scope(scope)
|
||||
policy_scopes[scope] ||= Pundit.policy_scope!(pundit_user, scope)
|
||||
end
|
||||
end
|
||||
end
|
258
spec/authorization_spec.rb
Normal file
258
spec/authorization_spec.rb
Normal file
|
@ -0,0 +1,258 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "spec_helper"
|
||||
|
||||
describe Pundit::Authorization do
|
||||
let(:controller) { Controller.new(user, "update", {}) }
|
||||
let(:user) { double }
|
||||
let(:post) { Post.new(user) }
|
||||
let(:customer_post) { Customer::Post.new(user) }
|
||||
let(:comment) { Comment.new }
|
||||
let(:article) { Article.new }
|
||||
let(:article_tag) { ArticleTag.new }
|
||||
let(:wiki) { Wiki.new }
|
||||
|
||||
describe "#verify_authorized" do
|
||||
it "does nothing when authorized" do
|
||||
controller.authorize(post)
|
||||
controller.verify_authorized
|
||||
end
|
||||
|
||||
it "raises an exception when not authorized" do
|
||||
expect { controller.verify_authorized }.to raise_error(Pundit::AuthorizationNotPerformedError)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#verify_policy_scoped" do
|
||||
it "does nothing when policy_scope is used" do
|
||||
controller.policy_scope(Post)
|
||||
controller.verify_policy_scoped
|
||||
end
|
||||
|
||||
it "raises an exception when policy_scope is not used" do
|
||||
expect { controller.verify_policy_scoped }.to raise_error(Pundit::PolicyScopingNotPerformedError)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#pundit_policy_authorized?" do
|
||||
it "is true when authorized" do
|
||||
controller.authorize(post)
|
||||
expect(controller.pundit_policy_authorized?).to be true
|
||||
end
|
||||
|
||||
it "is false when not authorized" do
|
||||
expect(controller.pundit_policy_authorized?).to be false
|
||||
end
|
||||
end
|
||||
|
||||
describe "#pundit_policy_scoped?" do
|
||||
it "is true when policy_scope is used" do
|
||||
controller.policy_scope(Post)
|
||||
expect(controller.pundit_policy_scoped?).to be true
|
||||
end
|
||||
|
||||
it "is false when policy scope is not used" do
|
||||
expect(controller.pundit_policy_scoped?).to be false
|
||||
end
|
||||
end
|
||||
|
||||
describe "#authorize" do
|
||||
it "infers the policy name and authorizes based on it" do
|
||||
expect(controller.authorize(post)).to be_truthy
|
||||
end
|
||||
|
||||
it "returns the record on successful authorization" do
|
||||
expect(controller.authorize(post)).to eq(post)
|
||||
end
|
||||
|
||||
it "returns the record when passed record with namespace " do
|
||||
expect(controller.authorize([:project, comment], :update?)).to eq(comment)
|
||||
end
|
||||
|
||||
it "returns the record when passed record with nested namespace " do
|
||||
expect(controller.authorize([:project, :admin, comment], :update?)).to eq(comment)
|
||||
end
|
||||
|
||||
it "returns the policy name symbol when passed record with headless policy" do
|
||||
expect(controller.authorize(:publication, :create?)).to eq(:publication)
|
||||
end
|
||||
|
||||
it "returns the class when passed record not a particular instance" do
|
||||
expect(controller.authorize(Post, :show?)).to eq(Post)
|
||||
end
|
||||
|
||||
it "can be given a different permission to check" do
|
||||
expect(controller.authorize(post, :show?)).to be_truthy
|
||||
expect { controller.authorize(post, :destroy?) }.to raise_error(Pundit::NotAuthorizedError)
|
||||
end
|
||||
|
||||
it "can be given a different policy class" do
|
||||
expect(controller.authorize(post, :create?, policy_class: PublicationPolicy)).to be_truthy
|
||||
end
|
||||
|
||||
it "works with anonymous class policies" do
|
||||
expect(controller.authorize(article_tag, :show?)).to be_truthy
|
||||
expect { controller.authorize(article_tag, :destroy?) }.to raise_error(Pundit::NotAuthorizedError)
|
||||
end
|
||||
|
||||
it "throws an exception when the permission check fails" do
|
||||
expect { controller.authorize(Post.new) }.to raise_error(Pundit::NotAuthorizedError)
|
||||
end
|
||||
|
||||
it "throws an exception when a policy cannot be found" do
|
||||
expect { controller.authorize(Article) }.to raise_error(Pundit::NotDefinedError)
|
||||
end
|
||||
|
||||
it "caches the policy" do
|
||||
expect(controller.policies[post]).to be_nil
|
||||
controller.authorize(post)
|
||||
expect(controller.policies[post]).not_to be_nil
|
||||
end
|
||||
|
||||
it "raises an error when the given record is nil" do
|
||||
expect { controller.authorize(nil, :destroy?) }.to raise_error(Pundit::NotAuthorizedError)
|
||||
end
|
||||
|
||||
it "raises an error with a invalid policy constructor" do
|
||||
expect { controller.authorize(wiki, :destroy?) }.to raise_error(Pundit::InvalidConstructorError)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#skip_authorization" do
|
||||
it "disables authorization verification" do
|
||||
controller.skip_authorization
|
||||
expect { controller.verify_authorized }.not_to raise_error
|
||||
end
|
||||
end
|
||||
|
||||
describe "#skip_policy_scope" do
|
||||
it "disables policy scope verification" do
|
||||
controller.skip_policy_scope
|
||||
expect { controller.verify_policy_scoped }.not_to raise_error
|
||||
end
|
||||
end
|
||||
|
||||
describe "#pundit_user" do
|
||||
it "returns the same thing as current_user" do
|
||||
expect(controller.pundit_user).to eq controller.current_user
|
||||
end
|
||||
end
|
||||
|
||||
describe "#policy" do
|
||||
it "returns an instantiated policy" do
|
||||
policy = controller.policy(post)
|
||||
expect(policy.user).to eq user
|
||||
expect(policy.post).to eq post
|
||||
end
|
||||
|
||||
it "throws an exception if the given policy can't be found" do
|
||||
expect { controller.policy(article) }.to raise_error(Pundit::NotDefinedError)
|
||||
end
|
||||
|
||||
it "raises an error with a invalid policy constructor" do
|
||||
expect { controller.policy(wiki) }.to raise_error(Pundit::InvalidConstructorError)
|
||||
end
|
||||
|
||||
it "allows policy to be injected" do
|
||||
new_policy = OpenStruct.new
|
||||
controller.policies[post] = new_policy
|
||||
|
||||
expect(controller.policy(post)).to eq new_policy
|
||||
end
|
||||
end
|
||||
|
||||
describe "#policy_scope" do
|
||||
it "returns an instantiated policy scope" do
|
||||
expect(controller.policy_scope(Post)).to eq :published
|
||||
end
|
||||
|
||||
it "allows policy scope class to be overriden" do
|
||||
expect(controller.policy_scope(Post, policy_scope_class: PublicationPolicy::Scope)).to eq :published
|
||||
end
|
||||
|
||||
it "throws an exception if the given policy can't be found" do
|
||||
expect { controller.policy_scope(Article) }.to raise_error(Pundit::NotDefinedError)
|
||||
end
|
||||
|
||||
it "raises an error with a invalid policy scope constructor" do
|
||||
expect { controller.policy_scope(Wiki) }.to raise_error(Pundit::InvalidConstructorError)
|
||||
end
|
||||
|
||||
it "allows policy_scope to be injected" do
|
||||
new_scope = OpenStruct.new
|
||||
controller.policy_scopes[Post] = new_scope
|
||||
|
||||
expect(controller.policy_scope(Post)).to eq new_scope
|
||||
end
|
||||
end
|
||||
|
||||
describe "#permitted_attributes" do
|
||||
it "checks policy for permitted attributes" do
|
||||
params = ActionController::Parameters.new(
|
||||
post: {
|
||||
title: "Hello",
|
||||
votes: 5,
|
||||
admin: true
|
||||
}
|
||||
)
|
||||
|
||||
action = "update"
|
||||
|
||||
expect(Controller.new(user, action, params).permitted_attributes(post).to_h).to eq(
|
||||
"title" => "Hello",
|
||||
"votes" => 5
|
||||
)
|
||||
expect(Controller.new(double, action, params).permitted_attributes(post).to_h).to eq("votes" => 5)
|
||||
end
|
||||
|
||||
it "checks policy for permitted attributes for record of a ActiveModel type" do
|
||||
params = ActionController::Parameters.new(
|
||||
customer_post: {
|
||||
title: "Hello",
|
||||
votes: 5,
|
||||
admin: true
|
||||
}
|
||||
)
|
||||
|
||||
action = "update"
|
||||
|
||||
expect(Controller.new(user, action, params).permitted_attributes(customer_post).to_h).to eq(
|
||||
"title" => "Hello",
|
||||
"votes" => 5
|
||||
)
|
||||
expect(Controller.new(double, action, params).permitted_attributes(customer_post).to_h).to eq(
|
||||
"votes" => 5
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#permitted_attributes_for_action" do
|
||||
it "is checked if it is defined in the policy" do
|
||||
params = ActionController::Parameters.new(
|
||||
post: {
|
||||
title: "Hello",
|
||||
body: "blah",
|
||||
votes: 5,
|
||||
admin: true
|
||||
}
|
||||
)
|
||||
|
||||
action = "revise"
|
||||
expect(Controller.new(user, action, params).permitted_attributes(post).to_h).to eq("body" => "blah")
|
||||
end
|
||||
|
||||
it "can be explicitly set" do
|
||||
params = ActionController::Parameters.new(
|
||||
post: {
|
||||
title: "Hello",
|
||||
body: "blah",
|
||||
votes: 5,
|
||||
admin: true
|
||||
}
|
||||
)
|
||||
|
||||
action = "update"
|
||||
expect(Controller.new(user, action, params).permitted_attributes(post, :revise).to_h).to eq("body" => "blah")
|
||||
end
|
||||
end
|
||||
end
|
|
@ -10,7 +10,6 @@ RSpec.describe Pundit do
|
|||
let(:comment) { Comment.new }
|
||||
let(:comment_four_five_six) { CommentFourFiveSix.new }
|
||||
let(:article) { Article.new }
|
||||
let(:controller) { Controller.new(user, "update", {}) }
|
||||
let(:artificial_blog) { ArtificialBlog.new }
|
||||
let(:article_tag) { ArticleTag.new }
|
||||
let(:comments_relation) { CommentsRelation.new }
|
||||
|
@ -18,7 +17,6 @@ RSpec.describe Pundit do
|
|||
let(:tag_four_five_six) { ProjectOneTwoThree::TagFourFiveSix.new(user) }
|
||||
let(:avatar_four_five_six) { ProjectOneTwoThree::AvatarFourFiveSix.new }
|
||||
let(:wiki) { Wiki.new }
|
||||
let(:thread) { Thread.new }
|
||||
|
||||
describe ".authorize" do
|
||||
it "infers the policy and authorizes based on it" do
|
||||
|
@ -397,252 +395,21 @@ RSpec.describe Pundit do
|
|||
end
|
||||
end
|
||||
|
||||
describe "#verify_authorized" do
|
||||
it "does nothing when authorized" do
|
||||
controller.authorize(post)
|
||||
controller.verify_authorized
|
||||
describe ".included" do
|
||||
it "includes Authorization module" do
|
||||
klass = Class.new
|
||||
klass.include Pundit
|
||||
|
||||
expect(klass).to include Pundit::Authorization
|
||||
end
|
||||
|
||||
it "raises an exception when not authorized" do
|
||||
expect { controller.verify_authorized }.to raise_error(Pundit::AuthorizationNotPerformedError)
|
||||
end
|
||||
end
|
||||
it "warns about deprecation" do
|
||||
allow(Pundit).to receive(:warn)
|
||||
|
||||
describe "#verify_policy_scoped" do
|
||||
it "does nothing when policy_scope is used" do
|
||||
controller.policy_scope(Post)
|
||||
controller.verify_policy_scoped
|
||||
end
|
||||
klass = Class.new
|
||||
klass.include Pundit
|
||||
|
||||
it "raises an exception when policy_scope is not used" do
|
||||
expect { controller.verify_policy_scoped }.to raise_error(Pundit::PolicyScopingNotPerformedError)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#pundit_policy_authorized?" do
|
||||
it "is true when authorized" do
|
||||
controller.authorize(post)
|
||||
expect(controller.pundit_policy_authorized?).to be true
|
||||
end
|
||||
|
||||
it "is false when not authorized" do
|
||||
expect(controller.pundit_policy_authorized?).to be false
|
||||
end
|
||||
end
|
||||
|
||||
describe "#pundit_policy_scoped?" do
|
||||
it "is true when policy_scope is used" do
|
||||
controller.policy_scope(Post)
|
||||
expect(controller.pundit_policy_scoped?).to be true
|
||||
end
|
||||
|
||||
it "is false when policy scope is not used" do
|
||||
expect(controller.pundit_policy_scoped?).to be false
|
||||
end
|
||||
end
|
||||
|
||||
describe "#authorize" do
|
||||
it "infers the policy name and authorizes based on it" do
|
||||
expect(controller.authorize(post)).to be_truthy
|
||||
end
|
||||
|
||||
it "returns the record on successful authorization" do
|
||||
expect(controller.authorize(post)).to eq(post)
|
||||
end
|
||||
|
||||
it "returns the record when passed record with namespace " do
|
||||
expect(controller.authorize([:project, comment], :update?)).to eq(comment)
|
||||
end
|
||||
|
||||
it "returns the record when passed record with nested namespace " do
|
||||
expect(controller.authorize([:project, :admin, comment], :update?)).to eq(comment)
|
||||
end
|
||||
|
||||
it "returns the policy name symbol when passed record with headless policy" do
|
||||
expect(controller.authorize(:publication, :create?)).to eq(:publication)
|
||||
end
|
||||
|
||||
it "returns the class when passed record not a particular instance" do
|
||||
expect(controller.authorize(Post, :show?)).to eq(Post)
|
||||
end
|
||||
|
||||
it "can be given a different permission to check" do
|
||||
expect(controller.authorize(post, :show?)).to be_truthy
|
||||
expect { controller.authorize(post, :destroy?) }.to raise_error(Pundit::NotAuthorizedError)
|
||||
end
|
||||
|
||||
it "can be given a different policy class" do
|
||||
expect(controller.authorize(post, :create?, policy_class: PublicationPolicy)).to be_truthy
|
||||
end
|
||||
|
||||
it "can be given a different policy class using namespaces" do
|
||||
expect(PublicationPolicy).to receive(:new).with(user, comment).and_call_original
|
||||
expect(controller.authorize([:project, comment], :create?, policy_class: PublicationPolicy)).to eql(comment)
|
||||
end
|
||||
|
||||
it "works with anonymous class policies" do
|
||||
expect(controller.authorize(article_tag, :show?)).to be_truthy
|
||||
expect { controller.authorize(article_tag, :destroy?) }.to raise_error(Pundit::NotAuthorizedError)
|
||||
end
|
||||
|
||||
it "throws an exception when the permission check fails" do
|
||||
expect { controller.authorize(Post.new) }.to raise_error(Pundit::NotAuthorizedError)
|
||||
end
|
||||
|
||||
it "throws an exception when a policy cannot be found" do
|
||||
expect { controller.authorize(Article) }.to raise_error(Pundit::NotDefinedError)
|
||||
end
|
||||
|
||||
it "caches the policy" do
|
||||
expect(controller.policies[post]).to be_nil
|
||||
controller.authorize(post)
|
||||
expect(controller.policies[post]).not_to be_nil
|
||||
end
|
||||
|
||||
it "raises an error when the given record is nil" do
|
||||
expect { controller.authorize(nil, :destroy?) }.to raise_error(Pundit::NotAuthorizedError)
|
||||
end
|
||||
|
||||
it "raises an error with a invalid policy constructor" do
|
||||
expect { controller.authorize(wiki, :destroy?) }.to raise_error(Pundit::InvalidConstructorError)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#skip_authorization" do
|
||||
it "disables authorization verification" do
|
||||
controller.skip_authorization
|
||||
expect { controller.verify_authorized }.not_to raise_error
|
||||
end
|
||||
end
|
||||
|
||||
describe "#skip_policy_scope" do
|
||||
it "disables policy scope verification" do
|
||||
controller.skip_policy_scope
|
||||
expect { controller.verify_policy_scoped }.not_to raise_error
|
||||
end
|
||||
end
|
||||
|
||||
describe "#pundit_user" do
|
||||
it "returns the same thing as current_user" do
|
||||
expect(controller.pundit_user).to eq controller.current_user
|
||||
end
|
||||
end
|
||||
|
||||
describe "#policy" do
|
||||
it "returns an instantiated policy" do
|
||||
policy = controller.policy(post)
|
||||
expect(policy.user).to eq user
|
||||
expect(policy.post).to eq post
|
||||
end
|
||||
|
||||
it "throws an exception if the given policy can't be found" do
|
||||
expect { controller.policy(article) }.to raise_error(Pundit::NotDefinedError)
|
||||
end
|
||||
|
||||
it "raises an error with a invalid policy constructor" do
|
||||
expect { controller.policy(wiki) }.to raise_error(Pundit::InvalidConstructorError)
|
||||
end
|
||||
|
||||
it "allows policy to be injected" do
|
||||
new_policy = OpenStruct.new
|
||||
controller.policies[post] = new_policy
|
||||
|
||||
expect(controller.policy(post)).to eq new_policy
|
||||
end
|
||||
end
|
||||
|
||||
describe "#policy_scope" do
|
||||
it "returns an instantiated policy scope" do
|
||||
expect(controller.policy_scope(Post)).to eq :published
|
||||
end
|
||||
|
||||
it "allows policy scope class to be overriden" do
|
||||
expect(controller.policy_scope(Post, policy_scope_class: PublicationPolicy::Scope)).to eq :published
|
||||
end
|
||||
|
||||
it "throws an exception if the given policy can't be found" do
|
||||
expect { controller.policy_scope(Article) }.to raise_error(Pundit::NotDefinedError)
|
||||
end
|
||||
|
||||
it "raises an error with a invalid policy scope constructor" do
|
||||
expect { controller.policy_scope(Wiki) }.to raise_error(Pundit::InvalidConstructorError)
|
||||
end
|
||||
|
||||
it "allows policy_scope to be injected" do
|
||||
new_scope = OpenStruct.new
|
||||
controller.policy_scopes[Post] = new_scope
|
||||
|
||||
expect(controller.policy_scope(Post)).to eq new_scope
|
||||
end
|
||||
end
|
||||
|
||||
describe "#permitted_attributes" do
|
||||
it "checks policy for permitted attributes" do
|
||||
params = ActionController::Parameters.new(
|
||||
post: {
|
||||
title: "Hello",
|
||||
votes: 5,
|
||||
admin: true
|
||||
}
|
||||
)
|
||||
|
||||
action = "update"
|
||||
|
||||
expect(Controller.new(user, action, params).permitted_attributes(post).to_h).to eq(
|
||||
"title" => "Hello",
|
||||
"votes" => 5
|
||||
)
|
||||
expect(Controller.new(double, action, params).permitted_attributes(post).to_h).to eq("votes" => 5)
|
||||
end
|
||||
|
||||
it "checks policy for permitted attributes for record of a ActiveModel type" do
|
||||
params = ActionController::Parameters.new(
|
||||
customer_post: {
|
||||
title: "Hello",
|
||||
votes: 5,
|
||||
admin: true
|
||||
}
|
||||
)
|
||||
|
||||
action = "update"
|
||||
|
||||
expect(Controller.new(user, action, params).permitted_attributes(customer_post).to_h).to eq(
|
||||
"title" => "Hello",
|
||||
"votes" => 5
|
||||
)
|
||||
expect(Controller.new(double, action, params).permitted_attributes(customer_post).to_h).to eq(
|
||||
"votes" => 5
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#permitted_attributes_for_action" do
|
||||
it "is checked if it is defined in the policy" do
|
||||
params = ActionController::Parameters.new(
|
||||
post: {
|
||||
title: "Hello",
|
||||
body: "blah",
|
||||
votes: 5,
|
||||
admin: true
|
||||
}
|
||||
)
|
||||
|
||||
action = "revise"
|
||||
expect(Controller.new(user, action, params).permitted_attributes(post).to_h).to eq("body" => "blah")
|
||||
end
|
||||
|
||||
it "can be explicitly set" do
|
||||
params = ActionController::Parameters.new(
|
||||
post: {
|
||||
title: "Hello",
|
||||
body: "blah",
|
||||
votes: 5,
|
||||
admin: true
|
||||
}
|
||||
)
|
||||
|
||||
action = "update"
|
||||
expect(Controller.new(user, action, params).permitted_attributes(post, :revise).to_h).to eq("body" => "blah")
|
||||
expect(Pundit).to have_received(:warn).with start_with("[DEPRECATION]")
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -200,10 +200,10 @@ class DenierPolicy < Struct.new(:user, :record)
|
|||
end
|
||||
|
||||
class Controller
|
||||
include Pundit
|
||||
include Pundit::Authorization
|
||||
# Mark protected methods public so they may be called in test
|
||||
# rubocop:disable Style/AccessModifierDeclarations
|
||||
public(*Pundit.protected_instance_methods)
|
||||
public(*Pundit::Authorization.protected_instance_methods)
|
||||
# rubocop:enable Style/AccessModifierDeclarations
|
||||
|
||||
attr_reader :current_user, :action_name, :params
|
||||
|
|
Loading…
Add table
Reference in a new issue