pundit 1.1.0 → 2.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,7 +1,6 @@
1
1
  require 'test_helper'
2
2
 
3
3
  class <%= class_name %>PolicyTest < ActiveSupport::TestCase
4
-
5
4
  def test_scope
6
5
  end
7
6
 
@@ -0,0 +1,168 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pundit
4
+ module Authorization
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ helper Helper if respond_to?(:helper)
9
+ if respond_to?(:helper_method)
10
+ helper_method :policy
11
+ helper_method :pundit_policy_scope
12
+ helper_method :pundit_user
13
+ end
14
+ end
15
+
16
+ protected
17
+
18
+ # @return [Boolean] whether authorization has been performed, i.e. whether
19
+ # one {#authorize} or {#skip_authorization} has been called
20
+ def pundit_policy_authorized?
21
+ !!@_pundit_policy_authorized
22
+ end
23
+
24
+ # @return [Boolean] whether policy scoping has been performed, i.e. whether
25
+ # one {#policy_scope} or {#skip_policy_scope} has been called
26
+ def pundit_policy_scoped?
27
+ !!@_pundit_policy_scoped
28
+ end
29
+
30
+ # Raises an error if authorization has not been performed, usually used as an
31
+ # `after_action` filter to prevent programmer error in forgetting to call
32
+ # {#authorize} or {#skip_authorization}.
33
+ #
34
+ # @see https://github.com/varvet/pundit#ensuring-policies-and-scopes-are-used
35
+ # @raise [AuthorizationNotPerformedError] if authorization has not been performed
36
+ # @return [void]
37
+ def verify_authorized
38
+ raise AuthorizationNotPerformedError, self.class unless pundit_policy_authorized?
39
+ end
40
+
41
+ # Raises an error if policy scoping has not been performed, usually used as an
42
+ # `after_action` filter to prevent programmer error in forgetting to call
43
+ # {#policy_scope} or {#skip_policy_scope} in index actions.
44
+ #
45
+ # @see https://github.com/varvet/pundit#ensuring-policies-and-scopes-are-used
46
+ # @raise [AuthorizationNotPerformedError] if policy scoping has not been performed
47
+ # @return [void]
48
+ def verify_policy_scoped
49
+ raise PolicyScopingNotPerformedError, self.class unless pundit_policy_scoped?
50
+ end
51
+
52
+ # Retrieves the policy for the given record, initializing it with the record
53
+ # and current user and finally throwing an error if the user is not
54
+ # authorized to perform the given action.
55
+ #
56
+ # @param record [Object, Array] the object we're checking permissions of
57
+ # @param query [Symbol, String] the predicate method to check on the policy (e.g. `:show?`).
58
+ # If omitted then this defaults to the Rails controller action name.
59
+ # @param policy_class [Class] the policy class we want to force use of
60
+ # @raise [NotAuthorizedError] if the given query method returned false
61
+ # @return [Object] Always returns the passed object record
62
+ def authorize(record, query = nil, policy_class: nil)
63
+ query ||= "#{action_name}?"
64
+
65
+ @_pundit_policy_authorized = true
66
+
67
+ Pundit.authorize(pundit_user, record, query, policy_class: policy_class, cache: policies)
68
+ end
69
+
70
+ # Allow this action not to perform authorization.
71
+ #
72
+ # @see https://github.com/varvet/pundit#ensuring-policies-and-scopes-are-used
73
+ # @return [void]
74
+ def skip_authorization
75
+ @_pundit_policy_authorized = :skipped
76
+ end
77
+
78
+ # Allow this action not to perform policy scoping.
79
+ #
80
+ # @see https://github.com/varvet/pundit#ensuring-policies-and-scopes-are-used
81
+ # @return [void]
82
+ def skip_policy_scope
83
+ @_pundit_policy_scoped = :skipped
84
+ end
85
+
86
+ # Retrieves the policy scope for the given record.
87
+ #
88
+ # @see https://github.com/varvet/pundit#scopes
89
+ # @param scope [Object] the object we're retrieving the policy scope for
90
+ # @param policy_scope_class [Class] the policy scope class we want to force use of
91
+ # @return [Scope{#resolve}, nil] instance of scope class which can resolve to a scope
92
+ def policy_scope(scope, policy_scope_class: nil)
93
+ @_pundit_policy_scoped = true
94
+ policy_scope_class ? policy_scope_class.new(pundit_user, scope).resolve : pundit_policy_scope(scope)
95
+ end
96
+
97
+ # Retrieves the policy for the given record.
98
+ #
99
+ # @see https://github.com/varvet/pundit#policies
100
+ # @param record [Object] the object we're retrieving the policy for
101
+ # @return [Object, nil] instance of policy class with query methods
102
+ def policy(record)
103
+ policies[record] ||= Pundit.policy!(pundit_user, record)
104
+ end
105
+
106
+ # Retrieves a set of permitted attributes from the policy by instantiating
107
+ # the policy class for the given record and calling `permitted_attributes` on
108
+ # it, or `permitted_attributes_for_{action}` if `action` is defined. It then infers
109
+ # what key the record should have in the params hash and retrieves the
110
+ # permitted attributes from the params hash under that key.
111
+ #
112
+ # @see https://github.com/varvet/pundit#strong-parameters
113
+ # @param record [Object] the object we're retrieving permitted attributes for
114
+ # @param action [Symbol, String] the name of the action being performed on the record (e.g. `:update`).
115
+ # If omitted then this defaults to the Rails controller action name.
116
+ # @return [Hash{String => Object}] the permitted attributes
117
+ def permitted_attributes(record, action = action_name)
118
+ policy = policy(record)
119
+ method_name = if policy.respond_to?("permitted_attributes_for_#{action}")
120
+ "permitted_attributes_for_#{action}"
121
+ else
122
+ "permitted_attributes"
123
+ end
124
+ pundit_params_for(record).permit(*policy.public_send(method_name))
125
+ end
126
+
127
+ # Retrieves the params for the given record.
128
+ #
129
+ # @param record [Object] the object we're retrieving params for
130
+ # @return [ActionController::Parameters] the params
131
+ def pundit_params_for(record)
132
+ params.require(PolicyFinder.new(record).param_key)
133
+ end
134
+
135
+ # Cache of policies. You should not rely on this method.
136
+ #
137
+ # @api private
138
+ # rubocop:disable Naming/MemoizedInstanceVariableName
139
+ def policies
140
+ @_pundit_policies ||= {}
141
+ end
142
+ # rubocop:enable Naming/MemoizedInstanceVariableName
143
+
144
+ # Cache of policy scope. You should not rely on this method.
145
+ #
146
+ # @api private
147
+ # rubocop:disable Naming/MemoizedInstanceVariableName
148
+ def policy_scopes
149
+ @_pundit_policy_scopes ||= {}
150
+ end
151
+ # rubocop:enable Naming/MemoizedInstanceVariableName
152
+
153
+ # Hook method which allows customizing which user is passed to policies and
154
+ # scopes initialized by {#authorize}, {#policy} and {#policy_scope}.
155
+ #
156
+ # @see https://github.com/varvet/pundit#customize-pundit-user
157
+ # @return [Object] the user object to be used with pundit
158
+ def pundit_user
159
+ current_user
160
+ end
161
+
162
+ private
163
+
164
+ def pundit_policy_scope(scope)
165
+ policy_scopes[scope] ||= Pundit.policy_scope!(pundit_user, scope)
166
+ end
167
+ end
168
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Pundit
2
4
  # Finds policy and scope classes for given object.
3
5
  # @api public
@@ -17,75 +19,69 @@ module Pundit
17
19
  end
18
20
 
19
21
  # @return [nil, Scope{#resolve}] scope class which can resolve to a scope
20
- # @see https://github.com/elabs/pundit#scopes
22
+ # @see https://github.com/varvet/pundit#scopes
21
23
  # @example
22
24
  # scope = finder.scope #=> UserPolicy::Scope
23
25
  # scope.resolve #=> <#ActiveRecord::Relation ...>
24
26
  #
25
27
  def scope
26
- policy::Scope if policy
27
- rescue NameError
28
- nil
28
+ "#{policy}::Scope".safe_constantize
29
29
  end
30
30
 
31
31
  # @return [nil, Class] policy class with query methods
32
- # @see https://github.com/elabs/pundit#policies
32
+ # @see https://github.com/varvet/pundit#policies
33
33
  # @example
34
34
  # policy = finder.policy #=> UserPolicy
35
35
  # policy.show? #=> true
36
36
  # policy.update? #=> false
37
37
  #
38
38
  def policy
39
- klass = find
40
- klass = klass.constantize if klass.is_a?(String)
41
- klass
42
- rescue NameError
43
- nil
39
+ klass = find(object)
40
+ klass.is_a?(String) ? klass.safe_constantize : klass
44
41
  end
45
42
 
46
43
  # @return [Scope{#resolve}] scope class which can resolve to a scope
47
44
  # @raise [NotDefinedError] if scope could not be determined
48
45
  #
49
46
  def scope!
50
- raise NotDefinedError, "unable to find policy scope of nil" if object.nil?
51
- scope or raise NotDefinedError, "unable to find scope `#{find}::Scope` for `#{object.inspect}`"
47
+ scope or raise NotDefinedError, "unable to find scope `#{find(object)}::Scope` for `#{object.inspect}`"
52
48
  end
53
49
 
54
50
  # @return [Class] policy class with query methods
55
51
  # @raise [NotDefinedError] if policy could not be determined
56
52
  #
57
53
  def policy!
58
- raise NotDefinedError, "unable to find policy of nil" if object.nil?
59
- policy or raise NotDefinedError, "unable to find policy `#{find}` for `#{object.inspect}`"
54
+ policy or raise NotDefinedError, "unable to find policy `#{find(object)}` for `#{object.inspect}`"
60
55
  end
61
56
 
62
57
  # @return [String] the name of the key this object would have in a params hash
63
58
  #
64
59
  def param_key
65
- if object.respond_to?(:model_name)
66
- object.model_name.param_key.to_s
67
- elsif object.is_a?(Class)
68
- object.to_s.demodulize.underscore
60
+ model = object.is_a?(Array) ? object.last : object
61
+
62
+ if model.respond_to?(:model_name)
63
+ model.model_name.param_key.to_s
64
+ elsif model.is_a?(Class)
65
+ model.to_s.demodulize.underscore
69
66
  else
70
- object.class.to_s.demodulize.underscore
67
+ model.class.to_s.demodulize.underscore
71
68
  end
72
69
  end
73
70
 
74
- private
71
+ private
75
72
 
76
- def find
77
- if object.nil?
78
- nil
79
- elsif object.respond_to?(:policy_class)
80
- object.policy_class
81
- elsif object.class.respond_to?(:policy_class)
82
- object.class.policy_class
73
+ def find(subject)
74
+ if subject.is_a?(Array)
75
+ modules = subject.dup
76
+ last = modules.pop
77
+ context = modules.map { |x| find_class_name(x) }.join("::")
78
+ [context, find(last)].join("::")
79
+ elsif subject.respond_to?(:policy_class)
80
+ subject.policy_class
81
+ elsif subject.class.respond_to?(:policy_class)
82
+ subject.class.policy_class
83
83
  else
84
- klass = if object.is_a?(Array)
85
- object.map { |x| find_class_name(x) }.join("::")
86
- else
87
- find_class_name(object)
88
- end
84
+ klass = find_class_name(subject)
89
85
  "#{klass}#{SUFFIX}"
90
86
  end
91
87
  end
data/lib/pundit/rspec.rb CHANGED
@@ -1,14 +1,15 @@
1
- require "active_support/core_ext/array/conversions"
1
+ # frozen_string_literal: true
2
2
 
3
3
  module Pundit
4
4
  module RSpec
5
5
  module Matchers
6
6
  extend ::RSpec::Matchers::DSL
7
7
 
8
+ # rubocop:disable Metrics/BlockLength
8
9
  matcher :permit do |user, record|
9
10
  match_proc = lambda do |policy|
10
11
  @violating_permissions = permissions.find_all do |permission|
11
- not policy.new(user, record).public_send(permission)
12
+ !policy.new(user, record).public_send(permission)
12
13
  end
13
14
  @violating_permissions.empty?
14
15
  end
@@ -22,14 +23,14 @@ module Pundit
22
23
 
23
24
  failure_message_proc = lambda do |policy|
24
25
  was_were = @violating_permissions.count > 1 ? "were" : "was"
25
- "Expected #{policy} to grant #{permissions.to_sentence} on \
26
- #{record} but #{@violating_permissions.to_sentence} #{was_were} not granted"
26
+ "Expected #{policy} to grant #{permissions.to_sentence} on " \
27
+ "#{record} but #{@violating_permissions.to_sentence} #{was_were} not granted"
27
28
  end
28
29
 
29
30
  failure_message_when_negated_proc = lambda do |policy|
30
31
  was_were = @violating_permissions.count > 1 ? "were" : "was"
31
- "Expected #{policy} not to grant #{permissions.to_sentence} on \
32
- #{record} but #{@violating_permissions.to_sentence} #{was_were} granted"
32
+ "Expected #{policy} not to grant #{permissions.to_sentence} on " \
33
+ "#{record} but #{@violating_permissions.to_sentence} #{was_were} granted"
33
34
  end
34
35
 
35
36
  if respond_to?(:match_when_negated)
@@ -49,6 +50,7 @@ module Pundit
49
50
  current_example.metadata[:permissions]
50
51
  end
51
52
  end
53
+ # rubocop:enable Metrics/BlockLength
52
54
  end
53
55
 
54
56
  module DSL
@@ -70,15 +72,9 @@ module Pundit
70
72
  end
71
73
 
72
74
  RSpec.configure do |config|
73
- if RSpec::Core::Version::STRING.split(".").first.to_i >= 3
74
- config.include(Pundit::RSpec::PolicyExampleGroup,
75
- type: :policy,
76
- file_path: %r{spec/policies}
77
- )
78
- else
79
- config.include(Pundit::RSpec::PolicyExampleGroup,
80
- type: :policy,
81
- example_group: { file_path: %r{spec/policies} }
82
- )
83
- end
75
+ config.include(
76
+ Pundit::RSpec::PolicyExampleGroup,
77
+ type: :policy,
78
+ file_path: %r{spec/policies}
79
+ )
84
80
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Pundit
2
- VERSION = "1.1.0"
4
+ VERSION = "2.2.0"
3
5
  end