diff --git a/CHANGELOG.md b/CHANGELOG.md index d2af09d..fe941da 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/README.md b/README.md index 1bdea7c..15a6a96 100644 --- a/README.md +++ b/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) diff --git a/lib/pundit.rb b/lib/pundit.rb index a7f9865..0ebda55 100644 --- a/lib/pundit.rb +++ b/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 diff --git a/lib/pundit/authorization.rb b/lib/pundit/authorization.rb new file mode 100644 index 0000000..1231f2a --- /dev/null +++ b/lib/pundit/authorization.rb @@ -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 diff --git a/spec/authorization_spec.rb b/spec/authorization_spec.rb new file mode 100644 index 0000000..2995bfd --- /dev/null +++ b/spec/authorization_spec.rb @@ -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 diff --git a/spec/pundit_spec.rb b/spec/pundit_spec.rb index 6b8fb19..5f3bdb5 100644 --- a/spec/pundit_spec.rb +++ b/spec/pundit_spec.rb @@ -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 diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index ad9f8ae..4595d25 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -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