pundit 1.1.0 → 2.3.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 +5 -5
- data/.gitignore +1 -0
- data/.rubocop.yml +29 -52
- data/.travis.yml +25 -10
- data/CHANGELOG.md +88 -0
- data/Gemfile +4 -1
- data/LICENSE.txt +1 -1
- data/README.md +304 -87
- data/Rakefile +2 -1
- data/config/rubocop-rspec.yml +5 -0
- data/lib/generators/pundit/install/install_generator.rb +4 -2
- data/lib/generators/pundit/install/templates/application_policy.rb +8 -8
- data/lib/generators/pundit/policy/policy_generator.rb +4 -2
- data/lib/generators/pundit/policy/templates/policy.rb +4 -3
- data/lib/generators/rspec/policy_generator.rb +4 -2
- data/lib/generators/rspec/templates/policy_spec.rb +1 -2
- data/lib/generators/test_unit/policy_generator.rb +4 -2
- data/lib/generators/test_unit/templates/policy_test.rb +0 -1
- data/lib/pundit/authorization.rb +168 -0
- data/lib/pundit/policy_finder.rb +28 -32
- data/lib/pundit/rspec.rb +13 -17
- data/lib/pundit/version.rb +3 -1
- data/lib/pundit.rb +76 -190
- data/pundit.gemspec +12 -9
- data/spec/authorization_spec.rb +258 -0
- data/spec/generators_spec.rb +43 -0
- data/spec/policies/post_policy_spec.rb +3 -1
- data/spec/policy_finder_spec.rb +187 -0
- data/spec/pundit_spec.rb +147 -196
- data/spec/spec_helper.rb +110 -30
- metadata +59 -25
data/lib/pundit.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require "pundit/version"
|
2
4
|
require "pundit/policy_finder"
|
3
5
|
require "active_support/concern"
|
@@ -5,6 +7,12 @@ require "active_support/core_ext/string/inflections"
|
|
5
7
|
require "active_support/core_ext/object/blank"
|
6
8
|
require "active_support/core_ext/module/introspection"
|
7
9
|
require "active_support/dependencies/autoload"
|
10
|
+
require "pundit/authorization"
|
11
|
+
|
12
|
+
# @api private
|
13
|
+
# To avoid name clashes with common Error naming when mixing in Pundit,
|
14
|
+
# keep it here with compact class style definition.
|
15
|
+
class Pundit::Error < StandardError; end # rubocop:disable Style/ClassAndModuleChildren
|
8
16
|
|
9
17
|
# @api public
|
10
18
|
module Pundit
|
@@ -13,10 +21,7 @@ module Pundit
|
|
13
21
|
# @api private
|
14
22
|
module Generators; end
|
15
23
|
|
16
|
-
#
|
17
|
-
class Error < StandardError; end
|
18
|
-
|
19
|
-
# Error that will be raiser when authorization has failed
|
24
|
+
# Error that will be raised when authorization has failed
|
20
25
|
class NotAuthorizedError < Error
|
21
26
|
attr_reader :query, :record, :policy
|
22
27
|
|
@@ -28,13 +33,16 @@ module Pundit
|
|
28
33
|
@record = options[:record]
|
29
34
|
@policy = options[:policy]
|
30
35
|
|
31
|
-
message = options.fetch(:message) { "not allowed to #{query} this #{record.
|
36
|
+
message = options.fetch(:message) { "not allowed to #{query} this #{record.class}" }
|
32
37
|
end
|
33
38
|
|
34
39
|
super(message)
|
35
40
|
end
|
36
41
|
end
|
37
42
|
|
43
|
+
# Error that will be raised if a policy or policy scope constructor is not called correctly.
|
44
|
+
class InvalidConstructorError < Error; end
|
45
|
+
|
38
46
|
# Error that will be raised if a controller action has not called the
|
39
47
|
# `authorize` or `skip_authorization` methods.
|
40
48
|
class AuthorizationNotPerformedError < Error; end
|
@@ -46,7 +54,12 @@ module Pundit
|
|
46
54
|
# Error that will be raised if a policy or policy scope is not defined.
|
47
55
|
class NotDefinedError < Error; end
|
48
56
|
|
49
|
-
|
57
|
+
def self.included(base)
|
58
|
+
ActiveSupport::Deprecation.warn <<~WARNING
|
59
|
+
'include Pundit' is deprecated. Please use 'include Pundit::Authorization' instead.
|
60
|
+
WARNING
|
61
|
+
base.include Authorization
|
62
|
+
end
|
50
63
|
|
51
64
|
class << self
|
52
65
|
# Retrieves the policy for the given record, initializing it with the
|
@@ -54,62 +67,99 @@ module Pundit
|
|
54
67
|
# authorized to perform the given action.
|
55
68
|
#
|
56
69
|
# @param user [Object] the user that initiated the action
|
57
|
-
# @param
|
58
|
-
# @param
|
70
|
+
# @param possibly_namespaced_record [Object, Array] the object we're checking permissions of
|
71
|
+
# @param query [Symbol, String] the predicate method to check on the policy (e.g. `:show?`)
|
72
|
+
# @param policy_class [Class] the policy class we want to force use of
|
73
|
+
# @param cache [#[], #[]=] a Hash-like object to cache the found policy instance in
|
59
74
|
# @raise [NotAuthorizedError] if the given query method returned false
|
60
|
-
# @return [
|
61
|
-
def authorize(user,
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
75
|
+
# @return [Object] Always returns the passed object record
|
76
|
+
def authorize(user, possibly_namespaced_record, query, policy_class: nil, cache: {})
|
77
|
+
record = pundit_model(possibly_namespaced_record)
|
78
|
+
policy = if policy_class
|
79
|
+
policy_class.new(user, record)
|
80
|
+
else
|
81
|
+
cache[possibly_namespaced_record] ||= policy!(user, possibly_namespaced_record)
|
66
82
|
end
|
67
83
|
|
68
|
-
|
84
|
+
raise NotAuthorizedError, query: query, record: record, policy: policy unless policy.public_send(query)
|
85
|
+
|
86
|
+
record
|
69
87
|
end
|
70
88
|
|
71
89
|
# Retrieves the policy scope for the given record.
|
72
90
|
#
|
73
|
-
# @see https://github.com/
|
91
|
+
# @see https://github.com/varvet/pundit#scopes
|
74
92
|
# @param user [Object] the user that initiated the action
|
75
|
-
# @param
|
93
|
+
# @param scope [Object] the object we're retrieving the policy scope for
|
94
|
+
# @raise [InvalidConstructorError] if the policy constructor called incorrectly
|
76
95
|
# @return [Scope{#resolve}, nil] instance of scope class which can resolve to a scope
|
77
96
|
def policy_scope(user, scope)
|
78
|
-
|
79
|
-
|
97
|
+
policy_scope_class = PolicyFinder.new(scope).scope
|
98
|
+
return unless policy_scope_class
|
99
|
+
|
100
|
+
begin
|
101
|
+
policy_scope = policy_scope_class.new(user, pundit_model(scope))
|
102
|
+
rescue ArgumentError
|
103
|
+
raise InvalidConstructorError, "Invalid #<#{policy_scope_class}> constructor is called"
|
104
|
+
end
|
105
|
+
|
106
|
+
policy_scope.resolve
|
80
107
|
end
|
81
108
|
|
82
109
|
# Retrieves the policy scope for the given record.
|
83
110
|
#
|
84
|
-
# @see https://github.com/
|
111
|
+
# @see https://github.com/varvet/pundit#scopes
|
85
112
|
# @param user [Object] the user that initiated the action
|
86
|
-
# @param
|
113
|
+
# @param scope [Object] the object we're retrieving the policy scope for
|
87
114
|
# @raise [NotDefinedError] if the policy scope cannot be found
|
115
|
+
# @raise [InvalidConstructorError] if the policy constructor called incorrectly
|
88
116
|
# @return [Scope{#resolve}] instance of scope class which can resolve to a scope
|
89
117
|
def policy_scope!(user, scope)
|
90
|
-
PolicyFinder.new(scope).scope
|
118
|
+
policy_scope_class = PolicyFinder.new(scope).scope!
|
119
|
+
return unless policy_scope_class
|
120
|
+
|
121
|
+
begin
|
122
|
+
policy_scope = policy_scope_class.new(user, pundit_model(scope))
|
123
|
+
rescue ArgumentError
|
124
|
+
raise InvalidConstructorError, "Invalid #<#{policy_scope_class}> constructor is called"
|
125
|
+
end
|
126
|
+
|
127
|
+
policy_scope.resolve
|
91
128
|
end
|
92
129
|
|
93
130
|
# Retrieves the policy for the given record.
|
94
131
|
#
|
95
|
-
# @see https://github.com/
|
132
|
+
# @see https://github.com/varvet/pundit#policies
|
96
133
|
# @param user [Object] the user that initiated the action
|
97
134
|
# @param record [Object] the object we're retrieving the policy for
|
135
|
+
# @raise [InvalidConstructorError] if the policy constructor called incorrectly
|
98
136
|
# @return [Object, nil] instance of policy class with query methods
|
99
137
|
def policy(user, record)
|
100
138
|
policy = PolicyFinder.new(record).policy
|
101
|
-
policy
|
139
|
+
policy&.new(user, pundit_model(record))
|
140
|
+
rescue ArgumentError
|
141
|
+
raise InvalidConstructorError, "Invalid #<#{policy}> constructor is called"
|
102
142
|
end
|
103
143
|
|
104
144
|
# Retrieves the policy for the given record.
|
105
145
|
#
|
106
|
-
# @see https://github.com/
|
146
|
+
# @see https://github.com/varvet/pundit#policies
|
107
147
|
# @param user [Object] the user that initiated the action
|
108
148
|
# @param record [Object] the object we're retrieving the policy for
|
109
149
|
# @raise [NotDefinedError] if the policy cannot be found
|
150
|
+
# @raise [InvalidConstructorError] if the policy constructor called incorrectly
|
110
151
|
# @return [Object] instance of policy class with query methods
|
111
152
|
def policy!(user, record)
|
112
|
-
PolicyFinder.new(record).policy
|
153
|
+
policy = PolicyFinder.new(record).policy!
|
154
|
+
policy.new(user, pundit_model(record))
|
155
|
+
rescue ArgumentError
|
156
|
+
raise InvalidConstructorError, "Invalid #<#{policy}> constructor is called"
|
157
|
+
end
|
158
|
+
|
159
|
+
private
|
160
|
+
|
161
|
+
def pundit_model(record)
|
162
|
+
record.is_a?(Array) ? record.last : record
|
113
163
|
end
|
114
164
|
end
|
115
165
|
|
@@ -119,168 +169,4 @@ module Pundit
|
|
119
169
|
pundit_policy_scope(scope)
|
120
170
|
end
|
121
171
|
end
|
122
|
-
|
123
|
-
included do
|
124
|
-
helper Helper if respond_to?(:helper)
|
125
|
-
if respond_to?(:helper_method)
|
126
|
-
helper_method :policy
|
127
|
-
helper_method :pundit_policy_scope
|
128
|
-
helper_method :pundit_user
|
129
|
-
end
|
130
|
-
if respond_to?(:hide_action)
|
131
|
-
hide_action :policy
|
132
|
-
hide_action :policy_scope
|
133
|
-
hide_action :policies
|
134
|
-
hide_action :policy_scopes
|
135
|
-
hide_action :authorize
|
136
|
-
hide_action :verify_authorized
|
137
|
-
hide_action :verify_policy_scoped
|
138
|
-
hide_action :permitted_attributes
|
139
|
-
hide_action :pundit_user
|
140
|
-
hide_action :skip_authorization
|
141
|
-
hide_action :skip_policy_scope
|
142
|
-
hide_action :pundit_policy_authorized?
|
143
|
-
hide_action :pundit_policy_scoped?
|
144
|
-
end
|
145
|
-
end
|
146
|
-
|
147
|
-
# @return [Boolean] whether authorization has been performed, i.e. whether
|
148
|
-
# one {#authorize} or {#skip_authorization} has been called
|
149
|
-
def pundit_policy_authorized?
|
150
|
-
!!@_pundit_policy_authorized
|
151
|
-
end
|
152
|
-
|
153
|
-
# @return [Boolean] whether policy scoping has been performed, i.e. whether
|
154
|
-
# one {#policy_scope} or {#skip_policy_scope} has been called
|
155
|
-
def pundit_policy_scoped?
|
156
|
-
!!@_pundit_policy_scoped
|
157
|
-
end
|
158
|
-
|
159
|
-
# Raises an error if authorization has not been performed, usually used as an
|
160
|
-
# `after_action` filter to prevent programmer error in forgetting to call
|
161
|
-
# {#authorize} or {#skip_authorization}.
|
162
|
-
#
|
163
|
-
# @see https://github.com/elabs/pundit#ensuring-policies-are-used
|
164
|
-
# @raise [AuthorizationNotPerformedError] if authorization has not been performed
|
165
|
-
# @return [void]
|
166
|
-
def verify_authorized
|
167
|
-
raise AuthorizationNotPerformedError, self.class unless pundit_policy_authorized?
|
168
|
-
end
|
169
|
-
|
170
|
-
# Raises an error if policy scoping has not been performed, usually used as an
|
171
|
-
# `after_action` filter to prevent programmer error in forgetting to call
|
172
|
-
# {#policy_scope} or {#skip_policy_scope} in index actions.
|
173
|
-
#
|
174
|
-
# @see https://github.com/elabs/pundit#ensuring-policies-are-used
|
175
|
-
# @raise [AuthorizationNotPerformedError] if policy scoping has not been performed
|
176
|
-
# @return [void]
|
177
|
-
def verify_policy_scoped
|
178
|
-
raise PolicyScopingNotPerformedError, self.class unless pundit_policy_scoped?
|
179
|
-
end
|
180
|
-
|
181
|
-
# Retrieves the policy for the given record, initializing it with the record
|
182
|
-
# and current user and finally throwing an error if the user is not
|
183
|
-
# authorized to perform the given action.
|
184
|
-
#
|
185
|
-
# @param record [Object] the object we're checking permissions of
|
186
|
-
# @param record [Symbol, nil] the query method to check on the policy (e.g. `:show?`)
|
187
|
-
# @raise [NotAuthorizedError] if the given query method returned false
|
188
|
-
# @return [true] Always returns true
|
189
|
-
def authorize(record, query = nil)
|
190
|
-
query ||= params[:action].to_s + "?"
|
191
|
-
|
192
|
-
@_pundit_policy_authorized = true
|
193
|
-
|
194
|
-
policy = policy(record)
|
195
|
-
|
196
|
-
unless policy.public_send(query)
|
197
|
-
raise NotAuthorizedError, query: query, record: record, policy: policy
|
198
|
-
end
|
199
|
-
|
200
|
-
true
|
201
|
-
end
|
202
|
-
|
203
|
-
# Allow this action not to perform authorization.
|
204
|
-
#
|
205
|
-
# @see https://github.com/elabs/pundit#ensuring-policies-are-used
|
206
|
-
# @return [void]
|
207
|
-
def skip_authorization
|
208
|
-
@_pundit_policy_authorized = true
|
209
|
-
end
|
210
|
-
|
211
|
-
# Allow this action not to perform policy scoping.
|
212
|
-
#
|
213
|
-
# @see https://github.com/elabs/pundit#ensuring-policies-are-used
|
214
|
-
# @return [void]
|
215
|
-
def skip_policy_scope
|
216
|
-
@_pundit_policy_scoped = true
|
217
|
-
end
|
218
|
-
|
219
|
-
# Retrieves the policy scope for the given record.
|
220
|
-
#
|
221
|
-
# @see https://github.com/elabs/pundit#scopes
|
222
|
-
# @param record [Object] the object we're retrieving the policy scope for
|
223
|
-
# @return [Scope{#resolve}, nil] instance of scope class which can resolve to a scope
|
224
|
-
def policy_scope(scope)
|
225
|
-
@_pundit_policy_scoped = true
|
226
|
-
pundit_policy_scope(scope)
|
227
|
-
end
|
228
|
-
|
229
|
-
# Retrieves the policy for the given record.
|
230
|
-
#
|
231
|
-
# @see https://github.com/elabs/pundit#policies
|
232
|
-
# @param record [Object] the object we're retrieving the policy for
|
233
|
-
# @return [Object, nil] instance of policy class with query methods
|
234
|
-
def policy(record)
|
235
|
-
policies[record] ||= Pundit.policy!(pundit_user, record)
|
236
|
-
end
|
237
|
-
|
238
|
-
# Retrieves a set of permitted attributes from the policy by instantiating
|
239
|
-
# the policy class for the given record and calling `permitted_attributes` on
|
240
|
-
# it, or `permitted_attributes_for_{action}` if it is defined. It then infers
|
241
|
-
# what key the record should have in the params hash and retrieves the
|
242
|
-
# permitted attributes from the params hash under that key.
|
243
|
-
#
|
244
|
-
# @see https://github.com/elabs/pundit#strong-parameters
|
245
|
-
# @param record [Object] the object we're retrieving permitted attributes for
|
246
|
-
# @return [Hash{String => Object}] the permitted attributes
|
247
|
-
def permitted_attributes(record, action = params[:action])
|
248
|
-
param_key = PolicyFinder.new(record).param_key
|
249
|
-
policy = policy(record)
|
250
|
-
method_name = if policy.respond_to?("permitted_attributes_for_#{action}")
|
251
|
-
"permitted_attributes_for_#{action}"
|
252
|
-
else
|
253
|
-
"permitted_attributes"
|
254
|
-
end
|
255
|
-
params.require(param_key).permit(policy.public_send(method_name))
|
256
|
-
end
|
257
|
-
|
258
|
-
# Cache of policies. You should not rely on this method.
|
259
|
-
#
|
260
|
-
# @api private
|
261
|
-
def policies
|
262
|
-
@_pundit_policies ||= {}
|
263
|
-
end
|
264
|
-
|
265
|
-
# Cache of policy scope. You should not rely on this method.
|
266
|
-
#
|
267
|
-
# @api private
|
268
|
-
def policy_scopes
|
269
|
-
@_pundit_policy_scopes ||= {}
|
270
|
-
end
|
271
|
-
|
272
|
-
# Hook method which allows customizing which user is passed to policies and
|
273
|
-
# scopes initialized by {#authorize}, {#policy} and {#policy_scope}.
|
274
|
-
#
|
275
|
-
# @see https://github.com/elabs/pundit#customize-pundit-user
|
276
|
-
# @return [Object] the user object to be used with pundit
|
277
|
-
def pundit_user
|
278
|
-
current_user
|
279
|
-
end
|
280
|
-
|
281
|
-
private
|
282
|
-
|
283
|
-
def pundit_policy_scope(scope)
|
284
|
-
policy_scopes[scope] ||= Pundit.policy_scope!(pundit_user, scope)
|
285
|
-
end
|
286
172
|
end
|
data/pundit.gemspec
CHANGED
@@ -1,30 +1,33 @@
|
|
1
|
-
#
|
2
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
lib = File.expand_path("lib", __dir__)
|
3
4
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
5
|
require "pundit/version"
|
5
6
|
|
6
7
|
Gem::Specification.new do |gem|
|
7
8
|
gem.name = "pundit"
|
8
9
|
gem.version = Pundit::VERSION
|
9
|
-
gem.authors = ["Jonas Nicklas", "
|
10
|
+
gem.authors = ["Jonas Nicklas", "Varvet AB"]
|
10
11
|
gem.email = ["jonas.nicklas@gmail.com", "dev@elabs.se"]
|
11
12
|
gem.description = "Object oriented authorization for Rails applications"
|
12
13
|
gem.summary = "OO authorization for Rails"
|
13
|
-
gem.homepage = "https://github.com/
|
14
|
+
gem.homepage = "https://github.com/varvet/pundit"
|
14
15
|
gem.license = "MIT"
|
15
16
|
|
16
|
-
gem.files = `git ls-files`.split(
|
17
|
+
gem.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR)
|
17
18
|
gem.executables = gem.files.grep(%r{^bin/}).map { |f| File.basename(f) }
|
18
19
|
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
19
20
|
gem.require_paths = ["lib"]
|
20
21
|
|
21
22
|
gem.add_dependency "activesupport", ">= 3.0.0"
|
22
|
-
gem.add_development_dependency "activemodel", ">= 3.0.0"
|
23
23
|
gem.add_development_dependency "actionpack", ">= 3.0.0"
|
24
|
-
gem.add_development_dependency "
|
25
|
-
gem.add_development_dependency "
|
24
|
+
gem.add_development_dependency "activemodel", ">= 3.0.0"
|
25
|
+
gem.add_development_dependency "bundler"
|
26
26
|
gem.add_development_dependency "pry"
|
27
|
+
gem.add_development_dependency "railties", ">= 3.0.0"
|
27
28
|
gem.add_development_dependency "rake"
|
29
|
+
gem.add_development_dependency "rspec", ">= 3.0.0"
|
30
|
+
gem.add_development_dependency "rubocop", "1.24.0"
|
31
|
+
gem.add_development_dependency "simplecov", ">= 0.17.0"
|
28
32
|
gem.add_development_dependency "yard"
|
29
|
-
gem.add_development_dependency "rubocop"
|
30
33
|
end
|
@@ -0,0 +1,258 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "spec_helper"
|
4
|
+
|
5
|
+
describe Pundit::Authorization do
|
6
|
+
let(:controller) { Controller.new(user, "update", {}) }
|
7
|
+
let(:user) { double }
|
8
|
+
let(:post) { Post.new(user) }
|
9
|
+
let(:customer_post) { Customer::Post.new(user) }
|
10
|
+
let(:comment) { Comment.new }
|
11
|
+
let(:article) { Article.new }
|
12
|
+
let(:article_tag) { ArticleTag.new }
|
13
|
+
let(:wiki) { Wiki.new }
|
14
|
+
|
15
|
+
describe "#verify_authorized" do
|
16
|
+
it "does nothing when authorized" do
|
17
|
+
controller.authorize(post)
|
18
|
+
controller.verify_authorized
|
19
|
+
end
|
20
|
+
|
21
|
+
it "raises an exception when not authorized" do
|
22
|
+
expect { controller.verify_authorized }.to raise_error(Pundit::AuthorizationNotPerformedError)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
describe "#verify_policy_scoped" do
|
27
|
+
it "does nothing when policy_scope is used" do
|
28
|
+
controller.policy_scope(Post)
|
29
|
+
controller.verify_policy_scoped
|
30
|
+
end
|
31
|
+
|
32
|
+
it "raises an exception when policy_scope is not used" do
|
33
|
+
expect { controller.verify_policy_scoped }.to raise_error(Pundit::PolicyScopingNotPerformedError)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
describe "#pundit_policy_authorized?" do
|
38
|
+
it "is true when authorized" do
|
39
|
+
controller.authorize(post)
|
40
|
+
expect(controller.pundit_policy_authorized?).to be true
|
41
|
+
end
|
42
|
+
|
43
|
+
it "is false when not authorized" do
|
44
|
+
expect(controller.pundit_policy_authorized?).to be false
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
describe "#pundit_policy_scoped?" do
|
49
|
+
it "is true when policy_scope is used" do
|
50
|
+
controller.policy_scope(Post)
|
51
|
+
expect(controller.pundit_policy_scoped?).to be true
|
52
|
+
end
|
53
|
+
|
54
|
+
it "is false when policy scope is not used" do
|
55
|
+
expect(controller.pundit_policy_scoped?).to be false
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
describe "#authorize" do
|
60
|
+
it "infers the policy name and authorizes based on it" do
|
61
|
+
expect(controller.authorize(post)).to be_truthy
|
62
|
+
end
|
63
|
+
|
64
|
+
it "returns the record on successful authorization" do
|
65
|
+
expect(controller.authorize(post)).to eq(post)
|
66
|
+
end
|
67
|
+
|
68
|
+
it "returns the record when passed record with namespace " do
|
69
|
+
expect(controller.authorize([:project, comment], :update?)).to eq(comment)
|
70
|
+
end
|
71
|
+
|
72
|
+
it "returns the record when passed record with nested namespace " do
|
73
|
+
expect(controller.authorize([:project, :admin, comment], :update?)).to eq(comment)
|
74
|
+
end
|
75
|
+
|
76
|
+
it "returns the policy name symbol when passed record with headless policy" do
|
77
|
+
expect(controller.authorize(:publication, :create?)).to eq(:publication)
|
78
|
+
end
|
79
|
+
|
80
|
+
it "returns the class when passed record not a particular instance" do
|
81
|
+
expect(controller.authorize(Post, :show?)).to eq(Post)
|
82
|
+
end
|
83
|
+
|
84
|
+
it "can be given a different permission to check" do
|
85
|
+
expect(controller.authorize(post, :show?)).to be_truthy
|
86
|
+
expect { controller.authorize(post, :destroy?) }.to raise_error(Pundit::NotAuthorizedError)
|
87
|
+
end
|
88
|
+
|
89
|
+
it "can be given a different policy class" do
|
90
|
+
expect(controller.authorize(post, :create?, policy_class: PublicationPolicy)).to be_truthy
|
91
|
+
end
|
92
|
+
|
93
|
+
it "works with anonymous class policies" do
|
94
|
+
expect(controller.authorize(article_tag, :show?)).to be_truthy
|
95
|
+
expect { controller.authorize(article_tag, :destroy?) }.to raise_error(Pundit::NotAuthorizedError)
|
96
|
+
end
|
97
|
+
|
98
|
+
it "throws an exception when the permission check fails" do
|
99
|
+
expect { controller.authorize(Post.new) }.to raise_error(Pundit::NotAuthorizedError)
|
100
|
+
end
|
101
|
+
|
102
|
+
it "throws an exception when a policy cannot be found" do
|
103
|
+
expect { controller.authorize(Article) }.to raise_error(Pundit::NotDefinedError)
|
104
|
+
end
|
105
|
+
|
106
|
+
it "caches the policy" do
|
107
|
+
expect(controller.policies[post]).to be_nil
|
108
|
+
controller.authorize(post)
|
109
|
+
expect(controller.policies[post]).not_to be_nil
|
110
|
+
end
|
111
|
+
|
112
|
+
it "raises an error when the given record is nil" do
|
113
|
+
expect { controller.authorize(nil, :destroy?) }.to raise_error(Pundit::NotAuthorizedError)
|
114
|
+
end
|
115
|
+
|
116
|
+
it "raises an error with a invalid policy constructor" do
|
117
|
+
expect { controller.authorize(wiki, :destroy?) }.to raise_error(Pundit::InvalidConstructorError)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
describe "#skip_authorization" do
|
122
|
+
it "disables authorization verification" do
|
123
|
+
controller.skip_authorization
|
124
|
+
expect { controller.verify_authorized }.not_to raise_error
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
describe "#skip_policy_scope" do
|
129
|
+
it "disables policy scope verification" do
|
130
|
+
controller.skip_policy_scope
|
131
|
+
expect { controller.verify_policy_scoped }.not_to raise_error
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
describe "#pundit_user" do
|
136
|
+
it "returns the same thing as current_user" do
|
137
|
+
expect(controller.pundit_user).to eq controller.current_user
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
describe "#policy" do
|
142
|
+
it "returns an instantiated policy" do
|
143
|
+
policy = controller.policy(post)
|
144
|
+
expect(policy.user).to eq user
|
145
|
+
expect(policy.post).to eq post
|
146
|
+
end
|
147
|
+
|
148
|
+
it "throws an exception if the given policy can't be found" do
|
149
|
+
expect { controller.policy(article) }.to raise_error(Pundit::NotDefinedError)
|
150
|
+
end
|
151
|
+
|
152
|
+
it "raises an error with a invalid policy constructor" do
|
153
|
+
expect { controller.policy(wiki) }.to raise_error(Pundit::InvalidConstructorError)
|
154
|
+
end
|
155
|
+
|
156
|
+
it "allows policy to be injected" do
|
157
|
+
new_policy = OpenStruct.new
|
158
|
+
controller.policies[post] = new_policy
|
159
|
+
|
160
|
+
expect(controller.policy(post)).to eq new_policy
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
describe "#policy_scope" do
|
165
|
+
it "returns an instantiated policy scope" do
|
166
|
+
expect(controller.policy_scope(Post)).to eq :published
|
167
|
+
end
|
168
|
+
|
169
|
+
it "allows policy scope class to be overriden" do
|
170
|
+
expect(controller.policy_scope(Post, policy_scope_class: PublicationPolicy::Scope)).to eq :published
|
171
|
+
end
|
172
|
+
|
173
|
+
it "throws an exception if the given policy can't be found" do
|
174
|
+
expect { controller.policy_scope(Article) }.to raise_error(Pundit::NotDefinedError)
|
175
|
+
end
|
176
|
+
|
177
|
+
it "raises an error with a invalid policy scope constructor" do
|
178
|
+
expect { controller.policy_scope(Wiki) }.to raise_error(Pundit::InvalidConstructorError)
|
179
|
+
end
|
180
|
+
|
181
|
+
it "allows policy_scope to be injected" do
|
182
|
+
new_scope = OpenStruct.new
|
183
|
+
controller.policy_scopes[Post] = new_scope
|
184
|
+
|
185
|
+
expect(controller.policy_scope(Post)).to eq new_scope
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
describe "#permitted_attributes" do
|
190
|
+
it "checks policy for permitted attributes" do
|
191
|
+
params = ActionController::Parameters.new(
|
192
|
+
post: {
|
193
|
+
title: "Hello",
|
194
|
+
votes: 5,
|
195
|
+
admin: true
|
196
|
+
}
|
197
|
+
)
|
198
|
+
|
199
|
+
action = "update"
|
200
|
+
|
201
|
+
expect(Controller.new(user, action, params).permitted_attributes(post).to_h).to eq(
|
202
|
+
"title" => "Hello",
|
203
|
+
"votes" => 5
|
204
|
+
)
|
205
|
+
expect(Controller.new(double, action, params).permitted_attributes(post).to_h).to eq("votes" => 5)
|
206
|
+
end
|
207
|
+
|
208
|
+
it "checks policy for permitted attributes for record of a ActiveModel type" do
|
209
|
+
params = ActionController::Parameters.new(
|
210
|
+
customer_post: {
|
211
|
+
title: "Hello",
|
212
|
+
votes: 5,
|
213
|
+
admin: true
|
214
|
+
}
|
215
|
+
)
|
216
|
+
|
217
|
+
action = "update"
|
218
|
+
|
219
|
+
expect(Controller.new(user, action, params).permitted_attributes(customer_post).to_h).to eq(
|
220
|
+
"title" => "Hello",
|
221
|
+
"votes" => 5
|
222
|
+
)
|
223
|
+
expect(Controller.new(double, action, params).permitted_attributes(customer_post).to_h).to eq(
|
224
|
+
"votes" => 5
|
225
|
+
)
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
describe "#permitted_attributes_for_action" do
|
230
|
+
it "is checked if it is defined in the policy" do
|
231
|
+
params = ActionController::Parameters.new(
|
232
|
+
post: {
|
233
|
+
title: "Hello",
|
234
|
+
body: "blah",
|
235
|
+
votes: 5,
|
236
|
+
admin: true
|
237
|
+
}
|
238
|
+
)
|
239
|
+
|
240
|
+
action = "revise"
|
241
|
+
expect(Controller.new(user, action, params).permitted_attributes(post).to_h).to eq("body" => "blah")
|
242
|
+
end
|
243
|
+
|
244
|
+
it "can be explicitly set" do
|
245
|
+
params = ActionController::Parameters.new(
|
246
|
+
post: {
|
247
|
+
title: "Hello",
|
248
|
+
body: "blah",
|
249
|
+
votes: 5,
|
250
|
+
admin: true
|
251
|
+
}
|
252
|
+
)
|
253
|
+
|
254
|
+
action = "update"
|
255
|
+
expect(Controller.new(user, action, params).permitted_attributes(post, :revise).to_h).to eq("body" => "blah")
|
256
|
+
end
|
257
|
+
end
|
258
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "spec_helper"
|
4
|
+
require "tmpdir"
|
5
|
+
|
6
|
+
require "rails/generators"
|
7
|
+
require "generators/pundit/install/install_generator"
|
8
|
+
require "generators/pundit/policy/policy_generator"
|
9
|
+
|
10
|
+
RSpec.describe "generators" do
|
11
|
+
before(:all) do
|
12
|
+
@tmpdir = Dir.mktmpdir
|
13
|
+
|
14
|
+
Dir.chdir(@tmpdir) do
|
15
|
+
Pundit::Generators::InstallGenerator.new([], { quiet: true }).invoke_all
|
16
|
+
Pundit::Generators::PolicyGenerator.new(%w[Widget], { quiet: true }).invoke_all
|
17
|
+
|
18
|
+
require "./app/policies/application_policy"
|
19
|
+
require "./app/policies/widget_policy"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
after(:all) do
|
24
|
+
FileUtils.remove_entry(@tmpdir)
|
25
|
+
end
|
26
|
+
|
27
|
+
describe "WidgetPolicy", type: :policy do
|
28
|
+
permissions :index?, :show?, :create?, :new?, :update?, :edit?, :destroy? do
|
29
|
+
it "has safe defaults" do
|
30
|
+
expect(WidgetPolicy).not_to permit(double("User"), double("Widget"))
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
describe "WidgetPolicy::Scope" do
|
35
|
+
describe "#resolve" do
|
36
|
+
it "raises a descriptive error" do
|
37
|
+
scope = WidgetPolicy::Scope.new(double("User"), double("User.all"))
|
38
|
+
expect { scope.resolve }.to raise_error(NotImplementedError, /WidgetPolicy::Scope/)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|