pundit 1.1.0 → 2.0.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
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