pundit 1.1.0 → 2.1.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/.rubocop.yml +30 -49
- data/.travis.yml +20 -10
- data/CHANGELOG.md +42 -0
- data/Gemfile +4 -1
- data/LICENSE.txt +1 -1
- data/README.md +241 -60
- data/Rakefile +2 -1
- data/lib/generators/pundit/install/install_generator.rb +1 -1
- data/lib/generators/pundit/install/templates/application_policy.rb +2 -6
- data/lib/generators/pundit/policy/policy_generator.rb +1 -1
- data/lib/generators/pundit/policy/templates/policy.rb +1 -1
- data/lib/generators/rspec/policy_generator.rb +1 -1
- data/lib/generators/rspec/templates/policy_spec.rb +1 -2
- data/lib/generators/test_unit/policy_generator.rb +1 -1
- data/lib/generators/test_unit/templates/policy_test.rb +0 -1
- data/lib/pundit.rb +103 -64
- data/lib/pundit/policy_finder.rb +27 -31
- data/lib/pundit/rspec.rb +13 -7
- data/lib/pundit/version.rb +3 -1
- data/pundit.gemspec +10 -9
- data/spec/policies/post_policy_spec.rb +2 -0
- data/spec/policy_finder_spec.rb +124 -0
- data/spec/pundit_spec.rb +174 -53
- data/spec/spec_helper.rb +87 -11
- metadata +26 -26
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
|
-
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 371516754ff155f90b2093a0ce80aacf097ab555027b19ea22b7c823de72a66a
|
|
4
|
+
data.tar.gz: 41e69a7d6a317b46ad35d1d1485d2119b443b8a430e5c78e62935ec502c7d08f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c77a792bec5d87f487fd3ee419d00745dcab754bd1bd38504d9987b71d80be3bd32fb1aab8419a8e63ef3c3718e1bd8a255ff0117be8f8a5c743c221d87fccdd
|
|
7
|
+
data.tar.gz: 3086b4036cdbafb499f462f22405f185c83d12c8d8175136531dd053733320574b3d5d05c8379895940d854d54d7abb59d6a0958a9d0e6fdfc03f7691883c3ab
|
data/.rubocop.yml
CHANGED
|
@@ -1,14 +1,22 @@
|
|
|
1
1
|
AllCops:
|
|
2
|
+
DisplayCopNames: true
|
|
3
|
+
TargetRubyVersion: 2.2
|
|
2
4
|
Exclude:
|
|
3
5
|
- "gemfiles/**/*"
|
|
4
6
|
- "vendor/**/*"
|
|
5
7
|
- "lib/generators/**/*"
|
|
6
8
|
|
|
9
|
+
Metrics/BlockLength:
|
|
10
|
+
Exclude:
|
|
11
|
+
- "**/*_spec.rb"
|
|
12
|
+
|
|
7
13
|
Metrics/MethodLength:
|
|
8
14
|
Max: 40
|
|
9
15
|
|
|
10
16
|
Metrics/ModuleLength:
|
|
11
17
|
Max: 200
|
|
18
|
+
Exclude:
|
|
19
|
+
- "**/*_spec.rb"
|
|
12
20
|
|
|
13
21
|
Metrics/LineLength:
|
|
14
22
|
Max: 120
|
|
@@ -22,74 +30,47 @@ Metrics/CyclomaticComplexity:
|
|
|
22
30
|
Metrics/PerceivedComplexity:
|
|
23
31
|
Enabled: false
|
|
24
32
|
|
|
25
|
-
|
|
26
|
-
Enabled: false
|
|
27
|
-
|
|
28
|
-
Style/AlignParameters:
|
|
33
|
+
Layout/AlignParameters:
|
|
29
34
|
EnforcedStyle: with_fixed_indentation
|
|
30
35
|
|
|
31
|
-
|
|
32
|
-
EnforcedStyle:
|
|
33
|
-
|
|
34
|
-
Style/StringLiteralsInInterpolation:
|
|
35
|
-
EnforcedStyle: double_quotes
|
|
36
|
-
|
|
37
|
-
Style/ClosingParenthesisIndentation:
|
|
38
|
-
Enabled: false
|
|
39
|
-
|
|
40
|
-
Style/OneLineConditional:
|
|
41
|
-
Enabled: false
|
|
42
|
-
|
|
43
|
-
Style/AndOr:
|
|
44
|
-
Enabled: false
|
|
45
|
-
|
|
46
|
-
Style/Not:
|
|
47
|
-
Enabled: false
|
|
48
|
-
|
|
49
|
-
Documentation:
|
|
50
|
-
Enabled: false # TODO: Enable again once we have more docs
|
|
51
|
-
|
|
52
|
-
Style/CaseIndentation:
|
|
53
|
-
IndentWhenRelativeTo: case
|
|
36
|
+
Layout/CaseIndentation:
|
|
37
|
+
EnforcedStyle: case
|
|
54
38
|
SupportedStyles:
|
|
55
39
|
- case
|
|
56
40
|
- end
|
|
57
41
|
IndentOneStep: true
|
|
58
42
|
|
|
59
|
-
|
|
60
|
-
PreferredDelimiters:
|
|
61
|
-
'%w': "[]"
|
|
62
|
-
'%W': "[]"
|
|
63
|
-
|
|
64
|
-
Style/AccessModifierIndentation:
|
|
43
|
+
Layout/AccessModifierIndentation:
|
|
65
44
|
EnforcedStyle: outdent
|
|
66
45
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
Style/IndentationWidth:
|
|
71
|
-
Enabled: false
|
|
46
|
+
Layout/EndAlignment:
|
|
47
|
+
EnforcedStyleAlignWith: variable
|
|
72
48
|
|
|
73
|
-
Style/
|
|
74
|
-
|
|
49
|
+
Style/FrozenStringLiteralComment:
|
|
50
|
+
Enabled: true
|
|
75
51
|
|
|
76
|
-
|
|
77
|
-
|
|
52
|
+
Style/PercentLiteralDelimiters:
|
|
53
|
+
PreferredDelimiters:
|
|
54
|
+
'%w': "[]"
|
|
55
|
+
'%W': "[]"
|
|
78
56
|
|
|
79
|
-
|
|
80
|
-
|
|
57
|
+
Style/StringLiterals:
|
|
58
|
+
EnforcedStyle: double_quotes
|
|
81
59
|
|
|
82
|
-
|
|
83
|
-
|
|
60
|
+
Style/StringLiteralsInInterpolation:
|
|
61
|
+
EnforcedStyle: double_quotes
|
|
84
62
|
|
|
85
|
-
Style/
|
|
63
|
+
Style/StructInheritance:
|
|
86
64
|
Enabled: false
|
|
87
65
|
|
|
88
|
-
Style/
|
|
66
|
+
Style/AndOr:
|
|
89
67
|
Enabled: false
|
|
90
68
|
|
|
91
|
-
Style/
|
|
69
|
+
Style/Not:
|
|
92
70
|
Enabled: false
|
|
93
71
|
|
|
94
72
|
Style/DoubleNegation:
|
|
95
73
|
Enabled: false
|
|
74
|
+
|
|
75
|
+
Documentation:
|
|
76
|
+
Enabled: false # TODO: Enable again once we have more docs
|
data/.travis.yml
CHANGED
|
@@ -1,11 +1,21 @@
|
|
|
1
1
|
language: ruby
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
2
|
+
before_install:
|
|
3
|
+
- gem install bundler -v 1.17.3
|
|
4
|
+
|
|
5
|
+
matrix:
|
|
6
|
+
include:
|
|
7
|
+
- rvm: 2.5.1 # Pre-installed Ruby version
|
|
8
|
+
script: bundle exec rake rubocop # ONLY lint once, first
|
|
9
|
+
- rvm: 2.1
|
|
10
|
+
- rvm: 2.2
|
|
11
|
+
- rvm: 2.3.5
|
|
12
|
+
- rvm: 2.4.6
|
|
13
|
+
- rvm: 2.5.5
|
|
14
|
+
- rvm: 2.6.3
|
|
15
|
+
- rvm: jruby-9.1.8.0
|
|
16
|
+
env:
|
|
17
|
+
- JRUBY_OPTS="--debug"
|
|
18
|
+
jdk: openjdk8
|
|
19
|
+
- rvm: jruby-9.2.8.0
|
|
20
|
+
env:
|
|
21
|
+
- JRUBY_OPTS="--debug"
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,47 @@
|
|
|
1
1
|
# Pundit
|
|
2
2
|
|
|
3
|
+
### Fixed
|
|
4
|
+
|
|
5
|
+
- Avoid name clashes with the Error class. (#590)
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
|
|
9
|
+
- Return a safer default NotAuthorizedError message. (#583)
|
|
10
|
+
|
|
11
|
+
## 2.0.1 (2019-01-18)
|
|
12
|
+
|
|
13
|
+
### Breaking changes
|
|
14
|
+
|
|
15
|
+
None
|
|
16
|
+
|
|
17
|
+
### Other changes
|
|
18
|
+
|
|
19
|
+
- Improve exception handling for `#policy_scope` and `#policy_scope!`. (#550)
|
|
20
|
+
- Add `:policy` metadata to RSpec template. (#566)
|
|
21
|
+
|
|
22
|
+
## 2.0.0 (2018-07-21)
|
|
23
|
+
|
|
24
|
+
No changes since beta1
|
|
25
|
+
|
|
26
|
+
## 2.0.0.beta1 (2018-07-04)
|
|
27
|
+
|
|
28
|
+
### Breaking changes
|
|
29
|
+
|
|
30
|
+
- Only pass last element of "namespace array" to policy and scope. (#529)
|
|
31
|
+
- Raise `InvalidConstructorError` if a policy or policy scope with an invalid constructor is called. (#462)
|
|
32
|
+
- Return passed object from `#authorize` method to make chaining possible. (#385)
|
|
33
|
+
|
|
34
|
+
### Other changes
|
|
35
|
+
|
|
36
|
+
- Add `policy_class` option to `authorize` to be able to override the policy. (#441)
|
|
37
|
+
- Add `policy_scope_class` option to `authorize` to be able to override the policy scope. (#441)
|
|
38
|
+
- Fix `param_key` issue when passed an array. (#529)
|
|
39
|
+
- Allow specification of a `NilClassPolicy`. (#525)
|
|
40
|
+
- Make sure `policy_class` override is called when passed an array. (#475)
|
|
41
|
+
|
|
42
|
+
- Use `action_name` instead of `params[:action]`. (#419)
|
|
43
|
+
- Add `pundit_params_for` method to make it easy to customize params fetching. (#502)
|
|
44
|
+
|
|
3
45
|
## 1.1.0 (2016-01-14)
|
|
4
46
|
|
|
5
47
|
- Can retrieve policies via an array of symbols/objects.
|
data/Gemfile
CHANGED
data/LICENSE.txt
CHANGED
data/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# Pundit
|
|
2
2
|
|
|
3
|
-
[](https://travis-ci.org/varvet/pundit)
|
|
4
|
+
[](https://codeclimate.com/github/varvet/pundit)
|
|
5
|
+
[](http://inch-ci.org/github/varvet/pundit)
|
|
6
6
|
[](http://badge.fury.io/rb/pundit)
|
|
7
7
|
|
|
8
8
|
Pundit provides a set of helpers which guide you in leveraging regular Ruby
|
|
@@ -12,13 +12,13 @@ scaleable authorization system.
|
|
|
12
12
|
Links:
|
|
13
13
|
|
|
14
14
|
- [API documentation](http://www.rubydoc.info/gems/pundit)
|
|
15
|
-
- [Source Code](https://github.com/
|
|
16
|
-
- [Contributing](https://github.com/
|
|
17
|
-
- [Code of Conduct](https://github.com/
|
|
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)
|
|
18
18
|
|
|
19
19
|
Sponsored by:
|
|
20
20
|
|
|
21
|
-
[<img src="
|
|
21
|
+
[<img src="https://www.varvet.com/images/wordmark-red.svg" alt="Varvet" height="50px"/>](https://www.varvet.com)
|
|
22
22
|
|
|
23
23
|
## Installation
|
|
24
24
|
|
|
@@ -116,7 +116,9 @@ and the given record. It then infers from the action name, that it should call
|
|
|
116
116
|
`authorize` would have done something like this:
|
|
117
117
|
|
|
118
118
|
``` ruby
|
|
119
|
-
|
|
119
|
+
unless PostPolicy.new(current_user, @post).update?
|
|
120
|
+
raise Pundit::NotAuthorizedError, "not allowed to update? this #{@post.inspect}"
|
|
121
|
+
end
|
|
120
122
|
```
|
|
121
123
|
|
|
122
124
|
You can pass a second argument to `authorize` if the name of the permission you
|
|
@@ -131,6 +133,18 @@ def publish
|
|
|
131
133
|
end
|
|
132
134
|
```
|
|
133
135
|
|
|
136
|
+
You can pass an argument to override the policy class if necessary. For example:
|
|
137
|
+
|
|
138
|
+
```ruby
|
|
139
|
+
def create
|
|
140
|
+
@publication = find_publication # assume this method returns any model that behaves like a publication
|
|
141
|
+
# @publication.class => Post
|
|
142
|
+
authorize @publication, policy_class: PublicationPolicy
|
|
143
|
+
@publication.publish!
|
|
144
|
+
redirect_to @publication
|
|
145
|
+
end
|
|
146
|
+
```
|
|
147
|
+
|
|
134
148
|
If you don't have an instance for the first argument to `authorize`, then you can pass
|
|
135
149
|
the class. For example:
|
|
136
150
|
|
|
@@ -151,6 +165,15 @@ def admin_list
|
|
|
151
165
|
end
|
|
152
166
|
```
|
|
153
167
|
|
|
168
|
+
`authorize` returns the object passed to it, so you can chain it like this:
|
|
169
|
+
|
|
170
|
+
Controller:
|
|
171
|
+
```ruby
|
|
172
|
+
def show
|
|
173
|
+
@user = authorize User.find(params[:id])
|
|
174
|
+
end
|
|
175
|
+
```
|
|
176
|
+
|
|
154
177
|
You can easily get a hold of an instance of the policy through the `policy`
|
|
155
178
|
method in both the view and controller. This is especially useful for
|
|
156
179
|
conditionally showing links or buttons in the view:
|
|
@@ -172,6 +195,10 @@ class DashboardPolicy < Struct.new(:user, :dashboard)
|
|
|
172
195
|
end
|
|
173
196
|
```
|
|
174
197
|
|
|
198
|
+
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
|
|
200
|
+
is what is passed as the record to `authorize` below.
|
|
201
|
+
|
|
175
202
|
```ruby
|
|
176
203
|
# In controllers
|
|
177
204
|
authorize :dashboard, :show?
|
|
@@ -184,54 +211,6 @@ authorize :dashboard, :show?
|
|
|
184
211
|
<% end %>
|
|
185
212
|
```
|
|
186
213
|
|
|
187
|
-
## Ensuring policies are used
|
|
188
|
-
|
|
189
|
-
Pundit adds a method called `verify_authorized` to your controllers. This
|
|
190
|
-
method will raise an exception if `authorize` has not yet been called. You
|
|
191
|
-
should run this method in an `after_action` to ensure that you haven't
|
|
192
|
-
forgotten to authorize the action. For example:
|
|
193
|
-
|
|
194
|
-
``` ruby
|
|
195
|
-
class ApplicationController < ActionController::Base
|
|
196
|
-
after_action :verify_authorized
|
|
197
|
-
end
|
|
198
|
-
```
|
|
199
|
-
|
|
200
|
-
Likewise, Pundit also adds `verify_policy_scoped` to your controller. This
|
|
201
|
-
will raise an exception in the vein of `verify_authorized`. However, it tracks
|
|
202
|
-
if `policy_scope` is used instead of `authorize`. This is mostly useful for
|
|
203
|
-
controller actions like `index` which find collections with a scope and don't
|
|
204
|
-
authorize individual instances.
|
|
205
|
-
|
|
206
|
-
``` ruby
|
|
207
|
-
class ApplicationController < ActionController::Base
|
|
208
|
-
after_action :verify_authorized, except: :index
|
|
209
|
-
after_action :verify_policy_scoped, only: :index
|
|
210
|
-
end
|
|
211
|
-
```
|
|
212
|
-
|
|
213
|
-
If you're using `verify_authorized` in your controllers but need to
|
|
214
|
-
conditionally bypass verification, you can use `skip_authorization`. For
|
|
215
|
-
bypassing `verify_policy_scoped`, use `skip_policy_scope`. These are useful
|
|
216
|
-
in circumstances where you don't want to disable verification for the
|
|
217
|
-
entire action, but have some cases where you intend to not authorize.
|
|
218
|
-
|
|
219
|
-
```ruby
|
|
220
|
-
def show
|
|
221
|
-
record = Record.find_by(attribute: "value")
|
|
222
|
-
if record.present?
|
|
223
|
-
authorize record
|
|
224
|
-
else
|
|
225
|
-
skip_authorization
|
|
226
|
-
end
|
|
227
|
-
end
|
|
228
|
-
```
|
|
229
|
-
|
|
230
|
-
If you need to perform some more sophisticated logic or you want to raise a custom
|
|
231
|
-
exception you can use the two lower level methods `pundit_policy_authorized?`
|
|
232
|
-
and `pundit_policy_scoped?` which return `true` or `false` depending on whether
|
|
233
|
-
`authorize` or `policy_scope` have been called, respectively.
|
|
234
|
-
|
|
235
214
|
## Scopes
|
|
236
215
|
|
|
237
216
|
Often, you will want to have some kind of view listing records which a
|
|
@@ -258,7 +237,7 @@ class PostPolicy < ApplicationPolicy
|
|
|
258
237
|
end
|
|
259
238
|
|
|
260
239
|
def update?
|
|
261
|
-
user.admin? or not
|
|
240
|
+
user.admin? or not record.published?
|
|
262
241
|
end
|
|
263
242
|
end
|
|
264
243
|
```
|
|
@@ -291,7 +270,7 @@ class PostPolicy < ApplicationPolicy
|
|
|
291
270
|
end
|
|
292
271
|
|
|
293
272
|
def update?
|
|
294
|
-
user.admin? or not
|
|
273
|
+
user.admin? or not record.published?
|
|
295
274
|
end
|
|
296
275
|
end
|
|
297
276
|
```
|
|
@@ -302,6 +281,19 @@ You can now use this class from your controller via the `policy_scope` method:
|
|
|
302
281
|
def index
|
|
303
282
|
@posts = policy_scope(Post)
|
|
304
283
|
end
|
|
284
|
+
|
|
285
|
+
def show
|
|
286
|
+
@post = policy_scope(Post).find(params[:id])
|
|
287
|
+
end
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
Like with the authorize method, you can also override the policy scope class:
|
|
291
|
+
|
|
292
|
+
``` ruby
|
|
293
|
+
def index
|
|
294
|
+
# publication_class => Post
|
|
295
|
+
@publications = policy_scope(publication_class, policy_scope_class: PublicationPolicy::Scope)
|
|
296
|
+
end
|
|
305
297
|
```
|
|
306
298
|
|
|
307
299
|
Just as with your policy, this will automatically infer that you want to use
|
|
@@ -322,6 +314,70 @@ You can, and are encouraged to, use this method in views:
|
|
|
322
314
|
<% end %>
|
|
323
315
|
```
|
|
324
316
|
|
|
317
|
+
## Ensuring policies and scopes are used
|
|
318
|
+
|
|
319
|
+
When you are developing an application with Pundit it can be easy to forget to
|
|
320
|
+
authorize some action. People are forgetful after all. Since Pundit encourages
|
|
321
|
+
you to add the `authorize` call manually to each controller action, it's really
|
|
322
|
+
easy to miss one.
|
|
323
|
+
|
|
324
|
+
Thankfully, Pundit has a handy feature which reminds you in case you forget.
|
|
325
|
+
Pundit tracks whether you have called `authorize` anywhere in your controller
|
|
326
|
+
action. Pundit also adds a method to your controllers called
|
|
327
|
+
`verify_authorized`. This method will raise an exception if `authorize` has not
|
|
328
|
+
yet been called. You should run this method in an `after_action` hook to ensure
|
|
329
|
+
that you haven't forgotten to authorize the action. For example:
|
|
330
|
+
|
|
331
|
+
``` ruby
|
|
332
|
+
class ApplicationController < ActionController::Base
|
|
333
|
+
include Pundit
|
|
334
|
+
after_action :verify_authorized
|
|
335
|
+
end
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
Likewise, Pundit also adds `verify_policy_scoped` to your controller. This
|
|
339
|
+
will raise an exception similar to `verify_authorized`. However, it tracks
|
|
340
|
+
if `policy_scope` is used instead of `authorize`. This is mostly useful for
|
|
341
|
+
controller actions like `index` which find collections with a scope and don't
|
|
342
|
+
authorize individual instances.
|
|
343
|
+
|
|
344
|
+
``` ruby
|
|
345
|
+
class ApplicationController < ActionController::Base
|
|
346
|
+
include Pundit
|
|
347
|
+
after_action :verify_authorized, except: :index
|
|
348
|
+
after_action :verify_policy_scoped, only: :index
|
|
349
|
+
end
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
**This verification mechanism only exists to aid you while developing your
|
|
353
|
+
application, so you don't forget to call `authorize`. It is not some kind of
|
|
354
|
+
failsafe mechanism or authorization mechanism. You should be able to remove
|
|
355
|
+
these filters without affecting how your app works in any way.**
|
|
356
|
+
|
|
357
|
+
Some people have found this feature confusing, while many others
|
|
358
|
+
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
|
|
360
|
+
using `verify_authorized` and `verify_policy_scoped`.
|
|
361
|
+
|
|
362
|
+
### Conditional verification
|
|
363
|
+
|
|
364
|
+
If you're using `verify_authorized` in your controllers but need to
|
|
365
|
+
conditionally bypass verification, you can use `skip_authorization`. For
|
|
366
|
+
bypassing `verify_policy_scoped`, use `skip_policy_scope`. These are useful
|
|
367
|
+
in circumstances where you don't want to disable verification for the
|
|
368
|
+
entire action, but have some cases where you intend to not authorize.
|
|
369
|
+
|
|
370
|
+
```ruby
|
|
371
|
+
def show
|
|
372
|
+
record = Record.find_by(attribute: "value")
|
|
373
|
+
if record.present?
|
|
374
|
+
authorize record
|
|
375
|
+
else
|
|
376
|
+
skip_authorization
|
|
377
|
+
end
|
|
378
|
+
end
|
|
379
|
+
```
|
|
380
|
+
|
|
325
381
|
## Manually specifying policy classes
|
|
326
382
|
|
|
327
383
|
Sometimes you might want to explicitly declare which policy to use for a given
|
|
@@ -362,7 +418,8 @@ rails g pundit:policy post
|
|
|
362
418
|
|
|
363
419
|
In many applications, only logged in users are really able to do anything. If
|
|
364
420
|
you're building such a system, it can be kind of cumbersome to check that the
|
|
365
|
-
user in a policy isn't `nil` for every single permission.
|
|
421
|
+
user in a policy isn't `nil` for every single permission. Aside from policies,
|
|
422
|
+
you can add this check to the base class for scopes.
|
|
366
423
|
|
|
367
424
|
We suggest that you define a filter that redirects unauthenticated users to the
|
|
368
425
|
login page. As a secondary defence, if you've defined an ApplicationPolicy, it
|
|
@@ -376,6 +433,37 @@ class ApplicationPolicy
|
|
|
376
433
|
@user = user
|
|
377
434
|
@record = record
|
|
378
435
|
end
|
|
436
|
+
|
|
437
|
+
class Scope
|
|
438
|
+
attr_reader :user, :scope
|
|
439
|
+
|
|
440
|
+
def initialize(user, scope)
|
|
441
|
+
raise Pundit::NotAuthorizedError, "must be logged in" unless user
|
|
442
|
+
@user = user
|
|
443
|
+
@scope = scope
|
|
444
|
+
end
|
|
445
|
+
end
|
|
446
|
+
end
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
## NilClassPolicy
|
|
450
|
+
|
|
451
|
+
To support a [null object pattern](https://en.wikipedia.org/wiki/Null_Object_pattern)
|
|
452
|
+
you may find that you want to implement a `NilClassPolicy`. This might be useful
|
|
453
|
+
where you want to extend your ApplicationPolicy to allow some tolerance of, for
|
|
454
|
+
example, associations which might be `nil`.
|
|
455
|
+
|
|
456
|
+
```ruby
|
|
457
|
+
class NilClassPolicy < ApplicationPolicy
|
|
458
|
+
class Scope < Scope
|
|
459
|
+
def resolve
|
|
460
|
+
raise Pundit::NotDefinedError, "Cannot scope NilClass"
|
|
461
|
+
end
|
|
462
|
+
end
|
|
463
|
+
|
|
464
|
+
def show?
|
|
465
|
+
false # Nobody can see nothing
|
|
466
|
+
end
|
|
379
467
|
end
|
|
380
468
|
```
|
|
381
469
|
|
|
@@ -402,6 +490,10 @@ class ApplicationController < ActionController::Base
|
|
|
402
490
|
end
|
|
403
491
|
```
|
|
404
492
|
|
|
493
|
+
Alternatively, you can globally handle Pundit::NotAuthorizedError's by having rails handle them as a 403 error and serving a 403 error page. Add the following to application.rb:
|
|
494
|
+
|
|
495
|
+
```config.action_dispatch.rescue_responses["Pundit::NotAuthorizedError"] = :forbidden```
|
|
496
|
+
|
|
405
497
|
## Creating custom error messages
|
|
406
498
|
|
|
407
499
|
`NotAuthorizedError`s provide information on what query (e.g. `:create?`), what
|
|
@@ -469,6 +561,48 @@ def pundit_user
|
|
|
469
561
|
end
|
|
470
562
|
```
|
|
471
563
|
|
|
564
|
+
## Policy Namespacing
|
|
565
|
+
In some cases it might be helpful to have multiple policies that serve different contexts for a
|
|
566
|
+
resource. A prime example of this is the case where User policies differ from Admin policies. To
|
|
567
|
+
authorize with a namespaced policy, pass the namespace into the `authorize` helper in an array:
|
|
568
|
+
|
|
569
|
+
```ruby
|
|
570
|
+
authorize(post) # => will look for a PostPolicy
|
|
571
|
+
authorize([:admin, post]) # => will look for an Admin::PostPolicy
|
|
572
|
+
authorize([:foo, :bar, post]) # => will look for a Foo::Bar::PostPolicy
|
|
573
|
+
|
|
574
|
+
policy_scope(Post) # => will look for a PostPolicy::Scope
|
|
575
|
+
policy_scope([:admin, Post]) # => will look for an Admin::PostPolicy::Scope
|
|
576
|
+
policy_scope([:foo, :bar, Post]) # => will look for a Foo::Bar::PostPolicy::Scope
|
|
577
|
+
```
|
|
578
|
+
|
|
579
|
+
If you are using namespaced policies for something like Admin views, it can be useful to
|
|
580
|
+
override the `policy_scope` and `authorize` helpers in your `AdminController` to automatically
|
|
581
|
+
apply the namespacing:
|
|
582
|
+
|
|
583
|
+
```ruby
|
|
584
|
+
class AdminController < ApplicationController
|
|
585
|
+
def policy_scope(scope)
|
|
586
|
+
super([:admin, scope])
|
|
587
|
+
end
|
|
588
|
+
|
|
589
|
+
def authorize(record, query = nil)
|
|
590
|
+
super([:admin, record], query)
|
|
591
|
+
end
|
|
592
|
+
end
|
|
593
|
+
|
|
594
|
+
class Admin::PostController < AdminController
|
|
595
|
+
def index
|
|
596
|
+
policy_scope(Post)
|
|
597
|
+
end
|
|
598
|
+
|
|
599
|
+
def show
|
|
600
|
+
post = Post.find(params[:id])
|
|
601
|
+
authorize(post)
|
|
602
|
+
end
|
|
603
|
+
end
|
|
604
|
+
```
|
|
605
|
+
|
|
472
606
|
## Additional context
|
|
473
607
|
|
|
474
608
|
Pundit strongly encourages you to model your application in such a way that the
|
|
@@ -564,6 +698,45 @@ class PostsController < ApplicationController
|
|
|
564
698
|
end
|
|
565
699
|
```
|
|
566
700
|
|
|
701
|
+
If you want to permit different attributes based on the current action, you can define a `permitted_attributes_for_#{action}` method on your policy:
|
|
702
|
+
|
|
703
|
+
```ruby
|
|
704
|
+
# app/policies/post_policy.rb
|
|
705
|
+
class PostPolicy < ApplicationPolicy
|
|
706
|
+
def permitted_attributes_for_create
|
|
707
|
+
[:title, :body]
|
|
708
|
+
end
|
|
709
|
+
|
|
710
|
+
def permitted_attributes_for_edit
|
|
711
|
+
[:body]
|
|
712
|
+
end
|
|
713
|
+
end
|
|
714
|
+
```
|
|
715
|
+
|
|
716
|
+
If you have defined an action-specific method on your policy for the current action, the `permitted_attributes` helper will call it instead of calling `permitted_attributes` on your controller.
|
|
717
|
+
|
|
718
|
+
If you need to fetch parameters based on namespaces different from the suggested one, override the below method, in your controller, and return an instance of `ActionController::Parameters`.
|
|
719
|
+
|
|
720
|
+
```ruby
|
|
721
|
+
def pundit_params_for(record)
|
|
722
|
+
params.require(PolicyFinder.new(record).param_key)
|
|
723
|
+
end
|
|
724
|
+
```
|
|
725
|
+
|
|
726
|
+
For example:
|
|
727
|
+
|
|
728
|
+
```ruby
|
|
729
|
+
# If you don't want to use require
|
|
730
|
+
def pundit_params_for(record)
|
|
731
|
+
params.fetch(PolicyFinder.new(record).param_key, {})
|
|
732
|
+
end
|
|
733
|
+
|
|
734
|
+
# If you are using something like the JSON API spec
|
|
735
|
+
def pundit_params_for(_record)
|
|
736
|
+
params.fetch(:data, {}).fetch(:attributes, {})
|
|
737
|
+
end
|
|
738
|
+
```
|
|
739
|
+
|
|
567
740
|
## RSpec
|
|
568
741
|
|
|
569
742
|
### Policy Specs
|
|
@@ -600,14 +773,22 @@ end
|
|
|
600
773
|
An alternative approach to Pundit policy specs is scoping them to a user context as outlined in this
|
|
601
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.
|
|
602
775
|
|
|
776
|
+
### Scope Specs
|
|
777
|
+
|
|
778
|
+
Pundit does not provide a DSL for testing scopes. Just test it like a regular Ruby class!
|
|
779
|
+
|
|
603
780
|
# External Resources
|
|
604
781
|
|
|
605
782
|
- [RailsApps Example Application: Pundit and Devise](https://github.com/RailsApps/rails-devise-pundit)
|
|
606
783
|
- [Migrating to Pundit from CanCan](http://blog.carbonfive.com/2013/10/21/migrating-to-pundit-from-cancan/)
|
|
607
784
|
- [Testing Pundit Policies with RSpec](http://thunderboltlabs.com/blog/2013/03/27/testing-pundit-policies-with-rspec/)
|
|
608
|
-
- [Using Pundit outside of a Rails controller](https://github.com/
|
|
785
|
+
- [Using Pundit outside of a Rails controller](https://github.com/varvet/pundit/pull/136)
|
|
609
786
|
- [Straightforward Rails Authorization with Pundit](http://www.sitepoint.com/straightforward-rails-authorization-with-pundit/)
|
|
610
787
|
|
|
788
|
+
## Other implementations
|
|
789
|
+
|
|
790
|
+
- [Flask-Pundit](https://github.com/anurag90x/flask-pundit) (Python) is a [Flask](http://flask.pocoo.org/) extension "heavily inspired by" Pundit
|
|
791
|
+
|
|
611
792
|
# License
|
|
612
793
|
|
|
613
794
|
Licensed under the MIT license, see the separate LICENSE.txt file.
|