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/README.md
CHANGED
@@ -1,37 +1,38 @@
|
|
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
|
9
9
|
classes and object oriented design patterns to build a simple, robust and
|
10
|
-
|
10
|
+
scalable authorization system.
|
11
11
|
|
12
12
|
Links:
|
13
13
|
|
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/
|
14
|
+
- [API documentation for the most recent version](http://www.rubydoc.info/gems/pundit)
|
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
|
|
25
|
-
|
26
|
-
|
25
|
+
> **Please note** that the README on GitHub is accurate with the _latest code on GitHub_. You are most likely using a released version of Pundit, so please refer to the [documentation for the latest released version of Pundit](https://www.rubydoc.info/gems/pundit).
|
26
|
+
|
27
|
+
``` sh
|
28
|
+
bundle add pundit
|
27
29
|
```
|
28
30
|
|
29
|
-
Include Pundit in your application controller:
|
31
|
+
Include `Pundit::Authorization` in your application controller:
|
30
32
|
|
31
33
|
``` ruby
|
32
34
|
class ApplicationController < ActionController::Base
|
33
|
-
include Pundit
|
34
|
-
protect_from_forgery
|
35
|
+
include Pundit::Authorization
|
35
36
|
end
|
36
37
|
```
|
37
38
|
|
@@ -61,7 +62,7 @@ class PostPolicy
|
|
61
62
|
end
|
62
63
|
|
63
64
|
def update?
|
64
|
-
user.admin?
|
65
|
+
user.admin? || !post.published?
|
65
66
|
end
|
66
67
|
end
|
67
68
|
```
|
@@ -116,7 +117,9 @@ and the given record. It then infers from the action name, that it should call
|
|
116
117
|
`authorize` would have done something like this:
|
117
118
|
|
118
119
|
``` ruby
|
119
|
-
|
120
|
+
unless PostPolicy.new(current_user, @post).update?
|
121
|
+
raise Pundit::NotAuthorizedError, "not allowed to update? this #{@post.inspect}"
|
122
|
+
end
|
120
123
|
```
|
121
124
|
|
122
125
|
You can pass a second argument to `authorize` if the name of the permission you
|
@@ -131,6 +134,18 @@ def publish
|
|
131
134
|
end
|
132
135
|
```
|
133
136
|
|
137
|
+
You can pass an argument to override the policy class if necessary. For example:
|
138
|
+
|
139
|
+
```ruby
|
140
|
+
def create
|
141
|
+
@publication = find_publication # assume this method returns any model that behaves like a publication
|
142
|
+
# @publication.class => Post
|
143
|
+
authorize @publication, policy_class: PublicationPolicy
|
144
|
+
@publication.publish!
|
145
|
+
redirect_to @publication
|
146
|
+
end
|
147
|
+
```
|
148
|
+
|
134
149
|
If you don't have an instance for the first argument to `authorize`, then you can pass
|
135
150
|
the class. For example:
|
136
151
|
|
@@ -151,6 +166,20 @@ def admin_list
|
|
151
166
|
end
|
152
167
|
```
|
153
168
|
|
169
|
+
`authorize` returns the instance passed to it, so you can chain it like this:
|
170
|
+
|
171
|
+
Controller:
|
172
|
+
```ruby
|
173
|
+
def show
|
174
|
+
@user = authorize User.find(params[:id])
|
175
|
+
end
|
176
|
+
|
177
|
+
# return the record even for namespaced policies
|
178
|
+
def show
|
179
|
+
@user = authorize [:admin, User.find(params[:id])]
|
180
|
+
end
|
181
|
+
```
|
182
|
+
|
154
183
|
You can easily get a hold of an instance of the policy through the `policy`
|
155
184
|
method in both the view and controller. This is especially useful for
|
156
185
|
conditionally showing links or buttons in the view:
|
@@ -167,14 +196,30 @@ you can retrieve it by passing a symbol.
|
|
167
196
|
|
168
197
|
```ruby
|
169
198
|
# app/policies/dashboard_policy.rb
|
170
|
-
class DashboardPolicy
|
171
|
-
|
199
|
+
class DashboardPolicy
|
200
|
+
attr_reader :user
|
201
|
+
|
202
|
+
# _record in this example will just be :dashboard
|
203
|
+
def initialize(user, _record)
|
204
|
+
@user = user
|
205
|
+
end
|
206
|
+
|
207
|
+
def show?
|
208
|
+
user.admin?
|
209
|
+
end
|
172
210
|
end
|
173
211
|
```
|
174
212
|
|
213
|
+
Note that the headless policy still needs to accept two arguments. The
|
214
|
+
second argument will just be the symbol `:dashboard` in this case which
|
215
|
+
is what is passed as the record to `authorize` below.
|
216
|
+
|
175
217
|
```ruby
|
176
218
|
# In controllers
|
177
|
-
|
219
|
+
def show
|
220
|
+
authorize :dashboard, :show?
|
221
|
+
...
|
222
|
+
end
|
178
223
|
```
|
179
224
|
|
180
225
|
```erb
|
@@ -184,54 +229,6 @@ authorize :dashboard, :show?
|
|
184
229
|
<% end %>
|
185
230
|
```
|
186
231
|
|
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
232
|
## Scopes
|
236
233
|
|
237
234
|
Often, you will want to have some kind of view listing records which a
|
@@ -241,8 +238,6 @@ define a class called a policy scope. It can look something like this:
|
|
241
238
|
``` ruby
|
242
239
|
class PostPolicy < ApplicationPolicy
|
243
240
|
class Scope
|
244
|
-
attr_reader :user, :scope
|
245
|
-
|
246
241
|
def initialize(user, scope)
|
247
242
|
@user = user
|
248
243
|
@scope = scope
|
@@ -255,10 +250,14 @@ class PostPolicy < ApplicationPolicy
|
|
255
250
|
scope.where(published: true)
|
256
251
|
end
|
257
252
|
end
|
253
|
+
|
254
|
+
private
|
255
|
+
|
256
|
+
attr_reader :user, :scope
|
258
257
|
end
|
259
258
|
|
260
259
|
def update?
|
261
|
-
user.admin? or not
|
260
|
+
user.admin? or not record.published?
|
262
261
|
end
|
263
262
|
end
|
264
263
|
```
|
@@ -291,7 +290,7 @@ class PostPolicy < ApplicationPolicy
|
|
291
290
|
end
|
292
291
|
|
293
292
|
def update?
|
294
|
-
user.admin? or not
|
293
|
+
user.admin? or not record.published?
|
295
294
|
end
|
296
295
|
end
|
297
296
|
```
|
@@ -302,15 +301,26 @@ You can now use this class from your controller via the `policy_scope` method:
|
|
302
301
|
def index
|
303
302
|
@posts = policy_scope(Post)
|
304
303
|
end
|
304
|
+
|
305
|
+
def show
|
306
|
+
@post = policy_scope(Post).find(params[:id])
|
307
|
+
end
|
308
|
+
```
|
309
|
+
|
310
|
+
Like with the authorize method, you can also override the policy scope class:
|
311
|
+
|
312
|
+
``` ruby
|
313
|
+
def index
|
314
|
+
# publication_class => Post
|
315
|
+
@publications = policy_scope(publication_class, policy_scope_class: PublicationPolicy::Scope)
|
316
|
+
end
|
305
317
|
```
|
306
318
|
|
307
|
-
|
308
|
-
the `PostPolicy::Scope` class, it will instantiate this class and call
|
309
|
-
`resolve` on the instance. In this case it is a shortcut for doing:
|
319
|
+
In this case it is a shortcut for doing:
|
310
320
|
|
311
321
|
``` ruby
|
312
322
|
def index
|
313
|
-
@
|
323
|
+
@publications = PublicationPolicy::Scope.new(current_user, Post).resolve
|
314
324
|
end
|
315
325
|
```
|
316
326
|
|
@@ -322,6 +332,70 @@ You can, and are encouraged to, use this method in views:
|
|
322
332
|
<% end %>
|
323
333
|
```
|
324
334
|
|
335
|
+
## Ensuring policies and scopes are used
|
336
|
+
|
337
|
+
When you are developing an application with Pundit it can be easy to forget to
|
338
|
+
authorize some action. People are forgetful after all. Since Pundit encourages
|
339
|
+
you to add the `authorize` call manually to each controller action, it's really
|
340
|
+
easy to miss one.
|
341
|
+
|
342
|
+
Thankfully, Pundit has a handy feature which reminds you in case you forget.
|
343
|
+
Pundit tracks whether you have called `authorize` anywhere in your controller
|
344
|
+
action. Pundit also adds a method to your controllers called
|
345
|
+
`verify_authorized`. This method will raise an exception if `authorize` has not
|
346
|
+
yet been called. You should run this method in an `after_action` hook to ensure
|
347
|
+
that you haven't forgotten to authorize the action. For example:
|
348
|
+
|
349
|
+
``` ruby
|
350
|
+
class ApplicationController < ActionController::Base
|
351
|
+
include Pundit::Authorization
|
352
|
+
after_action :verify_authorized
|
353
|
+
end
|
354
|
+
```
|
355
|
+
|
356
|
+
Likewise, Pundit also adds `verify_policy_scoped` to your controller. This
|
357
|
+
will raise an exception similar to `verify_authorized`. However, it tracks
|
358
|
+
if `policy_scope` is used instead of `authorize`. This is mostly useful for
|
359
|
+
controller actions like `index` which find collections with a scope and don't
|
360
|
+
authorize individual instances.
|
361
|
+
|
362
|
+
``` ruby
|
363
|
+
class ApplicationController < ActionController::Base
|
364
|
+
include Pundit::Authorization
|
365
|
+
after_action :verify_authorized, except: :index
|
366
|
+
after_action :verify_policy_scoped, only: :index
|
367
|
+
end
|
368
|
+
```
|
369
|
+
|
370
|
+
**This verification mechanism only exists to aid you while developing your
|
371
|
+
application, so you don't forget to call `authorize`. It is not some kind of
|
372
|
+
failsafe mechanism or authorization mechanism. You should be able to remove
|
373
|
+
these filters without affecting how your app works in any way.**
|
374
|
+
|
375
|
+
Some people have found this feature confusing, while many others
|
376
|
+
find it extremely helpful. If you fall into the category of people who find it
|
377
|
+
confusing then you do not need to use it. Pundit will work just fine without
|
378
|
+
using `verify_authorized` and `verify_policy_scoped`.
|
379
|
+
|
380
|
+
### Conditional verification
|
381
|
+
|
382
|
+
If you're using `verify_authorized` in your controllers but need to
|
383
|
+
conditionally bypass verification, you can use `skip_authorization`. For
|
384
|
+
bypassing `verify_policy_scoped`, use `skip_policy_scope`. These are useful
|
385
|
+
in circumstances where you don't want to disable verification for the
|
386
|
+
entire action, but have some cases where you intend to not authorize.
|
387
|
+
|
388
|
+
```ruby
|
389
|
+
def show
|
390
|
+
record = Record.find_by(attribute: "value")
|
391
|
+
if record.present?
|
392
|
+
authorize record
|
393
|
+
else
|
394
|
+
skip_authorization
|
395
|
+
end
|
396
|
+
end
|
397
|
+
```
|
398
|
+
|
325
399
|
## Manually specifying policy classes
|
326
400
|
|
327
401
|
Sometimes you might want to explicitly declare which policy to use for a given
|
@@ -335,6 +409,16 @@ class Post
|
|
335
409
|
end
|
336
410
|
```
|
337
411
|
|
412
|
+
Alternatively, you can declare an instance method:
|
413
|
+
|
414
|
+
``` ruby
|
415
|
+
class Post
|
416
|
+
def policy_class
|
417
|
+
PostablePolicy
|
418
|
+
end
|
419
|
+
end
|
420
|
+
```
|
421
|
+
|
338
422
|
## Just plain old Ruby
|
339
423
|
|
340
424
|
As you can see, Pundit doesn't do anything you couldn't have easily done
|
@@ -362,7 +446,8 @@ rails g pundit:policy post
|
|
362
446
|
|
363
447
|
In many applications, only logged in users are really able to do anything. If
|
364
448
|
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.
|
449
|
+
user in a policy isn't `nil` for every single permission. Aside from policies,
|
450
|
+
you can add this check to the base class for scopes.
|
366
451
|
|
367
452
|
We suggest that you define a filter that redirects unauthenticated users to the
|
368
453
|
login page. As a secondary defence, if you've defined an ApplicationPolicy, it
|
@@ -376,6 +461,37 @@ class ApplicationPolicy
|
|
376
461
|
@user = user
|
377
462
|
@record = record
|
378
463
|
end
|
464
|
+
|
465
|
+
class Scope
|
466
|
+
attr_reader :user, :scope
|
467
|
+
|
468
|
+
def initialize(user, scope)
|
469
|
+
raise Pundit::NotAuthorizedError, "must be logged in" unless user
|
470
|
+
@user = user
|
471
|
+
@scope = scope
|
472
|
+
end
|
473
|
+
end
|
474
|
+
end
|
475
|
+
```
|
476
|
+
|
477
|
+
## NilClassPolicy
|
478
|
+
|
479
|
+
To support a [null object pattern](https://en.wikipedia.org/wiki/Null_Object_pattern)
|
480
|
+
you may find that you want to implement a `NilClassPolicy`. This might be useful
|
481
|
+
where you want to extend your ApplicationPolicy to allow some tolerance of, for
|
482
|
+
example, associations which might be `nil`.
|
483
|
+
|
484
|
+
```ruby
|
485
|
+
class NilClassPolicy < ApplicationPolicy
|
486
|
+
class Scope < Scope
|
487
|
+
def resolve
|
488
|
+
raise Pundit::NotDefinedError, "Cannot scope NilClass"
|
489
|
+
end
|
490
|
+
end
|
491
|
+
|
492
|
+
def show?
|
493
|
+
false # Nobody can see nothing
|
494
|
+
end
|
379
495
|
end
|
380
496
|
```
|
381
497
|
|
@@ -388,8 +504,7 @@ method in every controller.
|
|
388
504
|
|
389
505
|
```ruby
|
390
506
|
class ApplicationController < ActionController::Base
|
391
|
-
|
392
|
-
include Pundit
|
507
|
+
include Pundit::Authorization
|
393
508
|
|
394
509
|
rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized
|
395
510
|
|
@@ -397,11 +512,15 @@ class ApplicationController < ActionController::Base
|
|
397
512
|
|
398
513
|
def user_not_authorized
|
399
514
|
flash[:alert] = "You are not authorized to perform this action."
|
400
|
-
|
515
|
+
redirect_back(fallback_location: root_path)
|
401
516
|
end
|
402
517
|
end
|
403
518
|
```
|
404
519
|
|
520
|
+
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:
|
521
|
+
|
522
|
+
```config.action_dispatch.rescue_responses["Pundit::NotAuthorizedError"] = :forbidden```
|
523
|
+
|
405
524
|
## Creating custom error messages
|
406
525
|
|
407
526
|
`NotAuthorizedError`s provide information on what query (e.g. `:create?`), what
|
@@ -422,7 +541,7 @@ class ApplicationController < ActionController::Base
|
|
422
541
|
policy_name = exception.policy.class.to_s.underscore
|
423
542
|
|
424
543
|
flash[:error] = t "#{policy_name}.#{exception.query}", scope: "pundit", default: :default
|
425
|
-
|
544
|
+
redirect_back(fallback_url: root_path)
|
426
545
|
end
|
427
546
|
end
|
428
547
|
```
|
@@ -469,6 +588,47 @@ def pundit_user
|
|
469
588
|
end
|
470
589
|
```
|
471
590
|
|
591
|
+
## Policy Namespacing
|
592
|
+
In some cases it might be helpful to have multiple policies that serve different contexts for a
|
593
|
+
resource. A prime example of this is the case where User policies differ from Admin policies. To
|
594
|
+
authorize with a namespaced policy, pass the namespace into the `authorize` helper in an array:
|
595
|
+
|
596
|
+
```ruby
|
597
|
+
authorize(post) # => will look for a PostPolicy
|
598
|
+
authorize([:admin, post]) # => will look for an Admin::PostPolicy
|
599
|
+
authorize([:foo, :bar, post]) # => will look for a Foo::Bar::PostPolicy
|
600
|
+
|
601
|
+
policy_scope(Post) # => will look for a PostPolicy::Scope
|
602
|
+
policy_scope([:admin, Post]) # => will look for an Admin::PostPolicy::Scope
|
603
|
+
policy_scope([:foo, :bar, Post]) # => will look for a Foo::Bar::PostPolicy::Scope
|
604
|
+
```
|
605
|
+
|
606
|
+
If you are using namespaced policies for something like Admin views, it can be useful to
|
607
|
+
override the `policy_scope` and `authorize` helpers in your `AdminController` to automatically
|
608
|
+
apply the namespacing:
|
609
|
+
|
610
|
+
```ruby
|
611
|
+
class AdminController < ApplicationController
|
612
|
+
def policy_scope(scope)
|
613
|
+
super([:admin, scope])
|
614
|
+
end
|
615
|
+
|
616
|
+
def authorize(record, query = nil)
|
617
|
+
super([:admin, record], query)
|
618
|
+
end
|
619
|
+
end
|
620
|
+
|
621
|
+
class Admin::PostController < AdminController
|
622
|
+
def index
|
623
|
+
policy_scope(Post)
|
624
|
+
end
|
625
|
+
|
626
|
+
def show
|
627
|
+
post = authorize Post.find(params[:id])
|
628
|
+
end
|
629
|
+
end
|
630
|
+
```
|
631
|
+
|
472
632
|
## Additional context
|
473
633
|
|
474
634
|
Pundit strongly encourages you to model your application in such a way that the
|
@@ -497,7 +657,7 @@ class UserContext
|
|
497
657
|
end
|
498
658
|
|
499
659
|
class ApplicationController
|
500
|
-
include Pundit
|
660
|
+
include Pundit::Authorization
|
501
661
|
|
502
662
|
def pundit_user
|
503
663
|
UserContext.new(current_user, request.ip)
|
@@ -507,9 +667,8 @@ end
|
|
507
667
|
|
508
668
|
## Strong parameters
|
509
669
|
|
510
|
-
In Rails
|
511
|
-
|
512
|
-
mass-assignment protection is handled in the controller. With Pundit you can
|
670
|
+
In Rails,
|
671
|
+
mass-assignment protection is handled in the controller. With Pundit you can
|
513
672
|
control which attributes a user has access to update via your policies. You can
|
514
673
|
set up a `permitted_attributes` method in your policy like this:
|
515
674
|
|
@@ -533,7 +692,7 @@ You can now retrieve these attributes from the policy:
|
|
533
692
|
class PostsController < ApplicationController
|
534
693
|
def update
|
535
694
|
@post = Post.find(params[:id])
|
536
|
-
if @post.
|
695
|
+
if @post.update(post_params)
|
537
696
|
redirect_to @post
|
538
697
|
else
|
539
698
|
render :edit
|
@@ -555,7 +714,7 @@ However, this is a bit cumbersome, so Pundit provides a convenient helper method
|
|
555
714
|
class PostsController < ApplicationController
|
556
715
|
def update
|
557
716
|
@post = Post.find(params[:id])
|
558
|
-
if @post.
|
717
|
+
if @post.update(permitted_attributes(@post))
|
559
718
|
redirect_to @post
|
560
719
|
else
|
561
720
|
render :edit
|
@@ -564,6 +723,45 @@ class PostsController < ApplicationController
|
|
564
723
|
end
|
565
724
|
```
|
566
725
|
|
726
|
+
If you want to permit different attributes based on the current action, you can define a `permitted_attributes_for_#{action}` method on your policy:
|
727
|
+
|
728
|
+
```ruby
|
729
|
+
# app/policies/post_policy.rb
|
730
|
+
class PostPolicy < ApplicationPolicy
|
731
|
+
def permitted_attributes_for_create
|
732
|
+
[:title, :body]
|
733
|
+
end
|
734
|
+
|
735
|
+
def permitted_attributes_for_edit
|
736
|
+
[:body]
|
737
|
+
end
|
738
|
+
end
|
739
|
+
```
|
740
|
+
|
741
|
+
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.
|
742
|
+
|
743
|
+
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`.
|
744
|
+
|
745
|
+
```ruby
|
746
|
+
def pundit_params_for(record)
|
747
|
+
params.require(PolicyFinder.new(record).param_key)
|
748
|
+
end
|
749
|
+
```
|
750
|
+
|
751
|
+
For example:
|
752
|
+
|
753
|
+
```ruby
|
754
|
+
# If you don't want to use require
|
755
|
+
def pundit_params_for(record)
|
756
|
+
params.fetch(PolicyFinder.new(record).param_key, {})
|
757
|
+
end
|
758
|
+
|
759
|
+
# If you are using something like the JSON API spec
|
760
|
+
def pundit_params_for(_record)
|
761
|
+
params.fetch(:data, {}).fetch(:attributes, {})
|
762
|
+
end
|
763
|
+
```
|
764
|
+
|
567
765
|
## RSpec
|
568
766
|
|
569
767
|
### Policy Specs
|
@@ -600,14 +798,33 @@ end
|
|
600
798
|
An alternative approach to Pundit policy specs is scoping them to a user context as outlined in this
|
601
799
|
[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
800
|
|
801
|
+
### Scope Specs
|
802
|
+
|
803
|
+
Pundit does not provide a DSL for testing scopes. Just test it like a regular Ruby class!
|
804
|
+
|
805
|
+
### Linting with RuboCop RSpec
|
806
|
+
|
807
|
+
When you lint your RSpec spec files with `rubocop-rspec`, it will fail to properly detect RSpec constructs that Pundit defines, `permissions`.
|
808
|
+
Make sure to use `rubocop-rspec` 2.0 or newer and add the following to your `.rubocop.yml`:
|
809
|
+
|
810
|
+
```yaml
|
811
|
+
inherit_gem:
|
812
|
+
pundit: config/rubocop-rspec.yml
|
813
|
+
```
|
814
|
+
|
603
815
|
# External Resources
|
604
816
|
|
605
817
|
- [RailsApps Example Application: Pundit and Devise](https://github.com/RailsApps/rails-devise-pundit)
|
606
818
|
- [Migrating to Pundit from CanCan](http://blog.carbonfive.com/2013/10/21/migrating-to-pundit-from-cancan/)
|
607
819
|
- [Testing Pundit Policies with RSpec](http://thunderboltlabs.com/blog/2013/03/27/testing-pundit-policies-with-rspec/)
|
608
|
-
- [
|
820
|
+
- [Testing Pundit with Minitest](https://github.com/varvet/pundit/issues/204#issuecomment-60166450)
|
821
|
+
- [Using Pundit outside of a Rails controller](https://github.com/varvet/pundit/pull/136)
|
609
822
|
- [Straightforward Rails Authorization with Pundit](http://www.sitepoint.com/straightforward-rails-authorization-with-pundit/)
|
610
823
|
|
824
|
+
## Other implementations
|
825
|
+
|
826
|
+
- [Flask-Pundit](https://github.com/anurag90x/flask-pundit) (Python) is a [Flask](http://flask.pocoo.org/) extension "heavily inspired by" Pundit
|
827
|
+
|
611
828
|
# License
|
612
829
|
|
613
830
|
Licensed under the MIT license, see the separate LICENSE.txt file.
|
data/Rakefile
CHANGED
@@ -1,10 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Pundit
|
2
4
|
module Generators
|
3
5
|
class InstallGenerator < ::Rails::Generators::Base
|
4
|
-
source_root File.expand_path(
|
6
|
+
source_root File.expand_path("templates", __dir__)
|
5
7
|
|
6
8
|
def copy_application_policy
|
7
|
-
template
|
9
|
+
template "application_policy.rb", "app/policies/application_policy.rb"
|
8
10
|
end
|
9
11
|
end
|
10
12
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
class ApplicationPolicy
|
2
4
|
attr_reader :user, :record
|
3
5
|
|
@@ -11,7 +13,7 @@ class ApplicationPolicy
|
|
11
13
|
end
|
12
14
|
|
13
15
|
def show?
|
14
|
-
|
16
|
+
false
|
15
17
|
end
|
16
18
|
|
17
19
|
def create?
|
@@ -34,20 +36,18 @@ class ApplicationPolicy
|
|
34
36
|
false
|
35
37
|
end
|
36
38
|
|
37
|
-
def scope
|
38
|
-
Pundit.policy_scope!(user, record.class)
|
39
|
-
end
|
40
|
-
|
41
39
|
class Scope
|
42
|
-
attr_reader :user, :scope
|
43
|
-
|
44
40
|
def initialize(user, scope)
|
45
41
|
@user = user
|
46
42
|
@scope = scope
|
47
43
|
end
|
48
44
|
|
49
45
|
def resolve
|
50
|
-
|
46
|
+
raise NotImplementedError, "You must define #resolve in #{self.class}"
|
51
47
|
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
attr_reader :user, :scope
|
52
52
|
end
|
53
53
|
end
|
@@ -1,10 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Pundit
|
2
4
|
module Generators
|
3
5
|
class PolicyGenerator < ::Rails::Generators::NamedBase
|
4
|
-
source_root File.expand_path(
|
6
|
+
source_root File.expand_path("templates", __dir__)
|
5
7
|
|
6
8
|
def create_policy
|
7
|
-
template
|
9
|
+
template "policy.rb", File.join("app/policies", class_path, "#{file_name}_policy.rb")
|
8
10
|
end
|
9
11
|
|
10
12
|
hook_for :test_framework
|