pundit 2.1.1 → 2.4.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,127 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pundit
4
+ class Context
5
+ def initialize(user:, policy_cache: CacheStore::NullStore.instance)
6
+ @user = user
7
+ @policy_cache = policy_cache
8
+ end
9
+
10
+ attr_reader :user
11
+
12
+ # @api private
13
+ attr_reader :policy_cache
14
+
15
+ # Retrieves the policy for the given record, initializing it with the
16
+ # record and user and finally throwing an error if the user is not
17
+ # authorized to perform the given action.
18
+ #
19
+ # @param user [Object] the user that initiated the action
20
+ # @param possibly_namespaced_record [Object, Array] the object we're checking permissions of
21
+ # @param query [Symbol, String] the predicate method to check on the policy (e.g. `:show?`)
22
+ # @param policy_class [Class] the policy class we want to force use of
23
+ # @raise [NotAuthorizedError] if the given query method returned false
24
+ # @return [Object] Always returns the passed object record
25
+ def authorize(possibly_namespaced_record, query:, policy_class:)
26
+ record = pundit_model(possibly_namespaced_record)
27
+ policy = if policy_class
28
+ policy_class.new(user, record)
29
+ else
30
+ policy!(possibly_namespaced_record)
31
+ end
32
+
33
+ raise NotAuthorizedError, query: query, record: record, policy: policy unless policy.public_send(query)
34
+
35
+ record
36
+ end
37
+
38
+ # Retrieves the policy scope for the given record.
39
+ #
40
+ # @see https://github.com/varvet/pundit#scopes
41
+ # @param user [Object] the user that initiated the action
42
+ # @param scope [Object] the object we're retrieving the policy scope for
43
+ # @raise [InvalidConstructorError] if the policy constructor called incorrectly
44
+ # @return [Scope{#resolve}, nil] instance of scope class which can resolve to a scope
45
+ def policy_scope(scope)
46
+ policy_scope_class = policy_finder(scope).scope
47
+ return unless policy_scope_class
48
+
49
+ begin
50
+ policy_scope = policy_scope_class.new(user, pundit_model(scope))
51
+ rescue ArgumentError
52
+ raise InvalidConstructorError, "Invalid #<#{policy_scope_class}> constructor is called"
53
+ end
54
+
55
+ policy_scope.resolve
56
+ end
57
+
58
+ # Retrieves the policy scope for the given record. Raises if not found.
59
+ #
60
+ # @see https://github.com/varvet/pundit#scopes
61
+ # @param user [Object] the user that initiated the action
62
+ # @param scope [Object] the object we're retrieving the policy scope for
63
+ # @raise [NotDefinedError] if the policy scope cannot be found
64
+ # @raise [InvalidConstructorError] if the policy constructor called incorrectly
65
+ # @return [Scope{#resolve}] instance of scope class which can resolve to a scope
66
+ def policy_scope!(scope)
67
+ policy_scope_class = policy_finder(scope).scope!
68
+ return unless policy_scope_class
69
+
70
+ begin
71
+ policy_scope = policy_scope_class.new(user, pundit_model(scope))
72
+ rescue ArgumentError
73
+ raise InvalidConstructorError, "Invalid #<#{policy_scope_class}> constructor is called"
74
+ end
75
+
76
+ policy_scope.resolve
77
+ end
78
+
79
+ # Retrieves the policy for the given record.
80
+ #
81
+ # @see https://github.com/varvet/pundit#policies
82
+ # @param user [Object] the user that initiated the action
83
+ # @param record [Object] the object we're retrieving the policy for
84
+ # @raise [InvalidConstructorError] if the policy constructor called incorrectly
85
+ # @return [Object, nil] instance of policy class with query methods
86
+ def policy(record)
87
+ cached_find(record, &:policy)
88
+ end
89
+
90
+ # Retrieves the policy for the given record. Raises if not found.
91
+ #
92
+ # @see https://github.com/varvet/pundit#policies
93
+ # @param user [Object] the user that initiated the action
94
+ # @param record [Object] the object we're retrieving the policy for
95
+ # @raise [NotDefinedError] if the policy cannot be found
96
+ # @raise [InvalidConstructorError] if the policy constructor called incorrectly
97
+ # @return [Object] instance of policy class with query methods
98
+ def policy!(record)
99
+ cached_find(record, &:policy!)
100
+ end
101
+
102
+ private
103
+
104
+ def cached_find(record)
105
+ policy_cache.fetch(user: user, record: record) do
106
+ klass = yield policy_finder(record)
107
+ next unless klass
108
+
109
+ model = pundit_model(record)
110
+
111
+ begin
112
+ klass.new(user, model)
113
+ rescue ArgumentError
114
+ raise InvalidConstructorError, "Invalid #<#{klass}> constructor is called"
115
+ end
116
+ end
117
+ end
118
+
119
+ def policy_finder(record)
120
+ PolicyFinder.new(record)
121
+ end
122
+
123
+ def pundit_model(record)
124
+ record.is_a?(Array) ? record.last : record
125
+ end
126
+ end
127
+ end
@@ -56,7 +56,7 @@ module Pundit
56
56
 
57
57
  # @return [String] the name of the key this object would have in a params hash
58
58
  #
59
- def param_key
59
+ def param_key # rubocop:disable Metrics/AbcSize
60
60
  model = object.is_a?(Array) ? object.last : object
61
61
 
62
62
  if model.respond_to?(:model_name)
data/lib/pundit/rspec.rb CHANGED
@@ -5,6 +5,16 @@ module Pundit
5
5
  module Matchers
6
6
  extend ::RSpec::Matchers::DSL
7
7
 
8
+ class << self
9
+ attr_writer :description
10
+
11
+ def description(user, record)
12
+ return @description.call(user, record) if defined?(@description) && @description.respond_to?(:call)
13
+
14
+ @description
15
+ end
16
+ end
17
+
8
18
  # rubocop:disable Metrics/BlockLength
9
19
  matcher :permit do |user, record|
10
20
  match_proc = lambda do |policy|
@@ -33,6 +43,10 @@ module Pundit
33
43
  "#{record} but #{@violating_permissions.to_sentence} #{was_were} granted"
34
44
  end
35
45
 
46
+ description do
47
+ Pundit::RSpec::Matchers.description(user, record) || super()
48
+ end
49
+
36
50
  if respond_to?(:match_when_negated)
37
51
  match(&match_proc)
38
52
  match_when_negated(&match_when_negated_proc)
@@ -55,7 +69,15 @@ module Pundit
55
69
 
56
70
  module DSL
57
71
  def permissions(*list, &block)
58
- describe(list.to_sentence, permissions: list, caller: caller) { instance_eval(&block) }
72
+ metadata = { permissions: list, caller: caller }
73
+
74
+ if list.last == :focus
75
+ list.pop
76
+ metadata[:focus] = true
77
+ end
78
+
79
+ description = list.to_sentence
80
+ describe(description, metadata) { instance_eval(&block) }
59
81
  end
60
82
  end
61
83
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Pundit
4
- VERSION = "2.1.1"
4
+ VERSION = "2.4.0"
5
5
  end
data/lib/pundit.rb CHANGED
@@ -7,6 +7,10 @@ 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
+ require "pundit/context"
12
+ require "pundit/cache_store/null_store"
13
+ require "pundit/cache_store/legacy_store"
10
14
 
11
15
  # @api private
12
16
  # To avoid name clashes with common Error naming when mixing in Pundit,
@@ -32,7 +36,10 @@ module Pundit
32
36
  @record = options[:record]
33
37
  @policy = options[:policy]
34
38
 
35
- message = options.fetch(:message) { "not allowed to #{query} this #{record.class}" }
39
+ message = options.fetch(:message) do
40
+ record_name = record.is_a?(Class) ? record.to_s : "this #{record.class}"
41
+ "not allowed to #{policy.class}##{query} #{record_name}"
42
+ end
36
43
  end
37
44
 
38
45
  super(message)
@@ -53,101 +60,45 @@ module Pundit
53
60
  # Error that will be raised if a policy or policy scope is not defined.
54
61
  class NotDefinedError < Error; end
55
62
 
56
- extend ActiveSupport::Concern
63
+ def self.included(base)
64
+ location = caller_locations(1, 1).first
65
+ warn <<~WARNING
66
+ 'include Pundit' is deprecated. Please use 'include Pundit::Authorization' instead.
67
+ (called from #{location.label} at #{location.path}:#{location.lineno})
68
+ WARNING
69
+ base.include Authorization
70
+ end
57
71
 
58
72
  class << self
59
- # Retrieves the policy for the given record, initializing it with the
60
- # record and user and finally throwing an error if the user is not
61
- # authorized to perform the given action.
62
- #
63
- # @param user [Object] the user that initiated the action
64
- # @param record [Object] the object we're checking permissions of
65
- # @param query [Symbol, String] the predicate method to check on the policy (e.g. `:show?`)
66
- # @param policy_class [Class] the policy class we want to force use of
67
- # @raise [NotAuthorizedError] if the given query method returned false
68
- # @return [Object] Always returns the passed object record
69
- def authorize(user, record, query, policy_class: nil)
70
- policy = policy_class ? policy_class.new(user, record) : policy!(user, record)
71
-
72
- raise NotAuthorizedError, query: query, record: record, policy: policy unless policy.public_send(query)
73
-
74
- record.is_a?(Array) ? record.last : record
75
- end
76
-
77
- # Retrieves the policy scope for the given record.
78
- #
79
- # @see https://github.com/varvet/pundit#scopes
80
- # @param user [Object] the user that initiated the action
81
- # @param scope [Object] the object we're retrieving the policy scope for
82
- # @raise [InvalidConstructorError] if the policy constructor called incorrectly
83
- # @return [Scope{#resolve}, nil] instance of scope class which can resolve to a scope
84
- def policy_scope(user, scope)
85
- policy_scope_class = PolicyFinder.new(scope).scope
86
- return unless policy_scope_class
87
-
88
- begin
89
- policy_scope = policy_scope_class.new(user, pundit_model(scope))
90
- rescue ArgumentError
91
- raise InvalidConstructorError, "Invalid #<#{policy_scope_class}> constructor is called"
73
+ # @see [Pundit::Context#authorize]
74
+ def authorize(user, record, query, policy_class: nil, cache: nil)
75
+ context = if cache
76
+ Context.new(user: user, policy_cache: cache)
77
+ else
78
+ Context.new(user: user)
92
79
  end
93
80
 
94
- policy_scope.resolve
81
+ context.authorize(record, query: query, policy_class: policy_class)
95
82
  end
96
83
 
97
- # Retrieves the policy scope for the given record.
98
- #
99
- # @see https://github.com/varvet/pundit#scopes
100
- # @param user [Object] the user that initiated the action
101
- # @param scope [Object] the object we're retrieving the policy scope for
102
- # @raise [NotDefinedError] if the policy scope cannot be found
103
- # @raise [InvalidConstructorError] if the policy constructor called incorrectly
104
- # @return [Scope{#resolve}] instance of scope class which can resolve to a scope
105
- def policy_scope!(user, scope)
106
- policy_scope_class = PolicyFinder.new(scope).scope!
107
- return unless policy_scope_class
108
-
109
- begin
110
- policy_scope = policy_scope_class.new(user, pundit_model(scope))
111
- rescue ArgumentError
112
- raise InvalidConstructorError, "Invalid #<#{policy_scope_class}> constructor is called"
113
- end
114
-
115
- policy_scope.resolve
84
+ # @see [Pundit::Context#policy_scope]
85
+ def policy_scope(user, *args, **kwargs, &block)
86
+ Context.new(user: user).policy_scope(*args, **kwargs, &block)
116
87
  end
117
88
 
118
- # Retrieves the policy for the given record.
119
- #
120
- # @see https://github.com/varvet/pundit#policies
121
- # @param user [Object] the user that initiated the action
122
- # @param record [Object] the object we're retrieving the policy for
123
- # @raise [InvalidConstructorError] if the policy constructor called incorrectly
124
- # @return [Object, nil] instance of policy class with query methods
125
- def policy(user, record)
126
- policy = PolicyFinder.new(record).policy
127
- policy&.new(user, pundit_model(record))
128
- rescue ArgumentError
129
- raise InvalidConstructorError, "Invalid #<#{policy}> constructor is called"
89
+ # @see [Pundit::Context#policy_scope!]
90
+ def policy_scope!(user, *args, **kwargs, &block)
91
+ Context.new(user: user).policy_scope!(*args, **kwargs, &block)
130
92
  end
131
93
 
132
- # Retrieves the policy for the given record.
133
- #
134
- # @see https://github.com/varvet/pundit#policies
135
- # @param user [Object] the user that initiated the action
136
- # @param record [Object] the object we're retrieving the policy for
137
- # @raise [NotDefinedError] if the policy cannot be found
138
- # @raise [InvalidConstructorError] if the policy constructor called incorrectly
139
- # @return [Object] instance of policy class with query methods
140
- def policy!(user, record)
141
- policy = PolicyFinder.new(record).policy!
142
- policy.new(user, pundit_model(record))
143
- rescue ArgumentError
144
- raise InvalidConstructorError, "Invalid #<#{policy}> constructor is called"
94
+ # @see [Pundit::Context#policy]
95
+ def policy(user, *args, **kwargs, &block)
96
+ Context.new(user: user).policy(*args, **kwargs, &block)
145
97
  end
146
98
 
147
- private
148
-
149
- def pundit_model(record)
150
- record.is_a?(Array) ? record.last : record
99
+ # @see [Pundit::Context#policy!]
100
+ def policy!(user, *args, **kwargs, &block)
101
+ Context.new(user: user).policy!(*args, **kwargs, &block)
151
102
  end
152
103
  end
153
104
 
@@ -157,169 +108,4 @@ module Pundit
157
108
  pundit_policy_scope(scope)
158
109
  end
159
110
  end
160
-
161
- included do
162
- helper Helper if respond_to?(:helper)
163
- if respond_to?(:helper_method)
164
- helper_method :policy
165
- helper_method :pundit_policy_scope
166
- helper_method :pundit_user
167
- end
168
- end
169
-
170
- protected
171
-
172
- # @return [Boolean] whether authorization has been performed, i.e. whether
173
- # one {#authorize} or {#skip_authorization} has been called
174
- def pundit_policy_authorized?
175
- !!@_pundit_policy_authorized
176
- end
177
-
178
- # @return [Boolean] whether policy scoping has been performed, i.e. whether
179
- # one {#policy_scope} or {#skip_policy_scope} has been called
180
- def pundit_policy_scoped?
181
- !!@_pundit_policy_scoped
182
- end
183
-
184
- # Raises an error if authorization has not been performed, usually used as an
185
- # `after_action` filter to prevent programmer error in forgetting to call
186
- # {#authorize} or {#skip_authorization}.
187
- #
188
- # @see https://github.com/varvet/pundit#ensuring-policies-and-scopes-are-used
189
- # @raise [AuthorizationNotPerformedError] if authorization has not been performed
190
- # @return [void]
191
- def verify_authorized
192
- raise AuthorizationNotPerformedError, self.class unless pundit_policy_authorized?
193
- end
194
-
195
- # Raises an error if policy scoping has not been performed, usually used as an
196
- # `after_action` filter to prevent programmer error in forgetting to call
197
- # {#policy_scope} or {#skip_policy_scope} in index actions.
198
- #
199
- # @see https://github.com/varvet/pundit#ensuring-policies-and-scopes-are-used
200
- # @raise [AuthorizationNotPerformedError] if policy scoping has not been performed
201
- # @return [void]
202
- def verify_policy_scoped
203
- raise PolicyScopingNotPerformedError, self.class unless pundit_policy_scoped?
204
- end
205
-
206
- # Retrieves the policy for the given record, initializing it with the record
207
- # and current user and finally throwing an error if the user is not
208
- # authorized to perform the given action.
209
- #
210
- # @param record [Object] the object we're checking permissions of
211
- # @param query [Symbol, String] the predicate method to check on the policy (e.g. `:show?`).
212
- # If omitted then this defaults to the Rails controller action name.
213
- # @param policy_class [Class] the policy class we want to force use of
214
- # @raise [NotAuthorizedError] if the given query method returned false
215
- # @return [Object] Always returns the passed object record
216
- def authorize(record, query = nil, policy_class: nil)
217
- query ||= "#{action_name}?"
218
-
219
- @_pundit_policy_authorized = true
220
-
221
- policy = policy_class ? policy_class.new(pundit_user, record) : policy(record)
222
-
223
- raise NotAuthorizedError, query: query, record: record, policy: policy unless policy.public_send(query)
224
-
225
- record.is_a?(Array) ? record.last : record
226
- end
227
-
228
- # Allow this action not to perform authorization.
229
- #
230
- # @see https://github.com/varvet/pundit#ensuring-policies-and-scopes-are-used
231
- # @return [void]
232
- def skip_authorization
233
- @_pundit_policy_authorized = true
234
- end
235
-
236
- # Allow this action not to perform policy scoping.
237
- #
238
- # @see https://github.com/varvet/pundit#ensuring-policies-and-scopes-are-used
239
- # @return [void]
240
- def skip_policy_scope
241
- @_pundit_policy_scoped = true
242
- end
243
-
244
- # Retrieves the policy scope for the given record.
245
- #
246
- # @see https://github.com/varvet/pundit#scopes
247
- # @param scope [Object] the object we're retrieving the policy scope for
248
- # @param policy_scope_class [Class] the policy scope class we want to force use of
249
- # @return [Scope{#resolve}, nil] instance of scope class which can resolve to a scope
250
- def policy_scope(scope, policy_scope_class: nil)
251
- @_pundit_policy_scoped = true
252
- policy_scope_class ? policy_scope_class.new(pundit_user, scope).resolve : pundit_policy_scope(scope)
253
- end
254
-
255
- # Retrieves the policy for the given record.
256
- #
257
- # @see https://github.com/varvet/pundit#policies
258
- # @param record [Object] the object we're retrieving the policy for
259
- # @return [Object, nil] instance of policy class with query methods
260
- def policy(record)
261
- policies[record] ||= Pundit.policy!(pundit_user, record)
262
- end
263
-
264
- # Retrieves a set of permitted attributes from the policy by instantiating
265
- # the policy class for the given record and calling `permitted_attributes` on
266
- # it, or `permitted_attributes_for_{action}` if `action` is defined. It then infers
267
- # what key the record should have in the params hash and retrieves the
268
- # permitted attributes from the params hash under that key.
269
- #
270
- # @see https://github.com/varvet/pundit#strong-parameters
271
- # @param record [Object] the object we're retrieving permitted attributes for
272
- # @param action [Symbol, String] the name of the action being performed on the record (e.g. `:update`).
273
- # If omitted then this defaults to the Rails controller action name.
274
- # @return [Hash{String => Object}] the permitted attributes
275
- def permitted_attributes(record, action = action_name)
276
- policy = policy(record)
277
- method_name = if policy.respond_to?("permitted_attributes_for_#{action}")
278
- "permitted_attributes_for_#{action}"
279
- else
280
- "permitted_attributes"
281
- end
282
- pundit_params_for(record).permit(*policy.public_send(method_name))
283
- end
284
-
285
- # Retrieves the params for the given record.
286
- #
287
- # @param record [Object] the object we're retrieving params for
288
- # @return [ActionController::Parameters] the params
289
- def pundit_params_for(record)
290
- params.require(PolicyFinder.new(record).param_key)
291
- end
292
-
293
- # Cache of policies. You should not rely on this method.
294
- #
295
- # @api private
296
- # rubocop:disable Naming/MemoizedInstanceVariableName
297
- def policies
298
- @_pundit_policies ||= {}
299
- end
300
- # rubocop:enable Naming/MemoizedInstanceVariableName
301
-
302
- # Cache of policy scope. You should not rely on this method.
303
- #
304
- # @api private
305
- # rubocop:disable Naming/MemoizedInstanceVariableName
306
- def policy_scopes
307
- @_pundit_policy_scopes ||= {}
308
- end
309
- # rubocop:enable Naming/MemoizedInstanceVariableName
310
-
311
- # Hook method which allows customizing which user is passed to policies and
312
- # scopes initialized by {#authorize}, {#policy} and {#policy_scope}.
313
- #
314
- # @see https://github.com/varvet/pundit#customize-pundit-user
315
- # @return [Object] the user object to be used with pundit
316
- def pundit_user
317
- current_user
318
- end
319
-
320
- private
321
-
322
- def pundit_policy_scope(scope)
323
- policy_scopes[scope] ||= Pundit.policy_scope!(pundit_user, scope)
324
- end
325
111
  end
data/pundit.gemspec CHANGED
@@ -8,7 +8,7 @@ Gem::Specification.new do |gem|
8
8
  gem.name = "pundit"
9
9
  gem.version = Pundit::VERSION
10
10
  gem.authors = ["Jonas Nicklas", "Varvet AB"]
11
- gem.email = ["jonas.nicklas@gmail.com", "dev@elabs.se"]
11
+ gem.email = ["jonas.nicklas@gmail.com", "info@varvet.com"]
12
12
  gem.description = "Object oriented authorization for Rails applications"
13
13
  gem.summary = "OO authorization for Rails"
14
14
  gem.homepage = "https://github.com/varvet/pundit"
@@ -19,14 +19,17 @@ Gem::Specification.new do |gem|
19
19
  gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
20
20
  gem.require_paths = ["lib"]
21
21
 
22
+ gem.metadata = { "rubygems_mfa_required" => "true" }
23
+
22
24
  gem.add_dependency "activesupport", ">= 3.0.0"
23
25
  gem.add_development_dependency "actionpack", ">= 3.0.0"
24
26
  gem.add_development_dependency "activemodel", ">= 3.0.0"
25
27
  gem.add_development_dependency "bundler"
26
28
  gem.add_development_dependency "pry"
29
+ gem.add_development_dependency "railties", ">= 3.0.0"
27
30
  gem.add_development_dependency "rake"
28
31
  gem.add_development_dependency "rspec", ">= 3.0.0"
29
- gem.add_development_dependency "rubocop", "0.74.0"
32
+ gem.add_development_dependency "rubocop"
30
33
  gem.add_development_dependency "simplecov", ">= 0.17.0"
31
34
  gem.add_development_dependency "yard"
32
35
  end