piko-pro-gem 0.0.1

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.
Files changed (31) hide show
  1. checksums.yaml +7 -0
  2. data/piko-pro-gem.gemspec +12 -0
  3. data/pundit-2.5.2/CHANGELOG.md +196 -0
  4. data/pundit-2.5.2/CONTRIBUTING.md +31 -0
  5. data/pundit-2.5.2/LICENSE.txt +22 -0
  6. data/pundit-2.5.2/README.md +891 -0
  7. data/pundit-2.5.2/SECURITY.md +19 -0
  8. data/pundit-2.5.2/config/rubocop-rspec.yml +5 -0
  9. data/pundit-2.5.2/lib/generators/pundit/install/USAGE +2 -0
  10. data/pundit-2.5.2/lib/generators/pundit/install/install_generator.rb +15 -0
  11. data/pundit-2.5.2/lib/generators/pundit/install/templates/application_policy.rb.tt +53 -0
  12. data/pundit-2.5.2/lib/generators/pundit/policy/USAGE +8 -0
  13. data/pundit-2.5.2/lib/generators/pundit/policy/policy_generator.rb +17 -0
  14. data/pundit-2.5.2/lib/generators/pundit/policy/templates/policy.rb.tt +16 -0
  15. data/pundit-2.5.2/lib/generators/rspec/policy_generator.rb +16 -0
  16. data/pundit-2.5.2/lib/generators/rspec/templates/policy_spec.rb.tt +27 -0
  17. data/pundit-2.5.2/lib/generators/test_unit/policy_generator.rb +16 -0
  18. data/pundit-2.5.2/lib/generators/test_unit/templates/policy_test.rb.tt +18 -0
  19. data/pundit-2.5.2/lib/pundit/authorization.rb +269 -0
  20. data/pundit-2.5.2/lib/pundit/cache_store/legacy_store.rb +27 -0
  21. data/pundit-2.5.2/lib/pundit/cache_store/null_store.rb +30 -0
  22. data/pundit-2.5.2/lib/pundit/cache_store.rb +24 -0
  23. data/pundit-2.5.2/lib/pundit/context.rb +190 -0
  24. data/pundit-2.5.2/lib/pundit/error.rb +71 -0
  25. data/pundit-2.5.2/lib/pundit/helper.rb +16 -0
  26. data/pundit-2.5.2/lib/pundit/policy_finder.rb +135 -0
  27. data/pundit-2.5.2/lib/pundit/railtie.rb +20 -0
  28. data/pundit-2.5.2/lib/pundit/rspec.rb +165 -0
  29. data/pundit-2.5.2/lib/pundit/version.rb +6 -0
  30. data/pundit-2.5.2/lib/pundit.rb +77 -0
  31. metadata +70 -0
@@ -0,0 +1,891 @@
1
+ # Pundit
2
+
3
+ [![Main](https://github.com/varvet/pundit/actions/workflows/main.yml/badge.svg)](https://github.com/varvet/pundit/actions/workflows/main.yml)
4
+ [![Maintainability](https://qlty.sh/gh/varvet/projects/pundit/maintainability.svg)](https://qlty.sh/gh/varvet/projects/pundit)
5
+ [![Inline docs](https://inch-ci.org/github/varvet/pundit.svg?branch=main)](https://inch-ci.org/github/varvet/pundit)
6
+ [![Gem Version](https://badge.fury.io/rb/pundit.svg)](https://badge.fury.io/rb/pundit)
7
+
8
+ Pundit provides a set of helpers which guide you in leveraging regular Ruby
9
+ classes and object oriented design patterns to build a straightforward, robust, and
10
+ scalable authorization system.
11
+
12
+ ## Links:
13
+
14
+ - [API documentation for the most recent version](https://www.rubydoc.info/gems/pundit)
15
+ - [Source Code](https://github.com/varvet/pundit)
16
+ - [Contributing](https://github.com/varvet/pundit/blob/main/CONTRIBUTING.md)
17
+ - [Code of Conduct](https://github.com/varvet/pundit/blob/main/CODE_OF_CONDUCT.md)
18
+
19
+ <strong>Sponsored by:</strong> <a href="https://www.varvet.com">Varvet<br><br><img src="https://github.com/varvet/pundit/assets/99166/aa9efa0a-6903-4037-abee-1824edc57f1a" alt="Varvet logo" height="120"></div>
20
+
21
+ ## Installation
22
+
23
+ > **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).
24
+
25
+ ``` sh
26
+ bundle add pundit
27
+ ```
28
+
29
+ Include `Pundit::Authorization` in your application controller:
30
+
31
+ ``` ruby
32
+ class ApplicationController < ActionController::Base
33
+ include Pundit::Authorization
34
+ end
35
+ ```
36
+
37
+ Optionally, you can run the generator, which will set up an application policy
38
+ with some useful defaults for you:
39
+
40
+ ``` sh
41
+ rails g pundit:install
42
+ ```
43
+
44
+ After generating your application policy, restart the Rails server so that Rails
45
+ can pick up any classes in the new `app/policies/` directory.
46
+
47
+ ## Policies
48
+
49
+ Pundit is focused around the notion of policy classes. We suggest that you put
50
+ these classes in `app/policies`. This is an example that allows updating a post
51
+ if the user is an admin, or if the post is unpublished:
52
+
53
+ ``` ruby
54
+ class PostPolicy
55
+ attr_reader :user, :post
56
+
57
+ def initialize(user, post)
58
+ @user = user
59
+ @post = post
60
+ end
61
+
62
+ def update?
63
+ user.admin? || !post.published?
64
+ end
65
+ end
66
+ ```
67
+
68
+ As you can see, this is a plain Ruby class. Pundit makes the following
69
+ assumptions about this class:
70
+
71
+ - The class has the same name as some kind of model class, only suffixed
72
+ with the word "Policy".
73
+ - The first argument is a user. In your controller, Pundit will call the
74
+ `current_user` method to retrieve what to send into this argument
75
+ - The second argument is some kind of model object, whose authorization
76
+ you want to check. This does not need to be an ActiveRecord or even
77
+ an ActiveModel object, it can be anything really.
78
+ - The class implements some kind of query method, in this case `update?`.
79
+ Usually, this will map to the name of a particular controller action.
80
+
81
+ That's it really.
82
+
83
+ Usually you'll want to inherit from the application policy created by the
84
+ generator, or set up your own base class to inherit from:
85
+
86
+ ``` ruby
87
+ class PostPolicy < ApplicationPolicy
88
+ def update?
89
+ user.admin? or not record.published?
90
+ end
91
+ end
92
+ ```
93
+
94
+ In the generated `ApplicationPolicy`, the model object is called `record`.
95
+
96
+ Supposing that you have an instance of class `Post`, Pundit now lets you do
97
+ this in your controller:
98
+
99
+ ``` ruby
100
+ def update
101
+ @post = Post.find(params[:id])
102
+ authorize @post
103
+ if @post.update(post_params)
104
+ redirect_to @post
105
+ else
106
+ render :edit
107
+ end
108
+ end
109
+ ```
110
+
111
+ The authorize method automatically infers that `Post` will have a matching
112
+ `PostPolicy` class, and instantiates this class, handing in the current user
113
+ and the given record. It then infers from the action name, that it should call
114
+ `update?` on this instance of the policy. In this case, you can imagine that
115
+ `authorize` would have done something like this:
116
+
117
+ ``` ruby
118
+ unless PostPolicy.new(current_user, @post).update?
119
+ raise Pundit::NotAuthorizedError, "not allowed to PostPolicy#update? this Post"
120
+ end
121
+ ```
122
+
123
+ You can pass a second argument to `authorize` if the name of the permission you
124
+ want to check doesn't match the action name. For example:
125
+
126
+ ``` ruby
127
+ def publish
128
+ @post = Post.find(params[:id])
129
+ authorize @post, :update?
130
+ @post.publish!
131
+ redirect_to @post
132
+ end
133
+ ```
134
+
135
+ You can pass an argument to override the policy class if necessary. For example:
136
+
137
+ ```ruby
138
+ def create
139
+ @publication = find_publication # assume this method returns any model that behaves like a publication
140
+ # @publication.class => Post
141
+ authorize @publication, policy_class: PublicationPolicy
142
+ @publication.publish!
143
+ redirect_to @publication
144
+ end
145
+ ```
146
+
147
+ If you don't have an instance for the first argument to `authorize`, then you can pass
148
+ the class. For example:
149
+
150
+ Policy:
151
+ ```ruby
152
+ class PostPolicy < ApplicationPolicy
153
+ def admin_list?
154
+ user.admin?
155
+ end
156
+ end
157
+ ```
158
+
159
+ Controller:
160
+ ```ruby
161
+ def admin_list
162
+ authorize Post # we don't have a particular post to authorize
163
+ # Rest of controller action
164
+ end
165
+ ```
166
+
167
+ `authorize` returns the instance passed to it, so you can chain it like this:
168
+
169
+ Controller:
170
+ ```ruby
171
+ def show
172
+ @user = authorize User.find(params[:id])
173
+ end
174
+
175
+ # return the record even for namespaced policies
176
+ def show
177
+ @user = authorize [:admin, User.find(params[:id])]
178
+ end
179
+ ```
180
+
181
+ You can easily get a hold of an instance of the policy through the `policy`
182
+ method in both the view and controller. This is especially useful for
183
+ conditionally showing links or buttons in the view:
184
+
185
+ ``` erb
186
+ <% if policy(@post).update? %>
187
+ <%= link_to "Edit post", edit_post_path(@post) %>
188
+ <% end %>
189
+ ```
190
+ ## Headless policies
191
+
192
+ Given there is a policy without a corresponding model / ruby class,
193
+ you can retrieve it by passing a symbol.
194
+
195
+ ```ruby
196
+ # app/policies/dashboard_policy.rb
197
+ class DashboardPolicy
198
+ attr_reader :user
199
+
200
+ # `_record` in this example will be :dashboard
201
+ def initialize(user, _record)
202
+ @user = user
203
+ end
204
+
205
+ def show?
206
+ user.admin?
207
+ end
208
+ end
209
+ ```
210
+
211
+ Note that the headless policy still needs to accept two arguments. The
212
+ second argument will be the symbol `:dashboard` in this case, which
213
+ is what is passed as the record to `authorize` below.
214
+
215
+ ```ruby
216
+ # In controllers
217
+ def show
218
+ authorize :dashboard, :show?
219
+ ...
220
+ end
221
+ ```
222
+
223
+ ```erb
224
+ # In views
225
+ <% if policy(:dashboard).show? %>
226
+ <%= link_to 'Dashboard', dashboard_path %>
227
+ <% end %>
228
+ ```
229
+
230
+ ## Scopes
231
+
232
+ Often, you will want to have some kind of view listing records which a
233
+ particular user has access to. When using Pundit, you are expected to
234
+ define a class called a policy scope. It can look something like this:
235
+
236
+ ``` ruby
237
+ class PostPolicy < ApplicationPolicy
238
+ class Scope
239
+ def initialize(user, scope)
240
+ @user = user
241
+ @scope = scope
242
+ end
243
+
244
+ def resolve
245
+ if user.admin?
246
+ scope.all
247
+ else
248
+ scope.where(published: true)
249
+ end
250
+ end
251
+
252
+ private
253
+
254
+ attr_reader :user, :scope
255
+ end
256
+
257
+ def update?
258
+ user.admin? or not record.published?
259
+ end
260
+ end
261
+ ```
262
+
263
+ Pundit makes the following assumptions about this class:
264
+
265
+ - The class has the name `Scope` and is nested under the policy class.
266
+ - The first argument is a user. In your controller, Pundit will call the
267
+ `current_user` method to retrieve what to send into this argument.
268
+ - The second argument is a scope of some kind on which to perform some kind of
269
+ query. It will usually be an ActiveRecord class or a
270
+ `ActiveRecord::Relation`, but it could be something else entirely.
271
+ - Instances of this class respond to the method `resolve`, which should return
272
+ some kind of result which can be iterated over. For ActiveRecord classes,
273
+ this would usually be an `ActiveRecord::Relation`.
274
+
275
+ You'll probably want to inherit from the application policy scope generated by the
276
+ generator, or create your own base class to inherit from:
277
+
278
+ ``` ruby
279
+ class PostPolicy < ApplicationPolicy
280
+ class Scope < ApplicationPolicy::Scope
281
+ def resolve
282
+ if user.admin?
283
+ scope.all
284
+ else
285
+ scope.where(published: true)
286
+ end
287
+ end
288
+ end
289
+
290
+ def update?
291
+ user.admin? or not record.published?
292
+ end
293
+ end
294
+ ```
295
+
296
+ You can now use this class from your controller via the `policy_scope` method:
297
+
298
+ ``` ruby
299
+ def index
300
+ @posts = policy_scope(Post)
301
+ end
302
+
303
+ def show
304
+ @post = policy_scope(Post).find(params[:id])
305
+ end
306
+ ```
307
+
308
+ Like with the authorize method, you can also override the policy scope class:
309
+
310
+ ``` ruby
311
+ def index
312
+ # publication_class => Post
313
+ @publications = policy_scope(publication_class, policy_scope_class: PublicationPolicy::Scope)
314
+ end
315
+ ```
316
+
317
+ In this case it is a shortcut for doing:
318
+
319
+ ``` ruby
320
+ def index
321
+ @publications = PublicationPolicy::Scope.new(current_user, Post).resolve
322
+ end
323
+ ```
324
+
325
+ You can, and are encouraged to, use this method in views:
326
+
327
+ ``` erb
328
+ <% policy_scope(@user.posts).each do |post| %>
329
+ <p><%= link_to post.title, post_path(post) %></p>
330
+ <% end %>
331
+ ```
332
+
333
+ ## Ensuring policies and scopes are used
334
+
335
+ When you are developing an application with Pundit it can be easy to forget to
336
+ authorize some action. People are forgetful after all. Since Pundit encourages
337
+ you to add the `authorize` call manually to each controller action, it's really
338
+ easy to miss one.
339
+
340
+ Thankfully, Pundit has a handy feature which reminds you in case you forget.
341
+ Pundit tracks whether you have called `authorize` anywhere in your controller
342
+ action. Pundit also adds a method to your controllers called
343
+ `verify_authorized`. This method will raise an exception if `authorize` has not
344
+ yet been called. You should run this method in an `after_action` hook to ensure
345
+ that you haven't forgotten to authorize the action. For example:
346
+
347
+ ``` ruby
348
+ class ApplicationController < ActionController::Base
349
+ include Pundit::Authorization
350
+ after_action :verify_authorized
351
+ end
352
+ ```
353
+
354
+ Likewise, Pundit also adds `verify_policy_scoped` to your controller. This
355
+ will raise an exception similar to `verify_authorized`. However, it tracks
356
+ if `policy_scope` is used instead of `authorize`. This is mostly useful for
357
+ controller actions like `index` which find collections with a scope and don't
358
+ authorize individual instances.
359
+
360
+ ``` ruby
361
+ class ApplicationController < ActionController::Base
362
+ include Pundit::Authorization
363
+ after_action :verify_pundit_authorization
364
+
365
+ def verify_pundit_authorization
366
+ if action_name == "index"
367
+ verify_policy_scoped
368
+ else
369
+ verify_authorized
370
+ end
371
+ end
372
+ end
373
+ ```
374
+
375
+ **This verification mechanism only exists to aid you while developing your
376
+ application, so you don't forget to call `authorize`. It is not some kind of
377
+ failsafe mechanism or authorization mechanism. You should be able to remove
378
+ these filters without affecting how your app works in any way.**
379
+
380
+ Some people have found this feature confusing, while many others
381
+ find it extremely helpful. If you fall into the category of people who find it
382
+ confusing then you do not need to use it. Pundit will work fine without
383
+ using `verify_authorized` and `verify_policy_scoped`.
384
+
385
+ ### Conditional verification
386
+
387
+ If you're using `verify_authorized` in your controllers but need to
388
+ conditionally bypass verification, you can use `skip_authorization`. For
389
+ bypassing `verify_policy_scoped`, use `skip_policy_scope`. These are useful
390
+ in circumstances where you don't want to disable verification for the
391
+ entire action, but have some cases where you intend to not authorize.
392
+
393
+ ```ruby
394
+ def show
395
+ record = Record.find_by(attribute: "value")
396
+ if record.present?
397
+ authorize record
398
+ else
399
+ skip_authorization
400
+ end
401
+ end
402
+ ```
403
+
404
+ ## Manually specifying policy classes
405
+
406
+ Sometimes you might want to explicitly declare which policy to use for a given
407
+ class, instead of letting Pundit infer it. This can be done like so:
408
+
409
+ ``` ruby
410
+ class Post
411
+ def self.policy_class
412
+ PostablePolicy
413
+ end
414
+ end
415
+ ```
416
+
417
+ Alternatively, you can declare an instance method:
418
+
419
+ ``` ruby
420
+ class Post
421
+ def policy_class
422
+ PostablePolicy
423
+ end
424
+ end
425
+ ```
426
+
427
+ ## Plain old Ruby
428
+
429
+ Pundit is a very small library on purpose, and it doesn't do anything you can't do yourself. There's no secret sauce here. It does as little as possible, and then gets out of your way.
430
+
431
+ With the few but powerful helpers available in Pundit, you have the power to build a well structured, fully working authorization system without using any special DSLs or funky syntax.
432
+
433
+ Remember that all of the policy and scope classes are plain Ruby classes, which means you can use the same mechanisms you always use to DRY things up. Encapsulate a set of permissions into a module and include them in multiple policies. Use `alias_method` to make some permissions behave the same as others. Inherit from a base set of permissions. Use metaprogramming if you really have to.
434
+
435
+ ## Generator
436
+
437
+ Use the supplied generator to generate policies:
438
+
439
+ ``` sh
440
+ rails g pundit:policy post
441
+ ```
442
+
443
+ ## Closed systems
444
+
445
+ In many applications, only logged in users are really able to do anything. If
446
+ you're building such a system, it can be kind of cumbersome to check that the
447
+ user in a policy isn't `nil` for every single permission. Aside from policies,
448
+ you can add this check to the base class for scopes.
449
+
450
+ We suggest that you define a filter that redirects unauthenticated users to the
451
+ login page. As a secondary defence, if you've defined an ApplicationPolicy, it
452
+ might be a good idea to raise an exception if somehow an unauthenticated user
453
+ got through. This way you can fail more gracefully.
454
+
455
+ ``` ruby
456
+ class ApplicationPolicy
457
+ def initialize(user, record)
458
+ raise Pundit::NotAuthorizedError, "must be logged in" unless user
459
+ @user = user
460
+ @record = record
461
+ end
462
+
463
+ class Scope
464
+ attr_reader :user, :scope
465
+
466
+ def initialize(user, scope)
467
+ raise Pundit::NotAuthorizedError, "must be logged in" unless user
468
+ @user = user
469
+ @scope = scope
470
+ end
471
+ end
472
+ end
473
+ ```
474
+
475
+ ## NilClassPolicy
476
+
477
+ To support a [null object pattern](https://en.wikipedia.org/wiki/Null_Object_pattern)
478
+ you may find that you want to implement a `NilClassPolicy`. This might be useful
479
+ where you want to extend your ApplicationPolicy to allow some tolerance of, for
480
+ example, associations which might be `nil`.
481
+
482
+ ```ruby
483
+ class NilClassPolicy < ApplicationPolicy
484
+ class Scope < ApplicationPolicy::Scope
485
+ def resolve
486
+ raise Pundit::NotDefinedError, "Cannot scope NilClass"
487
+ end
488
+ end
489
+
490
+ def show?
491
+ false # Nobody can see nothing
492
+ end
493
+ end
494
+ ```
495
+
496
+ ## Rescuing a denied Authorization in Rails
497
+
498
+ Pundit raises a `Pundit::NotAuthorizedError` you can
499
+ [rescue_from](https://guides.rubyonrails.org/action_controller_overview.html#rescue-from)
500
+ in your `ApplicationController`. You can customize the `user_not_authorized`
501
+ method in every controller.
502
+
503
+ ```ruby
504
+ class ApplicationController < ActionController::Base
505
+ include Pundit::Authorization
506
+
507
+ rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized
508
+
509
+ private
510
+
511
+ def user_not_authorized
512
+ flash[:alert] = "You are not authorized to perform this action."
513
+ redirect_back_or_to(root_path)
514
+ end
515
+ end
516
+ ```
517
+
518
+ 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:
519
+
520
+ ```config.action_dispatch.rescue_responses["Pundit::NotAuthorizedError"] = :forbidden```
521
+
522
+ ## Creating custom error messages
523
+
524
+ `NotAuthorizedError`s provide information on what query (e.g. `:create?`), what
525
+ record (e.g. an instance of `Post`), and what policy (e.g. an instance of
526
+ `PostPolicy`) caused the error to be raised.
527
+
528
+ One way to use these `query`, `record`, and `policy` properties is to connect
529
+ them with `I18n` to generate error messages. Here's how you might go about doing
530
+ that.
531
+
532
+ ```ruby
533
+ class ApplicationController < ActionController::Base
534
+ rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized
535
+
536
+ private
537
+
538
+ def user_not_authorized(exception)
539
+ policy_name = exception.policy.class.to_s.underscore
540
+
541
+ flash[:error] = t "#{policy_name}.#{exception.query}", scope: "pundit", default: :default
542
+ redirect_back_or_to(root_path)
543
+ end
544
+ end
545
+ ```
546
+
547
+ ```yaml
548
+ en:
549
+ pundit:
550
+ default: 'You cannot perform this action.'
551
+ post_policy:
552
+ update?: 'You cannot edit this post!'
553
+ create?: 'You cannot create posts!'
554
+ ```
555
+
556
+ This is an example. Pundit is agnostic as to how you implement your error messaging.
557
+
558
+ ## Manually retrieving policies and scopes
559
+
560
+ Sometimes you want to retrieve a policy for a record outside the controller or
561
+ view. For example when you delegate permissions from one policy to another.
562
+
563
+ You can easily retrieve policies and scopes like this:
564
+
565
+ ``` ruby
566
+ Pundit.policy!(user, post)
567
+ Pundit.policy(user, post)
568
+
569
+ Pundit.policy_scope!(user, Post)
570
+ Pundit.policy_scope(user, Post)
571
+ ```
572
+
573
+ The bang methods will raise an exception if the policy does not exist, whereas
574
+ those without the bang will return nil.
575
+
576
+ ## Customize Pundit user
577
+
578
+ On occasion, your controller may be unable to access `current_user`, or the method that should be invoked by Pundit may not be `current_user`. To address this, you can define a method in your controller named `pundit_user`.
579
+
580
+ ```ruby
581
+ def pundit_user
582
+ User.find_by_other_means
583
+ end
584
+ ```
585
+
586
+ For instance, Rails 8 includes a built-in [authentication generator](https://github.com/rails/rails/tree/8-0-stable/railties/lib/rails/generators/rails/authentication). If you choose to use it, the currently logged-in user is accessed via `Current.user` instead of `current_user`.
587
+
588
+ To ensure compatibility with Pundit, define a `pundit_user` method in `application_controller.rb` (or another suitable location) as follows:
589
+
590
+ ```ruby
591
+ def pundit_user
592
+ Current.user
593
+ end
594
+ ```
595
+
596
+ ### Handling User Switching in Pundit
597
+
598
+ When switching users in your application, it's important to reset the Pundit user context to ensure that authorization policies are applied correctly for the new user. Pundit caches the user context, so failing to reset it could result in incorrect permissions being applied.
599
+
600
+ To handle user switching, you can use the following pattern in your controller:
601
+
602
+ ```ruby
603
+ class ApplicationController
604
+ include Pundit::Authorization
605
+
606
+ def switch_user_to(user)
607
+ terminate_session if authenticated?
608
+ start_new_session_for user
609
+ pundit_reset!
610
+ end
611
+ end
612
+ ```
613
+
614
+ Make sure to invoke `pundit_reset!` whenever changing the user. This ensures the cached authorization context is reset, preventing any incorrect permissions from being applied.
615
+
616
+ ## Policy Namespacing
617
+ In some cases it might be helpful to have multiple policies that serve different contexts for a
618
+ resource. A prime example of this is the case where User policies differ from Admin policies. To
619
+ authorize with a namespaced policy, pass the namespace into the `authorize` helper in an array:
620
+
621
+ ```ruby
622
+ authorize(post) # => will look for a PostPolicy
623
+ authorize([:admin, post]) # => will look for an Admin::PostPolicy
624
+ authorize([:foo, :bar, post]) # => will look for a Foo::Bar::PostPolicy
625
+
626
+ policy_scope(Post) # => will look for a PostPolicy::Scope
627
+ policy_scope([:admin, Post]) # => will look for an Admin::PostPolicy::Scope
628
+ policy_scope([:foo, :bar, Post]) # => will look for a Foo::Bar::PostPolicy::Scope
629
+ ```
630
+
631
+ If you are using namespaced policies for something like Admin views, it can be useful to
632
+ override the `policy_scope` and `authorize` helpers in your `AdminController` to automatically
633
+ apply the namespacing:
634
+
635
+ ```ruby
636
+ class AdminController < ApplicationController
637
+ def policy_scope(scope)
638
+ super([:admin, scope])
639
+ end
640
+
641
+ def authorize(record, query = nil)
642
+ super([:admin, record], query)
643
+ end
644
+ end
645
+
646
+ class Admin::PostController < AdminController
647
+ def index
648
+ policy_scope(Post)
649
+ end
650
+
651
+ def show
652
+ post = authorize Post.find(params[:id])
653
+ end
654
+ end
655
+ ```
656
+
657
+ ## Additional context
658
+
659
+ Pundit strongly encourages you to model your application in such a way that the
660
+ only context you need for authorization is a user object and a domain model that
661
+ you want to check authorization for. If you find yourself needing more context than
662
+ that, consider whether you are authorizing the right domain model, maybe another
663
+ domain model (or a wrapper around multiple domain models) can provide the context
664
+ you need.
665
+
666
+ Pundit does not allow you to pass additional arguments to policies for precisely
667
+ this reason.
668
+
669
+ However, in very rare cases, you might need to authorize based on more context than just
670
+ the currently authenticated user. Suppose for example that authorization is dependent
671
+ on IP address in addition to the authenticated user. In that case, one option is to
672
+ create a special class which wraps up both user and IP and passes it to the policy.
673
+
674
+ ``` ruby
675
+ class UserContext
676
+ attr_reader :user, :ip
677
+
678
+ def initialize(user, ip)
679
+ @user = user
680
+ @ip = ip
681
+ end
682
+ end
683
+
684
+ class ApplicationController
685
+ include Pundit::Authorization
686
+
687
+ def pundit_user
688
+ UserContext.new(current_user, request.ip)
689
+ end
690
+ end
691
+ ```
692
+
693
+ ## Strong parameters
694
+
695
+ In Rails,
696
+ mass-assignment protection is handled in the controller. With Pundit you can
697
+ control which attributes a user has access to update via your policies. You can
698
+ set up a `permitted_attributes` method in your policy like this:
699
+
700
+ ```ruby
701
+ # app/policies/post_policy.rb
702
+ class PostPolicy < ApplicationPolicy
703
+ def permitted_attributes
704
+ if user.admin? || user.owner_of?(post)
705
+ [:title, :body, :tag_list]
706
+ else
707
+ [:tag_list]
708
+ end
709
+ end
710
+ end
711
+ ```
712
+
713
+ You can now retrieve these attributes from the policy:
714
+
715
+ ```ruby
716
+ # app/controllers/posts_controller.rb
717
+ class PostsController < ApplicationController
718
+ def update
719
+ @post = Post.find(params[:id])
720
+ if @post.update(post_params)
721
+ redirect_to @post
722
+ else
723
+ render :edit
724
+ end
725
+ end
726
+
727
+ private
728
+
729
+ def post_params
730
+ params.require(:post).permit(policy(@post).permitted_attributes)
731
+ end
732
+ end
733
+ ```
734
+
735
+ However, this is a bit cumbersome, so Pundit provides a convenient helper method:
736
+
737
+ ```ruby
738
+ # app/controllers/posts_controller.rb
739
+ class PostsController < ApplicationController
740
+ def update
741
+ @post = Post.find(params[:id])
742
+ if @post.update(permitted_attributes(@post))
743
+ redirect_to @post
744
+ else
745
+ render :edit
746
+ end
747
+ end
748
+ end
749
+ ```
750
+
751
+ If you want to permit different attributes based on the current action, you can define a `permitted_attributes_for_#{action}` method on your policy:
752
+
753
+ ```ruby
754
+ # app/policies/post_policy.rb
755
+ class PostPolicy < ApplicationPolicy
756
+ def permitted_attributes_for_create
757
+ [:title, :body]
758
+ end
759
+
760
+ def permitted_attributes_for_edit
761
+ [:body]
762
+ end
763
+ end
764
+ ```
765
+
766
+ 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.
767
+
768
+ 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`.
769
+
770
+ ```ruby
771
+ def pundit_params_for(record)
772
+ params.require(PolicyFinder.new(record).param_key)
773
+ end
774
+ ```
775
+
776
+ For example:
777
+
778
+ ```ruby
779
+ # If you don't want to use require
780
+ def pundit_params_for(record)
781
+ params.fetch(PolicyFinder.new(record).param_key, {})
782
+ end
783
+
784
+ # If you are using something like the JSON API spec
785
+ def pundit_params_for(_record)
786
+ params.fetch(:data, {}).fetch(:attributes, {})
787
+ end
788
+ ```
789
+
790
+ ## RSpec
791
+
792
+ ### Policy Specs
793
+
794
+ > [!TIP]
795
+ > An alternative approach to Pundit policy specs is scoping them to a user context as outlined in this
796
+ [excellent post](https://thunderboltlabs.com/blog/2013/03/27/testing-pundit-policies-with-rspec/) and implemented in the third party [pundit-matchers](https://github.com/punditcommunity/pundit-matchers) gem.
797
+
798
+ Pundit includes a mini-DSL for writing expressive tests for your policies in RSpec.
799
+ Require `pundit/rspec` in your `spec_helper.rb`:
800
+
801
+ ``` ruby
802
+ require "pundit/rspec"
803
+ ```
804
+
805
+ Then put your policy specs in `spec/policies`, and make them look somewhat like this:
806
+
807
+ ``` ruby
808
+ describe PostPolicy do
809
+ subject { described_class }
810
+
811
+ permissions :update?, :edit? do
812
+ it "denies access if post is published" do
813
+ expect(subject).not_to permit(User.new(admin: false), Post.new(published: true))
814
+ end
815
+
816
+ it "grants access if post is published and user is an admin" do
817
+ expect(subject).to permit(User.new(admin: true), Post.new(published: true))
818
+ end
819
+
820
+ it "grants access if post is unpublished" do
821
+ expect(subject).to permit(User.new(admin: false), Post.new(published: false))
822
+ end
823
+ end
824
+ end
825
+ ```
826
+
827
+ ### Custom matcher description
828
+
829
+ By default rspec includes an inspected `user` and `record` in the matcher description, which might become overly verbose:
830
+
831
+ ```
832
+ PostPolicy
833
+ update? and show?
834
+ is expected to permit #<User:0x0000000104aefd80> and #<Post:0x0000000104aef8d0 @user=#<User:0x0000000104aefd80>>
835
+ ```
836
+
837
+ You can override the default description with a static string, or a block:
838
+
839
+ ```ruby
840
+ # static alternative: Pundit::RSpec::Matchers.description = "permit the user"
841
+ Pundit::RSpec::Matchers.description = ->(user, record) do
842
+ "permit user with role #{user.role} to access record with ID #{record.id}"
843
+ end
844
+ ```
845
+
846
+ Which would make for a less chatty output:
847
+
848
+ ```
849
+ PostPolicy
850
+ update? and show?
851
+ is expected to permit user with role admin to access record with ID 130
852
+ ```
853
+
854
+ ### Focus Support
855
+
856
+ If your RSpec config has `filter_run_when_matching :focus`, you may tag the `permissions` helper like so:
857
+
858
+ ```
859
+ permissions :show?, :focus do
860
+ ```
861
+
862
+ ### Scope Specs
863
+
864
+ Pundit does not provide a DSL for testing scopes. Test them like you would a regular Ruby class!
865
+
866
+ ### Linting with RuboCop RSpec
867
+
868
+ When you lint your RSpec spec files with `rubocop-rspec`, it will fail to properly detect RSpec constructs that Pundit defines, `permissions`.
869
+ Make sure to use `rubocop-rspec` 2.0 or newer and add the following to your `.rubocop.yml`:
870
+
871
+ ```yaml
872
+ inherit_gem:
873
+ pundit: config/rubocop-rspec.yml
874
+ ```
875
+
876
+ # External Resources
877
+
878
+ - [RailsApps Example Application: Pundit and Devise](https://github.com/RailsApps/rails-devise-pundit)
879
+ - [Migrating to Pundit from CanCan](https://blog.carbonfive.com/2013/10/21/migrating-to-pundit-from-cancan/)
880
+ - [Testing Pundit Policies with RSpec](https://thunderboltlabs.com/blog/2013/03/27/testing-pundit-policies-with-rspec/)
881
+ - [Testing Pundit with Minitest](https://github.com/varvet/pundit/issues/204#issuecomment-60166450)
882
+ - [Using Pundit outside of a Rails controller](https://github.com/varvet/pundit/pull/136)
883
+ - [Straightforward Rails Authorization with Pundit](https://www.sitepoint.com/straightforward-rails-authorization-with-pundit/)
884
+
885
+ ## Other implementations
886
+
887
+ - [Flask-Pundit](https://github.com/anurag90x/flask-pundit) (Python) is a [Flask](https://flask.pocoo.org/) extension "heavily inspired by" Pundit
888
+
889
+ # License
890
+
891
+ Licensed under the MIT license, see the separate LICENSE.txt file.