pundit 1.1.0 → 2.0.0.beta1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: a2c188098c86ad5a0804044f80219014564501ef
4
- data.tar.gz: 2ab7f79275d9ae66e5d04cf980c11e2c2983a4ef
2
+ SHA256:
3
+ metadata.gz: 91c992d8bb2bc907e4c532fb868f0e6f4105d9f4a79f0871593ec374f6f173b0
4
+ data.tar.gz: a855c49b6ada65cd8d1309ed104d6d089236ec46fcade8e872b66be6b152a054
5
5
  SHA512:
6
- metadata.gz: 55fbbf71ad514c0cfe4f8933dea59915314f749efa53ab5579f2da9dfcf2b4786343cefa53d3a35e26f4a346776c1c513884595a39561d280b259e6b6fb9b31a
7
- data.tar.gz: bbcf9417801b22deac78afe2d5ea8a268193daf0e011f861006c25b1d0124d1f462aa37d4d38e623b9d438cb29ceb52b250575118e053862b74561795bbdd7a4
6
+ metadata.gz: 17b426b7b0a1410809be20410d20f2f291d26d3fde05bb333b68464f720c7824989f5063ba30c107459debaabb572f9a682738a74f35ebc08c4b3b6638350a3a
7
+ data.tar.gz: e28ba68e5f8c1a430a4cb1335b0ad95ff2d4dc35d5c2842e74321aad26e42cf8f1c383b217cbac8aab8449f20ef1d9faf10fe1432d27287fce961bea04e53b4e
@@ -1,14 +1,22 @@
1
1
  AllCops:
2
+ DisplayCopNames: true
3
+ TargetRubyVersion: 2.1
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
@@ -25,7 +33,7 @@ Metrics/PerceivedComplexity:
25
33
  Style/StructInheritance:
26
34
  Enabled: false
27
35
 
28
- Style/AlignParameters:
36
+ Layout/AlignParameters:
29
37
  EnforcedStyle: with_fixed_indentation
30
38
 
31
39
  Style/StringLiterals:
@@ -34,7 +42,7 @@ Style/StringLiterals:
34
42
  Style/StringLiteralsInInterpolation:
35
43
  EnforcedStyle: double_quotes
36
44
 
37
- Style/ClosingParenthesisIndentation:
45
+ Layout/ClosingParenthesisIndentation:
38
46
  Enabled: false
39
47
 
40
48
  Style/OneLineConditional:
@@ -49,8 +57,8 @@ Style/Not:
49
57
  Documentation:
50
58
  Enabled: false # TODO: Enable again once we have more docs
51
59
 
52
- Style/CaseIndentation:
53
- IndentWhenRelativeTo: case
60
+ Layout/CaseIndentation:
61
+ EnforcedStyle: case
54
62
  SupportedStyles:
55
63
  - case
56
64
  - end
@@ -61,22 +69,22 @@ Style/PercentLiteralDelimiters:
61
69
  '%w': "[]"
62
70
  '%W': "[]"
63
71
 
64
- Style/AccessModifierIndentation:
72
+ Layout/AccessModifierIndentation:
65
73
  EnforcedStyle: outdent
66
74
 
67
75
  Style/SignalException:
68
76
  Enabled: false
69
77
 
70
- Style/IndentationWidth:
78
+ Layout/IndentationWidth:
71
79
  Enabled: false
72
80
 
73
81
  Style/TrivialAccessors:
74
82
  ExactNameMatch: true
75
83
 
76
- Lint/EndAlignment:
77
- AlignWith: variable
84
+ Layout/EndAlignment:
85
+ EnforcedStyleAlignWith: variable
78
86
 
79
- Lint/DefEndAlignment:
87
+ Layout/DefEndAlignment:
80
88
  Enabled: false
81
89
 
82
90
  Lint/HandleExceptions:
@@ -88,7 +96,7 @@ Style/SpecialGlobalVars:
88
96
  Style/TrivialAccessors:
89
97
  Enabled: false
90
98
 
91
- Style/IndentHash:
99
+ Layout/IndentHash:
92
100
  Enabled: false
93
101
 
94
102
  Style/DoubleNegation:
@@ -1,11 +1,21 @@
1
1
  language: ruby
2
2
  sudo: false
3
- rvm:
4
- - 2.0.0
5
- - 2.1
6
- - 2.2
7
- - jruby-19mode
8
- - rbx-2
9
- env:
10
- - RSPEC_VERSION="<2.99"
11
- - RSPEC_VERSION="~>3.0
3
+ before_install:
4
+ - gem update --system
5
+ - gem install bundler
6
+
7
+ matrix:
8
+ include:
9
+ - rvm: 2.5.1
10
+ script: bundle exec rake rubocop # ONLY lint once, first
11
+ - rvm: 2.1
12
+ - rvm: 2.2.8
13
+ - rvm: 2.3.5
14
+ - rvm: 2.4.2
15
+ - rvm: 2.5.1
16
+ - rvm: jruby-9.1.8.0
17
+ env:
18
+ - JRUBY_OPTS="--debug"
19
+ - rvm: jruby-9.2.0.0
20
+ env:
21
+ - JRUBY_OPTS="--debug"
@@ -1,5 +1,18 @@
1
1
  # Pundit
2
2
 
3
+ ## 2.0.0.beta1 (2018-07-04)
4
+
5
+ - Add `policy_class` option to `authorize` to be able to override the policy. (#441)
6
+ - Add `policy_scope_class` option to `authorize` to be able to override the policy scope. (#441)
7
+ - Fix `param_key` issue when passed an array. (#529)
8
+ - Only pass last element of "namespace array" to policy and scope. (#529)
9
+ - Allow specification of a `NilClassPolicy`. (#525)
10
+ - Make sure `policy_class` override is called when passed an array. (#475)
11
+ - Raise `InvalidConstructorError` if a policy or policy scope with an invalid constructor is called. (#462)
12
+ - Use `action_name` instead of `params[:action]`. (#419)
13
+ - Add `pundit_params_for` method to make it easy to customize params fetching. (#502)
14
+ - Return passed object from `#authorize` method to make chaining possible. (#385)
15
+
3
16
  ## 1.1.0 (2016-01-14)
4
17
 
5
18
  - Can retrieve policies via an array of symbols/objects.
data/Gemfile CHANGED
@@ -1,4 +1,16 @@
1
1
  source "https://rubygems.org"
2
2
 
3
- gem "rspec", ENV["RSPEC_VERSION"] unless ENV["RSPEC_VERSION"].to_s.empty?
3
+ ruby RUBY_VERSION
4
+
4
5
  gemspec
6
+
7
+ group :development, :test do
8
+ gem "actionpack"
9
+ gem "activemodel"
10
+ gem "bundler"
11
+ gem "pry"
12
+ gem "rake"
13
+ gem "rspec"
14
+ gem "rubocop"
15
+ gem "yard"
16
+ end
data/README.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # Pundit
2
2
 
3
- [![Build Status](https://secure.travis-ci.org/elabs/pundit.svg?branch=master)](https://travis-ci.org/elabs/pundit)
4
- [![Code Climate](https://codeclimate.com/github/elabs/pundit.svg)](https://codeclimate.com/github/elabs/pundit)
5
- [![Inline docs](http://inch-ci.org/github/elabs/pundit.svg?branch=master)](http://inch-ci.org/github/elabs/pundit)
3
+ [![Build Status](https://secure.travis-ci.org/varvet/pundit.svg?branch=master)](https://travis-ci.org/varvet/pundit)
4
+ [![Code Climate](https://codeclimate.com/github/varvet/pundit.svg)](https://codeclimate.com/github/varvet/pundit)
5
+ [![Inline docs](http://inch-ci.org/github/varvet/pundit.svg?branch=master)](http://inch-ci.org/github/varvet/pundit)
6
6
  [![Gem Version](https://badge.fury.io/rb/pundit.svg)](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/elabs/pundit)
16
- - [Contributing](https://github.com/elabs/pundit/blob/master/CONTRIBUTING.md)
17
- - [Code of Conduct](https://github.com/elabs/pundit/blob/master/CODE_OF_CONDUCT.md)
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="http://d3cv91luii1z1d.cloudfront.net/logo-gh.png" alt="Elabs" height="50px"/>](http://elabs.se)
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
- raise "not authorized" unless PostPolicy.new(current_user, @post).update?
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:
@@ -184,54 +207,6 @@ authorize :dashboard, :show?
184
207
  <% end %>
185
208
  ```
186
209
 
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
210
  ## Scopes
236
211
 
237
212
  Often, you will want to have some kind of view listing records which a
@@ -258,7 +233,7 @@ class PostPolicy < ApplicationPolicy
258
233
  end
259
234
 
260
235
  def update?
261
- user.admin? or not post.published?
236
+ user.admin? or not record.published?
262
237
  end
263
238
  end
264
239
  ```
@@ -291,7 +266,7 @@ class PostPolicy < ApplicationPolicy
291
266
  end
292
267
 
293
268
  def update?
294
- user.admin? or not post.published?
269
+ user.admin? or not record.published?
295
270
  end
296
271
  end
297
272
  ```
@@ -302,6 +277,19 @@ You can now use this class from your controller via the `policy_scope` method:
302
277
  def index
303
278
  @posts = policy_scope(Post)
304
279
  end
280
+
281
+ def show
282
+ @post = policy_scope(Post).find(params[:id])
283
+ end
284
+ ```
285
+
286
+ Like with the authorize method, you can also override the policy scope class:
287
+
288
+ ``` ruby
289
+ def index
290
+ # publication_class => Post
291
+ @publications = policy_scope(publication_class, policy_scope_class: PublicationPolicy::Scope)
292
+ end
305
293
  ```
306
294
 
307
295
  Just as with your policy, this will automatically infer that you want to use
@@ -322,6 +310,70 @@ You can, and are encouraged to, use this method in views:
322
310
  <% end %>
323
311
  ```
324
312
 
313
+ ## Ensuring policies and scopes are used
314
+
315
+ When you are developing an application with Pundit it can be easy to forget to
316
+ authorize some action. People are forgetful after all. Since Pundit encourages
317
+ you to add the `authorize` call manually to each controller action, it's really
318
+ easy to miss one.
319
+
320
+ Thankfully, Pundit has a handy feature which reminds you in case you forget.
321
+ Pundit tracks whether you have called `authorize` anywhere in your controller
322
+ action. Pundit also adds a method to your controllers called
323
+ `verify_authorized`. This method will raise an exception if `authorize` has not
324
+ yet been called. You should run this method in an `after_action` hook to ensure
325
+ that you haven't forgotten to authorize the action. For example:
326
+
327
+ ``` ruby
328
+ class ApplicationController < ActionController::Base
329
+ include Pundit
330
+ after_action :verify_authorized
331
+ end
332
+ ```
333
+
334
+ Likewise, Pundit also adds `verify_policy_scoped` to your controller. This
335
+ will raise an exception similar to `verify_authorized`. However, it tracks
336
+ if `policy_scope` is used instead of `authorize`. This is mostly useful for
337
+ controller actions like `index` which find collections with a scope and don't
338
+ authorize individual instances.
339
+
340
+ ``` ruby
341
+ class ApplicationController < ActionController::Base
342
+ include Pundit
343
+ after_action :verify_authorized, except: :index
344
+ after_action :verify_policy_scoped, only: :index
345
+ end
346
+ ```
347
+
348
+ **This verification mechanism only exists to aid you while developing your
349
+ application, so you don't forget to call `authorize`. It is not some kind of
350
+ failsafe mechanism or authorization mechanism. You should be able to remove
351
+ these filters without affecting how your app works in any way.**
352
+
353
+ Some people have found this feature confusing, while many others
354
+ find it extremely helpful. If you fall into the category of people who find it
355
+ confusing then you do not need to use it. Pundit will work just fine without
356
+ using `verify_authorized` and `verify_policy_scoped`.
357
+
358
+ ### Conditional verification
359
+
360
+ If you're using `verify_authorized` in your controllers but need to
361
+ conditionally bypass verification, you can use `skip_authorization`. For
362
+ bypassing `verify_policy_scoped`, use `skip_policy_scope`. These are useful
363
+ in circumstances where you don't want to disable verification for the
364
+ entire action, but have some cases where you intend to not authorize.
365
+
366
+ ```ruby
367
+ def show
368
+ record = Record.find_by(attribute: "value")
369
+ if record.present?
370
+ authorize record
371
+ else
372
+ skip_authorization
373
+ end
374
+ end
375
+ ```
376
+
325
377
  ## Manually specifying policy classes
326
378
 
327
379
  Sometimes you might want to explicitly declare which policy to use for a given
@@ -362,7 +414,8 @@ rails g pundit:policy post
362
414
 
363
415
  In many applications, only logged in users are really able to do anything. If
364
416
  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.
417
+ user in a policy isn't `nil` for every single permission. Aside from policies,
418
+ you can add this check to the base class for scopes.
366
419
 
367
420
  We suggest that you define a filter that redirects unauthenticated users to the
368
421
  login page. As a secondary defence, if you've defined an ApplicationPolicy, it
@@ -376,6 +429,37 @@ class ApplicationPolicy
376
429
  @user = user
377
430
  @record = record
378
431
  end
432
+
433
+ class Scope
434
+ attr_reader :user, :scope
435
+
436
+ def initialize(user, scope)
437
+ raise Pundit::NotAuthorizedError, "must be logged in" unless user
438
+ @user = user
439
+ @scope = scope
440
+ end
441
+ end
442
+ end
443
+ ```
444
+
445
+ ## NilClassPolicy
446
+
447
+ To support a [null object pattern](https://en.wikipedia.org/wiki/Null_Object_pattern)
448
+ you may find that you want to implement a `NilClassPolicy`. This might be useful
449
+ where you want to extend your ApplicationPolicy to allow some tolerance of, for
450
+ example, associations which might be `nil`.
451
+
452
+ ```ruby
453
+ class NilClassPolicy < ApplicationPolicy
454
+ class Scope < Scope
455
+ def resolve
456
+ raise Pundit::NotDefinedError, "Cannot scope NilClass"
457
+ end
458
+ end
459
+
460
+ def show?
461
+ false # Nobody can see nothing
462
+ end
379
463
  end
380
464
  ```
381
465
 
@@ -402,6 +486,10 @@ class ApplicationController < ActionController::Base
402
486
  end
403
487
  ```
404
488
 
489
+ 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:
490
+
491
+ ```config.action_dispatch.rescue_responses["Pundit::NotAuthorizedError"] = :forbidden```
492
+
405
493
  ## Creating custom error messages
406
494
 
407
495
  `NotAuthorizedError`s provide information on what query (e.g. `:create?`), what
@@ -469,6 +557,48 @@ def pundit_user
469
557
  end
470
558
  ```
471
559
 
560
+ ## Policy Namespacing
561
+ In some cases it might be helpful to have multiple policies that serve different contexts for a
562
+ resource. A prime example of this is the case where User policies differ from Admin policies. To
563
+ authorize with a namespaced policy, pass the namespace into the `authorize` helper in an array:
564
+
565
+ ```ruby
566
+ authorize(post) # => will look for a PostPolicy
567
+ authorize([:admin, post]) # => will look for an Admin::PostPolicy
568
+ authorize([:foo, :bar, post]) # => will look for a Foo::Bar::PostPolicy
569
+
570
+ policy_scope(Post) # => will look for a PostPolicy::Scope
571
+ policy_scope([:admin, Post]) # => will look for an Admin::PostPolicy::Scope
572
+ policy_scope([:foo, :bar, Post]) # => will look for a Foo::Bar::PostPolicy::Scope
573
+ ```
574
+
575
+ If you are using namespaced policies for something like Admin views, it can be useful to
576
+ override the `policy_scope` and `authorize` helpers in your `AdminController` to automatically
577
+ apply the namespacing:
578
+
579
+ ```ruby
580
+ class AdminController < ApplicationController
581
+ def policy_scope(scope)
582
+ super([:admin, scope])
583
+ end
584
+
585
+ def authorize(record, query = nil)
586
+ super([:admin, record], query)
587
+ end
588
+ end
589
+
590
+ class Admin::PostController < AdminController
591
+ def index
592
+ policy_scope(Post)
593
+ end
594
+
595
+ def show
596
+ post = Post.find(params[:id])
597
+ authorize(post)
598
+ end
599
+ end
600
+ ```
601
+
472
602
  ## Additional context
473
603
 
474
604
  Pundit strongly encourages you to model your application in such a way that the
@@ -564,6 +694,45 @@ class PostsController < ApplicationController
564
694
  end
565
695
  ```
566
696
 
697
+ If you want to permit different attributes based on the current action, you can define a `permitted_attributes_for_#{action}` method on your policy:
698
+
699
+ ```ruby
700
+ # app/policies/post_policy.rb
701
+ class PostPolicy < ApplicationPolicy
702
+ def permitted_attributes_for_create
703
+ [:title, :body]
704
+ end
705
+
706
+ def permitted_attributes_for_edit
707
+ [:body]
708
+ end
709
+ end
710
+ ```
711
+
712
+ 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.
713
+
714
+ 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`.
715
+
716
+ ```ruby
717
+ def pundit_params_for(record)
718
+ params.require(PolicyFinder.new(record).param_key)
719
+ end
720
+ ```
721
+
722
+ For example:
723
+
724
+ ```ruby
725
+ # If you don't want to use require
726
+ def pundit_params_for(record)
727
+ params.fetch(PolicyFinder.new(record).param_key, {})
728
+ end
729
+
730
+ # If you are using something like the JSON API spec
731
+ def pundit_params_for(_record)
732
+ params.fetch(:data, {}).fetch(:attributes, {})
733
+ end
734
+ ```
735
+
567
736
  ## RSpec
568
737
 
569
738
  ### Policy Specs
@@ -600,12 +769,16 @@ end
600
769
  An alternative approach to Pundit policy specs is scoping them to a user context as outlined in this
601
770
  [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
771
 
772
+ ### Scope Specs
773
+
774
+ Pundit does not provide a DSL for testing scopes. Just test it like a regular Ruby class!
775
+
603
776
  # External Resources
604
777
 
605
778
  - [RailsApps Example Application: Pundit and Devise](https://github.com/RailsApps/rails-devise-pundit)
606
779
  - [Migrating to Pundit from CanCan](http://blog.carbonfive.com/2013/10/21/migrating-to-pundit-from-cancan/)
607
780
  - [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/elabs/pundit/pull/136)
781
+ - [Using Pundit outside of a Rails controller](https://github.com/varvet/pundit/pull/136)
609
782
  - [Straightforward Rails Authorization with Pundit](http://www.sitepoint.com/straightforward-rails-authorization-with-pundit/)
610
783
 
611
784
  # License