From 2c80766b5179db99f3026de06f562f35ac73f2ba Mon Sep 17 00:00:00 2001 From: Pablo Crivella Date: Mon, 19 Dec 2016 11:54:01 +0100 Subject: [PATCH 1/4] Allow overriding policy class on authorize method --- README.md | 12 ++++++++++++ lib/pundit.rb | 5 +++-- spec/pundit_spec.rb | 4 ++++ spec/spec_helper.rb | 12 ++++++++++++ 4 files changed, 31 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 6dae878..eeaf10b 100644 --- a/README.md +++ b/README.md @@ -133,6 +133,18 @@ def publish end ``` +You can pass an argument to override the policy class if necessary. For example: + +```ruby +def create + @publication = find_publication # assume this method returns any model that behaves like a publication + # @publication.class => Post + authorize @publication, policy_class: PublicationPolicy + @publication.publish! + redirect_to @publication +end +``` + If you don't have an instance for the first argument to `authorize`, then you can pass the class. For example: diff --git a/lib/pundit.rb b/lib/pundit.rb index 7d0b088..48fe1db 100644 --- a/lib/pundit.rb +++ b/lib/pundit.rb @@ -189,14 +189,15 @@ protected # @param record [Object] 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 wan't 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) + def authorize(record, query = nil, policy_class: nil) query ||= "#{action_name}?" @_pundit_policy_authorized = true - policy = policy(record) + policy = policy_class ? policy_class.new(pundit_user, record) : policy(record) raise NotAuthorizedError, query: query, record: record, policy: policy unless policy.public_send(query) diff --git a/spec/pundit_spec.rb b/spec/pundit_spec.rb index c1d01ce..db3bbbe 100644 --- a/spec/pundit_spec.rb +++ b/spec/pundit_spec.rb @@ -375,6 +375,10 @@ describe Pundit do 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) diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 9671bae..0d48b86 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -91,6 +91,18 @@ class CommentPolicy < Struct.new(:user, :comment) end end +class PublicationPolicy < Struct.new(:user, :publication) + class Scope < Struct.new(:user, :scope) + def resolve + scope + end + end + + def create? + true + end +end + class Comment extend ActiveModel::Naming end From de564936517e0818c57553334fa14bbb3e67d607 Mon Sep 17 00:00:00 2001 From: Pablo Crivella Date: Mon, 19 Dec 2016 16:03:40 +0100 Subject: [PATCH 2/4] Allow to override policy scope class on controller#policy_scope method --- README.md | 9 +++++++++ lib/pundit.rb | 5 +++-- spec/pundit_spec.rb | 4 ++++ spec/spec_helper.rb | 2 +- 4 files changed, 17 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index eeaf10b..ca28773 100644 --- a/README.md +++ b/README.md @@ -283,6 +283,15 @@ def show end ``` +Like with the authorize method, you can also override the policy scope class: + +``` ruby +def index + # publication_class => Post + @publications = policy_scope(publication_class, policy_scope_class: PublicationPolicy::Scope) +end +``` + Just as with your policy, this will automatically infer that you want to use the `PostPolicy::Scope` class, it will instantiate this class and call `resolve` on the instance. In this case it is a shortcut for doing: diff --git a/lib/pundit.rb b/lib/pundit.rb index 48fe1db..7014361 100644 --- a/lib/pundit.rb +++ b/lib/pundit.rb @@ -224,10 +224,11 @@ protected # # @see https://github.com/elabs/pundit#scopes # @param scope [Object] the object we're retrieving the policy scope for + # @param policy_scope_class [Class] the policy scope class we wan't to force use of # @return [Scope{#resolve}, nil] instance of scope class which can resolve to a scope - def policy_scope(scope) + def policy_scope(scope, policy_scope_class: nil) @_pundit_policy_scoped = true - pundit_policy_scope(scope) + policy_scope_class ? policy_scope_class.new(pundit_user, scope).resolve : pundit_policy_scope(scope) end # Retrieves the policy for the given record. diff --git a/spec/pundit_spec.rb b/spec/pundit_spec.rb index db3bbbe..f38ca8e 100644 --- a/spec/pundit_spec.rb +++ b/spec/pundit_spec.rb @@ -455,6 +455,10 @@ describe Pundit 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 diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 0d48b86..cad590d 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -94,7 +94,7 @@ end class PublicationPolicy < Struct.new(:user, :publication) class Scope < Struct.new(:user, :scope) def resolve - scope + scope.published end end From 55eea50ab522fbb79f58acc9c4f563986fd16f2e Mon Sep 17 00:00:00 2001 From: Pablo Crivella Date: Sun, 1 Jul 2018 20:04:25 +0200 Subject: [PATCH 3/4] Add policy_class arg to Pundit.authorize method --- lib/pundit.rb | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/pundit.rb b/lib/pundit.rb index 7014361..89e33a4 100644 --- a/lib/pundit.rb +++ b/lib/pundit.rb @@ -61,10 +61,11 @@ module Pundit # @param user [Object] the user that initiated the action # @param record [Object] the object we're checking permissions of # @param query [Symbol, String] the predicate method to check on the policy (e.g. `:show?`) + # @param policy_class [Class] the policy class we want to force use of # @raise [NotAuthorizedError] if the given query method returned false # @return [Object] Always returns the passed object record - def authorize(user, record, query) - policy = policy!(user, record) + def authorize(user, record, query, policy_class: nil) + policy = policy_class ? policy_class.new(user, record) : policy!(user, record) raise NotAuthorizedError, query: query, record: record, policy: policy unless policy.public_send(query) @@ -189,7 +190,7 @@ protected # @param record [Object] 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 wan't to force use of + # @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) @@ -224,7 +225,7 @@ protected # # @see https://github.com/elabs/pundit#scopes # @param scope [Object] the object we're retrieving the policy scope for - # @param policy_scope_class [Class] the policy scope class we wan't to force use of + # @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 From 2cd4cc48d4a039c8a9eb011bb4ba93d97f209d57 Mon Sep 17 00:00:00 2001 From: Pablo Crivella Date: Sun, 1 Jul 2018 20:05:12 +0200 Subject: [PATCH 4/4] Add test for Pundit.authorize --- spec/pundit_spec.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/spec/pundit_spec.rb b/spec/pundit_spec.rb index f38ca8e..6d64054 100644 --- a/spec/pundit_spec.rb +++ b/spec/pundit_spec.rb @@ -22,6 +22,10 @@ describe Pundit do expect(Pundit.authorize(user, post, :update?)).to be_truthy end + it "can be given a different policy class" do + expect(Pundit.authorize(user, post, :create?, policy_class: PublicationPolicy)).to be_truthy + end + it "works with anonymous class policies" do expect(Pundit.authorize(user, article_tag, :show?)).to be_truthy expect { Pundit.authorize(user, article_tag, :destroy?) }.to raise_error(Pundit::NotAuthorizedError)