pundit 1.1.0 → 2.0.0.beta1

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.
data/Rakefile CHANGED
@@ -16,4 +16,3 @@ YARD::Rake::YardocTask.new do |t|
16
16
  end
17
17
 
18
18
  task default: :spec
19
- task default: :rubocop unless RUBY_ENGINE == "rbx"
@@ -1,7 +1,7 @@
1
1
  module Pundit
2
2
  module Generators
3
3
  class InstallGenerator < ::Rails::Generators::Base
4
- source_root File.expand_path(File.join(File.dirname(__FILE__), 'templates'))
4
+ source_root File.expand_path('templates', __dir__)
5
5
 
6
6
  def copy_application_policy
7
7
  template 'application_policy.rb', 'app/policies/application_policy.rb'
@@ -11,7 +11,7 @@ class ApplicationPolicy
11
11
  end
12
12
 
13
13
  def show?
14
- scope.where(:id => record.id).exists?
14
+ false
15
15
  end
16
16
 
17
17
  def create?
@@ -34,10 +34,6 @@ class ApplicationPolicy
34
34
  false
35
35
  end
36
36
 
37
- def scope
38
- Pundit.policy_scope!(user, record.class)
39
- end
40
-
41
37
  class Scope
42
38
  attr_reader :user, :scope
43
39
 
@@ -47,7 +43,7 @@ class ApplicationPolicy
47
43
  end
48
44
 
49
45
  def resolve
50
- scope
46
+ scope.all
51
47
  end
52
48
  end
53
49
  end
@@ -1,7 +1,7 @@
1
1
  module Pundit
2
2
  module Generators
3
3
  class PolicyGenerator < ::Rails::Generators::NamedBase
4
- source_root File.expand_path(File.join(File.dirname(__FILE__), 'templates'))
4
+ source_root File.expand_path('templates', __dir__)
5
5
 
6
6
  def create_policy
7
7
  template 'policy.rb', File.join('app/policies', class_path, "#{file_name}_policy.rb")
@@ -2,7 +2,7 @@
2
2
  class <%= class_name %>Policy < ApplicationPolicy
3
3
  class Scope < Scope
4
4
  def resolve
5
- scope
5
+ scope.all
6
6
  end
7
7
  end
8
8
  end
@@ -1,7 +1,7 @@
1
1
  module Rspec
2
2
  module Generators
3
3
  class PolicyGenerator < ::Rails::Generators::NamedBase
4
- source_root File.expand_path(File.join(File.dirname(__FILE__), 'templates'))
4
+ source_root File.expand_path('templates', __dir__)
5
5
 
6
6
  def create_policy_spec
7
7
  template 'policy_spec.rb', File.join('spec/policies', class_path, "#{file_name}_policy_spec.rb")
@@ -1,7 +1,6 @@
1
1
  require '<%= File.exists?('spec/rails_helper.rb') ? 'rails_helper' : 'spec_helper' %>'
2
2
 
3
3
  RSpec.describe <%= class_name %>Policy do
4
-
5
4
  let(:user) { User.new }
6
5
 
7
6
  subject { described_class }
@@ -1,7 +1,7 @@
1
1
  module TestUnit
2
2
  module Generators
3
3
  class PolicyGenerator < ::Rails::Generators::NamedBase
4
- source_root File.expand_path(File.join(File.dirname(__FILE__), 'templates'))
4
+ source_root File.expand_path('templates', __dir__)
5
5
 
6
6
  def create_policy_test
7
7
  template 'policy_test.rb', File.join('test/policies', class_path, "#{file_name}_policy_test.rb")
@@ -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
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "pundit/version"
2
4
  require "pundit/policy_finder"
3
5
  require "active_support/concern"
@@ -8,7 +10,7 @@ require "active_support/dependencies/autoload"
8
10
 
9
11
  # @api public
10
12
  module Pundit
11
- SUFFIX = "Policy"
13
+ SUFFIX = "Policy".freeze
12
14
 
13
15
  # @api private
14
16
  module Generators; end
@@ -16,7 +18,7 @@ module Pundit
16
18
  # @api private
17
19
  class Error < StandardError; end
18
20
 
19
- # Error that will be raiser when authorization has failed
21
+ # Error that will be raised when authorization has failed
20
22
  class NotAuthorizedError < Error
21
23
  attr_reader :query, :record, :policy
22
24
 
@@ -35,6 +37,9 @@ module Pundit
35
37
  end
36
38
  end
37
39
 
40
+ # Error that will be raised if a policy or policy scope constructor is not called correctly.
41
+ class InvalidConstructorError < Error; end
42
+
38
43
  # Error that will be raised if a controller action has not called the
39
44
  # `authorize` or `skip_authorization` methods.
40
45
  class AuthorizationNotPerformedError < Error; end
@@ -55,61 +60,80 @@ module Pundit
55
60
  #
56
61
  # @param user [Object] the user that initiated the action
57
62
  # @param record [Object] the object we're checking permissions of
58
- # @param record [Symbol] the query method to check on the policy (e.g. `:show?`)
63
+ # @param query [Symbol, String] the predicate method to check on the policy (e.g. `:show?`)
64
+ # @param policy_class [Class] the policy class we want to force use of
59
65
  # @raise [NotAuthorizedError] if the given query method returned false
60
- # @return [true] Always returns true
61
- def authorize(user, record, query)
62
- policy = policy!(user, record)
66
+ # @return [Object] Always returns the passed object record
67
+ def authorize(user, record, query, policy_class: nil)
68
+ policy = policy_class ? policy_class.new(user, record) : policy!(user, record)
63
69
 
64
- unless policy.public_send(query)
65
- raise NotAuthorizedError, query: query, record: record, policy: policy
66
- end
70
+ raise NotAuthorizedError, query: query, record: record, policy: policy unless policy.public_send(query)
67
71
 
68
- true
72
+ record
69
73
  end
70
74
 
71
75
  # Retrieves the policy scope for the given record.
72
76
  #
73
- # @see https://github.com/elabs/pundit#scopes
77
+ # @see https://github.com/varvet/pundit#scopes
74
78
  # @param user [Object] the user that initiated the action
75
- # @param record [Object] the object we're retrieving the policy scope for
79
+ # @param scope [Object] the object we're retrieving the policy scope for
80
+ # @raise [InvalidConstructorError] if the policy constructor called incorrectly
76
81
  # @return [Scope{#resolve}, nil] instance of scope class which can resolve to a scope
77
82
  def policy_scope(user, scope)
78
83
  policy_scope = PolicyFinder.new(scope).scope
79
- policy_scope.new(user, scope).resolve if policy_scope
84
+ policy_scope.new(user, pundit_model(scope)).resolve if policy_scope
85
+ rescue ArgumentError
86
+ raise InvalidConstructorError, "Invalid #<#{policy_scope}> constructor is called"
80
87
  end
81
88
 
82
89
  # Retrieves the policy scope for the given record.
83
90
  #
84
- # @see https://github.com/elabs/pundit#scopes
91
+ # @see https://github.com/varvet/pundit#scopes
85
92
  # @param user [Object] the user that initiated the action
86
- # @param record [Object] the object we're retrieving the policy scope for
93
+ # @param scope [Object] the object we're retrieving the policy scope for
87
94
  # @raise [NotDefinedError] if the policy scope cannot be found
95
+ # @raise [InvalidConstructorError] if the policy constructor called incorrectly
88
96
  # @return [Scope{#resolve}] instance of scope class which can resolve to a scope
89
97
  def policy_scope!(user, scope)
90
- PolicyFinder.new(scope).scope!.new(user, scope).resolve
98
+ policy_scope = PolicyFinder.new(scope).scope!
99
+ policy_scope.new(user, pundit_model(scope)).resolve
100
+ rescue ArgumentError
101
+ raise InvalidConstructorError, "Invalid #<#{policy_scope}> constructor is called"
91
102
  end
92
103
 
93
104
  # Retrieves the policy for the given record.
94
105
  #
95
- # @see https://github.com/elabs/pundit#policies
106
+ # @see https://github.com/varvet/pundit#policies
96
107
  # @param user [Object] the user that initiated the action
97
108
  # @param record [Object] the object we're retrieving the policy for
109
+ # @raise [InvalidConstructorError] if the policy constructor called incorrectly
98
110
  # @return [Object, nil] instance of policy class with query methods
99
111
  def policy(user, record)
100
112
  policy = PolicyFinder.new(record).policy
101
- policy.new(user, record) if policy
113
+ policy.new(user, pundit_model(record)) if policy
114
+ rescue ArgumentError
115
+ raise InvalidConstructorError, "Invalid #<#{policy}> constructor is called"
102
116
  end
103
117
 
104
118
  # Retrieves the policy for the given record.
105
119
  #
106
- # @see https://github.com/elabs/pundit#policies
120
+ # @see https://github.com/varvet/pundit#policies
107
121
  # @param user [Object] the user that initiated the action
108
122
  # @param record [Object] the object we're retrieving the policy for
109
123
  # @raise [NotDefinedError] if the policy cannot be found
124
+ # @raise [InvalidConstructorError] if the policy constructor called incorrectly
110
125
  # @return [Object] instance of policy class with query methods
111
126
  def policy!(user, record)
112
- PolicyFinder.new(record).policy!.new(user, record)
127
+ policy = PolicyFinder.new(record).policy!
128
+ policy.new(user, pundit_model(record))
129
+ rescue ArgumentError
130
+ raise InvalidConstructorError, "Invalid #<#{policy}> constructor is called"
131
+ end
132
+
133
+ private
134
+
135
+ def pundit_model(record)
136
+ record.is_a?(Array) ? record.last : record
113
137
  end
114
138
  end
115
139
 
@@ -127,23 +151,10 @@ module Pundit
127
151
  helper_method :pundit_policy_scope
128
152
  helper_method :pundit_user
129
153
  end
130
- if respond_to?(:hide_action)
131
- hide_action :policy
132
- hide_action :policy_scope
133
- hide_action :policies
134
- hide_action :policy_scopes
135
- hide_action :authorize
136
- hide_action :verify_authorized
137
- hide_action :verify_policy_scoped
138
- hide_action :permitted_attributes
139
- hide_action :pundit_user
140
- hide_action :skip_authorization
141
- hide_action :skip_policy_scope
142
- hide_action :pundit_policy_authorized?
143
- hide_action :pundit_policy_scoped?
144
- end
145
154
  end
146
155
 
156
+ protected
157
+
147
158
  # @return [Boolean] whether authorization has been performed, i.e. whether
148
159
  # one {#authorize} or {#skip_authorization} has been called
149
160
  def pundit_policy_authorized?
@@ -160,7 +171,7 @@ module Pundit
160
171
  # `after_action` filter to prevent programmer error in forgetting to call
161
172
  # {#authorize} or {#skip_authorization}.
162
173
  #
163
- # @see https://github.com/elabs/pundit#ensuring-policies-are-used
174
+ # @see https://github.com/varvet/pundit#ensuring-policies-and-scopes-are-used
164
175
  # @raise [AuthorizationNotPerformedError] if authorization has not been performed
165
176
  # @return [void]
166
177
  def verify_authorized
@@ -171,7 +182,7 @@ module Pundit
171
182
  # `after_action` filter to prevent programmer error in forgetting to call
172
183
  # {#policy_scope} or {#skip_policy_scope} in index actions.
173
184
  #
174
- # @see https://github.com/elabs/pundit#ensuring-policies-are-used
185
+ # @see https://github.com/varvet/pundit#ensuring-policies-and-scopes-are-used
175
186
  # @raise [AuthorizationNotPerformedError] if policy scoping has not been performed
176
187
  # @return [void]
177
188
  def verify_policy_scoped
@@ -183,26 +194,26 @@ module Pundit
183
194
  # authorized to perform the given action.
184
195
  #
185
196
  # @param record [Object] the object we're checking permissions of
186
- # @param record [Symbol, nil] the query method to check on the policy (e.g. `:show?`)
197
+ # @param query [Symbol, String] the predicate method to check on the policy (e.g. `:show?`).
198
+ # If omitted then this defaults to the Rails controller action name.
199
+ # @param policy_class [Class] the policy class we want to force use of
187
200
  # @raise [NotAuthorizedError] if the given query method returned false
188
- # @return [true] Always returns true
189
- def authorize(record, query = nil)
190
- query ||= params[:action].to_s + "?"
201
+ # @return [Object] Always returns the passed object record
202
+ def authorize(record, query = nil, policy_class: nil)
203
+ query ||= "#{action_name}?"
191
204
 
192
205
  @_pundit_policy_authorized = true
193
206
 
194
- policy = policy(record)
207
+ policy = policy_class ? policy_class.new(pundit_user, record) : policy(record)
195
208
 
196
- unless policy.public_send(query)
197
- raise NotAuthorizedError, query: query, record: record, policy: policy
198
- end
209
+ raise NotAuthorizedError, query: query, record: record, policy: policy unless policy.public_send(query)
199
210
 
200
- true
211
+ record
201
212
  end
202
213
 
203
214
  # Allow this action not to perform authorization.
204
215
  #
205
- # @see https://github.com/elabs/pundit#ensuring-policies-are-used
216
+ # @see https://github.com/varvet/pundit#ensuring-policies-and-scopes-are-used
206
217
  # @return [void]
207
218
  def skip_authorization
208
219
  @_pundit_policy_authorized = true
@@ -210,7 +221,7 @@ module Pundit
210
221
 
211
222
  # Allow this action not to perform policy scoping.
212
223
  #
213
- # @see https://github.com/elabs/pundit#ensuring-policies-are-used
224
+ # @see https://github.com/varvet/pundit#ensuring-policies-and-scopes-are-used
214
225
  # @return [void]
215
226
  def skip_policy_scope
216
227
  @_pundit_policy_scoped = true
@@ -218,17 +229,18 @@ module Pundit
218
229
 
219
230
  # Retrieves the policy scope for the given record.
220
231
  #
221
- # @see https://github.com/elabs/pundit#scopes
222
- # @param record [Object] the object we're retrieving the policy scope for
232
+ # @see https://github.com/varvet/pundit#scopes
233
+ # @param scope [Object] the object we're retrieving the policy scope for
234
+ # @param policy_scope_class [Class] the policy scope class we want to force use of
223
235
  # @return [Scope{#resolve}, nil] instance of scope class which can resolve to a scope
224
- def policy_scope(scope)
236
+ def policy_scope(scope, policy_scope_class: nil)
225
237
  @_pundit_policy_scoped = true
226
- pundit_policy_scope(scope)
238
+ policy_scope_class ? policy_scope_class.new(pundit_user, scope).resolve : pundit_policy_scope(scope)
227
239
  end
228
240
 
229
241
  # Retrieves the policy for the given record.
230
242
  #
231
- # @see https://github.com/elabs/pundit#policies
243
+ # @see https://github.com/varvet/pundit#policies
232
244
  # @param record [Object] the object we're retrieving the policy for
233
245
  # @return [Object, nil] instance of policy class with query methods
234
246
  def policy(record)
@@ -237,42 +249,55 @@ module Pundit
237
249
 
238
250
  # Retrieves a set of permitted attributes from the policy by instantiating
239
251
  # the policy class for the given record and calling `permitted_attributes` on
240
- # it, or `permitted_attributes_for_{action}` if it is defined. It then infers
252
+ # it, or `permitted_attributes_for_{action}` if `action` is defined. It then infers
241
253
  # what key the record should have in the params hash and retrieves the
242
254
  # permitted attributes from the params hash under that key.
243
255
  #
244
- # @see https://github.com/elabs/pundit#strong-parameters
256
+ # @see https://github.com/varvet/pundit#strong-parameters
245
257
  # @param record [Object] the object we're retrieving permitted attributes for
258
+ # @param action [Symbol, String] the name of the action being performed on the record (e.g. `:update`).
259
+ # If omitted then this defaults to the Rails controller action name.
246
260
  # @return [Hash{String => Object}] the permitted attributes
247
- def permitted_attributes(record, action = params[:action])
248
- param_key = PolicyFinder.new(record).param_key
261
+ def permitted_attributes(record, action = action_name)
249
262
  policy = policy(record)
250
263
  method_name = if policy.respond_to?("permitted_attributes_for_#{action}")
251
264
  "permitted_attributes_for_#{action}"
252
265
  else
253
266
  "permitted_attributes"
254
267
  end
255
- params.require(param_key).permit(policy.public_send(method_name))
268
+ pundit_params_for(record).permit(*policy.public_send(method_name))
269
+ end
270
+
271
+ # Retrieves the params for the given record.
272
+ #
273
+ # @param record [Object] the object we're retrieving params for
274
+ # @return [ActionController::Parameters] the params
275
+ def pundit_params_for(record)
276
+ params.require(PolicyFinder.new(record).param_key)
256
277
  end
257
278
 
258
279
  # Cache of policies. You should not rely on this method.
259
280
  #
260
281
  # @api private
282
+ # rubocop:disable Naming/MemoizedInstanceVariableName
261
283
  def policies
262
284
  @_pundit_policies ||= {}
263
285
  end
286
+ # rubocop:enable Naming/MemoizedInstanceVariableName
264
287
 
265
288
  # Cache of policy scope. You should not rely on this method.
266
289
  #
267
290
  # @api private
291
+ # rubocop:disable Naming/MemoizedInstanceVariableName
268
292
  def policy_scopes
269
293
  @_pundit_policy_scopes ||= {}
270
294
  end
295
+ # rubocop:enable Naming/MemoizedInstanceVariableName
271
296
 
272
297
  # Hook method which allows customizing which user is passed to policies and
273
298
  # scopes initialized by {#authorize}, {#policy} and {#policy_scope}.
274
299
  #
275
- # @see https://github.com/elabs/pundit#customize-pundit-user
300
+ # @see https://github.com/varvet/pundit#customize-pundit-user
276
301
  # @return [Object] the user object to be used with pundit
277
302
  def pundit_user
278
303
  current_user
@@ -17,75 +17,69 @@ module Pundit
17
17
  end
18
18
 
19
19
  # @return [nil, Scope{#resolve}] scope class which can resolve to a scope
20
- # @see https://github.com/elabs/pundit#scopes
20
+ # @see https://github.com/varvet/pundit#scopes
21
21
  # @example
22
22
  # scope = finder.scope #=> UserPolicy::Scope
23
23
  # scope.resolve #=> <#ActiveRecord::Relation ...>
24
24
  #
25
25
  def scope
26
- policy::Scope if policy
27
- rescue NameError
28
- nil
26
+ "#{policy}::Scope".safe_constantize
29
27
  end
30
28
 
31
29
  # @return [nil, Class] policy class with query methods
32
- # @see https://github.com/elabs/pundit#policies
30
+ # @see https://github.com/varvet/pundit#policies
33
31
  # @example
34
32
  # policy = finder.policy #=> UserPolicy
35
33
  # policy.show? #=> true
36
34
  # policy.update? #=> false
37
35
  #
38
36
  def policy
39
- klass = find
40
- klass = klass.constantize if klass.is_a?(String)
41
- klass
42
- rescue NameError
43
- nil
37
+ klass = find(object)
38
+ klass.is_a?(String) ? klass.safe_constantize : klass
44
39
  end
45
40
 
46
41
  # @return [Scope{#resolve}] scope class which can resolve to a scope
47
42
  # @raise [NotDefinedError] if scope could not be determined
48
43
  #
49
44
  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}`"
45
+ scope or raise NotDefinedError, "unable to find scope `#{find(object)}::Scope` for `#{object.inspect}`"
52
46
  end
53
47
 
54
48
  # @return [Class] policy class with query methods
55
49
  # @raise [NotDefinedError] if policy could not be determined
56
50
  #
57
51
  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}`"
52
+ policy or raise NotDefinedError, "unable to find policy `#{find(object)}` for `#{object.inspect}`"
60
53
  end
61
54
 
62
55
  # @return [String] the name of the key this object would have in a params hash
63
56
  #
64
57
  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
58
+ model = object.is_a?(Array) ? object.last : object
59
+
60
+ if model.respond_to?(:model_name)
61
+ model.model_name.param_key.to_s
62
+ elsif model.is_a?(Class)
63
+ model.to_s.demodulize.underscore
69
64
  else
70
- object.class.to_s.demodulize.underscore
65
+ model.class.to_s.demodulize.underscore
71
66
  end
72
67
  end
73
68
 
74
69
  private
75
70
 
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
71
+ def find(subject)
72
+ if subject.is_a?(Array)
73
+ modules = subject.dup
74
+ last = modules.pop
75
+ context = modules.map { |x| find_class_name(x) }.join("::")
76
+ [context, find(last)].join("::")
77
+ elsif subject.respond_to?(:policy_class)
78
+ subject.policy_class
79
+ elsif subject.class.respond_to?(:policy_class)
80
+ subject.class.policy_class
83
81
  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
82
+ klass = find_class_name(subject)
89
83
  "#{klass}#{SUFFIX}"
90
84
  end
91
85
  end