pundit 2.1.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 (91) hide show
  1. checksums.yaml +4 -4
  2. data/.github/ISSUE_TEMPLATE/bug_report.md +20 -0
  3. data/.github/ISSUE_TEMPLATE/feature_request.md +26 -0
  4. data/.github/PULL_REQUEST_TEMPLATE/gem_release_template.md +8 -0
  5. data/.github/pull_request_template.md +9 -0
  6. data/.github/workflows/main.yml +147 -0
  7. data/.github/workflows/push_gem.yml +33 -0
  8. data/.gitignore +1 -0
  9. data/.rubocop.yml +26 -29
  10. data/.rubocop_ignore_git.yml +7 -0
  11. data/.yardopts +1 -1
  12. data/CHANGELOG.md +120 -21
  13. data/CODE_OF_CONDUCT.md +1 -1
  14. data/CONTRIBUTING.md +3 -5
  15. data/Gemfile +23 -2
  16. data/README.md +175 -78
  17. data/Rakefile +1 -0
  18. data/SECURITY.md +19 -0
  19. data/config/rubocop-rspec.yml +5 -0
  20. data/lib/generators/pundit/install/install_generator.rb +6 -2
  21. data/lib/generators/pundit/install/templates/{application_policy.rb → application_policy.rb.tt} +7 -3
  22. data/lib/generators/pundit/policy/policy_generator.rb +6 -2
  23. data/lib/generators/pundit/policy/templates/policy.rb.tt +16 -0
  24. data/lib/generators/rspec/policy_generator.rb +7 -2
  25. data/lib/generators/rspec/templates/{policy_spec.rb → policy_spec.rb.tt} +1 -1
  26. data/lib/generators/test_unit/policy_generator.rb +7 -2
  27. data/lib/pundit/authorization.rb +251 -0
  28. data/lib/pundit/cache_store/legacy_store.rb +24 -0
  29. data/lib/pundit/cache_store/null_store.rb +27 -0
  30. data/lib/pundit/cache_store.rb +22 -0
  31. data/lib/pundit/context.rb +177 -0
  32. data/lib/pundit/policy_finder.rb +24 -3
  33. data/lib/pundit/railtie.rb +19 -0
  34. data/lib/pundit/rspec.rb +93 -20
  35. data/lib/pundit/version.rb +2 -1
  36. data/lib/pundit.rb +68 -257
  37. data/pundit.gemspec +10 -10
  38. data/spec/authorization_spec.rb +331 -0
  39. data/spec/generators_spec.rb +43 -0
  40. data/spec/policies/post_policy_spec.rb +28 -1
  41. data/spec/policy_finder_spec.rb +84 -17
  42. data/spec/pundit/helper_spec.rb +18 -0
  43. data/spec/pundit_spec.rb +110 -233
  44. data/spec/rspec_dsl_spec.rb +81 -0
  45. data/spec/simple_cov_check_action_formatter.rb +79 -0
  46. data/spec/spec_helper.rb +29 -265
  47. data/spec/support/lib/controller.rb +38 -0
  48. data/spec/support/lib/custom_cache.rb +19 -0
  49. data/spec/support/lib/instance_tracking.rb +20 -0
  50. data/spec/support/models/article.rb +4 -0
  51. data/spec/support/models/article_tag.rb +7 -0
  52. data/spec/support/models/artificial_blog.rb +7 -0
  53. data/spec/support/models/blog.rb +4 -0
  54. data/spec/support/models/comment.rb +5 -0
  55. data/spec/support/models/comment_four_five_six.rb +5 -0
  56. data/spec/support/models/comment_scope.rb +13 -0
  57. data/spec/support/models/comments_relation.rb +15 -0
  58. data/spec/support/models/customer/post.rb +11 -0
  59. data/spec/support/models/default_scope_contains_error.rb +5 -0
  60. data/spec/support/models/dummy_current_user.rb +7 -0
  61. data/spec/support/models/foo.rb +4 -0
  62. data/spec/support/models/post.rb +25 -0
  63. data/spec/support/models/post_four_five_six.rb +9 -0
  64. data/spec/support/models/project_one_two_three/avatar_four_five_six.rb +7 -0
  65. data/spec/support/models/project_one_two_three/tag_four_five_six.rb +11 -0
  66. data/spec/support/models/wiki.rb +4 -0
  67. data/spec/support/policies/article_tag_other_name_policy.rb +13 -0
  68. data/spec/support/policies/base_policy.rb +23 -0
  69. data/spec/support/policies/blog_policy.rb +5 -0
  70. data/spec/support/policies/comment_policy.rb +11 -0
  71. data/spec/support/policies/criteria_policy.rb +5 -0
  72. data/spec/support/policies/default_scope_contains_error_policy.rb +10 -0
  73. data/spec/support/policies/denier_policy.rb +7 -0
  74. data/spec/support/policies/dummy_current_user_policy.rb +9 -0
  75. data/spec/support/policies/nil_class_policy.rb +17 -0
  76. data/spec/support/policies/post_policy.rb +36 -0
  77. data/spec/support/policies/project/admin/comment_policy.rb +15 -0
  78. data/spec/support/policies/project/comment_policy.rb +17 -0
  79. data/spec/support/policies/project/criteria_policy.rb +7 -0
  80. data/spec/support/policies/project/post_policy.rb +13 -0
  81. data/spec/support/policies/project_one_two_three/avatar_four_five_six_policy.rb +6 -0
  82. data/spec/support/policies/project_one_two_three/comment_four_five_six_policy.rb +6 -0
  83. data/spec/support/policies/project_one_two_three/criteria_four_five_six_policy.rb +6 -0
  84. data/spec/support/policies/project_one_two_three/post_four_five_six_policy.rb +6 -0
  85. data/spec/support/policies/project_one_two_three/tag_four_five_six_policy.rb +6 -0
  86. data/spec/support/policies/publication_policy.rb +13 -0
  87. data/spec/support/policies/wiki_policy.rb +8 -0
  88. metadata +80 -130
  89. data/.travis.yml +0 -21
  90. data/lib/generators/pundit/policy/templates/policy.rb +0 -9
  91. /data/lib/generators/test_unit/templates/{policy_test.rb → policy_test.rb.tt} +0 -0
data/spec/spec_helper.rb CHANGED
@@ -1,271 +1,35 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ if ENV["COVERAGE"]
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
+ ])
14
+ SimpleCov.start do
15
+ add_filter "/spec/"
16
+ enable_coverage :branch
17
+ primary_coverage :branch
18
+ end
19
+ end
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
+
3
24
  require "pundit"
4
25
  require "pundit/rspec"
5
-
6
- require "rack"
7
- require "rack/test"
8
- require "pry"
9
- require "active_support"
10
- require "active_support/core_ext"
11
26
  require "active_model/naming"
12
- require "action_controller/metal/strong_parameters"
13
-
14
- I18n.enforce_available_locales = false
15
-
16
- module PunditSpecHelper
17
- extend RSpec::Matchers::DSL
18
-
19
- matcher :be_truthy do
20
- match do |actual|
21
- actual
22
- end
23
- end
24
- end
25
-
26
- RSpec.configure do |config|
27
- config.include PunditSpecHelper
28
- end
29
-
30
- class PostPolicy < Struct.new(:user, :post)
31
- class Scope < Struct.new(:user, :scope)
32
- def resolve
33
- scope.published
34
- end
35
- end
36
-
37
- def update?
38
- post.user == user
39
- end
40
-
41
- def destroy?
42
- false
43
- end
44
-
45
- def show?
46
- true
47
- end
48
-
49
- def permitted_attributes
50
- if post.user == user
51
- %i[title votes]
52
- else
53
- [:votes]
54
- end
55
- end
56
-
57
- def permitted_attributes_for_revise
58
- [:body]
59
- end
60
- end
61
-
62
- class Post < Struct.new(:user)
63
- def self.published
64
- :published
65
- end
66
-
67
- def self.read
68
- :read
69
- end
70
-
71
- def to_s
72
- "Post"
73
- end
74
-
75
- def inspect
76
- "#<Post>"
77
- end
78
- end
79
-
80
- module Customer
81
- class Post < Post
82
- def model_name
83
- OpenStruct.new(param_key: "customer_post")
84
- end
85
-
86
- def self.policy_class
87
- PostPolicy
88
- end
89
-
90
- def policy_class
91
- self.class.policy_class
92
- end
93
- end
94
- end
95
-
96
- class CommentScope
97
- attr_reader :original_object
98
- def initialize(original_object)
99
- @original_object = original_object
100
- end
101
-
102
- def ==(other)
103
- original_object == other.original_object
104
- end
105
- end
106
-
107
- class CommentPolicy < Struct.new(:user, :comment)
108
- class Scope < Struct.new(:user, :scope)
109
- def resolve
110
- CommentScope.new(scope)
111
- end
112
- end
113
- end
114
-
115
- class PublicationPolicy < Struct.new(:user, :publication)
116
- class Scope < Struct.new(:user, :scope)
117
- def resolve
118
- scope.published
119
- end
120
- end
121
-
122
- def create?
123
- true
124
- end
125
- end
126
-
127
- class Comment
128
- extend ActiveModel::Naming
129
- end
130
-
131
- class CommentsRelation
132
- def initialize(empty = false)
133
- @empty = empty
134
- end
135
-
136
- def blank?
137
- @empty
138
- end
139
27
 
140
- def model_name
141
- Comment.model_name
142
- end
143
- end
144
-
145
- class Article; end
146
-
147
- class BlogPolicy < Struct.new(:user, :blog); end
148
-
149
- class Blog; end
150
-
151
- class ArtificialBlog < Blog
152
- def self.policy_class
153
- BlogPolicy
154
- end
155
- end
156
-
157
- class ArticleTagOtherNamePolicy < Struct.new(:user, :tag)
158
- def show?
159
- true
160
- end
161
-
162
- def destroy?
163
- false
164
- end
165
- end
166
-
167
- class ArticleTag
168
- def self.policy_class
169
- ArticleTagOtherNamePolicy
170
- end
171
- end
172
-
173
- class CriteriaPolicy < Struct.new(:user, :criteria); end
174
-
175
- module Project
176
- class CommentPolicy < Struct.new(:user, :comment)
177
- class Scope < Struct.new(:user, :scope)
178
- def resolve
179
- scope
180
- end
181
- end
182
- end
183
-
184
- class CriteriaPolicy < Struct.new(:user, :criteria); end
185
-
186
- class PostPolicy < Struct.new(:user, :post)
187
- class Scope < Struct.new(:user, :scope)
188
- def resolve
189
- scope.read
190
- end
191
- end
192
- end
193
- end
194
-
195
- class DenierPolicy < Struct.new(:user, :record)
196
- def update?
197
- false
198
- end
199
- end
200
-
201
- class Controller
202
- include Pundit
203
- # Mark protected methods public so they may be called in test
204
- # rubocop:disable Layout/AccessModifierIndentation, Style/AccessModifierDeclarations
205
- public(*Pundit.protected_instance_methods)
206
- # rubocop:enable Layout/AccessModifierIndentation, Style/AccessModifierDeclarations
207
-
208
- attr_reader :current_user, :action_name, :params
209
-
210
- def initialize(current_user, action_name, params)
211
- @current_user = current_user
212
- @action_name = action_name
213
- @params = params
214
- end
215
- end
216
-
217
- class NilClassPolicy < Struct.new(:user, :record)
218
- class Scope
219
- def initialize(*)
220
- raise Pundit::NotDefinedError, "Cannot scope NilClass"
221
- end
222
- end
223
-
224
- def show?
225
- false
226
- end
227
-
228
- def destroy?
229
- false
230
- end
231
- end
232
-
233
- class Wiki; end
234
- class WikiPolicy
235
- class Scope
236
- # deliberate typo method
237
- def initalize; end
238
- end
239
- end
240
-
241
- class Thread
242
- def self.all; end
243
- end
244
- class ThreadPolicy < Struct.new(:user, :thread)
245
- class Scope < Struct.new(:user, :scope)
246
- def resolve
247
- # deliberate wrong useage of the method
248
- scope.all(:unvalid, :parameters)
249
- end
250
- end
251
- end
252
-
253
- class PostFourFiveSix < Struct.new(:user); end
254
-
255
- class CommentFourFiveSix; extend ActiveModel::Naming; end
256
-
257
- module ProjectOneTwoThree
258
- class CommentFourFiveSixPolicy < Struct.new(:user, :post); end
259
-
260
- class CriteriaFourFiveSixPolicy < Struct.new(:user, :criteria); end
261
-
262
- class PostFourFiveSixPolicy < Struct.new(:user, :post); end
263
-
264
- class TagFourFiveSix < Struct.new(:user); end
265
-
266
- class TagFourFiveSixPolicy < Struct.new(:user, :tag); end
267
-
268
- class AvatarFourFiveSix; extend ActiveModel::Naming; end
269
-
270
- class AvatarFourFiveSixPolicy < Struct.new(:user, :avatar); end
271
- 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
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ class DenierPolicy < BasePolicy
4
+ def update?
5
+ false
6
+ end
7
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ class DummyCurrentUserPolicy < BasePolicy
4
+ class Scope < BasePolicy::BaseScope
5
+ def resolve
6
+ user
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ class NilClassPolicy < BasePolicy
4
+ class Scope
5
+ def initialize(*)
6
+ raise Pundit::NotDefinedError, "Cannot scope NilClass"
7
+ end
8
+ end
9
+
10
+ def show?
11
+ false
12
+ end
13
+
14
+ def destroy?
15
+ false
16
+ end
17
+ end