pundit 1.1.0 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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