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/lib/pundit/rspec.rb
CHANGED
@@ -1,12 +1,36 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
# Array#to_sentence
|
3
4
|
require "active_support/core_ext/array/conversions"
|
4
5
|
|
5
6
|
module Pundit
|
7
|
+
# Namespace for Pundit's RSpec integration.
|
6
8
|
module RSpec
|
9
|
+
# Namespace for Pundit's RSpec matchers.
|
7
10
|
module Matchers
|
8
11
|
extend ::RSpec::Matchers::DSL
|
9
12
|
|
13
|
+
# @!method description=(description)
|
14
|
+
class << self
|
15
|
+
# Used to build a suitable description for the Pundit `permit` matcher.
|
16
|
+
# @api public
|
17
|
+
# @param value [String, Proc]
|
18
|
+
# @example
|
19
|
+
# Pundit::RSpec::Matchers.description = ->(user, record) do
|
20
|
+
# "permit user with role #{user.role} to access record with ID #{record.id}"
|
21
|
+
# end
|
22
|
+
attr_writer :description
|
23
|
+
|
24
|
+
# Used to retrieve a suitable description for the Pundit `permit` matcher.
|
25
|
+
# @api private
|
26
|
+
# @private
|
27
|
+
def description(user, record)
|
28
|
+
return @description.call(user, record) if defined?(@description) && @description.respond_to?(:call)
|
29
|
+
|
30
|
+
@description
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
10
34
|
# rubocop:disable Metrics/BlockLength
|
11
35
|
matcher :permit do |user, record|
|
12
36
|
match_proc = lambda do |policy|
|
@@ -24,15 +48,25 @@ module Pundit
|
|
24
48
|
end
|
25
49
|
|
26
50
|
failure_message_proc = lambda do |policy|
|
27
|
-
was_were = @violating_permissions.count > 1 ? "were" : "was"
|
28
51
|
"Expected #{policy} to grant #{permissions.to_sentence} on " \
|
29
|
-
"#{record} but #{@violating_permissions.to_sentence} #{
|
52
|
+
"#{record} but #{@violating_permissions.to_sentence} #{was_or_were} not granted"
|
30
53
|
end
|
31
54
|
|
32
55
|
failure_message_when_negated_proc = lambda do |policy|
|
33
|
-
was_were = @violating_permissions.count > 1 ? "were" : "was"
|
34
56
|
"Expected #{policy} not to grant #{permissions.to_sentence} on " \
|
35
|
-
"#{record} but #{@violating_permissions.to_sentence} #{
|
57
|
+
"#{record} but #{@violating_permissions.to_sentence} #{was_or_were} granted"
|
58
|
+
end
|
59
|
+
|
60
|
+
def was_or_were
|
61
|
+
if @violating_permissions.count > 1
|
62
|
+
"were"
|
63
|
+
else
|
64
|
+
"was"
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
description do
|
69
|
+
Pundit::RSpec::Matchers.description(user, record) || super()
|
36
70
|
end
|
37
71
|
|
38
72
|
if respond_to?(:match_when_negated)
|
@@ -41,26 +75,73 @@ module Pundit
|
|
41
75
|
failure_message(&failure_message_proc)
|
42
76
|
failure_message_when_negated(&failure_message_when_negated_proc)
|
43
77
|
else
|
78
|
+
# :nocov:
|
79
|
+
# Compatibility with RSpec < 3.0, released 2014-06-01.
|
44
80
|
match_for_should(&match_proc)
|
45
81
|
match_for_should_not(&match_when_negated_proc)
|
46
82
|
failure_message_for_should(&failure_message_proc)
|
47
83
|
failure_message_for_should_not(&failure_message_when_negated_proc)
|
84
|
+
# :nocov:
|
85
|
+
end
|
86
|
+
|
87
|
+
if ::RSpec.respond_to?(:current_example)
|
88
|
+
def current_example
|
89
|
+
::RSpec.current_example
|
90
|
+
end
|
91
|
+
else
|
92
|
+
# :nocov:
|
93
|
+
# Compatibility with RSpec < 3.0, released 2014-06-01.
|
94
|
+
def current_example
|
95
|
+
example
|
96
|
+
end
|
97
|
+
# :nocov:
|
48
98
|
end
|
49
99
|
|
50
100
|
def permissions
|
51
|
-
current_example
|
52
|
-
|
101
|
+
current_example.metadata.fetch(:permissions) do
|
102
|
+
raise KeyError, <<~ERROR.strip
|
103
|
+
No permissions in example metadata, did you forget to wrap with `permissions :show?, ...`?
|
104
|
+
ERROR
|
105
|
+
end
|
53
106
|
end
|
54
107
|
end
|
55
108
|
# rubocop:enable Metrics/BlockLength
|
56
109
|
end
|
57
110
|
|
111
|
+
# Mixed in to all policy example groups to provide a DSL.
|
58
112
|
module DSL
|
113
|
+
# @example
|
114
|
+
# describe PostPolicy do
|
115
|
+
# permissions :show?, :update? do
|
116
|
+
# it { is_expected.to permit(user, own_post) }
|
117
|
+
# end
|
118
|
+
# end
|
119
|
+
#
|
120
|
+
# @example focused example group
|
121
|
+
# describe PostPolicy do
|
122
|
+
# permissions :show?, :update?, :focus do
|
123
|
+
# it { is_expected.to permit(user, own_post) }
|
124
|
+
# end
|
125
|
+
# end
|
126
|
+
#
|
127
|
+
# @param list [Symbol, Array<Symbol>] a permission to describe
|
128
|
+
# @return [void]
|
59
129
|
def permissions(*list, &block)
|
60
|
-
|
130
|
+
metadata = { permissions: list, caller: caller }
|
131
|
+
|
132
|
+
if list.last == :focus
|
133
|
+
list.pop
|
134
|
+
metadata[:focus] = true
|
135
|
+
end
|
136
|
+
|
137
|
+
description = list.to_sentence
|
138
|
+
describe(description, metadata) { instance_eval(&block) }
|
61
139
|
end
|
62
140
|
end
|
63
141
|
|
142
|
+
# Mixed in to all policy example groups.
|
143
|
+
#
|
144
|
+
# @private not useful
|
64
145
|
module PolicyExampleGroup
|
65
146
|
include Pundit::RSpec::Matchers
|
66
147
|
|
@@ -74,17 +155,9 @@ module Pundit
|
|
74
155
|
end
|
75
156
|
|
76
157
|
RSpec.configure do |config|
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
)
|
83
|
-
else
|
84
|
-
config.include(
|
85
|
-
Pundit::RSpec::PolicyExampleGroup,
|
86
|
-
type: :policy,
|
87
|
-
example_group: { file_path: %r{spec/policies} }
|
88
|
-
)
|
89
|
-
end
|
158
|
+
config.include(
|
159
|
+
Pundit::RSpec::PolicyExampleGroup,
|
160
|
+
type: :policy,
|
161
|
+
file_path: %r{spec/policies}
|
162
|
+
)
|
90
163
|
end
|
data/lib/pundit/version.rb
CHANGED
data/lib/pundit.rb
CHANGED
@@ -1,29 +1,53 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "active_support"
|
4
|
+
|
3
5
|
require "pundit/version"
|
4
6
|
require "pundit/policy_finder"
|
5
|
-
require "
|
6
|
-
require "
|
7
|
-
require "
|
8
|
-
require "
|
9
|
-
require "
|
7
|
+
require "pundit/authorization"
|
8
|
+
require "pundit/context"
|
9
|
+
require "pundit/cache_store"
|
10
|
+
require "pundit/cache_store/null_store"
|
11
|
+
require "pundit/cache_store/legacy_store"
|
12
|
+
require "pundit/railtie" if defined?(Rails)
|
10
13
|
|
11
14
|
# @api private
|
12
15
|
# To avoid name clashes with common Error naming when mixing in Pundit,
|
13
16
|
# keep it here with compact class style definition.
|
14
17
|
class Pundit::Error < StandardError; end # rubocop:disable Style/ClassAndModuleChildren
|
15
18
|
|
19
|
+
# Hello? Yes, this is Pundit.
|
20
|
+
#
|
16
21
|
# @api public
|
17
22
|
module Pundit
|
18
|
-
|
23
|
+
# @api private
|
24
|
+
# @deprecated See {Pundit::PolicyFinder}
|
25
|
+
SUFFIX = Pundit::PolicyFinder::SUFFIX
|
19
26
|
|
20
27
|
# @api private
|
28
|
+
# @private
|
21
29
|
module Generators; end
|
22
30
|
|
23
31
|
# Error that will be raised when authorization has failed
|
24
32
|
class NotAuthorizedError < Error
|
25
|
-
|
26
|
-
|
33
|
+
# @see #initialize
|
34
|
+
attr_reader :query
|
35
|
+
# @see #initialize
|
36
|
+
attr_reader :record
|
37
|
+
# @see #initialize
|
38
|
+
attr_reader :policy
|
39
|
+
|
40
|
+
# @overload initialize(message)
|
41
|
+
# Create an error with a simple error message.
|
42
|
+
# @param [String] message A simple error message string.
|
43
|
+
#
|
44
|
+
# @overload initialize(options)
|
45
|
+
# Create an error with the specified attributes.
|
46
|
+
# @param [Hash] options The error options.
|
47
|
+
# @option options [String] :message Optional custom error message. Will default to a generalized message.
|
48
|
+
# @option options [Symbol] :query The name of the policy method that was checked.
|
49
|
+
# @option options [Object] :record The object that was being checked with the policy.
|
50
|
+
# @option options [Class] :policy The class of policy that was used for the check.
|
27
51
|
def initialize(options = {})
|
28
52
|
if options.is_a? String
|
29
53
|
message = options
|
@@ -32,7 +56,10 @@ module Pundit
|
|
32
56
|
@record = options[:record]
|
33
57
|
@policy = options[:policy]
|
34
58
|
|
35
|
-
message = options.fetch(:message)
|
59
|
+
message = options.fetch(:message) do
|
60
|
+
record_name = record.is_a?(Class) ? record.to_s : "this #{record.class}"
|
61
|
+
"not allowed to #{policy.class}##{query} #{record_name}"
|
62
|
+
end
|
36
63
|
end
|
37
64
|
|
38
65
|
super(message)
|
@@ -53,273 +80,57 @@ module Pundit
|
|
53
80
|
# Error that will be raised if a policy or policy scope is not defined.
|
54
81
|
class NotDefinedError < Error; end
|
55
82
|
|
56
|
-
|
83
|
+
def self.included(base)
|
84
|
+
location = caller_locations(1, 1).first
|
85
|
+
warn <<~WARNING
|
86
|
+
'include Pundit' is deprecated. Please use 'include Pundit::Authorization' instead.
|
87
|
+
(called from #{location.label} at #{location.path}:#{location.lineno})
|
88
|
+
WARNING
|
89
|
+
base.include Authorization
|
90
|
+
end
|
57
91
|
|
58
92
|
class << self
|
59
|
-
#
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
# @param policy_class [Class] the policy class we want to force use of
|
67
|
-
# @raise [NotAuthorizedError] if the given query method returned false
|
68
|
-
# @return [Object] Always returns the passed object record
|
69
|
-
def authorize(user, record, query, policy_class: nil)
|
70
|
-
policy = policy_class ? policy_class.new(user, record) : policy!(user, record)
|
71
|
-
|
72
|
-
raise NotAuthorizedError, query: query, record: record, policy: policy unless policy.public_send(query)
|
73
|
-
|
74
|
-
record
|
75
|
-
end
|
76
|
-
|
77
|
-
# Retrieves the policy scope for the given record.
|
78
|
-
#
|
79
|
-
# @see https://github.com/varvet/pundit#scopes
|
80
|
-
# @param user [Object] the user that initiated the action
|
81
|
-
# @param scope [Object] the object we're retrieving the policy scope for
|
82
|
-
# @raise [InvalidConstructorError] if the policy constructor called incorrectly
|
83
|
-
# @return [Scope{#resolve}, nil] instance of scope class which can resolve to a scope
|
84
|
-
def policy_scope(user, scope)
|
85
|
-
policy_scope_class = PolicyFinder.new(scope).scope
|
86
|
-
return unless policy_scope_class
|
87
|
-
|
88
|
-
begin
|
89
|
-
policy_scope = policy_scope_class.new(user, pundit_model(scope))
|
90
|
-
rescue ArgumentError
|
91
|
-
raise InvalidConstructorError, "Invalid #<#{policy_scope_class}> constructor is called"
|
93
|
+
# @see Pundit::Context#authorize
|
94
|
+
def authorize(user, record, query, policy_class: nil, cache: nil)
|
95
|
+
context = if cache
|
96
|
+
policy_cache = CacheStore::LegacyStore.new(cache)
|
97
|
+
Context.new(user: user, policy_cache: policy_cache)
|
98
|
+
else
|
99
|
+
Context.new(user: user)
|
92
100
|
end
|
93
101
|
|
94
|
-
|
102
|
+
context.authorize(record, query: query, policy_class: policy_class)
|
95
103
|
end
|
96
104
|
|
97
|
-
#
|
98
|
-
|
99
|
-
|
100
|
-
# @param user [Object] the user that initiated the action
|
101
|
-
# @param scope [Object] the object we're retrieving the policy scope for
|
102
|
-
# @raise [NotDefinedError] if the policy scope cannot be found
|
103
|
-
# @raise [InvalidConstructorError] if the policy constructor called incorrectly
|
104
|
-
# @return [Scope{#resolve}] instance of scope class which can resolve to a scope
|
105
|
-
def policy_scope!(user, scope)
|
106
|
-
policy_scope_class = PolicyFinder.new(scope).scope!
|
107
|
-
return unless policy_scope_class
|
108
|
-
|
109
|
-
begin
|
110
|
-
policy_scope = policy_scope_class.new(user, pundit_model(scope))
|
111
|
-
rescue ArgumentError
|
112
|
-
raise InvalidConstructorError, "Invalid #<#{policy_scope_class}> constructor is called"
|
113
|
-
end
|
114
|
-
|
115
|
-
policy_scope.resolve
|
105
|
+
# @see Pundit::Context#policy_scope
|
106
|
+
def policy_scope(user, *args, **kwargs, &block)
|
107
|
+
Context.new(user: user).policy_scope(*args, **kwargs, &block)
|
116
108
|
end
|
117
109
|
|
118
|
-
#
|
119
|
-
|
120
|
-
|
121
|
-
# @param user [Object] the user that initiated the action
|
122
|
-
# @param record [Object] the object we're retrieving the policy for
|
123
|
-
# @raise [InvalidConstructorError] if the policy constructor called incorrectly
|
124
|
-
# @return [Object, nil] instance of policy class with query methods
|
125
|
-
def policy(user, record)
|
126
|
-
policy = PolicyFinder.new(record).policy
|
127
|
-
policy.new(user, pundit_model(record)) if policy
|
128
|
-
rescue ArgumentError
|
129
|
-
raise InvalidConstructorError, "Invalid #<#{policy}> constructor is called"
|
110
|
+
# @see Pundit::Context#policy_scope!
|
111
|
+
def policy_scope!(user, *args, **kwargs, &block)
|
112
|
+
Context.new(user: user).policy_scope!(*args, **kwargs, &block)
|
130
113
|
end
|
131
114
|
|
132
|
-
#
|
133
|
-
|
134
|
-
|
135
|
-
# @param user [Object] the user that initiated the action
|
136
|
-
# @param record [Object] the object we're retrieving the policy for
|
137
|
-
# @raise [NotDefinedError] if the policy cannot be found
|
138
|
-
# @raise [InvalidConstructorError] if the policy constructor called incorrectly
|
139
|
-
# @return [Object] instance of policy class with query methods
|
140
|
-
def policy!(user, record)
|
141
|
-
policy = PolicyFinder.new(record).policy!
|
142
|
-
policy.new(user, pundit_model(record))
|
143
|
-
rescue ArgumentError
|
144
|
-
raise InvalidConstructorError, "Invalid #<#{policy}> constructor is called"
|
115
|
+
# @see Pundit::Context#policy
|
116
|
+
def policy(user, *args, **kwargs, &block)
|
117
|
+
Context.new(user: user).policy(*args, **kwargs, &block)
|
145
118
|
end
|
146
119
|
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
record.is_a?(Array) ? record.last : record
|
120
|
+
# @see Pundit::Context#policy!
|
121
|
+
def policy!(user, *args, **kwargs, &block)
|
122
|
+
Context.new(user: user).policy!(*args, **kwargs, &block)
|
151
123
|
end
|
152
124
|
end
|
153
125
|
|
126
|
+
# Rails view helpers, to allow a slightly different view-specific
|
127
|
+
# implementation of the methods in {Pundit::Authorization}.
|
128
|
+
#
|
154
129
|
# @api private
|
155
130
|
module Helper
|
131
|
+
# @see Pundit::Authorization#pundit_policy_scope
|
156
132
|
def policy_scope(scope)
|
157
133
|
pundit_policy_scope(scope)
|
158
134
|
end
|
159
135
|
end
|
160
|
-
|
161
|
-
included do
|
162
|
-
helper Helper if respond_to?(:helper)
|
163
|
-
if respond_to?(:helper_method)
|
164
|
-
helper_method :policy
|
165
|
-
helper_method :pundit_policy_scope
|
166
|
-
helper_method :pundit_user
|
167
|
-
end
|
168
|
-
end
|
169
|
-
|
170
|
-
protected
|
171
|
-
|
172
|
-
# @return [Boolean] whether authorization has been performed, i.e. whether
|
173
|
-
# one {#authorize} or {#skip_authorization} has been called
|
174
|
-
def pundit_policy_authorized?
|
175
|
-
!!@_pundit_policy_authorized
|
176
|
-
end
|
177
|
-
|
178
|
-
# @return [Boolean] whether policy scoping has been performed, i.e. whether
|
179
|
-
# one {#policy_scope} or {#skip_policy_scope} has been called
|
180
|
-
def pundit_policy_scoped?
|
181
|
-
!!@_pundit_policy_scoped
|
182
|
-
end
|
183
|
-
|
184
|
-
# Raises an error if authorization has not been performed, usually used as an
|
185
|
-
# `after_action` filter to prevent programmer error in forgetting to call
|
186
|
-
# {#authorize} or {#skip_authorization}.
|
187
|
-
#
|
188
|
-
# @see https://github.com/varvet/pundit#ensuring-policies-and-scopes-are-used
|
189
|
-
# @raise [AuthorizationNotPerformedError] if authorization has not been performed
|
190
|
-
# @return [void]
|
191
|
-
def verify_authorized
|
192
|
-
raise AuthorizationNotPerformedError, self.class unless pundit_policy_authorized?
|
193
|
-
end
|
194
|
-
|
195
|
-
# Raises an error if policy scoping has not been performed, usually used as an
|
196
|
-
# `after_action` filter to prevent programmer error in forgetting to call
|
197
|
-
# {#policy_scope} or {#skip_policy_scope} in index actions.
|
198
|
-
#
|
199
|
-
# @see https://github.com/varvet/pundit#ensuring-policies-and-scopes-are-used
|
200
|
-
# @raise [AuthorizationNotPerformedError] if policy scoping has not been performed
|
201
|
-
# @return [void]
|
202
|
-
def verify_policy_scoped
|
203
|
-
raise PolicyScopingNotPerformedError, self.class unless pundit_policy_scoped?
|
204
|
-
end
|
205
|
-
|
206
|
-
# Retrieves the policy for the given record, initializing it with the record
|
207
|
-
# and current user and finally throwing an error if the user is not
|
208
|
-
# authorized to perform the given action.
|
209
|
-
#
|
210
|
-
# @param record [Object] the object we're checking permissions of
|
211
|
-
# @param query [Symbol, String] the predicate method to check on the policy (e.g. `:show?`).
|
212
|
-
# If omitted then this defaults to the Rails controller action name.
|
213
|
-
# @param policy_class [Class] the policy class we want to force use of
|
214
|
-
# @raise [NotAuthorizedError] if the given query method returned false
|
215
|
-
# @return [Object] Always returns the passed object record
|
216
|
-
def authorize(record, query = nil, policy_class: nil)
|
217
|
-
query ||= "#{action_name}?"
|
218
|
-
|
219
|
-
@_pundit_policy_authorized = true
|
220
|
-
|
221
|
-
policy = policy_class ? policy_class.new(pundit_user, record) : policy(record)
|
222
|
-
|
223
|
-
raise NotAuthorizedError, query: query, record: record, policy: policy unless policy.public_send(query)
|
224
|
-
|
225
|
-
record
|
226
|
-
end
|
227
|
-
|
228
|
-
# Allow this action not to perform authorization.
|
229
|
-
#
|
230
|
-
# @see https://github.com/varvet/pundit#ensuring-policies-and-scopes-are-used
|
231
|
-
# @return [void]
|
232
|
-
def skip_authorization
|
233
|
-
@_pundit_policy_authorized = true
|
234
|
-
end
|
235
|
-
|
236
|
-
# Allow this action not to perform policy scoping.
|
237
|
-
#
|
238
|
-
# @see https://github.com/varvet/pundit#ensuring-policies-and-scopes-are-used
|
239
|
-
# @return [void]
|
240
|
-
def skip_policy_scope
|
241
|
-
@_pundit_policy_scoped = true
|
242
|
-
end
|
243
|
-
|
244
|
-
# Retrieves the policy scope for the given record.
|
245
|
-
#
|
246
|
-
# @see https://github.com/varvet/pundit#scopes
|
247
|
-
# @param scope [Object] the object we're retrieving the policy scope for
|
248
|
-
# @param policy_scope_class [Class] the policy scope class we want to force use of
|
249
|
-
# @return [Scope{#resolve}, nil] instance of scope class which can resolve to a scope
|
250
|
-
def policy_scope(scope, policy_scope_class: nil)
|
251
|
-
@_pundit_policy_scoped = true
|
252
|
-
policy_scope_class ? policy_scope_class.new(pundit_user, scope).resolve : pundit_policy_scope(scope)
|
253
|
-
end
|
254
|
-
|
255
|
-
# Retrieves the policy for the given record.
|
256
|
-
#
|
257
|
-
# @see https://github.com/varvet/pundit#policies
|
258
|
-
# @param record [Object] the object we're retrieving the policy for
|
259
|
-
# @return [Object, nil] instance of policy class with query methods
|
260
|
-
def policy(record)
|
261
|
-
policies[record] ||= Pundit.policy!(pundit_user, record)
|
262
|
-
end
|
263
|
-
|
264
|
-
# Retrieves a set of permitted attributes from the policy by instantiating
|
265
|
-
# the policy class for the given record and calling `permitted_attributes` on
|
266
|
-
# it, or `permitted_attributes_for_{action}` if `action` is defined. It then infers
|
267
|
-
# what key the record should have in the params hash and retrieves the
|
268
|
-
# permitted attributes from the params hash under that key.
|
269
|
-
#
|
270
|
-
# @see https://github.com/varvet/pundit#strong-parameters
|
271
|
-
# @param record [Object] the object we're retrieving permitted attributes for
|
272
|
-
# @param action [Symbol, String] the name of the action being performed on the record (e.g. `:update`).
|
273
|
-
# If omitted then this defaults to the Rails controller action name.
|
274
|
-
# @return [Hash{String => Object}] the permitted attributes
|
275
|
-
def permitted_attributes(record, action = action_name)
|
276
|
-
policy = policy(record)
|
277
|
-
method_name = if policy.respond_to?("permitted_attributes_for_#{action}")
|
278
|
-
"permitted_attributes_for_#{action}"
|
279
|
-
else
|
280
|
-
"permitted_attributes"
|
281
|
-
end
|
282
|
-
pundit_params_for(record).permit(*policy.public_send(method_name))
|
283
|
-
end
|
284
|
-
|
285
|
-
# Retrieves the params for the given record.
|
286
|
-
#
|
287
|
-
# @param record [Object] the object we're retrieving params for
|
288
|
-
# @return [ActionController::Parameters] the params
|
289
|
-
def pundit_params_for(record)
|
290
|
-
params.require(PolicyFinder.new(record).param_key)
|
291
|
-
end
|
292
|
-
|
293
|
-
# Cache of policies. You should not rely on this method.
|
294
|
-
#
|
295
|
-
# @api private
|
296
|
-
# rubocop:disable Naming/MemoizedInstanceVariableName
|
297
|
-
def policies
|
298
|
-
@_pundit_policies ||= {}
|
299
|
-
end
|
300
|
-
# rubocop:enable Naming/MemoizedInstanceVariableName
|
301
|
-
|
302
|
-
# Cache of policy scope. You should not rely on this method.
|
303
|
-
#
|
304
|
-
# @api private
|
305
|
-
# rubocop:disable Naming/MemoizedInstanceVariableName
|
306
|
-
def policy_scopes
|
307
|
-
@_pundit_policy_scopes ||= {}
|
308
|
-
end
|
309
|
-
# rubocop:enable Naming/MemoizedInstanceVariableName
|
310
|
-
|
311
|
-
# Hook method which allows customizing which user is passed to policies and
|
312
|
-
# scopes initialized by {#authorize}, {#policy} and {#policy_scope}.
|
313
|
-
#
|
314
|
-
# @see https://github.com/varvet/pundit#customize-pundit-user
|
315
|
-
# @return [Object] the user object to be used with pundit
|
316
|
-
def pundit_user
|
317
|
-
current_user
|
318
|
-
end
|
319
|
-
|
320
|
-
private
|
321
|
-
|
322
|
-
def pundit_policy_scope(scope)
|
323
|
-
policy_scopes[scope] ||= Pundit.policy_scope!(pundit_user, scope)
|
324
|
-
end
|
325
136
|
end
|
data/pundit.gemspec
CHANGED
@@ -8,7 +8,7 @@ Gem::Specification.new do |gem|
|
|
8
8
|
gem.name = "pundit"
|
9
9
|
gem.version = Pundit::VERSION
|
10
10
|
gem.authors = ["Jonas Nicklas", "Varvet AB"]
|
11
|
-
gem.email = ["jonas.nicklas@gmail.com", "
|
11
|
+
gem.email = ["jonas.nicklas@gmail.com", "info@varvet.com"]
|
12
12
|
gem.description = "Object oriented authorization for Rails applications"
|
13
13
|
gem.summary = "OO authorization for Rails"
|
14
14
|
gem.homepage = "https://github.com/varvet/pundit"
|
@@ -16,16 +16,16 @@ Gem::Specification.new do |gem|
|
|
16
16
|
|
17
17
|
gem.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR)
|
18
18
|
gem.executables = gem.files.grep(%r{^bin/}).map { |f| File.basename(f) }
|
19
|
-
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
20
19
|
gem.require_paths = ["lib"]
|
21
20
|
|
21
|
+
gem.metadata = {
|
22
|
+
"rubygems_mfa_required" => "true",
|
23
|
+
"bug_tracker_uri" => "https://github.com/varvet/pundit/issues",
|
24
|
+
"changelog_uri" => "https://github.com/varvet/pundit/blob/main/CHANGELOG.md",
|
25
|
+
"documentation_uri" => "https://github.com/varvet/pundit/blob/main/README.md",
|
26
|
+
"homepage_uri" => "https://github.com/varvet/pundit",
|
27
|
+
"source_code_uri" => "https://github.com/varvet/pundit"
|
28
|
+
}
|
29
|
+
|
22
30
|
gem.add_dependency "activesupport", ">= 3.0.0"
|
23
|
-
gem.add_development_dependency "actionpack", ">= 3.0.0"
|
24
|
-
gem.add_development_dependency "activemodel", ">= 3.0.0"
|
25
|
-
gem.add_development_dependency "bundler"
|
26
|
-
gem.add_development_dependency "pry"
|
27
|
-
gem.add_development_dependency "rake"
|
28
|
-
gem.add_development_dependency "rspec", ">= 2.0.0"
|
29
|
-
gem.add_development_dependency "rubocop", "0.57.2"
|
30
|
-
gem.add_development_dependency "yard"
|
31
31
|
end
|