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/README.md CHANGED
@@ -1,37 +1,36 @@
1
1
  # Pundit
2
2
 
3
- [![Build Status](https://secure.travis-ci.org/varvet/pundit.svg?branch=master)](https://travis-ci.org/varvet/pundit)
4
- [![Code Climate](https://codeclimate.com/github/varvet/pundit.svg)](https://codeclimate.com/github/varvet/pundit)
5
- [![Inline docs](http://inch-ci.org/github/varvet/pundit.svg?branch=master)](http://inch-ci.org/github/varvet/pundit)
6
- [![Gem Version](https://badge.fury.io/rb/pundit.svg)](http://badge.fury.io/rb/pundit)
3
+ [![Main](https://github.com/varvet/pundit/actions/workflows/main.yml/badge.svg)](https://github.com/varvet/pundit/actions/workflows/main.yml)
4
+ [![Code Climate](https://api.codeclimate.com/v1/badges/a940030f96c9fb43046a/maintainability)](https://codeclimate.com/github/varvet/pundit/maintainability)
5
+ [![Inline docs](https://inch-ci.org/github/varvet/pundit.svg?branch=main)](https://inch-ci.org/github/varvet/pundit)
6
+ [![Gem Version](https://badge.fury.io/rb/pundit.svg)](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 simple, robust and
10
- scaleable authorization system.
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](http://www.rubydoc.info/gems/pundit)
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/master/CONTRIBUTING.md)
17
- - [Code of Conduct](https://github.com/varvet/pundit/blob/master/CODE_OF_CONDUCT.md)
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
- ``` ruby
26
- gem "pundit"
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 a simple example that allows updating
52
- a post if the user is an admin, or if the post is unpublished:
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? or not post.published?
63
+ user.admin? || !post.published?
65
64
  end
66
65
  end
67
66
  ```
68
67
 
69
- As you can see, this is just a plain Ruby class. Pundit makes the following
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 #{@post.inspect}"
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 object passed to it, so you can chain it like this:
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 < Struct.new(:user, :dashboard)
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 just be the symbol `:dashboard` in this case which
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
- authorize :dashboard, :show?
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
- Just as with your policy, this will automatically infer that you want to use
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
- @posts = PostPolicy::Scope.new(current_user, Post).resolve
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 :verify_authorized, except: :index
348
- after_action :verify_policy_scoped, only: :index
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 just fine without
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
- ## Just plain old Ruby
417
+ Alternatively, you can declare an instance method:
395
418
 
396
- As you can see, Pundit doesn't do anything you couldn't have easily done
397
- yourself. It's a very small library, it just provides a few neat helpers.
398
- Together these give you the power of building a well structured, fully working
399
- authorization system without using any special DSLs or funky syntax or
400
- anything.
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 just plain Ruby classes,
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](http://guides.rubyonrails.org/action_controller_overview.html#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
- protect_from_forgery
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
- redirect_to(request.referrer || root_path)
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
- redirect_to(request.referrer || root_path)
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
- Of course, this is just an example. Pundit is agnostic as to how you implement
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
- In some cases your controller might not have access to `current_user`, or your
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 4 (or Rails 3.2 with the
645
- [strong_parameters](https://github.com/rails/strong_parameters) gem),
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.update_attributes(post_params)
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.update_attributes(permitted_attributes(@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
- An alternative approach to Pundit policy specs is scoping them to a user context as outlined in this
774
- [excellent post](http://thunderboltlabs.com/blog/2013/03/27/testing-pundit-policies-with-rspec/) and implemented in the third party [pundit-matchers](https://github.com/chrisalley/pundit-matchers) gem.
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. Just test it like a regular Ruby class!
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](http://blog.carbonfive.com/2013/10/21/migrating-to-pundit-from-cancan/)
784
- - [Testing Pundit Policies with RSpec](http://thunderboltlabs.com/blog/2013/03/27/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](http://www.sitepoint.com/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](http://flask.pocoo.org/) extension "heavily inspired by" Pundit
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
@@ -15,6 +15,7 @@ end
15
15
 
16
16
  YARD::Rake::YardocTask.new do |t|
17
17
  t.files = ["lib/**/*.rb"]
18
+ t.stats_options = ["--list-undoc"]
18
19
  end
19
20
 
20
21
  task default: :spec
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!
@@ -0,0 +1,5 @@
1
+ RSpec:
2
+ Language:
3
+ ExampleGroups:
4
+ Regular:
5
+ - permissions
@@ -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('templates', __dir__)
8
+ source_root File.expand_path("templates", __dir__)
5
9
 
6
10
  def copy_application_policy
7
- template 'application_policy.rb', 'app/policies/application_policy.rb'
11
+ template "application_policy.rb.tt", "app/policies/application_policy.rb"
8
12
  end
9
13
  end
10
14
  end
@@ -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
- scope.all
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('templates', __dir__)
8
+ source_root File.expand_path("templates", __dir__)
5
9
 
6
10
  def create_policy
7
- template 'policy.rb', File.join('app/policies', class_path, "#{file_name}_policy.rb")
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('templates', __dir__)
9
+ source_root File.expand_path("templates", __dir__)
5
10
 
6
11
  def create_policy_spec
7
- template 'policy_spec.rb', File.join('spec/policies', class_path, "#{file_name}_policy_spec.rb")
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,4 +1,4 @@
1
- require '<%= File.exists?('spec/rails_helper.rb') ? 'rails_helper' : 'spec_helper' %>'
1
+ require '<%= File.exist?('spec/rails_helper.rb') ? 'rails_helper' : 'spec_helper' %>'
2
2
 
3
3
  RSpec.describe <%= class_name %>Policy, type: :policy do
4
4
  let(:user) { User.new }
@@ -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('templates', __dir__)
9
+ source_root File.expand_path("templates", __dir__)
5
10
 
6
11
  def create_policy_test
7
- template 'policy_test.rb', File.join('test/policies', class_path, "#{file_name}_policy_test.rb")
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