pundit 2.0.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.
@@ -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
@@ -66,7 +68,7 @@ module Pundit
66
68
  end
67
69
  end
68
70
 
69
- private
71
+ private
70
72
 
71
73
  def find(subject)
72
74
  if subject.is_a?(Array)
data/lib/pundit/rspec.rb CHANGED
@@ -1,4 +1,4 @@
1
- require "active_support/core_ext/array/conversions"
1
+ # frozen_string_literal: true
2
2
 
3
3
  module Pundit
4
4
  module RSpec
@@ -72,17 +72,9 @@ module Pundit
72
72
  end
73
73
 
74
74
  RSpec.configure do |config|
75
- if RSpec::Core::Version::STRING.split(".").first.to_i >= 3
76
- config.include(
77
- Pundit::RSpec::PolicyExampleGroup,
78
- type: :policy,
79
- file_path: %r{spec/policies}
80
- )
81
- else
82
- config.include(
83
- Pundit::RSpec::PolicyExampleGroup,
84
- type: :policy,
85
- example_group: { file_path: %r{spec/policies} }
86
- )
87
- end
75
+ config.include(
76
+ Pundit::RSpec::PolicyExampleGroup,
77
+ type: :policy,
78
+ file_path: %r{spec/policies}
79
+ )
88
80
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Pundit
4
- VERSION = "2.0.0".freeze
4
+ VERSION = "2.2.0"
5
5
  end
data/lib/pundit.rb CHANGED
@@ -7,17 +7,20 @@ require "active_support/core_ext/string/inflections"
7
7
  require "active_support/core_ext/object/blank"
8
8
  require "active_support/core_ext/module/introspection"
9
9
  require "active_support/dependencies/autoload"
10
+ require "pundit/authorization"
11
+
12
+ # @api private
13
+ # To avoid name clashes with common Error naming when mixing in Pundit,
14
+ # keep it here with compact class style definition.
15
+ class Pundit::Error < StandardError; end # rubocop:disable Style/ClassAndModuleChildren
10
16
 
11
17
  # @api public
12
18
  module Pundit
13
- SUFFIX = "Policy".freeze
19
+ SUFFIX = "Policy"
14
20
 
15
21
  # @api private
16
22
  module Generators; end
17
23
 
18
- # @api private
19
- class Error < StandardError; end
20
-
21
24
  # Error that will be raised when authorization has failed
22
25
  class NotAuthorizedError < Error
23
26
  attr_reader :query, :record, :policy
@@ -30,7 +33,7 @@ module Pundit
30
33
  @record = options[:record]
31
34
  @policy = options[:policy]
32
35
 
33
- message = options.fetch(:message) { "not allowed to #{query} this #{record.inspect}" }
36
+ message = options.fetch(:message) { "not allowed to #{query} this #{record.class}" }
34
37
  end
35
38
 
36
39
  super(message)
@@ -51,7 +54,12 @@ module Pundit
51
54
  # Error that will be raised if a policy or policy scope is not defined.
52
55
  class NotDefinedError < Error; end
53
56
 
54
- extend ActiveSupport::Concern
57
+ def self.included(base)
58
+ ActiveSupport::Deprecation.warn <<~WARNING.strip_heredoc
59
+ 'include Pundit' is deprecated. Please use 'include Pundit::Authorization' instead.
60
+ WARNING
61
+ base.include Authorization
62
+ end
55
63
 
56
64
  class << self
57
65
  # Retrieves the policy for the given record, initializing it with the
@@ -59,13 +67,19 @@ module Pundit
59
67
  # authorized to perform the given action.
60
68
  #
61
69
  # @param user [Object] the user that initiated the action
62
- # @param record [Object] the object we're checking permissions of
70
+ # @param possibly_namespaced_record [Object, Array] the object we're checking permissions of
63
71
  # @param query [Symbol, String] the predicate method to check on the policy (e.g. `:show?`)
64
72
  # @param policy_class [Class] the policy class we want to force use of
73
+ # @param cache [#[], #[]=] a Hash-like object to cache the found policy instance in
65
74
  # @raise [NotAuthorizedError] if the given query method returned false
66
75
  # @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)
76
+ def authorize(user, possibly_namespaced_record, query, policy_class: nil, cache: {})
77
+ record = pundit_model(possibly_namespaced_record)
78
+ policy = if policy_class
79
+ policy_class.new(user, record)
80
+ else
81
+ cache[possibly_namespaced_record] ||= policy!(user, possibly_namespaced_record)
82
+ end
69
83
 
70
84
  raise NotAuthorizedError, query: query, record: record, policy: policy unless policy.public_send(query)
71
85
 
@@ -80,10 +94,16 @@ module Pundit
80
94
  # @raise [InvalidConstructorError] if the policy constructor called incorrectly
81
95
  # @return [Scope{#resolve}, nil] instance of scope class which can resolve to a scope
82
96
  def policy_scope(user, scope)
83
- policy_scope = PolicyFinder.new(scope).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"
97
+ policy_scope_class = PolicyFinder.new(scope).scope
98
+ return unless policy_scope_class
99
+
100
+ begin
101
+ policy_scope = policy_scope_class.new(user, pundit_model(scope))
102
+ rescue ArgumentError
103
+ raise InvalidConstructorError, "Invalid #<#{policy_scope_class}> constructor is called"
104
+ end
105
+
106
+ policy_scope.resolve
87
107
  end
88
108
 
89
109
  # Retrieves the policy scope for the given record.
@@ -95,10 +115,16 @@ module Pundit
95
115
  # @raise [InvalidConstructorError] if the policy constructor called incorrectly
96
116
  # @return [Scope{#resolve}] instance of scope class which can resolve to a scope
97
117
  def policy_scope!(user, scope)
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"
118
+ policy_scope_class = PolicyFinder.new(scope).scope!
119
+ return unless policy_scope_class
120
+
121
+ begin
122
+ policy_scope = policy_scope_class.new(user, pundit_model(scope))
123
+ rescue ArgumentError
124
+ raise InvalidConstructorError, "Invalid #<#{policy_scope_class}> constructor is called"
125
+ end
126
+
127
+ policy_scope.resolve
102
128
  end
103
129
 
104
130
  # Retrieves the policy for the given record.
@@ -110,7 +136,7 @@ module Pundit
110
136
  # @return [Object, nil] instance of policy class with query methods
111
137
  def policy(user, record)
112
138
  policy = PolicyFinder.new(record).policy
113
- policy.new(user, pundit_model(record)) if policy
139
+ policy&.new(user, pundit_model(record))
114
140
  rescue ArgumentError
115
141
  raise InvalidConstructorError, "Invalid #<#{policy}> constructor is called"
116
142
  end
@@ -130,7 +156,7 @@ module Pundit
130
156
  raise InvalidConstructorError, "Invalid #<#{policy}> constructor is called"
131
157
  end
132
158
 
133
- private
159
+ private
134
160
 
135
161
  def pundit_model(record)
136
162
  record.is_a?(Array) ? record.last : record
@@ -143,169 +169,4 @@ module Pundit
143
169
  pundit_policy_scope(scope)
144
170
  end
145
171
  end
146
-
147
- included do
148
- helper Helper if respond_to?(:helper)
149
- if respond_to?(:helper_method)
150
- helper_method :policy
151
- helper_method :pundit_policy_scope
152
- helper_method :pundit_user
153
- end
154
- end
155
-
156
- protected
157
-
158
- # @return [Boolean] whether authorization has been performed, i.e. whether
159
- # one {#authorize} or {#skip_authorization} has been called
160
- def pundit_policy_authorized?
161
- !!@_pundit_policy_authorized
162
- end
163
-
164
- # @return [Boolean] whether policy scoping has been performed, i.e. whether
165
- # one {#policy_scope} or {#skip_policy_scope} has been called
166
- def pundit_policy_scoped?
167
- !!@_pundit_policy_scoped
168
- end
169
-
170
- # Raises an error if authorization has not been performed, usually used as an
171
- # `after_action` filter to prevent programmer error in forgetting to call
172
- # {#authorize} or {#skip_authorization}.
173
- #
174
- # @see https://github.com/varvet/pundit#ensuring-policies-and-scopes-are-used
175
- # @raise [AuthorizationNotPerformedError] if authorization has not been performed
176
- # @return [void]
177
- def verify_authorized
178
- raise AuthorizationNotPerformedError, self.class unless pundit_policy_authorized?
179
- end
180
-
181
- # Raises an error if policy scoping has not been performed, usually used as an
182
- # `after_action` filter to prevent programmer error in forgetting to call
183
- # {#policy_scope} or {#skip_policy_scope} in index actions.
184
- #
185
- # @see https://github.com/varvet/pundit#ensuring-policies-and-scopes-are-used
186
- # @raise [AuthorizationNotPerformedError] if policy scoping has not been performed
187
- # @return [void]
188
- def verify_policy_scoped
189
- raise PolicyScopingNotPerformedError, self.class unless pundit_policy_scoped?
190
- end
191
-
192
- # Retrieves the policy for the given record, initializing it with the record
193
- # and current user and finally throwing an error if the user is not
194
- # authorized to perform the given action.
195
- #
196
- # @param record [Object] the object we're checking permissions of
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
200
- # @raise [NotAuthorizedError] if the given query method returned false
201
- # @return [Object] Always returns the passed object record
202
- def authorize(record, query = nil, policy_class: nil)
203
- query ||= "#{action_name}?"
204
-
205
- @_pundit_policy_authorized = true
206
-
207
- policy = policy_class ? policy_class.new(pundit_user, record) : policy(record)
208
-
209
- raise NotAuthorizedError, query: query, record: record, policy: policy unless policy.public_send(query)
210
-
211
- record
212
- end
213
-
214
- # Allow this action not to perform authorization.
215
- #
216
- # @see https://github.com/varvet/pundit#ensuring-policies-and-scopes-are-used
217
- # @return [void]
218
- def skip_authorization
219
- @_pundit_policy_authorized = true
220
- end
221
-
222
- # Allow this action not to perform policy scoping.
223
- #
224
- # @see https://github.com/varvet/pundit#ensuring-policies-and-scopes-are-used
225
- # @return [void]
226
- def skip_policy_scope
227
- @_pundit_policy_scoped = true
228
- end
229
-
230
- # Retrieves the policy scope for the given record.
231
- #
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
235
- # @return [Scope{#resolve}, nil] instance of scope class which can resolve to a scope
236
- def policy_scope(scope, policy_scope_class: nil)
237
- @_pundit_policy_scoped = true
238
- policy_scope_class ? policy_scope_class.new(pundit_user, scope).resolve : pundit_policy_scope(scope)
239
- end
240
-
241
- # Retrieves the policy for the given record.
242
- #
243
- # @see https://github.com/varvet/pundit#policies
244
- # @param record [Object] the object we're retrieving the policy for
245
- # @return [Object, nil] instance of policy class with query methods
246
- def policy(record)
247
- policies[record] ||= Pundit.policy!(pundit_user, record)
248
- end
249
-
250
- # Retrieves a set of permitted attributes from the policy by instantiating
251
- # the policy class for the given record and calling `permitted_attributes` on
252
- # it, or `permitted_attributes_for_{action}` if `action` is defined. It then infers
253
- # what key the record should have in the params hash and retrieves the
254
- # permitted attributes from the params hash under that key.
255
- #
256
- # @see https://github.com/varvet/pundit#strong-parameters
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.
260
- # @return [Hash{String => Object}] the permitted attributes
261
- def permitted_attributes(record, action = action_name)
262
- policy = policy(record)
263
- method_name = if policy.respond_to?("permitted_attributes_for_#{action}")
264
- "permitted_attributes_for_#{action}"
265
- else
266
- "permitted_attributes"
267
- end
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)
277
- end
278
-
279
- # Cache of policies. You should not rely on this method.
280
- #
281
- # @api private
282
- # rubocop:disable Naming/MemoizedInstanceVariableName
283
- def policies
284
- @_pundit_policies ||= {}
285
- end
286
- # rubocop:enable Naming/MemoizedInstanceVariableName
287
-
288
- # Cache of policy scope. You should not rely on this method.
289
- #
290
- # @api private
291
- # rubocop:disable Naming/MemoizedInstanceVariableName
292
- def policy_scopes
293
- @_pundit_policy_scopes ||= {}
294
- end
295
- # rubocop:enable Naming/MemoizedInstanceVariableName
296
-
297
- # Hook method which allows customizing which user is passed to policies and
298
- # scopes initialized by {#authorize}, {#policy} and {#policy_scope}.
299
- #
300
- # @see https://github.com/varvet/pundit#customize-pundit-user
301
- # @return [Object] the user object to be used with pundit
302
- def pundit_user
303
- current_user
304
- end
305
-
306
- private
307
-
308
- def pundit_policy_scope(scope)
309
- policy_scopes[scope] ||= Pundit.policy_scope!(pundit_user, scope)
310
- end
311
172
  end
data/pundit.gemspec CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  lib = File.expand_path("lib", __dir__)
2
4
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
5
  require "pundit/version"
@@ -5,17 +7,27 @@ require "pundit/version"
5
7
  Gem::Specification.new do |gem|
6
8
  gem.name = "pundit"
7
9
  gem.version = Pundit::VERSION
8
- gem.authors = ["Jonas Nicklas", "Elabs AB"]
10
+ gem.authors = ["Jonas Nicklas", "Varvet AB"]
9
11
  gem.email = ["jonas.nicklas@gmail.com", "dev@elabs.se"]
10
12
  gem.description = "Object oriented authorization for Rails applications"
11
13
  gem.summary = "OO authorization for Rails"
12
14
  gem.homepage = "https://github.com/varvet/pundit"
13
15
  gem.license = "MIT"
14
16
 
15
- gem.files = `git ls-files`.split($/)
17
+ gem.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR)
16
18
  gem.executables = gem.files.grep(%r{^bin/}).map { |f| File.basename(f) }
17
19
  gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
18
20
  gem.require_paths = ["lib"]
19
21
 
20
22
  gem.add_dependency "activesupport", ">= 3.0.0"
23
+ gem.add_development_dependency "actionpack", ">= 3.0.0"
24
+ gem.add_development_dependency "activemodel", ">= 3.0.0"
25
+ gem.add_development_dependency "bundler"
26
+ gem.add_development_dependency "pry"
27
+ gem.add_development_dependency "railties", ">= 3.0.0"
28
+ gem.add_development_dependency "rake"
29
+ gem.add_development_dependency "rspec", ">= 3.0.0"
30
+ gem.add_development_dependency "rubocop", "1.24.0"
31
+ gem.add_development_dependency "simplecov", ">= 0.17.0"
32
+ gem.add_development_dependency "yard"
21
33
  end