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.
- checksums.yaml +4 -4
- data/.github/ISSUE_TEMPLATE/bug_report.md +20 -0
- data/.github/ISSUE_TEMPLATE/feature_request.md +26 -0
- data/.github/PULL_REQUEST_TEMPLATE/gem_release_template.md +8 -0
- data/.github/pull_request_template.md +9 -0
- data/.github/workflows/main.yml +147 -0
- data/.github/workflows/push_gem.yml +33 -0
- data/.gitignore +1 -0
- data/.rubocop.yml +26 -29
- data/.rubocop_ignore_git.yml +7 -0
- data/.yardopts +1 -1
- data/CHANGELOG.md +120 -21
- data/CODE_OF_CONDUCT.md +1 -1
- data/CONTRIBUTING.md +3 -5
- data/Gemfile +23 -2
- data/README.md +175 -78
- data/Rakefile +1 -0
- data/SECURITY.md +19 -0
- data/config/rubocop-rspec.yml +5 -0
- data/lib/generators/pundit/install/install_generator.rb +6 -2
- data/lib/generators/pundit/install/templates/{application_policy.rb → application_policy.rb.tt} +7 -3
- data/lib/generators/pundit/policy/policy_generator.rb +6 -2
- data/lib/generators/pundit/policy/templates/policy.rb.tt +16 -0
- data/lib/generators/rspec/policy_generator.rb +7 -2
- data/lib/generators/rspec/templates/{policy_spec.rb → policy_spec.rb.tt} +1 -1
- data/lib/generators/test_unit/policy_generator.rb +7 -2
- data/lib/pundit/authorization.rb +251 -0
- data/lib/pundit/cache_store/legacy_store.rb +24 -0
- data/lib/pundit/cache_store/null_store.rb +27 -0
- data/lib/pundit/cache_store.rb +22 -0
- data/lib/pundit/context.rb +177 -0
- data/lib/pundit/policy_finder.rb +24 -3
- data/lib/pundit/railtie.rb +19 -0
- data/lib/pundit/rspec.rb +93 -20
- data/lib/pundit/version.rb +2 -1
- data/lib/pundit.rb +68 -257
- data/pundit.gemspec +10 -10
- data/spec/authorization_spec.rb +331 -0
- data/spec/generators_spec.rb +43 -0
- data/spec/policies/post_policy_spec.rb +28 -1
- data/spec/policy_finder_spec.rb +84 -17
- data/spec/pundit/helper_spec.rb +18 -0
- data/spec/pundit_spec.rb +110 -233
- data/spec/rspec_dsl_spec.rb +81 -0
- data/spec/simple_cov_check_action_formatter.rb +79 -0
- data/spec/spec_helper.rb +29 -265
- data/spec/support/lib/controller.rb +38 -0
- data/spec/support/lib/custom_cache.rb +19 -0
- data/spec/support/lib/instance_tracking.rb +20 -0
- data/spec/support/models/article.rb +4 -0
- data/spec/support/models/article_tag.rb +7 -0
- data/spec/support/models/artificial_blog.rb +7 -0
- data/spec/support/models/blog.rb +4 -0
- data/spec/support/models/comment.rb +5 -0
- data/spec/support/models/comment_four_five_six.rb +5 -0
- data/spec/support/models/comment_scope.rb +13 -0
- data/spec/support/models/comments_relation.rb +15 -0
- data/spec/support/models/customer/post.rb +11 -0
- data/spec/support/models/default_scope_contains_error.rb +5 -0
- data/spec/support/models/dummy_current_user.rb +7 -0
- data/spec/support/models/foo.rb +4 -0
- data/spec/support/models/post.rb +25 -0
- data/spec/support/models/post_four_five_six.rb +9 -0
- data/spec/support/models/project_one_two_three/avatar_four_five_six.rb +7 -0
- data/spec/support/models/project_one_two_three/tag_four_five_six.rb +11 -0
- data/spec/support/models/wiki.rb +4 -0
- data/spec/support/policies/article_tag_other_name_policy.rb +13 -0
- data/spec/support/policies/base_policy.rb +23 -0
- data/spec/support/policies/blog_policy.rb +5 -0
- data/spec/support/policies/comment_policy.rb +11 -0
- data/spec/support/policies/criteria_policy.rb +5 -0
- data/spec/support/policies/default_scope_contains_error_policy.rb +10 -0
- data/spec/support/policies/denier_policy.rb +7 -0
- data/spec/support/policies/dummy_current_user_policy.rb +9 -0
- data/spec/support/policies/nil_class_policy.rb +17 -0
- data/spec/support/policies/post_policy.rb +36 -0
- data/spec/support/policies/project/admin/comment_policy.rb +15 -0
- data/spec/support/policies/project/comment_policy.rb +17 -0
- data/spec/support/policies/project/criteria_policy.rb +7 -0
- data/spec/support/policies/project/post_policy.rb +13 -0
- data/spec/support/policies/project_one_two_three/avatar_four_five_six_policy.rb +6 -0
- data/spec/support/policies/project_one_two_three/comment_four_five_six_policy.rb +6 -0
- data/spec/support/policies/project_one_two_three/criteria_four_five_six_policy.rb +6 -0
- data/spec/support/policies/project_one_two_three/post_four_five_six_policy.rb +6 -0
- data/spec/support/policies/project_one_two_three/tag_four_five_six_policy.rb +6 -0
- data/spec/support/policies/publication_policy.rb +13 -0
- data/spec/support/policies/wiki_policy.rb +8 -0
- metadata +80 -130
- data/.travis.yml +0 -21
- data/lib/generators/pundit/policy/templates/policy.rb +0 -9
- /data/lib/generators/test_unit/templates/{policy_test.rb → policy_test.rb.tt} +0 -0
data/README.md
CHANGED
@@ -1,37 +1,36 @@
|
|
1
1
|
# Pundit
|
2
2
|
|
3
|
-
[](
|
3
|
+
[](https://github.com/varvet/pundit/actions/workflows/main.yml)
|
4
|
+
[](https://codeclimate.com/github/varvet/pundit/maintainability)
|
5
|
+
[](https://inch-ci.org/github/varvet/pundit)
|
6
|
+
[](https://badge.fury.io/rb/pundit)
|
7
7
|
|
8
8
|
Pundit provides a set of helpers which guide you in leveraging regular Ruby
|
9
|
-
classes and object oriented design patterns to build a
|
10
|
-
|
9
|
+
classes and object oriented design patterns to build a straightforward, robust, and
|
10
|
+
scalable authorization system.
|
11
11
|
|
12
|
-
Links:
|
12
|
+
## Links:
|
13
13
|
|
14
|
-
- [API documentation](
|
14
|
+
- [API documentation for the most recent version](https://www.rubydoc.info/gems/pundit)
|
15
15
|
- [Source Code](https://github.com/varvet/pundit)
|
16
|
-
- [Contributing](https://github.com/varvet/pundit/blob/
|
17
|
-
- [Code of Conduct](https://github.com/varvet/pundit/blob/
|
16
|
+
- [Contributing](https://github.com/varvet/pundit/blob/main/CONTRIBUTING.md)
|
17
|
+
- [Code of Conduct](https://github.com/varvet/pundit/blob/main/CODE_OF_CONDUCT.md)
|
18
18
|
|
19
|
-
Sponsored by
|
20
|
-
|
21
|
-
[<img src="https://www.varvet.com/images/wordmark-red.svg" alt="Varvet" height="50px"/>](https://www.varvet.com)
|
19
|
+
<strong>Sponsored by:</strong> <a href="https://www.varvet.com">Varvet<br><br><img src="https://github.com/varvet/pundit/assets/99166/aa9efa0a-6903-4037-abee-1824edc57f1a" alt="Varvet logo" height="120"></div>
|
22
20
|
|
23
21
|
## Installation
|
24
22
|
|
25
|
-
|
26
|
-
|
23
|
+
> **Please note** that the README on GitHub is accurate with the _latest code on GitHub_. You are most likely using a released version of Pundit, so please refer to the [documentation for the latest released version of Pundit](https://www.rubydoc.info/gems/pundit).
|
24
|
+
|
25
|
+
``` sh
|
26
|
+
bundle add pundit
|
27
27
|
```
|
28
28
|
|
29
|
-
Include Pundit in your application controller:
|
29
|
+
Include `Pundit::Authorization` in your application controller:
|
30
30
|
|
31
31
|
``` ruby
|
32
32
|
class ApplicationController < ActionController::Base
|
33
|
-
include Pundit
|
34
|
-
protect_from_forgery
|
33
|
+
include Pundit::Authorization
|
35
34
|
end
|
36
35
|
```
|
37
36
|
|
@@ -48,8 +47,8 @@ can pick up any classes in the new `app/policies/` directory.
|
|
48
47
|
## Policies
|
49
48
|
|
50
49
|
Pundit is focused around the notion of policy classes. We suggest that you put
|
51
|
-
these classes in `app/policies`. This is
|
52
|
-
|
50
|
+
these classes in `app/policies`. This is an example that allows updating a post
|
51
|
+
if the user is an admin, or if the post is unpublished:
|
53
52
|
|
54
53
|
``` ruby
|
55
54
|
class PostPolicy
|
@@ -61,12 +60,12 @@ class PostPolicy
|
|
61
60
|
end
|
62
61
|
|
63
62
|
def update?
|
64
|
-
user.admin?
|
63
|
+
user.admin? || !post.published?
|
65
64
|
end
|
66
65
|
end
|
67
66
|
```
|
68
67
|
|
69
|
-
As you can see, this is
|
68
|
+
As you can see, this is a plain Ruby class. Pundit makes the following
|
70
69
|
assumptions about this class:
|
71
70
|
|
72
71
|
- The class has the same name as some kind of model class, only suffixed
|
@@ -117,7 +116,7 @@ and the given record. It then infers from the action name, that it should call
|
|
117
116
|
|
118
117
|
``` ruby
|
119
118
|
unless PostPolicy.new(current_user, @post).update?
|
120
|
-
raise Pundit::NotAuthorizedError, "not allowed to update? this
|
119
|
+
raise Pundit::NotAuthorizedError, "not allowed to PostPolicy#update? this Post"
|
121
120
|
end
|
122
121
|
```
|
123
122
|
|
@@ -165,13 +164,18 @@ def admin_list
|
|
165
164
|
end
|
166
165
|
```
|
167
166
|
|
168
|
-
`authorize` returns the
|
167
|
+
`authorize` returns the instance passed to it, so you can chain it like this:
|
169
168
|
|
170
169
|
Controller:
|
171
170
|
```ruby
|
172
171
|
def show
|
173
172
|
@user = authorize User.find(params[:id])
|
174
173
|
end
|
174
|
+
|
175
|
+
# return the record even for namespaced policies
|
176
|
+
def show
|
177
|
+
@user = authorize [:admin, User.find(params[:id])]
|
178
|
+
end
|
175
179
|
```
|
176
180
|
|
177
181
|
You can easily get a hold of an instance of the policy through the `policy`
|
@@ -190,18 +194,30 @@ you can retrieve it by passing a symbol.
|
|
190
194
|
|
191
195
|
```ruby
|
192
196
|
# app/policies/dashboard_policy.rb
|
193
|
-
class DashboardPolicy
|
194
|
-
|
197
|
+
class DashboardPolicy
|
198
|
+
attr_reader :user
|
199
|
+
|
200
|
+
# `_record` in this example will be :dashboard
|
201
|
+
def initialize(user, _record)
|
202
|
+
@user = user
|
203
|
+
end
|
204
|
+
|
205
|
+
def show?
|
206
|
+
user.admin?
|
207
|
+
end
|
195
208
|
end
|
196
209
|
```
|
197
210
|
|
198
211
|
Note that the headless policy still needs to accept two arguments. The
|
199
|
-
second argument will
|
212
|
+
second argument will be the symbol `:dashboard` in this case, which
|
200
213
|
is what is passed as the record to `authorize` below.
|
201
214
|
|
202
215
|
```ruby
|
203
216
|
# In controllers
|
204
|
-
|
217
|
+
def show
|
218
|
+
authorize :dashboard, :show?
|
219
|
+
...
|
220
|
+
end
|
205
221
|
```
|
206
222
|
|
207
223
|
```erb
|
@@ -220,8 +236,6 @@ define a class called a policy scope. It can look something like this:
|
|
220
236
|
``` ruby
|
221
237
|
class PostPolicy < ApplicationPolicy
|
222
238
|
class Scope
|
223
|
-
attr_reader :user, :scope
|
224
|
-
|
225
239
|
def initialize(user, scope)
|
226
240
|
@user = user
|
227
241
|
@scope = scope
|
@@ -234,6 +248,10 @@ class PostPolicy < ApplicationPolicy
|
|
234
248
|
scope.where(published: true)
|
235
249
|
end
|
236
250
|
end
|
251
|
+
|
252
|
+
private
|
253
|
+
|
254
|
+
attr_reader :user, :scope
|
237
255
|
end
|
238
256
|
|
239
257
|
def update?
|
@@ -259,7 +277,7 @@ generator, or create your own base class to inherit from:
|
|
259
277
|
|
260
278
|
``` ruby
|
261
279
|
class PostPolicy < ApplicationPolicy
|
262
|
-
class Scope < Scope
|
280
|
+
class Scope < ApplicationPolicy::Scope
|
263
281
|
def resolve
|
264
282
|
if user.admin?
|
265
283
|
scope.all
|
@@ -296,13 +314,11 @@ def index
|
|
296
314
|
end
|
297
315
|
```
|
298
316
|
|
299
|
-
|
300
|
-
the `PostPolicy::Scope` class, it will instantiate this class and call
|
301
|
-
`resolve` on the instance. In this case it is a shortcut for doing:
|
317
|
+
In this case it is a shortcut for doing:
|
302
318
|
|
303
319
|
``` ruby
|
304
320
|
def index
|
305
|
-
@
|
321
|
+
@publications = PublicationPolicy::Scope.new(current_user, Post).resolve
|
306
322
|
end
|
307
323
|
```
|
308
324
|
|
@@ -330,7 +346,7 @@ that you haven't forgotten to authorize the action. For example:
|
|
330
346
|
|
331
347
|
``` ruby
|
332
348
|
class ApplicationController < ActionController::Base
|
333
|
-
include Pundit
|
349
|
+
include Pundit::Authorization
|
334
350
|
after_action :verify_authorized
|
335
351
|
end
|
336
352
|
```
|
@@ -343,9 +359,16 @@ authorize individual instances.
|
|
343
359
|
|
344
360
|
``` ruby
|
345
361
|
class ApplicationController < ActionController::Base
|
346
|
-
include Pundit
|
347
|
-
after_action :
|
348
|
-
|
362
|
+
include Pundit::Authorization
|
363
|
+
after_action :verify_pundit_authorization
|
364
|
+
|
365
|
+
def verify_pundit_authorization
|
366
|
+
if action_name == "index"
|
367
|
+
verify_policy_scoped
|
368
|
+
else
|
369
|
+
verify_authorized
|
370
|
+
end
|
371
|
+
end
|
349
372
|
end
|
350
373
|
```
|
351
374
|
|
@@ -356,7 +379,7 @@ these filters without affecting how your app works in any way.**
|
|
356
379
|
|
357
380
|
Some people have found this feature confusing, while many others
|
358
381
|
find it extremely helpful. If you fall into the category of people who find it
|
359
|
-
confusing then you do not need to use it. Pundit will work
|
382
|
+
confusing then you do not need to use it. Pundit will work fine without
|
360
383
|
using `verify_authorized` and `verify_policy_scoped`.
|
361
384
|
|
362
385
|
### Conditional verification
|
@@ -391,20 +414,23 @@ class Post
|
|
391
414
|
end
|
392
415
|
```
|
393
416
|
|
394
|
-
|
417
|
+
Alternatively, you can declare an instance method:
|
395
418
|
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
419
|
+
``` ruby
|
420
|
+
class Post
|
421
|
+
def policy_class
|
422
|
+
PostablePolicy
|
423
|
+
end
|
424
|
+
end
|
425
|
+
```
|
426
|
+
|
427
|
+
## Plain old Ruby
|
428
|
+
|
429
|
+
Pundit is a very small library on purpose, and it doesn't do anything you can't do yourself. There's no secret sauce here. It does as little as possible, and then gets out of your way.
|
430
|
+
|
431
|
+
With the few but powerful helpers available in Pundit, you have the power to build a well structured, fully working authorization system without using any special DSLs or funky syntax.
|
401
432
|
|
402
|
-
Remember that all of the policy and scope classes are
|
403
|
-
which means you can use the same mechanisms you always use to DRY things up.
|
404
|
-
Encapsulate a set of permissions into a module and include them in multiple
|
405
|
-
policies. Use `alias_method` to make some permissions behave the same as
|
406
|
-
others. Inherit from a base set of permissions. Use metaprogramming if you
|
407
|
-
really have to.
|
433
|
+
Remember that all of the policy and scope classes are plain Ruby classes, which means you can use the same mechanisms you always use to DRY things up. Encapsulate a set of permissions into a module and include them in multiple policies. Use `alias_method` to make some permissions behave the same as others. Inherit from a base set of permissions. Use metaprogramming if you really have to.
|
408
434
|
|
409
435
|
## Generator
|
410
436
|
|
@@ -455,7 +481,7 @@ example, associations which might be `nil`.
|
|
455
481
|
|
456
482
|
```ruby
|
457
483
|
class NilClassPolicy < ApplicationPolicy
|
458
|
-
class Scope < Scope
|
484
|
+
class Scope < ApplicationPolicy::Scope
|
459
485
|
def resolve
|
460
486
|
raise Pundit::NotDefinedError, "Cannot scope NilClass"
|
461
487
|
end
|
@@ -470,14 +496,13 @@ end
|
|
470
496
|
## Rescuing a denied Authorization in Rails
|
471
497
|
|
472
498
|
Pundit raises a `Pundit::NotAuthorizedError` you can
|
473
|
-
[rescue_from](
|
499
|
+
[rescue_from](https://guides.rubyonrails.org/action_controller_overview.html#rescue-from)
|
474
500
|
in your `ApplicationController`. You can customize the `user_not_authorized`
|
475
501
|
method in every controller.
|
476
502
|
|
477
503
|
```ruby
|
478
504
|
class ApplicationController < ActionController::Base
|
479
|
-
|
480
|
-
include Pundit
|
505
|
+
include Pundit::Authorization
|
481
506
|
|
482
507
|
rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized
|
483
508
|
|
@@ -485,7 +510,7 @@ class ApplicationController < ActionController::Base
|
|
485
510
|
|
486
511
|
def user_not_authorized
|
487
512
|
flash[:alert] = "You are not authorized to perform this action."
|
488
|
-
|
513
|
+
redirect_back_or_to(root_path)
|
489
514
|
end
|
490
515
|
end
|
491
516
|
```
|
@@ -514,7 +539,7 @@ class ApplicationController < ActionController::Base
|
|
514
539
|
policy_name = exception.policy.class.to_s.underscore
|
515
540
|
|
516
541
|
flash[:error] = t "#{policy_name}.#{exception.query}", scope: "pundit", default: :default
|
517
|
-
|
542
|
+
redirect_back_or_to(root_path)
|
518
543
|
end
|
519
544
|
end
|
520
545
|
```
|
@@ -528,8 +553,7 @@ en:
|
|
528
553
|
create?: 'You cannot create posts!'
|
529
554
|
```
|
530
555
|
|
531
|
-
|
532
|
-
your error messaging.
|
556
|
+
This is an example. Pundit is agnostic as to how you implement your error messaging.
|
533
557
|
|
534
558
|
## Manually retrieving policies and scopes
|
535
559
|
|
@@ -551,9 +575,7 @@ those without the bang will return nil.
|
|
551
575
|
|
552
576
|
## Customize Pundit user
|
553
577
|
|
554
|
-
|
555
|
-
`current_user` is not the method that should be invoked by Pundit. Simply
|
556
|
-
define a method in your controller called `pundit_user`.
|
578
|
+
On occasion, your controller may be unable to access `current_user`, or the method that should be invoked by Pundit may not be `current_user`. To address this, you can define a method in your controller named `pundit_user`.
|
557
579
|
|
558
580
|
```ruby
|
559
581
|
def pundit_user
|
@@ -561,6 +583,36 @@ def pundit_user
|
|
561
583
|
end
|
562
584
|
```
|
563
585
|
|
586
|
+
For instance, Rails 8 includes a built-in [authentication generator](https://github.com/rails/rails/tree/8-0-stable/railties/lib/rails/generators/rails/authentication). If you choose to use it, the currently logged-in user is accessed via `Current.user` instead of `current_user`.
|
587
|
+
|
588
|
+
To ensure compatibility with Pundit, define a `pundit_user` method in `application_controller.rb` (or another suitable location) as follows:
|
589
|
+
|
590
|
+
```ruby
|
591
|
+
def pundit_user
|
592
|
+
Current.user
|
593
|
+
end
|
594
|
+
```
|
595
|
+
|
596
|
+
### Handling User Switching in Pundit
|
597
|
+
|
598
|
+
When switching users in your application, it's important to reset the Pundit user context to ensure that authorization policies are applied correctly for the new user. Pundit caches the user context, so failing to reset it could result in incorrect permissions being applied.
|
599
|
+
|
600
|
+
To handle user switching, you can use the following pattern in your controller:
|
601
|
+
|
602
|
+
```ruby
|
603
|
+
class ApplicationController
|
604
|
+
include Pundit::Authorization
|
605
|
+
|
606
|
+
def switch_user_to(user)
|
607
|
+
terminate_session if authenticated?
|
608
|
+
start_new_session_for user
|
609
|
+
pundit_reset!
|
610
|
+
end
|
611
|
+
end
|
612
|
+
```
|
613
|
+
|
614
|
+
Make sure to invoke `pundit_reset!` whenever changing the user. This ensures the cached authorization context is reset, preventing any incorrect permissions from being applied.
|
615
|
+
|
564
616
|
## Policy Namespacing
|
565
617
|
In some cases it might be helpful to have multiple policies that serve different contexts for a
|
566
618
|
resource. A prime example of this is the case where User policies differ from Admin policies. To
|
@@ -597,8 +649,7 @@ class Admin::PostController < AdminController
|
|
597
649
|
end
|
598
650
|
|
599
651
|
def show
|
600
|
-
post = Post.find(params[:id])
|
601
|
-
authorize(post)
|
652
|
+
post = authorize Post.find(params[:id])
|
602
653
|
end
|
603
654
|
end
|
604
655
|
```
|
@@ -631,7 +682,7 @@ class UserContext
|
|
631
682
|
end
|
632
683
|
|
633
684
|
class ApplicationController
|
634
|
-
include Pundit
|
685
|
+
include Pundit::Authorization
|
635
686
|
|
636
687
|
def pundit_user
|
637
688
|
UserContext.new(current_user, request.ip)
|
@@ -641,9 +692,8 @@ end
|
|
641
692
|
|
642
693
|
## Strong parameters
|
643
694
|
|
644
|
-
In Rails
|
645
|
-
|
646
|
-
mass-assignment protection is handled in the controller. With Pundit you can
|
695
|
+
In Rails,
|
696
|
+
mass-assignment protection is handled in the controller. With Pundit you can
|
647
697
|
control which attributes a user has access to update via your policies. You can
|
648
698
|
set up a `permitted_attributes` method in your policy like this:
|
649
699
|
|
@@ -667,7 +717,7 @@ You can now retrieve these attributes from the policy:
|
|
667
717
|
class PostsController < ApplicationController
|
668
718
|
def update
|
669
719
|
@post = Post.find(params[:id])
|
670
|
-
if @post.
|
720
|
+
if @post.update(post_params)
|
671
721
|
redirect_to @post
|
672
722
|
else
|
673
723
|
render :edit
|
@@ -689,7 +739,7 @@ However, this is a bit cumbersome, so Pundit provides a convenient helper method
|
|
689
739
|
class PostsController < ApplicationController
|
690
740
|
def update
|
691
741
|
@post = Post.find(params[:id])
|
692
|
-
if @post.
|
742
|
+
if @post.update(permitted_attributes(@post))
|
693
743
|
redirect_to @post
|
694
744
|
else
|
695
745
|
render :edit
|
@@ -741,6 +791,10 @@ end
|
|
741
791
|
|
742
792
|
### Policy Specs
|
743
793
|
|
794
|
+
> [!TIP]
|
795
|
+
> An alternative approach to Pundit policy specs is scoping them to a user context as outlined in this
|
796
|
+
[excellent post](https://thunderboltlabs.com/blog/2013/03/27/testing-pundit-policies-with-rspec/) and implemented in the third party [pundit-matchers](https://github.com/punditcommunity/pundit-matchers) gem.
|
797
|
+
|
744
798
|
Pundit includes a mini-DSL for writing expressive tests for your policies in RSpec.
|
745
799
|
Require `pundit/rspec` in your `spec_helper.rb`:
|
746
800
|
|
@@ -770,24 +824,67 @@ describe PostPolicy do
|
|
770
824
|
end
|
771
825
|
```
|
772
826
|
|
773
|
-
|
774
|
-
|
827
|
+
### Custom matcher description
|
828
|
+
|
829
|
+
By default rspec includes an inspected `user` and `record` in the matcher description, which might become overly verbose:
|
830
|
+
|
831
|
+
```
|
832
|
+
PostPolicy
|
833
|
+
update? and show?
|
834
|
+
is expected to permit #<User:0x0000000104aefd80> and #<Post:0x0000000104aef8d0 @user=#<User:0x0000000104aefd80>>
|
835
|
+
```
|
836
|
+
|
837
|
+
You can override the default description with a static string, or a block:
|
838
|
+
|
839
|
+
```ruby
|
840
|
+
# static alternative: Pundit::RSpec::Matchers.description = "permit the user"
|
841
|
+
Pundit::RSpec::Matchers.description = ->(user, record) do
|
842
|
+
"permit user with role #{user.role} to access record with ID #{record.id}"
|
843
|
+
end
|
844
|
+
```
|
845
|
+
|
846
|
+
Which would make for a less chatty output:
|
847
|
+
|
848
|
+
```
|
849
|
+
PostPolicy
|
850
|
+
update? and show?
|
851
|
+
is expected to permit user with role admin to access record with ID 130
|
852
|
+
```
|
853
|
+
|
854
|
+
### Focus Support
|
855
|
+
|
856
|
+
If your RSpec config has `filter_run_when_matching :focus`, you may tag the `permissions` helper like so:
|
857
|
+
|
858
|
+
```
|
859
|
+
permissions :show?, :focus do
|
860
|
+
```
|
775
861
|
|
776
862
|
### Scope Specs
|
777
863
|
|
778
|
-
Pundit does not provide a DSL for testing scopes.
|
864
|
+
Pundit does not provide a DSL for testing scopes. Test them like you would a regular Ruby class!
|
865
|
+
|
866
|
+
### Linting with RuboCop RSpec
|
867
|
+
|
868
|
+
When you lint your RSpec spec files with `rubocop-rspec`, it will fail to properly detect RSpec constructs that Pundit defines, `permissions`.
|
869
|
+
Make sure to use `rubocop-rspec` 2.0 or newer and add the following to your `.rubocop.yml`:
|
870
|
+
|
871
|
+
```yaml
|
872
|
+
inherit_gem:
|
873
|
+
pundit: config/rubocop-rspec.yml
|
874
|
+
```
|
779
875
|
|
780
876
|
# External Resources
|
781
877
|
|
782
878
|
- [RailsApps Example Application: Pundit and Devise](https://github.com/RailsApps/rails-devise-pundit)
|
783
|
-
- [Migrating to Pundit from CanCan](
|
784
|
-
- [Testing Pundit Policies with RSpec](
|
879
|
+
- [Migrating to Pundit from CanCan](https://blog.carbonfive.com/2013/10/21/migrating-to-pundit-from-cancan/)
|
880
|
+
- [Testing Pundit Policies with RSpec](https://thunderboltlabs.com/blog/2013/03/27/testing-pundit-policies-with-rspec/)
|
881
|
+
- [Testing Pundit with Minitest](https://github.com/varvet/pundit/issues/204#issuecomment-60166450)
|
785
882
|
- [Using Pundit outside of a Rails controller](https://github.com/varvet/pundit/pull/136)
|
786
|
-
- [Straightforward Rails Authorization with Pundit](
|
883
|
+
- [Straightforward Rails Authorization with Pundit](https://www.sitepoint.com/straightforward-rails-authorization-with-pundit/)
|
787
884
|
|
788
885
|
## Other implementations
|
789
886
|
|
790
|
-
- [Flask-Pundit](https://github.com/anurag90x/flask-pundit) (Python) is a [Flask](
|
887
|
+
- [Flask-Pundit](https://github.com/anurag90x/flask-pundit) (Python) is a [Flask](https://flask.pocoo.org/) extension "heavily inspired by" Pundit
|
791
888
|
|
792
889
|
# License
|
793
890
|
|
data/Rakefile
CHANGED
data/SECURITY.md
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
# Security Policy
|
2
|
+
|
3
|
+
Please do not file an issue on GitHub, or send a PR addressing the issue.
|
4
|
+
|
5
|
+
## Supported versions
|
6
|
+
|
7
|
+
Most recent major version only.
|
8
|
+
|
9
|
+
## Reporting a vulnerability
|
10
|
+
|
11
|
+
Contact one of the maintainers directly:
|
12
|
+
|
13
|
+
* [@Burgestrand](https://github.com/Burgestrand)
|
14
|
+
* [@dgmstuart](https://github.com/dgmstuart)
|
15
|
+
* [@varvet](https://github.com/varvet)
|
16
|
+
|
17
|
+
You can report vulnerabilities on GitHub too: https://github.com/varvet/pundit/security
|
18
|
+
|
19
|
+
Thank you!
|
@@ -1,10 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Pundit
|
4
|
+
# @private
|
2
5
|
module Generators
|
6
|
+
# @private
|
3
7
|
class InstallGenerator < ::Rails::Generators::Base
|
4
|
-
source_root File.expand_path(
|
8
|
+
source_root File.expand_path("templates", __dir__)
|
5
9
|
|
6
10
|
def copy_application_policy
|
7
|
-
template
|
11
|
+
template "application_policy.rb.tt", "app/policies/application_policy.rb"
|
8
12
|
end
|
9
13
|
end
|
10
14
|
end
|
data/lib/generators/pundit/install/templates/{application_policy.rb → application_policy.rb.tt}
RENAMED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
class ApplicationPolicy
|
2
4
|
attr_reader :user, :record
|
3
5
|
|
@@ -35,15 +37,17 @@ class ApplicationPolicy
|
|
35
37
|
end
|
36
38
|
|
37
39
|
class Scope
|
38
|
-
attr_reader :user, :scope
|
39
|
-
|
40
40
|
def initialize(user, scope)
|
41
41
|
@user = user
|
42
42
|
@scope = scope
|
43
43
|
end
|
44
44
|
|
45
45
|
def resolve
|
46
|
-
|
46
|
+
raise NoMethodError, "You must define #resolve in #{self.class}"
|
47
47
|
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
attr_reader :user, :scope
|
48
52
|
end
|
49
53
|
end
|
@@ -1,10 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Pundit
|
4
|
+
# @private
|
2
5
|
module Generators
|
6
|
+
# @private
|
3
7
|
class PolicyGenerator < ::Rails::Generators::NamedBase
|
4
|
-
source_root File.expand_path(
|
8
|
+
source_root File.expand_path("templates", __dir__)
|
5
9
|
|
6
10
|
def create_policy
|
7
|
-
template
|
11
|
+
template "policy.rb.tt", File.join("app/policies", class_path, "#{file_name}_policy.rb")
|
8
12
|
end
|
9
13
|
|
10
14
|
hook_for :test_framework
|
@@ -0,0 +1,16 @@
|
|
1
|
+
<% module_namespacing do -%>
|
2
|
+
class <%= class_name %>Policy < ApplicationPolicy
|
3
|
+
# NOTE: Up to Pundit v2.3.1, the inheritance was declared as
|
4
|
+
# `Scope < Scope` rather than `Scope < ApplicationPolicy::Scope`.
|
5
|
+
# In most cases the behavior will be identical, but if updating existing
|
6
|
+
# code, beware of possible changes to the ancestors:
|
7
|
+
# https://gist.github.com/Burgestrand/4b4bc22f31c8a95c425fc0e30d7ef1f5
|
8
|
+
|
9
|
+
class Scope < ApplicationPolicy::Scope
|
10
|
+
# NOTE: Be explicit about which records you allow access to!
|
11
|
+
# def resolve
|
12
|
+
# scope.all
|
13
|
+
# end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
<% end -%>
|
@@ -1,10 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# @private
|
1
4
|
module Rspec
|
5
|
+
# @private
|
2
6
|
module Generators
|
7
|
+
# @private
|
3
8
|
class PolicyGenerator < ::Rails::Generators::NamedBase
|
4
|
-
source_root File.expand_path(
|
9
|
+
source_root File.expand_path("templates", __dir__)
|
5
10
|
|
6
11
|
def create_policy_spec
|
7
|
-
template
|
12
|
+
template "policy_spec.rb.tt", File.join("spec/policies", class_path, "#{file_name}_policy_spec.rb")
|
8
13
|
end
|
9
14
|
end
|
10
15
|
end
|
@@ -1,10 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# @private
|
1
4
|
module TestUnit
|
5
|
+
# @private
|
2
6
|
module Generators
|
7
|
+
# @private
|
3
8
|
class PolicyGenerator < ::Rails::Generators::NamedBase
|
4
|
-
source_root File.expand_path(
|
9
|
+
source_root File.expand_path("templates", __dir__)
|
5
10
|
|
6
11
|
def create_policy_test
|
7
|
-
template
|
12
|
+
template "policy_test.rb.tt", File.join("test/policies", class_path, "#{file_name}_policy_test.rb")
|
8
13
|
end
|
9
14
|
end
|
10
15
|
end
|