Extract Pundit controller authorization module

This commit is contained in:
Jan Jedrychowski 2019-12-10 00:54:34 +01:00 committed by Kim Burgestrand
parent 46af6620b0
commit 4d9b5849f4
7 changed files with 454 additions and 414 deletions

View File

@ -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

View File

@ -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)

View File

@ -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
View 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
View 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

View File

@ -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

View File

@ -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