pundit 2.3.2 → 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 +4 -4
- data/.github/workflows/main.yml +92 -52
- data/.github/workflows/push_gem.yml +4 -4
- data/.rubocop.yml +18 -8
- data/.rubocop_ignore_git.yml +7 -0
- data/.yardopts +1 -1
- data/CHANGELOG.md +68 -37
- data/CODE_OF_CONDUCT.md +1 -1
- data/CONTRIBUTING.md +1 -0
- data/Gemfile +22 -2
- data/README.md +88 -15
- data/Rakefile +1 -0
- data/lib/generators/pundit/install/install_generator.rb +3 -1
- data/lib/generators/pundit/policy/policy_generator.rb +3 -1
- data/lib/generators/rspec/policy_generator.rb +4 -1
- data/lib/generators/test_unit/policy_generator.rb +4 -1
- data/lib/pundit/authorization.rb +152 -77
- data/lib/pundit/cache_store/legacy_store.rb +7 -0
- data/lib/pundit/cache_store/null_store.rb +9 -0
- data/lib/pundit/cache_store.rb +22 -0
- data/lib/pundit/context.rb +76 -26
- data/lib/pundit/policy_finder.rb +22 -1
- data/lib/pundit/railtie.rb +19 -0
- data/lib/pundit/rspec.rb +90 -7
- data/lib/pundit/version.rb +2 -1
- data/lib/pundit.rb +43 -15
- data/pundit.gemspec +8 -12
- data/spec/authorization_spec.rb +61 -4
- data/spec/policies/post_policy_spec.rb +27 -0
- data/spec/policy_finder_spec.rb +5 -1
- data/spec/pundit/helper_spec.rb +18 -0
- data/spec/pundit_spec.rb +58 -15
- data/spec/rspec_dsl_spec.rb +81 -0
- data/spec/simple_cov_check_action_formatter.rb +79 -0
- data/spec/spec_helper.rb +22 -339
- 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 +66 -158
- /data/.github/{PULL_REQUEST_TEMPLATE/pull_request_template.md → pull_request_template.md} +0 -0
- /data/lib/generators/pundit/install/templates/{application_policy.rb → application_policy.rb.tt} +0 -0
- /data/lib/generators/pundit/policy/templates/{policy.rb → policy.rb.tt} +0 -0
- /data/lib/generators/rspec/templates/{policy_spec.rb → policy_spec.rb.tt} +0 -0
- /data/lib/generators/test_unit/templates/{policy_test.rb → policy_test.rb.tt} +0 -0
data/README.md
CHANGED
@@ -2,8 +2,8 @@
|
|
2
2
|
|
3
3
|
[](https://github.com/varvet/pundit/actions/workflows/main.yml)
|
4
4
|
[](https://codeclimate.com/github/varvet/pundit/maintainability)
|
5
|
-
[](
|
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
9
|
classes and object oriented design patterns to build a straightforward, robust, and
|
@@ -11,7 +11,7 @@ scalable authorization system.
|
|
11
11
|
|
12
12
|
## Links:
|
13
13
|
|
14
|
-
- [API documentation for the most recent version](
|
14
|
+
- [API documentation for the most recent version](https://www.rubydoc.info/gems/pundit)
|
15
15
|
- [Source Code](https://github.com/varvet/pundit)
|
16
16
|
- [Contributing](https://github.com/varvet/pundit/blob/main/CONTRIBUTING.md)
|
17
17
|
- [Code of Conduct](https://github.com/varvet/pundit/blob/main/CODE_OF_CONDUCT.md)
|
@@ -116,7 +116,7 @@ and the given record. It then infers from the action name, that it should call
|
|
116
116
|
|
117
117
|
``` ruby
|
118
118
|
unless PostPolicy.new(current_user, @post).update?
|
119
|
-
raise Pundit::NotAuthorizedError, "not allowed to update? this
|
119
|
+
raise Pundit::NotAuthorizedError, "not allowed to PostPolicy#update? this Post"
|
120
120
|
end
|
121
121
|
```
|
122
122
|
|
@@ -360,8 +360,15 @@ authorize individual instances.
|
|
360
360
|
``` ruby
|
361
361
|
class ApplicationController < ActionController::Base
|
362
362
|
include Pundit::Authorization
|
363
|
-
after_action :
|
364
|
-
|
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
|
365
372
|
end
|
366
373
|
```
|
367
374
|
|
@@ -489,7 +496,7 @@ end
|
|
489
496
|
## Rescuing a denied Authorization in Rails
|
490
497
|
|
491
498
|
Pundit raises a `Pundit::NotAuthorizedError` you can
|
492
|
-
[rescue_from](
|
499
|
+
[rescue_from](https://guides.rubyonrails.org/action_controller_overview.html#rescue-from)
|
493
500
|
in your `ApplicationController`. You can customize the `user_not_authorized`
|
494
501
|
method in every controller.
|
495
502
|
|
@@ -503,7 +510,7 @@ class ApplicationController < ActionController::Base
|
|
503
510
|
|
504
511
|
def user_not_authorized
|
505
512
|
flash[:alert] = "You are not authorized to perform this action."
|
506
|
-
|
513
|
+
redirect_back_or_to(root_path)
|
507
514
|
end
|
508
515
|
end
|
509
516
|
```
|
@@ -532,7 +539,7 @@ class ApplicationController < ActionController::Base
|
|
532
539
|
policy_name = exception.policy.class.to_s.underscore
|
533
540
|
|
534
541
|
flash[:error] = t "#{policy_name}.#{exception.query}", scope: "pundit", default: :default
|
535
|
-
|
542
|
+
redirect_back_or_to(root_path)
|
536
543
|
end
|
537
544
|
end
|
538
545
|
```
|
@@ -576,6 +583,36 @@ def pundit_user
|
|
576
583
|
end
|
577
584
|
```
|
578
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
|
+
|
579
616
|
## Policy Namespacing
|
580
617
|
In some cases it might be helpful to have multiple policies that serve different contexts for a
|
581
618
|
resource. A prime example of this is the case where User policies differ from Admin policies. To
|
@@ -754,6 +791,10 @@ end
|
|
754
791
|
|
755
792
|
### Policy Specs
|
756
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
|
+
|
757
798
|
Pundit includes a mini-DSL for writing expressive tests for your policies in RSpec.
|
758
799
|
Require `pundit/rspec` in your `spec_helper.rb`:
|
759
800
|
|
@@ -783,8 +824,40 @@ describe PostPolicy do
|
|
783
824
|
end
|
784
825
|
```
|
785
826
|
|
786
|
-
|
787
|
-
|
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
|
+
```
|
788
861
|
|
789
862
|
### Scope Specs
|
790
863
|
|
@@ -803,15 +876,15 @@ inherit_gem:
|
|
803
876
|
# External Resources
|
804
877
|
|
805
878
|
- [RailsApps Example Application: Pundit and Devise](https://github.com/RailsApps/rails-devise-pundit)
|
806
|
-
- [Migrating to Pundit from CanCan](
|
807
|
-
- [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/)
|
808
881
|
- [Testing Pundit with Minitest](https://github.com/varvet/pundit/issues/204#issuecomment-60166450)
|
809
882
|
- [Using Pundit outside of a Rails controller](https://github.com/varvet/pundit/pull/136)
|
810
|
-
- [Straightforward Rails Authorization with Pundit](
|
883
|
+
- [Straightforward Rails Authorization with Pundit](https://www.sitepoint.com/straightforward-rails-authorization-with-pundit/)
|
811
884
|
|
812
885
|
## Other implementations
|
813
886
|
|
814
|
-
- [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
|
815
888
|
|
816
889
|
# License
|
817
890
|
|
data/Rakefile
CHANGED
@@ -1,12 +1,14 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Pundit
|
4
|
+
# @private
|
4
5
|
module Generators
|
6
|
+
# @private
|
5
7
|
class InstallGenerator < ::Rails::Generators::Base
|
6
8
|
source_root File.expand_path("templates", __dir__)
|
7
9
|
|
8
10
|
def copy_application_policy
|
9
|
-
template "application_policy.rb", "app/policies/application_policy.rb"
|
11
|
+
template "application_policy.rb.tt", "app/policies/application_policy.rb"
|
10
12
|
end
|
11
13
|
end
|
12
14
|
end
|
@@ -1,12 +1,14 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Pundit
|
4
|
+
# @private
|
4
5
|
module Generators
|
6
|
+
# @private
|
5
7
|
class PolicyGenerator < ::Rails::Generators::NamedBase
|
6
8
|
source_root File.expand_path("templates", __dir__)
|
7
9
|
|
8
10
|
def create_policy
|
9
|
-
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")
|
10
12
|
end
|
11
13
|
|
12
14
|
hook_for :test_framework
|
@@ -1,12 +1,15 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
# @private
|
3
4
|
module Rspec
|
5
|
+
# @private
|
4
6
|
module Generators
|
7
|
+
# @private
|
5
8
|
class PolicyGenerator < ::Rails::Generators::NamedBase
|
6
9
|
source_root File.expand_path("templates", __dir__)
|
7
10
|
|
8
11
|
def create_policy_spec
|
9
|
-
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")
|
10
13
|
end
|
11
14
|
end
|
12
15
|
end
|
@@ -1,12 +1,15 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
# @private
|
3
4
|
module TestUnit
|
5
|
+
# @private
|
4
6
|
module Generators
|
7
|
+
# @private
|
5
8
|
class PolicyGenerator < ::Rails::Generators::NamedBase
|
6
9
|
source_root File.expand_path("templates", __dir__)
|
7
10
|
|
8
11
|
def create_policy_test
|
9
|
-
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")
|
10
13
|
end
|
11
14
|
end
|
12
15
|
end
|
data/lib/pundit/authorization.rb
CHANGED
@@ -1,6 +1,14 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Pundit
|
4
|
+
# Pundit DSL to include in your controllers to provide authorization helpers.
|
5
|
+
#
|
6
|
+
# @example
|
7
|
+
# class ApplicationController < ActionController::Base
|
8
|
+
# include Pundit::Authorization
|
9
|
+
# end
|
10
|
+
# @see #pundit
|
11
|
+
# @api public
|
4
12
|
module Authorization
|
5
13
|
extend ActiveSupport::Concern
|
6
14
|
|
@@ -15,7 +23,13 @@ module Pundit
|
|
15
23
|
|
16
24
|
protected
|
17
25
|
|
18
|
-
#
|
26
|
+
# An instance of {Pundit::Context} initialized with the current user.
|
27
|
+
#
|
28
|
+
# @note this method is memoized and will return the same instance during the request.
|
29
|
+
# @api public
|
30
|
+
# @return [Pundit::Context]
|
31
|
+
# @see #pundit_user
|
32
|
+
# @see #policies
|
19
33
|
def pundit
|
20
34
|
@pundit ||= Pundit::Context.new(
|
21
35
|
user: pundit_user,
|
@@ -23,40 +37,38 @@ module Pundit
|
|
23
37
|
)
|
24
38
|
end
|
25
39
|
|
26
|
-
#
|
27
|
-
#
|
28
|
-
def pundit_policy_authorized?
|
29
|
-
!!@_pundit_policy_authorized
|
30
|
-
end
|
31
|
-
|
32
|
-
# @return [Boolean] whether policy scoping has been performed, i.e. whether
|
33
|
-
# one {#policy_scope} or {#skip_policy_scope} has been called
|
34
|
-
def pundit_policy_scoped?
|
35
|
-
!!@_pundit_policy_scoped
|
36
|
-
end
|
37
|
-
|
38
|
-
# Raises an error if authorization has not been performed, usually used as an
|
39
|
-
# `after_action` filter to prevent programmer error in forgetting to call
|
40
|
-
# {#authorize} or {#skip_authorization}.
|
40
|
+
# Hook method which allows customizing which user is passed to policies and
|
41
|
+
# scopes initialized by {#authorize}, {#policy} and {#policy_scope}.
|
41
42
|
#
|
42
|
-
# @
|
43
|
-
# @
|
44
|
-
# @
|
45
|
-
|
46
|
-
|
43
|
+
# @note Make sure to call `pundit_reset!` if this changes during a request.
|
44
|
+
# @see https://github.com/varvet/pundit#customize-pundit-user
|
45
|
+
# @see #pundit
|
46
|
+
# @see #pundit_reset!
|
47
|
+
# @return [Object] the user object to be used with pundit
|
48
|
+
def pundit_user
|
49
|
+
current_user
|
47
50
|
end
|
48
51
|
|
49
|
-
#
|
50
|
-
#
|
51
|
-
#
|
52
|
+
# Clears the cached Pundit authorization data.
|
53
|
+
#
|
54
|
+
# This method should be called when the pundit_user is changed,
|
55
|
+
# such as during user switching, to ensure that stale authorization
|
56
|
+
# data is not used. Pundit caches authorization policies and scopes
|
57
|
+
# for the pundit_user, so calling this method will reset those
|
58
|
+
# caches and ensure that the next authorization checks are performed
|
59
|
+
# with the correct context for the new pundit_user.
|
52
60
|
#
|
53
|
-
# @see https://github.com/varvet/pundit#ensuring-policies-and-scopes-are-used
|
54
|
-
# @raise [AuthorizationNotPerformedError] if policy scoping has not been performed
|
55
61
|
# @return [void]
|
56
|
-
def
|
57
|
-
|
62
|
+
def pundit_reset!
|
63
|
+
@pundit = nil
|
64
|
+
@_pundit_policies = nil
|
65
|
+
@_pundit_policy_scopes = nil
|
66
|
+
@_pundit_policy_authorized = nil
|
67
|
+
@_pundit_policy_scoped = nil
|
58
68
|
end
|
59
69
|
|
70
|
+
# @!group Policies
|
71
|
+
|
60
72
|
# Retrieves the policy for the given record, initializing it with the record
|
61
73
|
# and current user and finally throwing an error if the user is not
|
62
74
|
# authorized to perform the given action.
|
@@ -66,7 +78,9 @@ module Pundit
|
|
66
78
|
# If omitted then this defaults to the Rails controller action name.
|
67
79
|
# @param policy_class [Class] the policy class we want to force use of
|
68
80
|
# @raise [NotAuthorizedError] if the given query method returned false
|
69
|
-
# @return [
|
81
|
+
# @return [record] Always returns the passed object record
|
82
|
+
# @see Pundit::Context#authorize
|
83
|
+
# @see #verify_authorized
|
70
84
|
def authorize(record, query = nil, policy_class: nil)
|
71
85
|
query ||= "#{action_name}?"
|
72
86
|
|
@@ -79,29 +93,45 @@ module Pundit
|
|
79
93
|
#
|
80
94
|
# @see https://github.com/varvet/pundit#ensuring-policies-and-scopes-are-used
|
81
95
|
# @return [void]
|
96
|
+
# @see #verify_authorized
|
82
97
|
def skip_authorization
|
83
98
|
@_pundit_policy_authorized = :skipped
|
84
99
|
end
|
85
100
|
|
86
|
-
#
|
101
|
+
# @return [Boolean] wether or not authorization has been performed
|
102
|
+
# @see #authorize
|
103
|
+
# @see #skip_authorization
|
104
|
+
def pundit_policy_authorized?
|
105
|
+
!!@_pundit_policy_authorized
|
106
|
+
end
|
107
|
+
|
108
|
+
# Raises an error if authorization has not been performed.
|
109
|
+
#
|
110
|
+
# Usually used as an `after_action` filter to prevent programmer error in
|
111
|
+
# forgetting to call {#authorize} or {#skip_authorization}.
|
87
112
|
#
|
88
113
|
# @see https://github.com/varvet/pundit#ensuring-policies-and-scopes-are-used
|
114
|
+
# @raise [AuthorizationNotPerformedError] if authorization has not been performed
|
89
115
|
# @return [void]
|
90
|
-
|
91
|
-
|
116
|
+
# @see #authorize
|
117
|
+
# @see #skip_authorization
|
118
|
+
def verify_authorized
|
119
|
+
raise AuthorizationNotPerformedError, self.class unless pundit_policy_authorized?
|
92
120
|
end
|
93
121
|
|
94
|
-
#
|
122
|
+
# rubocop:disable Naming/MemoizedInstanceVariableName
|
123
|
+
|
124
|
+
# Cache of policies. You should not rely on this method.
|
95
125
|
#
|
96
|
-
# @
|
97
|
-
|
98
|
-
|
99
|
-
# @return [Scope{#resolve}, nil] instance of scope class which can resolve to a scope
|
100
|
-
def policy_scope(scope, policy_scope_class: nil)
|
101
|
-
@_pundit_policy_scoped = true
|
102
|
-
policy_scope_class ? policy_scope_class.new(pundit_user, scope).resolve : pundit_policy_scope(scope)
|
126
|
+
# @api private
|
127
|
+
def policies
|
128
|
+
@_pundit_policies ||= {}
|
103
129
|
end
|
104
130
|
|
131
|
+
# rubocop:enable Naming/MemoizedInstanceVariableName
|
132
|
+
|
133
|
+
# @!endgroup
|
134
|
+
|
105
135
|
# Retrieves the policy for the given record.
|
106
136
|
#
|
107
137
|
# @see https://github.com/varvet/pundit#policies
|
@@ -111,11 +141,87 @@ module Pundit
|
|
111
141
|
pundit.policy!(record)
|
112
142
|
end
|
113
143
|
|
114
|
-
#
|
115
|
-
|
116
|
-
#
|
117
|
-
#
|
118
|
-
#
|
144
|
+
# @!group Policy Scopes
|
145
|
+
|
146
|
+
# Retrieves the policy scope for the given record.
|
147
|
+
#
|
148
|
+
# @see https://github.com/varvet/pundit#scopes
|
149
|
+
# @param scope [Object] the object we're retrieving the policy scope for
|
150
|
+
# @param policy_scope_class [#resolve] the policy scope class we want to force use of
|
151
|
+
# @return [#resolve, nil] instance of scope class which can resolve to a scope
|
152
|
+
def policy_scope(scope, policy_scope_class: nil)
|
153
|
+
@_pundit_policy_scoped = true
|
154
|
+
policy_scope_class ? policy_scope_class.new(pundit_user, scope).resolve : pundit_policy_scope(scope)
|
155
|
+
end
|
156
|
+
|
157
|
+
# Allow this action not to perform policy scoping.
|
158
|
+
#
|
159
|
+
# @see https://github.com/varvet/pundit#ensuring-policies-and-scopes-are-used
|
160
|
+
# @return [void]
|
161
|
+
# @see #verify_policy_scoped
|
162
|
+
def skip_policy_scope
|
163
|
+
@_pundit_policy_scoped = :skipped
|
164
|
+
end
|
165
|
+
|
166
|
+
# @return [Boolean] wether or not policy scoping has been performed
|
167
|
+
# @see #policy_scope
|
168
|
+
# @see #skip_policy_scope
|
169
|
+
def pundit_policy_scoped?
|
170
|
+
!!@_pundit_policy_scoped
|
171
|
+
end
|
172
|
+
|
173
|
+
# Raises an error if policy scoping has not been performed.
|
174
|
+
#
|
175
|
+
# Usually used as an `after_action` filter to prevent programmer error in
|
176
|
+
# forgetting to call {#policy_scope} or {#skip_policy_scope} in index
|
177
|
+
# actions.
|
178
|
+
#
|
179
|
+
# @see https://github.com/varvet/pundit#ensuring-policies-and-scopes-are-used
|
180
|
+
# @raise [AuthorizationNotPerformedError] if policy scoping has not been performed
|
181
|
+
# @return [void]
|
182
|
+
# @see #policy_scope
|
183
|
+
# @see #skip_policy_scope
|
184
|
+
def verify_policy_scoped
|
185
|
+
raise PolicyScopingNotPerformedError, self.class unless pundit_policy_scoped?
|
186
|
+
end
|
187
|
+
|
188
|
+
# rubocop:disable Naming/MemoizedInstanceVariableName
|
189
|
+
|
190
|
+
# Cache of policy scope. You should not rely on this method.
|
191
|
+
#
|
192
|
+
# @api private
|
193
|
+
def policy_scopes
|
194
|
+
@_pundit_policy_scopes ||= {}
|
195
|
+
end
|
196
|
+
|
197
|
+
# rubocop:enable Naming/MemoizedInstanceVariableName
|
198
|
+
|
199
|
+
# This was added to allow calling `policy_scope!` without flipping the
|
200
|
+
# `pundit_policy_scoped?` flag.
|
201
|
+
#
|
202
|
+
# It's used internally by `policy_scope`, as well as from the views
|
203
|
+
# when they call `policy_scope`. It works because views get their helper
|
204
|
+
# from {Pundit::Helper}.
|
205
|
+
#
|
206
|
+
# @note This also memoizes the instance with `scope` as the key.
|
207
|
+
# @see Pundit::Helper#policy_scope
|
208
|
+
# @api private
|
209
|
+
def pundit_policy_scope(scope)
|
210
|
+
policy_scopes[scope] ||= pundit.policy_scope!(scope)
|
211
|
+
end
|
212
|
+
private :pundit_policy_scope
|
213
|
+
|
214
|
+
# @!endgroup
|
215
|
+
|
216
|
+
# @!group Strong Parameters
|
217
|
+
|
218
|
+
# Retrieves a set of permitted attributes from the policy.
|
219
|
+
#
|
220
|
+
# Done by instantiating the policy class for the given record and calling
|
221
|
+
# `permitted_attributes` on it, or `permitted_attributes_for_{action}` if
|
222
|
+
# `action` is defined. It then infers what key the record should have in the
|
223
|
+
# params hash and retrieves the permitted attributes from the params hash
|
224
|
+
# under that key.
|
119
225
|
#
|
120
226
|
# @see https://github.com/varvet/pundit#strong-parameters
|
121
227
|
# @param record [Object] the object we're retrieving permitted attributes for
|
@@ -140,37 +246,6 @@ module Pundit
|
|
140
246
|
params.require(PolicyFinder.new(record).param_key)
|
141
247
|
end
|
142
248
|
|
143
|
-
#
|
144
|
-
#
|
145
|
-
# @api private
|
146
|
-
# rubocop:disable Naming/MemoizedInstanceVariableName
|
147
|
-
def policies
|
148
|
-
@_pundit_policies ||= {}
|
149
|
-
end
|
150
|
-
# rubocop:enable Naming/MemoizedInstanceVariableName
|
151
|
-
|
152
|
-
# Cache of policy scope. You should not rely on this method.
|
153
|
-
#
|
154
|
-
# @api private
|
155
|
-
# rubocop:disable Naming/MemoizedInstanceVariableName
|
156
|
-
def policy_scopes
|
157
|
-
@_pundit_policy_scopes ||= {}
|
158
|
-
end
|
159
|
-
# rubocop:enable Naming/MemoizedInstanceVariableName
|
160
|
-
|
161
|
-
# Hook method which allows customizing which user is passed to policies and
|
162
|
-
# scopes initialized by {#authorize}, {#policy} and {#policy_scope}.
|
163
|
-
#
|
164
|
-
# @see https://github.com/varvet/pundit#customize-pundit-user
|
165
|
-
# @return [Object] the user object to be used with pundit
|
166
|
-
def pundit_user
|
167
|
-
current_user
|
168
|
-
end
|
169
|
-
|
170
|
-
private
|
171
|
-
|
172
|
-
def pundit_policy_scope(scope)
|
173
|
-
policy_scopes[scope] ||= pundit.policy_scope!(scope)
|
174
|
-
end
|
249
|
+
# @!endgroup
|
175
250
|
end
|
176
251
|
end
|
@@ -2,12 +2,19 @@
|
|
2
2
|
|
3
3
|
module Pundit
|
4
4
|
module CacheStore
|
5
|
+
# A cache store that uses only the record as a cache key, and ignores the user.
|
6
|
+
#
|
7
|
+
# The original cache mechanism used by Pundit.
|
8
|
+
#
|
5
9
|
# @api private
|
6
10
|
class LegacyStore
|
7
11
|
def initialize(hash = {})
|
8
12
|
@store = hash
|
9
13
|
end
|
10
14
|
|
15
|
+
# A cache store that uses only the record as a cache key, and ignores the user.
|
16
|
+
#
|
17
|
+
# @note `nil` results are not cached.
|
11
18
|
def fetch(user:, record:)
|
12
19
|
_ = user
|
13
20
|
@store[record] ||= yield
|
@@ -2,14 +2,23 @@
|
|
2
2
|
|
3
3
|
module Pundit
|
4
4
|
module CacheStore
|
5
|
+
# A cache store that does not cache anything.
|
6
|
+
#
|
7
|
+
# Use `NullStore.instance` to get the singleton instance, it is thread-safe.
|
8
|
+
#
|
9
|
+
# @see Pundit::Context#initialize
|
5
10
|
# @api private
|
6
11
|
class NullStore
|
7
12
|
@instance = new
|
8
13
|
|
9
14
|
class << self
|
15
|
+
# @return [NullStore] the singleton instance
|
10
16
|
attr_reader :instance
|
11
17
|
end
|
12
18
|
|
19
|
+
# Always yields, does not cache anything.
|
20
|
+
# @yield
|
21
|
+
# @return [any] whatever the block returns.
|
13
22
|
def fetch(*, **)
|
14
23
|
yield
|
15
24
|
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Pundit
|
4
|
+
# Namespace for cache store implementations.
|
5
|
+
#
|
6
|
+
# Cache stores are used to cache policy lookups, so you get the same policy
|
7
|
+
# instance for the same record.
|
8
|
+
module CacheStore
|
9
|
+
# @!group Cache Store Interface
|
10
|
+
|
11
|
+
# @!method fetch(user:, record:, &block)
|
12
|
+
# Looks up a stored policy or generate a new one.
|
13
|
+
#
|
14
|
+
# @note This is a method template, but the method does not exist in this module.
|
15
|
+
# @param user [Object] the user that initiated the action
|
16
|
+
# @param record [Object] the object being accessed
|
17
|
+
# @param block [Proc] the block to execute if missing
|
18
|
+
# @return [Object] the policy
|
19
|
+
|
20
|
+
# @!endgroup
|
21
|
+
end
|
22
|
+
end
|