pundit 2.4.0 → 2.5.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.
Files changed (78) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/main.yml +92 -57
  3. data/.rubocop.yml +18 -8
  4. data/.rubocop_ignore_git.yml +7 -0
  5. data/.yardopts +1 -1
  6. data/CHANGELOG.md +61 -42
  7. data/Gemfile +22 -2
  8. data/README.md +30 -0
  9. data/Rakefile +1 -0
  10. data/lib/generators/pundit/install/install_generator.rb +3 -1
  11. data/lib/generators/pundit/policy/policy_generator.rb +3 -1
  12. data/lib/generators/rspec/policy_generator.rb +4 -1
  13. data/lib/generators/test_unit/policy_generator.rb +4 -1
  14. data/lib/pundit/authorization.rb +152 -77
  15. data/lib/pundit/cache_store/legacy_store.rb +7 -0
  16. data/lib/pundit/cache_store/null_store.rb +9 -0
  17. data/lib/pundit/cache_store.rb +22 -0
  18. data/lib/pundit/context.rb +76 -26
  19. data/lib/pundit/policy_finder.rb +22 -1
  20. data/lib/pundit/railtie.rb +19 -0
  21. data/lib/pundit/rspec.rb +67 -6
  22. data/lib/pundit/version.rb +2 -1
  23. data/lib/pundit.rb +39 -14
  24. data/pundit.gemspec +8 -12
  25. data/spec/authorization_spec.rb +60 -3
  26. data/spec/policy_finder_spec.rb +5 -1
  27. data/spec/pundit/helper_spec.rb +18 -0
  28. data/spec/pundit_spec.rb +37 -11
  29. data/spec/rspec_dsl_spec.rb +81 -0
  30. data/spec/simple_cov_check_action_formatter.rb +79 -0
  31. data/spec/spec_helper.rb +22 -339
  32. data/spec/support/lib/controller.rb +38 -0
  33. data/spec/support/lib/custom_cache.rb +19 -0
  34. data/spec/support/lib/instance_tracking.rb +20 -0
  35. data/spec/support/models/article.rb +4 -0
  36. data/spec/support/models/article_tag.rb +7 -0
  37. data/spec/support/models/artificial_blog.rb +7 -0
  38. data/spec/support/models/blog.rb +4 -0
  39. data/spec/support/models/comment.rb +5 -0
  40. data/spec/support/models/comment_four_five_six.rb +5 -0
  41. data/spec/support/models/comment_scope.rb +13 -0
  42. data/spec/support/models/comments_relation.rb +15 -0
  43. data/spec/support/models/customer/post.rb +11 -0
  44. data/spec/support/models/default_scope_contains_error.rb +5 -0
  45. data/spec/support/models/dummy_current_user.rb +7 -0
  46. data/spec/support/models/foo.rb +4 -0
  47. data/spec/support/models/post.rb +25 -0
  48. data/spec/support/models/post_four_five_six.rb +9 -0
  49. data/spec/support/models/project_one_two_three/avatar_four_five_six.rb +7 -0
  50. data/spec/support/models/project_one_two_three/tag_four_five_six.rb +11 -0
  51. data/spec/support/models/wiki.rb +4 -0
  52. data/spec/support/policies/article_tag_other_name_policy.rb +13 -0
  53. data/spec/support/policies/base_policy.rb +23 -0
  54. data/spec/support/policies/blog_policy.rb +5 -0
  55. data/spec/support/policies/comment_policy.rb +11 -0
  56. data/spec/support/policies/criteria_policy.rb +5 -0
  57. data/spec/support/policies/default_scope_contains_error_policy.rb +10 -0
  58. data/spec/support/policies/denier_policy.rb +7 -0
  59. data/spec/support/policies/dummy_current_user_policy.rb +9 -0
  60. data/spec/support/policies/nil_class_policy.rb +17 -0
  61. data/spec/support/policies/post_policy.rb +36 -0
  62. data/spec/support/policies/project/admin/comment_policy.rb +15 -0
  63. data/spec/support/policies/project/comment_policy.rb +17 -0
  64. data/spec/support/policies/project/criteria_policy.rb +7 -0
  65. data/spec/support/policies/project/post_policy.rb +13 -0
  66. data/spec/support/policies/project_one_two_three/avatar_four_five_six_policy.rb +6 -0
  67. data/spec/support/policies/project_one_two_three/comment_four_five_six_policy.rb +6 -0
  68. data/spec/support/policies/project_one_two_three/criteria_four_five_six_policy.rb +6 -0
  69. data/spec/support/policies/project_one_two_three/post_four_five_six_policy.rb +6 -0
  70. data/spec/support/policies/project_one_two_three/tag_four_five_six_policy.rb +6 -0
  71. data/spec/support/policies/publication_policy.rb +13 -0
  72. data/spec/support/policies/wiki_policy.rb +8 -0
  73. metadata +62 -158
  74. data/spec/dsl_spec.rb +0 -30
  75. /data/lib/generators/pundit/install/templates/{application_policy.rb → application_policy.rb.tt} +0 -0
  76. /data/lib/generators/pundit/policy/templates/{policy.rb → policy.rb.tt} +0 -0
  77. /data/lib/generators/rspec/templates/{policy_spec.rb → policy_spec.rb.tt} +0 -0
  78. /data/lib/generators/test_unit/templates/{policy_test.rb → policy_test.rb.tt} +0 -0
data/spec/spec_helper.rb CHANGED
@@ -2,351 +2,34 @@
2
2
 
3
3
  if ENV["COVERAGE"]
4
4
  require "simplecov"
5
+ require "simplecov_json_formatter"
6
+ require_relative "simple_cov_check_action_formatter"
7
+ SimpleCov.formatters = SimpleCov::Formatter::MultiFormatter.new([
8
+ SimpleCov::Formatter::HTMLFormatter,
9
+ SimpleCov::Formatter::JSONFormatter,
10
+ SimpleCovCheckActionFormatter.with_options(
11
+ output_filename: "simplecov-check-action.json"
12
+ )
13
+ ])
5
14
  SimpleCov.start do
6
15
  add_filter "/spec/"
16
+ enable_coverage :branch
17
+ primary_coverage :branch
7
18
  end
8
19
  end
9
20
 
21
+ # @see https://github.com/rails/rails/issues/54260
22
+ require "logger" if RUBY_ENGINE == "jruby" && RUBY_ENGINE_VERSION.start_with?("9.3")
23
+
10
24
  require "pundit"
11
25
  require "pundit/rspec"
12
-
13
- require "rack"
14
- require "rack/test"
15
- require "pry"
16
- require "active_support"
17
- require "active_support/core_ext"
18
26
  require "active_model/naming"
19
- require "action_controller/metal/strong_parameters"
20
-
21
- module InstanceTracking
22
- module ClassMethods
23
- def instances
24
- @instances || 0
25
- end
26
-
27
- attr_writer :instances
28
- end
29
-
30
- def self.prepended(other)
31
- other.extend(ClassMethods)
32
- end
33
-
34
- def initialize(*args, **kwargs, &block)
35
- self.class.instances += 1
36
- super(*args, **kwargs, &block)
37
- end
38
- end
39
-
40
- class BasePolicy
41
- prepend InstanceTracking
42
-
43
- class BaseScope
44
- prepend InstanceTracking
45
-
46
- def initialize(user, scope)
47
- @user = user
48
- @scope = scope
49
- end
50
-
51
- attr_reader :user, :scope
52
- end
53
-
54
- def initialize(user, record)
55
- @user = user
56
- @record = record
57
- end
58
-
59
- attr_reader :user, :record
60
- end
61
-
62
- class PostPolicy < BasePolicy
63
- class Scope < BaseScope
64
- def resolve
65
- scope.published
66
- end
67
- end
68
-
69
- alias post record
70
-
71
- def update?
72
- post.user == user
73
- end
74
-
75
- def destroy?
76
- false
77
- end
78
-
79
- def show?
80
- true
81
- end
82
-
83
- def permitted_attributes
84
- if post.user == user
85
- %i[title votes]
86
- else
87
- [:votes]
88
- end
89
- end
90
-
91
- def permitted_attributes_for_revise
92
- [:body]
93
- end
94
- end
95
-
96
- class Post
97
- def initialize(user = nil)
98
- @user = user
99
- end
100
-
101
- attr_reader :user
102
-
103
- def self.published
104
- :published
105
- end
106
-
107
- def self.read
108
- :read
109
- end
110
-
111
- def to_s
112
- "Post"
113
- end
114
-
115
- def inspect
116
- "#<Post>"
117
- end
118
- end
119
-
120
- module Customer
121
- class Post < ::Post
122
- def model_name
123
- OpenStruct.new(param_key: "customer_post")
124
- end
125
-
126
- def self.policy_class
127
- PostPolicy
128
- end
129
- end
130
- end
131
-
132
- class CommentScope
133
- attr_reader :original_object
134
-
135
- def initialize(original_object)
136
- @original_object = original_object
137
- end
138
-
139
- def ==(other)
140
- original_object == other.original_object
141
- end
142
- end
143
-
144
- class CommentPolicy < BasePolicy
145
- class Scope < BaseScope
146
- def resolve
147
- CommentScope.new(scope)
148
- end
149
- end
150
-
151
- alias comment record
152
- end
153
-
154
- class PublicationPolicy < BasePolicy
155
- class Scope < BaseScope
156
- def resolve
157
- scope.published
158
- end
159
- end
160
-
161
- def create?
162
- true
163
- end
164
- end
165
-
166
- class Comment
167
- extend ActiveModel::Naming
168
- end
169
-
170
- class CommentsRelation
171
- def initialize(empty: false)
172
- @empty = empty
173
- end
174
-
175
- def blank?
176
- @empty
177
- end
178
-
179
- def self.model_name
180
- Comment.model_name
181
- end
182
- end
183
-
184
- class Article; end
185
-
186
- class BlogPolicy < BasePolicy
187
- alias blog record
188
- end
189
-
190
- class Blog; end
191
-
192
- class ArtificialBlog < Blog
193
- def self.policy_class
194
- BlogPolicy
195
- end
196
- end
197
-
198
- class ArticleTagOtherNamePolicy < BasePolicy
199
- def show?
200
- true
201
- end
202
-
203
- def destroy?
204
- false
205
- end
206
-
207
- alias tag record
208
- end
209
-
210
- class ArticleTag
211
- def self.policy_class
212
- ArticleTagOtherNamePolicy
213
- end
214
- end
215
-
216
- class CriteriaPolicy < BasePolicy
217
- alias criteria record
218
- end
219
-
220
- module Project
221
- class CommentPolicy < BasePolicy
222
- class Scope < BaseScope
223
- def resolve
224
- scope
225
- end
226
- end
227
-
228
- def update?
229
- true
230
- end
231
-
232
- alias comment record
233
- end
234
-
235
- class CriteriaPolicy < BasePolicy
236
- alias criteria record
237
- end
238
-
239
- class PostPolicy < BasePolicy
240
- class Scope < BaseScope
241
- def resolve
242
- scope.read
243
- end
244
- end
245
-
246
- alias post record
247
- end
248
-
249
- module Admin
250
- class CommentPolicy < BasePolicy
251
- def update?
252
- true
253
- end
254
-
255
- def destroy?
256
- false
257
- end
258
- end
259
- end
260
- end
261
-
262
- class DenierPolicy < BasePolicy
263
- def update?
264
- false
265
- end
266
- end
267
27
 
268
- class Controller
269
- include Pundit::Authorization
270
- # Mark protected methods public so they may be called in test
271
- # rubocop:disable Style/AccessModifierDeclarations
272
- public(*Pundit::Authorization.protected_instance_methods)
273
- # rubocop:enable Style/AccessModifierDeclarations
274
-
275
- attr_reader :current_user, :action_name, :params
276
-
277
- def initialize(current_user, action_name, params)
278
- @current_user = current_user
279
- @action_name = action_name
280
- @params = params
281
- end
282
- end
283
-
284
- class NilClassPolicy < BasePolicy
285
- class Scope
286
- def initialize(*)
287
- raise Pundit::NotDefinedError, "Cannot scope NilClass"
288
- end
289
- end
290
-
291
- def show?
292
- false
293
- end
294
-
295
- def destroy?
296
- false
297
- end
298
- end
299
-
300
- class Wiki; end
301
-
302
- class WikiPolicy
303
- class Scope
304
- # deliberate typo method
305
- def initalize; end
306
- end
307
- end
308
-
309
- class Thread
310
- def self.all; end
311
- end
312
-
313
- class ThreadPolicy < BasePolicy
314
- class Scope < BaseScope
315
- def resolve
316
- # deliberate wrong usage of the method
317
- scope.all(:unvalid, :parameters)
318
- end
319
- end
320
- end
321
-
322
- class PostFourFiveSix
323
- def initialize(user)
324
- @user = user
325
- end
326
-
327
- attr_reader(:user)
328
- end
329
-
330
- class CommentFourFiveSix; extend ActiveModel::Naming; end
331
-
332
- module ProjectOneTwoThree
333
- class CommentFourFiveSixPolicy < BasePolicy; end
334
-
335
- class CriteriaFourFiveSixPolicy < BasePolicy; end
336
-
337
- class PostFourFiveSixPolicy < BasePolicy; end
338
-
339
- class TagFourFiveSix
340
- def initialize(user)
341
- @user = user
342
- end
343
-
344
- attr_reader(:user)
345
- end
346
-
347
- class TagFourFiveSixPolicy < BasePolicy; end
348
-
349
- class AvatarFourFiveSix; extend ActiveModel::Naming; end
350
-
351
- class AvatarFourFiveSixPolicy < BasePolicy; end
352
- end
28
+ # Load all supporting files: models, policies, etc.
29
+ require "zeitwerk"
30
+ loader = Zeitwerk::Loader.new
31
+ loader.push_dir(File.expand_path("support/models", __dir__))
32
+ loader.push_dir(File.expand_path("support/policies", __dir__))
33
+ loader.push_dir(File.expand_path("support/lib", __dir__))
34
+ loader.setup
35
+ loader.eager_load
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Controller
4
+ attr_accessor :current_user
5
+ attr_reader :action_name, :params
6
+
7
+ class View
8
+ def initialize(controller)
9
+ @controller = controller
10
+ end
11
+
12
+ attr_reader :controller
13
+ end
14
+
15
+ class << self
16
+ def helper(mod)
17
+ View.include(mod)
18
+ end
19
+
20
+ def helper_method(method)
21
+ View.class_eval <<-RUBY, __FILE__, __LINE__ + 1
22
+ def #{method}(*args, **kwargs, &block)
23
+ controller.send(:#{method}, *args, **kwargs, &block)
24
+ end
25
+ RUBY
26
+ end
27
+ end
28
+
29
+ include Pundit::Authorization
30
+ # Mark protected methods public so they may be called in test
31
+ public(*Pundit::Authorization.protected_instance_methods)
32
+
33
+ def initialize(current_user, action_name, params)
34
+ @current_user = current_user
35
+ @action_name = action_name
36
+ @params = params
37
+ end
38
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ class CustomCache
4
+ def initialize
5
+ @store = {}
6
+ end
7
+
8
+ def to_h
9
+ @store
10
+ end
11
+
12
+ def [](key)
13
+ @store[key]
14
+ end
15
+
16
+ def []=(key, value)
17
+ @store[key] = value
18
+ end
19
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module InstanceTracking
4
+ module ClassMethods
5
+ def instances
6
+ @instances || 0
7
+ end
8
+
9
+ attr_writer :instances
10
+ end
11
+
12
+ def self.prepended(other)
13
+ other.extend(ClassMethods)
14
+ end
15
+
16
+ def initialize(*args, **kwargs, &block)
17
+ self.class.instances += 1
18
+ super(*args, **kwargs, &block)
19
+ end
20
+ end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Article
4
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ArticleTag
4
+ def self.policy_class
5
+ ArticleTagOtherNamePolicy
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ArtificialBlog < Blog
4
+ def self.policy_class
5
+ BlogPolicy
6
+ end
7
+ end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Blog
4
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Comment
4
+ extend ActiveModel::Naming
5
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ class CommentFourFiveSix
4
+ extend ActiveModel::Naming
5
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ class CommentScope
4
+ attr_reader :original_object
5
+
6
+ def initialize(original_object)
7
+ @original_object = original_object
8
+ end
9
+
10
+ def ==(other)
11
+ original_object == other.original_object
12
+ end
13
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ class CommentsRelation
4
+ def initialize(empty: false)
5
+ @empty = empty
6
+ end
7
+
8
+ def blank?
9
+ @empty
10
+ end
11
+
12
+ def self.model_name
13
+ Comment.model_name
14
+ end
15
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Customer
4
+ class Post < ::Post
5
+ extend ActiveModel::Naming
6
+
7
+ def self.policy_class
8
+ PostPolicy
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ class DefaultScopeContainsError
4
+ def self.all; end
5
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ class DummyCurrentUser
4
+ def update?
5
+ user
6
+ end
7
+ end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Foo
4
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Post
4
+ def initialize(user = nil)
5
+ @user = user
6
+ end
7
+
8
+ attr_reader :user
9
+
10
+ def self.published
11
+ :published
12
+ end
13
+
14
+ def self.read
15
+ :read
16
+ end
17
+
18
+ def to_s
19
+ "Post"
20
+ end
21
+
22
+ def inspect
23
+ "#<Post>"
24
+ end
25
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ class PostFourFiveSix
4
+ def initialize(user)
5
+ @user = user
6
+ end
7
+
8
+ attr_reader(:user)
9
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ProjectOneTwoThree
4
+ class AvatarFourFiveSix
5
+ extend ActiveModel::Naming
6
+ end
7
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ProjectOneTwoThree
4
+ class TagFourFiveSix
5
+ def initialize(user)
6
+ @user = user
7
+ end
8
+
9
+ attr_reader(:user)
10
+ end
11
+ end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Wiki
4
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ArticleTagOtherNamePolicy < BasePolicy
4
+ def show?
5
+ true
6
+ end
7
+
8
+ def destroy?
9
+ false
10
+ end
11
+
12
+ alias tag record
13
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ class BasePolicy
4
+ prepend InstanceTracking
5
+
6
+ class BaseScope
7
+ prepend InstanceTracking
8
+
9
+ def initialize(user, scope)
10
+ @user = user
11
+ @scope = scope
12
+ end
13
+
14
+ attr_reader :user, :scope
15
+ end
16
+
17
+ def initialize(user, record)
18
+ @user = user
19
+ @record = record
20
+ end
21
+
22
+ attr_reader :user, :record
23
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ class BlogPolicy < BasePolicy
4
+ alias blog record
5
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ class CommentPolicy < BasePolicy
4
+ class Scope < BaseScope
5
+ def resolve
6
+ CommentScope.new(scope)
7
+ end
8
+ end
9
+
10
+ alias comment record
11
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ class CriteriaPolicy < BasePolicy
4
+ alias criteria record
5
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ class DefaultScopeContainsErrorPolicy < BasePolicy
4
+ class Scope < BaseScope
5
+ def resolve
6
+ # deliberate wrong usage of the method
7
+ raise "This is an arbitrary error that should bubble up"
8
+ end
9
+ end
10
+ end