pundit 2.0.0 → 2.1.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6b973703e2b1653c804d138fb24c204807e41885a81f5b172656d032d7c2a75a
4
- data.tar.gz: 01211cab222a4c16f274e041c074b2d2a1763a4943ab26b6dba938272297a48d
3
+ metadata.gz: def6c710d9f9d1705ca43cdad9364bb34ce5d878b8a3a9bf26d336802e40efeb
4
+ data.tar.gz: 0d6618cb61dfef8ae18f811a73b3f059d9945f8def7f9b2f794ae095b3a0f0cf
5
5
  SHA512:
6
- metadata.gz: 812528978ec4e8d3322af071c3ebd5b31f4123be449d3fe9bfea1e1fd2845704e0fee308d4cdd6e787636987b1d7a03527f8eae5fe5968483a1f4c5f751b40ef
7
- data.tar.gz: c433160a559102336b9a268ec1311a47e8f54e427ad8618b048634435259612a92d9b9187fefb6d7cc4a1ce5576a37f3e51b7adad1e3773d21c2bb6e9827c26f
6
+ metadata.gz: 7105cd0a84469071de9211e19f7e7a31f4ea8b5283d7b1ed007b6533805de18d105c09128a9936144612f3daa8fadfdf0698f7448d8db94e47bb459efaa11c4b
7
+ data.tar.gz: 3805e2664f21f30c66e9a9a05606f0d0d18b1607137e794948c7907bd33c6d6e64a45b9abb97d2f1a21df0d46497ff708679442807e152fa167abc461c8c0abe
data/.gitignore CHANGED
@@ -2,6 +2,7 @@
2
2
  *.rbc
3
3
  .bundle
4
4
  .config
5
+ .coverage
5
6
  .yardoc
6
7
  Gemfile.lock
7
8
  InstalledFiles
data/.rubocop.yml CHANGED
@@ -1,10 +1,7 @@
1
1
  AllCops:
2
- DisplayCopNames: true
3
- TargetRubyVersion: 2.1
2
+ TargetRubyVersion: 2.6
4
3
  Exclude:
5
- - "gemfiles/**/*"
6
- - "vendor/**/*"
7
- - "lib/generators/**/*"
4
+ - "lib/generators/**/templates/**/*"
8
5
 
9
6
  Metrics/BlockLength:
10
7
  Exclude:
@@ -30,33 +27,9 @@ Metrics/CyclomaticComplexity:
30
27
  Metrics/PerceivedComplexity:
31
28
  Enabled: false
32
29
 
33
- Style/StructInheritance:
34
- Enabled: false
35
-
36
30
  Layout/AlignParameters:
37
31
  EnforcedStyle: with_fixed_indentation
38
32
 
39
- Style/StringLiterals:
40
- EnforcedStyle: double_quotes
41
-
42
- Style/StringLiteralsInInterpolation:
43
- EnforcedStyle: double_quotes
44
-
45
- Layout/ClosingParenthesisIndentation:
46
- Enabled: false
47
-
48
- Style/OneLineConditional:
49
- Enabled: false
50
-
51
- Style/AndOr:
52
- Enabled: false
53
-
54
- Style/Not:
55
- Enabled: false
56
-
57
- Documentation:
58
- Enabled: false # TODO: Enable again once we have more docs
59
-
60
33
  Layout/CaseIndentation:
61
34
  EnforcedStyle: case
62
35
  SupportedStyles:
@@ -64,40 +37,31 @@ Layout/CaseIndentation:
64
37
  - end
65
38
  IndentOneStep: true
66
39
 
40
+ Layout/EndAlignment:
41
+ EnforcedStyleAlignWith: variable
42
+
67
43
  Style/PercentLiteralDelimiters:
68
44
  PreferredDelimiters:
69
45
  '%w': "[]"
70
46
  '%W': "[]"
71
47
 
72
- Layout/AccessModifierIndentation:
73
- EnforcedStyle: outdent
74
-
75
- Style/SignalException:
76
- Enabled: false
77
-
78
- Layout/IndentationWidth:
79
- Enabled: false
80
-
81
- Style/TrivialAccessors:
82
- ExactNameMatch: true
83
-
84
- Layout/EndAlignment:
85
- EnforcedStyleAlignWith: variable
86
-
87
- Layout/DefEndAlignment:
88
- Enabled: false
48
+ Style/StringLiterals:
49
+ EnforcedStyle: double_quotes
89
50
 
90
- Lint/HandleExceptions:
91
- Enabled: false
51
+ Style/StringLiteralsInInterpolation:
52
+ EnforcedStyle: double_quotes
92
53
 
93
- Style/SpecialGlobalVars:
54
+ Style/StructInheritance:
94
55
  Enabled: false
95
56
 
96
- Style/TrivialAccessors:
57
+ Style/AndOr:
97
58
  Enabled: false
98
59
 
99
- Layout/IndentHash:
60
+ Style/Not:
100
61
  Enabled: false
101
62
 
102
63
  Style/DoubleNegation:
103
64
  Enabled: false
65
+
66
+ Documentation:
67
+ Enabled: false # TODO: Enable again once we have more docs
data/.travis.yml CHANGED
@@ -1,21 +1,25 @@
1
1
  language: ruby
2
- sudo: false
3
- before_install:
4
- - gem update --system
5
- - gem install bundler
2
+ dist: focal
6
3
 
7
4
  matrix:
8
5
  include:
9
- - rvm: 2.5.1
6
+ - name: "RuboCop lint on pre-installed Ruby version"
7
+ rvm: 2.7.1 # Pre-installed Ruby version
8
+ before_install:
9
+ - gem install bundler
10
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
11
+ - rvm: 2.6.7
12
+ before_script:
13
+ - curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter
14
+ - chmod +x ./cc-test-reporter
15
+ - ./cc-test-reporter before-build
16
+ after_script:
17
+ - ./cc-test-reporter after-build --exit-code $TRAVIS_TEST_RESULT
18
+ - rvm: 2.7.3
19
+ - rvm: 3.0.1
20
+ - rvm: jruby-9.2.17.0
20
21
  env:
21
22
  - JRUBY_OPTS="--debug"
23
+ - rvm: truffleruby-head
24
+ allow_failures:
25
+ - rvm: truffleruby-head
data/CHANGELOG.md CHANGED
@@ -1,21 +1,74 @@
1
1
  # Pundit
2
2
 
3
+ ## Unreleased
4
+
5
+ ## 2.1.1 (2021-08-13)
6
+
7
+ Friday 13th-release!
8
+
9
+ Careful! The bugfix below (#626) could break existing code. If you rely on the
10
+ return value for `authorize` and namespaced policies you might need to do some
11
+ changes.
12
+
13
+ ### Fixed
14
+
15
+ - `.authorize` and `#authorize` return the instance, even for namespaced
16
+ policies (#626)
17
+
18
+ ### Changed
19
+
20
+ - Generate application scope with `protected` attr_readers. (#616)
21
+
22
+ ### Removed
23
+
24
+ - Dropped support for Ruby end-of-life versions: 2.1 and 2.2. (#604)
25
+ - Dropped support for Ruby end-of-life versions: 2.3 (#633)
26
+ - Dropped support for Ruby end-of-life versions: 2.4, 2.5 and JRuby 9.1 (#676)
27
+ - Dropped support for RSpec 2 (#615)
28
+
29
+ ## 2.1.0 (2019-08-14)
30
+
31
+ ### Fixed
32
+
33
+ - Avoid name clashes with the Error class. (#590)
34
+
35
+ ### Changed
36
+
37
+ - Return a safer default NotAuthorizedError message. (#583)
38
+
39
+ ## 2.0.1 (2019-01-18)
40
+
41
+ ### Breaking changes
42
+
43
+ None
44
+
45
+ ### Other changes
46
+
47
+ - Improve exception handling for `#policy_scope` and `#policy_scope!`. (#550)
48
+ - Add `:policy` metadata to RSpec template. (#566)
49
+
3
50
  ## 2.0.0 (2018-07-21)
4
51
 
5
52
  No changes since beta1
6
53
 
7
54
  ## 2.0.0.beta1 (2018-07-04)
8
55
 
56
+ ### Breaking changes
57
+
58
+ - Only pass last element of "namespace array" to policy and scope. (#529)
59
+ - Raise `InvalidConstructorError` if a policy or policy scope with an invalid constructor is called. (#462)
60
+ - Return passed object from `#authorize` method to make chaining possible. (#385)
61
+
62
+ ### Other changes
63
+
9
64
  - Add `policy_class` option to `authorize` to be able to override the policy. (#441)
10
65
  - Add `policy_scope_class` option to `authorize` to be able to override the policy scope. (#441)
11
66
  - Fix `param_key` issue when passed an array. (#529)
12
- - Only pass last element of "namespace array" to policy and scope. (#529)
13
67
  - Allow specification of a `NilClassPolicy`. (#525)
14
68
  - Make sure `policy_class` override is called when passed an array. (#475)
15
- - Raise `InvalidConstructorError` if a policy or policy scope with an invalid constructor is called. (#462)
69
+
16
70
  - Use `action_name` instead of `params[:action]`. (#419)
17
71
  - Add `pundit_params_for` method to make it easy to customize params fetching. (#502)
18
- - Return passed object from `#authorize` method to make chaining possible. (#385)
19
72
 
20
73
  ## 1.1.0 (2016-01-14)
21
74
 
data/Gemfile CHANGED
@@ -1,16 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  source "https://rubygems.org"
2
4
 
3
5
  ruby RUBY_VERSION
4
6
 
5
7
  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/LICENSE.txt CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2012 Jonas Nicklas, Elabs AB
1
+ Copyright (c) 2019 Jonas Nicklas, Varvet AB
2
2
 
3
3
  MIT License
4
4
 
data/README.md CHANGED
@@ -7,7 +7,7 @@
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
- scaleable authorization system.
10
+ scalable authorization system.
11
11
 
12
12
  Links:
13
13
 
@@ -31,7 +31,6 @@ Include Pundit in your application controller:
31
31
  ``` ruby
32
32
  class ApplicationController < ActionController::Base
33
33
  include Pundit
34
- protect_from_forgery
35
34
  end
36
35
  ```
37
36
 
@@ -61,7 +60,7 @@ class PostPolicy
61
60
  end
62
61
 
63
62
  def update?
64
- user.admin? or not post.published?
63
+ user.admin? || !post.published?
65
64
  end
66
65
  end
67
66
  ```
@@ -165,13 +164,18 @@ def admin_list
165
164
  end
166
165
  ```
167
166
 
168
- `authorize` returns the object passed to it, so you can chain it like this:
167
+ `authorize` returns the instance passed to it, so you can chain it like this:
169
168
 
170
169
  Controller:
171
170
  ```ruby
172
171
  def show
173
172
  @user = authorize User.find(params[:id])
174
173
  end
174
+
175
+ # return the record even for namespaced policies
176
+ def show
177
+ @user = authorize [:admin, User.find(params[:id])]
178
+ end
175
179
  ```
176
180
 
177
181
  You can easily get a hold of an instance of the policy through the `policy`
@@ -195,6 +199,10 @@ class DashboardPolicy < Struct.new(:user, :dashboard)
195
199
  end
196
200
  ```
197
201
 
202
+ Note that the headless policy still needs to accept two arguments. The
203
+ second argument will just be the symbol `:dashboard` in this case which
204
+ is what is passed as the record to `authorize` below.
205
+
198
206
  ```ruby
199
207
  # In controllers
200
208
  authorize :dashboard, :show?
@@ -216,8 +224,6 @@ define a class called a policy scope. It can look something like this:
216
224
  ``` ruby
217
225
  class PostPolicy < ApplicationPolicy
218
226
  class Scope
219
- attr_reader :user, :scope
220
-
221
227
  def initialize(user, scope)
222
228
  @user = user
223
229
  @scope = scope
@@ -230,6 +236,10 @@ class PostPolicy < ApplicationPolicy
230
236
  scope.where(published: true)
231
237
  end
232
238
  end
239
+
240
+ private
241
+
242
+ attr_reader :user, :scope
233
243
  end
234
244
 
235
245
  def update?
@@ -292,13 +302,11 @@ def index
292
302
  end
293
303
  ```
294
304
 
295
- Just as with your policy, this will automatically infer that you want to use
296
- the `PostPolicy::Scope` class, it will instantiate this class and call
297
- `resolve` on the instance. In this case it is a shortcut for doing:
305
+ In this case it is a shortcut for doing:
298
306
 
299
307
  ``` ruby
300
308
  def index
301
- @posts = PostPolicy::Scope.new(current_user, Post).resolve
309
+ @publications = PublicationPolicy::Scope.new(current_user, Post).resolve
302
310
  end
303
311
  ```
304
312
 
@@ -387,6 +395,16 @@ class Post
387
395
  end
388
396
  ```
389
397
 
398
+ Alternatively, you can declare an instance method:
399
+
400
+ ``` ruby
401
+ class Post
402
+ def policy_class
403
+ PostablePolicy
404
+ end
405
+ end
406
+ ```
407
+
390
408
  ## Just plain old Ruby
391
409
 
392
410
  As you can see, Pundit doesn't do anything you couldn't have easily done
@@ -472,7 +490,6 @@ method in every controller.
472
490
 
473
491
  ```ruby
474
492
  class ApplicationController < ActionController::Base
475
- protect_from_forgery
476
493
  include Pundit
477
494
 
478
495
  rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized
@@ -593,8 +610,7 @@ class Admin::PostController < AdminController
593
610
  end
594
611
 
595
612
  def show
596
- post = Post.find(params[:id])
597
- authorize(post)
613
+ post = authorize Post.find(params[:id])
598
614
  end
599
615
  end
600
616
  ```
@@ -637,9 +653,8 @@ end
637
653
 
638
654
  ## Strong parameters
639
655
 
640
- In Rails 4 (or Rails 3.2 with the
641
- [strong_parameters](https://github.com/rails/strong_parameters) gem),
642
- mass-assignment protection is handled in the controller. With Pundit you can
656
+ In Rails,
657
+ mass-assignment protection is handled in the controller. With Pundit you can
643
658
  control which attributes a user has access to update via your policies. You can
644
659
  set up a `permitted_attributes` method in your policy like this:
645
660
 
@@ -778,9 +793,14 @@ Pundit does not provide a DSL for testing scopes. Just test it like a regular Ru
778
793
  - [RailsApps Example Application: Pundit and Devise](https://github.com/RailsApps/rails-devise-pundit)
779
794
  - [Migrating to Pundit from CanCan](http://blog.carbonfive.com/2013/10/21/migrating-to-pundit-from-cancan/)
780
795
  - [Testing Pundit Policies with RSpec](http://thunderboltlabs.com/blog/2013/03/27/testing-pundit-policies-with-rspec/)
796
+ - [Testing Pundit with Minitest](https://github.com/varvet/pundit/issues/204#issuecomment-60166450)
781
797
  - [Using Pundit outside of a Rails controller](https://github.com/varvet/pundit/pull/136)
782
798
  - [Straightforward Rails Authorization with Pundit](http://www.sitepoint.com/straightforward-rails-authorization-with-pundit/)
783
799
 
800
+ ## Other implementations
801
+
802
+ - [Flask-Pundit](https://github.com/anurag90x/flask-pundit) (Python) is a [Flask](http://flask.pocoo.org/) extension "heavily inspired by" Pundit
803
+
784
804
  # License
785
805
 
786
806
  Licensed under the MIT license, see the separate LICENSE.txt file.
data/Rakefile CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "rubygems"
2
4
  require "bundler/gem_tasks"
3
5
  require "rspec/core/rake_task"
@@ -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('templates', __dir__)
6
+ source_root File.expand_path("templates", __dir__)
5
7
 
6
8
  def copy_application_policy
7
- template 'application_policy.rb', 'app/policies/application_policy.rb'
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
 
@@ -35,8 +37,6 @@ class ApplicationPolicy
35
37
  end
36
38
 
37
39
  class Scope
38
- attr_reader :user, :scope
39
-
40
40
  def initialize(user, scope)
41
41
  @user = user
42
42
  @scope = scope
@@ -45,5 +45,9 @@ class ApplicationPolicy
45
45
  def resolve
46
46
  scope.all
47
47
  end
48
+
49
+ private
50
+
51
+ attr_reader :user, :scope
48
52
  end
49
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('templates', __dir__)
6
+ source_root File.expand_path("templates", __dir__)
5
7
 
6
8
  def create_policy
7
- template 'policy.rb', File.join('app/policies', class_path, "#{file_name}_policy.rb")
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
@@ -1,10 +1,12 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Rspec
2
4
  module Generators
3
5
  class PolicyGenerator < ::Rails::Generators::NamedBase
4
- source_root File.expand_path('templates', __dir__)
6
+ source_root File.expand_path("templates", __dir__)
5
7
 
6
8
  def create_policy_spec
7
- template 'policy_spec.rb', File.join('spec/policies', class_path, "#{file_name}_policy_spec.rb")
9
+ template "policy_spec.rb", File.join("spec/policies", class_path, "#{file_name}_policy_spec.rb")
8
10
  end
9
11
  end
10
12
  end
@@ -1,6 +1,6 @@
1
1
  require '<%= File.exists?('spec/rails_helper.rb') ? 'rails_helper' : 'spec_helper' %>'
2
2
 
3
- RSpec.describe <%= class_name %>Policy do
3
+ RSpec.describe <%= class_name %>Policy, type: :policy do
4
4
  let(:user) { User.new }
5
5
 
6
6
  subject { described_class }
@@ -1,10 +1,12 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module TestUnit
2
4
  module Generators
3
5
  class PolicyGenerator < ::Rails::Generators::NamedBase
4
- source_root File.expand_path('templates', __dir__)
6
+ source_root File.expand_path("templates", __dir__)
5
7
 
6
8
  def create_policy_test
7
- template 'policy_test.rb', File.join('test/policies', class_path, "#{file_name}_policy_test.rb")
9
+ template "policy_test.rb", File.join("test/policies", class_path, "#{file_name}_policy_test.rb")
8
10
  end
9
11
  end
10
12
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Pundit
2
4
  # Finds policy and scope classes for given object.
3
5
  # @api public
@@ -66,7 +68,7 @@ module Pundit
66
68
  end
67
69
  end
68
70
 
69
- private
71
+ private
70
72
 
71
73
  def find(subject)
72
74
  if subject.is_a?(Array)
data/lib/pundit/rspec.rb CHANGED
@@ -1,4 +1,4 @@
1
- require "active_support/core_ext/array/conversions"
1
+ # frozen_string_literal: true
2
2
 
3
3
  module Pundit
4
4
  module RSpec
@@ -72,17 +72,9 @@ module Pundit
72
72
  end
73
73
 
74
74
  RSpec.configure do |config|
75
- if RSpec::Core::Version::STRING.split(".").first.to_i >= 3
76
- config.include(
77
- Pundit::RSpec::PolicyExampleGroup,
78
- type: :policy,
79
- file_path: %r{spec/policies}
80
- )
81
- else
82
- config.include(
83
- Pundit::RSpec::PolicyExampleGroup,
84
- type: :policy,
85
- example_group: { file_path: %r{spec/policies} }
86
- )
87
- end
75
+ config.include(
76
+ Pundit::RSpec::PolicyExampleGroup,
77
+ type: :policy,
78
+ file_path: %r{spec/policies}
79
+ )
88
80
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Pundit
4
- VERSION = "2.0.0".freeze
4
+ VERSION = "2.1.1"
5
5
  end
data/lib/pundit.rb CHANGED
@@ -8,16 +8,18 @@ require "active_support/core_ext/object/blank"
8
8
  require "active_support/core_ext/module/introspection"
9
9
  require "active_support/dependencies/autoload"
10
10
 
11
+ # @api private
12
+ # To avoid name clashes with common Error naming when mixing in Pundit,
13
+ # keep it here with compact class style definition.
14
+ class Pundit::Error < StandardError; end # rubocop:disable Style/ClassAndModuleChildren
15
+
11
16
  # @api public
12
17
  module Pundit
13
- SUFFIX = "Policy".freeze
18
+ SUFFIX = "Policy"
14
19
 
15
20
  # @api private
16
21
  module Generators; end
17
22
 
18
- # @api private
19
- class Error < StandardError; end
20
-
21
23
  # Error that will be raised when authorization has failed
22
24
  class NotAuthorizedError < Error
23
25
  attr_reader :query, :record, :policy
@@ -30,7 +32,7 @@ module Pundit
30
32
  @record = options[:record]
31
33
  @policy = options[:policy]
32
34
 
33
- message = options.fetch(:message) { "not allowed to #{query} this #{record.inspect}" }
35
+ message = options.fetch(:message) { "not allowed to #{query} this #{record.class}" }
34
36
  end
35
37
 
36
38
  super(message)
@@ -69,7 +71,7 @@ module Pundit
69
71
 
70
72
  raise NotAuthorizedError, query: query, record: record, policy: policy unless policy.public_send(query)
71
73
 
72
- record
74
+ record.is_a?(Array) ? record.last : record
73
75
  end
74
76
 
75
77
  # Retrieves the policy scope for the given record.
@@ -80,10 +82,16 @@ module Pundit
80
82
  # @raise [InvalidConstructorError] if the policy constructor called incorrectly
81
83
  # @return [Scope{#resolve}, nil] instance of scope class which can resolve to a scope
82
84
  def policy_scope(user, scope)
83
- policy_scope = PolicyFinder.new(scope).scope
84
- policy_scope.new(user, pundit_model(scope)).resolve if policy_scope
85
- rescue ArgumentError
86
- raise InvalidConstructorError, "Invalid #<#{policy_scope}> constructor is called"
85
+ policy_scope_class = PolicyFinder.new(scope).scope
86
+ return unless policy_scope_class
87
+
88
+ begin
89
+ policy_scope = policy_scope_class.new(user, pundit_model(scope))
90
+ rescue ArgumentError
91
+ raise InvalidConstructorError, "Invalid #<#{policy_scope_class}> constructor is called"
92
+ end
93
+
94
+ policy_scope.resolve
87
95
  end
88
96
 
89
97
  # Retrieves the policy scope for the given record.
@@ -95,10 +103,16 @@ module Pundit
95
103
  # @raise [InvalidConstructorError] if the policy constructor called incorrectly
96
104
  # @return [Scope{#resolve}] instance of scope class which can resolve to a scope
97
105
  def policy_scope!(user, scope)
98
- policy_scope = PolicyFinder.new(scope).scope!
99
- policy_scope.new(user, pundit_model(scope)).resolve
100
- rescue ArgumentError
101
- raise InvalidConstructorError, "Invalid #<#{policy_scope}> constructor is called"
106
+ policy_scope_class = PolicyFinder.new(scope).scope!
107
+ return unless policy_scope_class
108
+
109
+ begin
110
+ policy_scope = policy_scope_class.new(user, pundit_model(scope))
111
+ rescue ArgumentError
112
+ raise InvalidConstructorError, "Invalid #<#{policy_scope_class}> constructor is called"
113
+ end
114
+
115
+ policy_scope.resolve
102
116
  end
103
117
 
104
118
  # Retrieves the policy for the given record.
@@ -110,7 +124,7 @@ module Pundit
110
124
  # @return [Object, nil] instance of policy class with query methods
111
125
  def policy(user, record)
112
126
  policy = PolicyFinder.new(record).policy
113
- policy.new(user, pundit_model(record)) if policy
127
+ policy&.new(user, pundit_model(record))
114
128
  rescue ArgumentError
115
129
  raise InvalidConstructorError, "Invalid #<#{policy}> constructor is called"
116
130
  end
@@ -130,7 +144,7 @@ module Pundit
130
144
  raise InvalidConstructorError, "Invalid #<#{policy}> constructor is called"
131
145
  end
132
146
 
133
- private
147
+ private
134
148
 
135
149
  def pundit_model(record)
136
150
  record.is_a?(Array) ? record.last : record
@@ -153,7 +167,7 @@ module Pundit
153
167
  end
154
168
  end
155
169
 
156
- protected
170
+ protected
157
171
 
158
172
  # @return [Boolean] whether authorization has been performed, i.e. whether
159
173
  # one {#authorize} or {#skip_authorization} has been called
@@ -208,7 +222,7 @@ protected
208
222
 
209
223
  raise NotAuthorizedError, query: query, record: record, policy: policy unless policy.public_send(query)
210
224
 
211
- record
225
+ record.is_a?(Array) ? record.last : record
212
226
  end
213
227
 
214
228
  # Allow this action not to perform authorization.
@@ -303,7 +317,7 @@ protected
303
317
  current_user
304
318
  end
305
319
 
306
- private
320
+ private
307
321
 
308
322
  def pundit_policy_scope(scope)
309
323
  policy_scopes[scope] ||= Pundit.policy_scope!(pundit_user, scope)
data/pundit.gemspec CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  lib = File.expand_path("lib", __dir__)
2
4
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
5
  require "pundit/version"
@@ -5,17 +7,26 @@ require "pundit/version"
5
7
  Gem::Specification.new do |gem|
6
8
  gem.name = "pundit"
7
9
  gem.version = Pundit::VERSION
8
- gem.authors = ["Jonas Nicklas", "Elabs AB"]
10
+ gem.authors = ["Jonas Nicklas", "Varvet AB"]
9
11
  gem.email = ["jonas.nicklas@gmail.com", "dev@elabs.se"]
10
12
  gem.description = "Object oriented authorization for Rails applications"
11
13
  gem.summary = "OO authorization for Rails"
12
14
  gem.homepage = "https://github.com/varvet/pundit"
13
15
  gem.license = "MIT"
14
16
 
15
- gem.files = `git ls-files`.split($/)
17
+ gem.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR)
16
18
  gem.executables = gem.files.grep(%r{^bin/}).map { |f| File.basename(f) }
17
19
  gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
18
20
  gem.require_paths = ["lib"]
19
21
 
20
22
  gem.add_dependency "activesupport", ">= 3.0.0"
23
+ gem.add_development_dependency "actionpack", ">= 3.0.0"
24
+ gem.add_development_dependency "activemodel", ">= 3.0.0"
25
+ gem.add_development_dependency "bundler"
26
+ gem.add_development_dependency "pry"
27
+ gem.add_development_dependency "rake"
28
+ gem.add_development_dependency "rspec", ">= 3.0.0"
29
+ gem.add_development_dependency "rubocop", "0.74.0"
30
+ gem.add_development_dependency "simplecov", ">= 0.17.0"
31
+ gem.add_development_dependency "yard"
21
32
  end
@@ -1,6 +1,8 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "spec_helper"
2
4
 
3
- describe PostPolicy do
5
+ RSpec.describe PostPolicy do
4
6
  let(:user) { double }
5
7
  let(:own_post) { double(user: user) }
6
8
  let(:other_post) { double(user: double) }
@@ -1,6 +1,8 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "spec_helper"
2
4
 
3
- describe Pundit::PolicyFinder do
5
+ RSpec.describe Pundit::PolicyFinder do
4
6
  let(:user) { double }
5
7
  let(:post) { Post.new(user) }
6
8
  let(:comment) { CommentFourFiveSix.new }
@@ -22,37 +24,100 @@ describe Pundit::PolicyFinder do
22
24
  end
23
25
 
24
26
  describe "#policy" do
25
- subject { described_class.new(post) }
27
+ context "with an instance" do
28
+ it "returns the associated policy" do
29
+ object = described_class.new(post)
30
+
31
+ expect(object.policy).to eq PostPolicy
32
+ end
33
+ end
34
+
35
+ context "with an array of symbols" do
36
+ it "returns the associated namespaced policy" do
37
+ object = described_class.new(%i[project post])
38
+
39
+ expect(object.policy).to eq Project::PostPolicy
40
+ end
41
+ end
26
42
 
27
- it "returns a policy" do
28
- expect(subject.policy).to eq PostPolicy
43
+ context "with an array of a symbol and an instance" do
44
+ it "returns the associated namespaced policy" do
45
+ object = described_class.new([:project, post])
46
+
47
+ expect(object.policy).to eq Project::PostPolicy
48
+ end
29
49
  end
30
50
 
31
- context "with a string" do
32
- it "returns a policy" do
33
- allow(subject).to receive(:find).and_return "PostPolicy"
34
- expect(subject.policy).to eq PostPolicy
51
+ context "with an array of a symbol and a class with a specified policy class" do
52
+ it "returns the associated namespaced policy" do
53
+ object = described_class.new([:project, Customer::Post])
54
+
55
+ expect(object.policy).to eq Project::PostPolicy
56
+ end
57
+ end
58
+
59
+ context "with an array of a symbol and a class with a specified model name" do
60
+ it "returns the associated namespaced policy" do
61
+ object = described_class.new([:project, CommentsRelation])
62
+
63
+ expect(object.policy).to eq Project::CommentPolicy
35
64
  end
36
65
  end
37
66
 
38
67
  context "with a class" do
39
- it "returns a policy" do
40
- allow(subject).to receive(:find).and_return PostPolicy
41
- expect(subject.policy).to eq PostPolicy
68
+ it "returns the associated policy" do
69
+ object = described_class.new(Post)
70
+
71
+ expect(object.policy).to eq PostPolicy
72
+ end
73
+ end
74
+
75
+ context "with a class which has a specified policy class" do
76
+ it "returns the associated policy" do
77
+ object = described_class.new(Customer::Post)
78
+
79
+ expect(object.policy).to eq PostPolicy
80
+ end
81
+ end
82
+
83
+ context "with an instance which has a specified policy class" do
84
+ it "returns the associated policy" do
85
+ object = described_class.new(Customer::Post.new(user))
86
+
87
+ expect(object.policy).to eq PostPolicy
88
+ end
89
+ end
90
+
91
+ context "with a class which has a specified model name" do
92
+ it "returns the associated policy" do
93
+ object = described_class.new(CommentsRelation)
94
+
95
+ expect(object.policy).to eq CommentPolicy
96
+ end
97
+ end
98
+
99
+ context "with an instance which has a specified policy class" do
100
+ it "returns the associated policy" do
101
+ object = described_class.new(CommentsRelation.new)
102
+
103
+ expect(object.policy).to eq CommentPolicy
42
104
  end
43
105
  end
44
106
 
45
107
  context "with nil" do
46
- it "returns nil" do
47
- allow(subject).to receive(:find).and_return nil
48
- expect(subject.policy).to eq nil
108
+ it "returns a NilClassPolicy" do
109
+ object = described_class.new(nil)
110
+
111
+ expect(object.policy).to eq NilClassPolicy
49
112
  end
50
113
  end
51
114
 
52
- context "with a string that can't be constantized" do
115
+ context "with a class that doesn't have an associated policy" do
53
116
  it "returns nil" do
54
- allow(subject).to receive(:find).and_return "FooPolicy"
55
- expect(subject.policy).to eq nil
117
+ class Foo; end
118
+ object = described_class.new(Foo)
119
+
120
+ expect(object.policy).to eq nil
56
121
  end
57
122
  end
58
123
  end
data/spec/pundit_spec.rb CHANGED
@@ -1,6 +1,8 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "spec_helper"
2
4
 
3
- describe Pundit do
5
+ RSpec.describe Pundit do
4
6
  let(:user) { double }
5
7
  let(:post) { Post.new(user) }
6
8
  let(:customer_post) { Customer::Post.new(user) }
@@ -16,12 +18,33 @@ describe Pundit do
16
18
  let(:tag_four_five_six) { ProjectOneTwoThree::TagFourFiveSix.new(user) }
17
19
  let(:avatar_four_five_six) { ProjectOneTwoThree::AvatarFourFiveSix.new }
18
20
  let(:wiki) { Wiki.new }
21
+ let(:thread) { Thread.new }
19
22
 
20
23
  describe ".authorize" do
21
24
  it "infers the policy and authorizes based on it" do
22
25
  expect(Pundit.authorize(user, post, :update?)).to be_truthy
23
26
  end
24
27
 
28
+ it "returns the record on successful authorization" do
29
+ expect(Pundit.authorize(user, post, :update?)).to eq(post)
30
+ end
31
+
32
+ it "returns the record when passed record with namespace " do
33
+ expect(Pundit.authorize(user, [:project, comment], :update?)).to eq(comment)
34
+ end
35
+
36
+ it "returns the record when passed record with nested namespace " do
37
+ expect(Pundit.authorize(user, [:project, :admin, comment], :update?)).to eq(comment)
38
+ end
39
+
40
+ it "returns the policy name symbol when passed record with headless policy" do
41
+ expect(Pundit.authorize(user, :publication, :create?)).to eq(:publication)
42
+ end
43
+
44
+ it "returns the class when passed record not a particular instance" do
45
+ expect(Pundit.authorize(user, Post, :show?)).to eq(Post)
46
+ end
47
+
25
48
  it "can be given a different policy class" do
26
49
  expect(Pundit.authorize(user, post, :create?, policy_class: PublicationPolicy)).to be_truthy
27
50
  end
@@ -35,7 +58,7 @@ describe Pundit do
35
58
  # rubocop:disable Style/MultilineBlockChain
36
59
  expect do
37
60
  Pundit.authorize(user, post, :destroy?)
38
- end.to raise_error(Pundit::NotAuthorizedError, "not allowed to destroy? this #<Post>") do |error|
61
+ end.to raise_error(Pundit::NotAuthorizedError, "not allowed to destroy? this Post") do |error|
39
62
  expect(error.query).to eq :destroy?
40
63
  expect(error.record).to eq post
41
64
  expect(error.policy).to eq Pundit.policy(user, post)
@@ -88,6 +111,12 @@ describe Pundit do
88
111
  Pundit.policy_scope(user, Wiki)
89
112
  end.to raise_error(Pundit::InvalidConstructorError, "Invalid #<WikiPolicy::Scope> constructor is called")
90
113
  end
114
+
115
+ it "raises an original error with a policy scope that contains error" do
116
+ expect do
117
+ Pundit.policy_scope(user, Thread)
118
+ end.to raise_error(ArgumentError)
119
+ end
91
120
  end
92
121
 
93
122
  describe ".policy_scope!" do
@@ -401,7 +430,23 @@ describe Pundit do
401
430
  end
402
431
 
403
432
  it "returns the record on successful authorization" do
404
- expect(controller.authorize(post)).to be(post)
433
+ expect(controller.authorize(post)).to eq(post)
434
+ end
435
+
436
+ it "returns the record when passed record with namespace " do
437
+ expect(controller.authorize([:project, comment], :update?)).to eq(comment)
438
+ end
439
+
440
+ it "returns the record when passed record with nested namespace " do
441
+ expect(controller.authorize([:project, :admin, comment], :update?)).to eq(comment)
442
+ end
443
+
444
+ it "returns the policy name symbol when passed record with headless policy" do
445
+ expect(controller.authorize(:publication, :create?)).to eq(:publication)
446
+ end
447
+
448
+ it "returns the class when passed record not a particular instance" do
449
+ expect(controller.authorize(Post, :show?)).to eq(Post)
405
450
  end
406
451
 
407
452
  it "can be given a different permission to check" do
@@ -511,11 +556,13 @@ describe Pundit do
511
556
 
512
557
  describe "#permitted_attributes" do
513
558
  it "checks policy for permitted attributes" do
514
- params = ActionController::Parameters.new(post: {
515
- title: "Hello",
516
- votes: 5,
517
- admin: true
518
- })
559
+ params = ActionController::Parameters.new(
560
+ post: {
561
+ title: "Hello",
562
+ votes: 5,
563
+ admin: true
564
+ }
565
+ )
519
566
 
520
567
  action = "update"
521
568
 
@@ -527,11 +574,13 @@ describe Pundit do
527
574
  end
528
575
 
529
576
  it "checks policy for permitted attributes for record of a ActiveModel type" do
530
- params = ActionController::Parameters.new(customer_post: {
531
- title: "Hello",
532
- votes: 5,
533
- admin: true
534
- })
577
+ params = ActionController::Parameters.new(
578
+ customer_post: {
579
+ title: "Hello",
580
+ votes: 5,
581
+ admin: true
582
+ }
583
+ )
535
584
 
536
585
  action = "update"
537
586
 
@@ -547,24 +596,28 @@ describe Pundit do
547
596
 
548
597
  describe "#permitted_attributes_for_action" do
549
598
  it "is checked if it is defined in the policy" do
550
- params = ActionController::Parameters.new(post: {
551
- title: "Hello",
552
- body: "blah",
553
- votes: 5,
554
- admin: true
555
- })
599
+ params = ActionController::Parameters.new(
600
+ post: {
601
+ title: "Hello",
602
+ body: "blah",
603
+ votes: 5,
604
+ admin: true
605
+ }
606
+ )
556
607
 
557
608
  action = "revise"
558
609
  expect(Controller.new(user, action, params).permitted_attributes(post).to_h).to eq("body" => "blah")
559
610
  end
560
611
 
561
612
  it "can be explicitly set" do
562
- params = ActionController::Parameters.new(post: {
563
- title: "Hello",
564
- body: "blah",
565
- votes: 5,
566
- admin: true
567
- })
613
+ params = ActionController::Parameters.new(
614
+ post: {
615
+ title: "Hello",
616
+ body: "blah",
617
+ votes: 5,
618
+ admin: true
619
+ }
620
+ )
568
621
 
569
622
  action = "update"
570
623
  expect(Controller.new(user, action, params).permitted_attributes(post, :revise).to_h).to eq("body" => "blah")
data/spec/spec_helper.rb CHANGED
@@ -1,3 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "simplecov"
4
+ SimpleCov.start do
5
+ add_filter "/spec/"
6
+ end
7
+
1
8
  require "pundit"
2
9
  require "pundit/rspec"
3
10
 
@@ -9,22 +16,6 @@ require "active_support/core_ext"
9
16
  require "active_model/naming"
10
17
  require "action_controller/metal/strong_parameters"
11
18
 
12
- I18n.enforce_available_locales = false
13
-
14
- module PunditSpecHelper
15
- extend RSpec::Matchers::DSL
16
-
17
- matcher :be_truthy do
18
- match do |actual|
19
- actual
20
- end
21
- end
22
- end
23
-
24
- RSpec.configure do |config|
25
- config.include PunditSpecHelper
26
- end
27
-
28
19
  class PostPolicy < Struct.new(:user, :post)
29
20
  class Scope < Struct.new(:user, :scope)
30
21
  def resolve
@@ -84,10 +75,6 @@ module Customer
84
75
  def self.policy_class
85
76
  PostPolicy
86
77
  end
87
-
88
- def policy_class
89
- self.class.policy_class
90
- end
91
78
  end
92
79
  end
93
80
 
@@ -135,7 +122,7 @@ class CommentsRelation
135
122
  @empty
136
123
  end
137
124
 
138
- def model_name
125
+ def self.model_name
139
126
  Comment.model_name
140
127
  end
141
128
  end
@@ -172,6 +159,10 @@ class CriteriaPolicy < Struct.new(:user, :criteria); end
172
159
 
173
160
  module Project
174
161
  class CommentPolicy < Struct.new(:user, :comment)
162
+ def update?
163
+ true
164
+ end
165
+
175
166
  class Scope < Struct.new(:user, :scope)
176
167
  def resolve
177
168
  scope
@@ -188,6 +179,14 @@ module Project
188
179
  end
189
180
  end
190
181
  end
182
+
183
+ module Admin
184
+ class CommentPolicy < Struct.new(:user, :comment)
185
+ def update?
186
+ true
187
+ end
188
+ end
189
+ end
191
190
  end
192
191
 
193
192
  class DenierPolicy < Struct.new(:user, :record)
@@ -199,9 +198,9 @@ end
199
198
  class Controller
200
199
  include Pundit
201
200
  # Mark protected methods public so they may be called in test
202
- # rubocop:disable Layout/AccessModifierIndentation, Style/AccessModifierDeclarations
201
+ # rubocop:disable Style/AccessModifierDeclarations
203
202
  public(*Pundit.protected_instance_methods)
204
- # rubocop:enable Layout/AccessModifierIndentation, Style/AccessModifierDeclarations
203
+ # rubocop:enable Style/AccessModifierDeclarations
205
204
 
206
205
  attr_reader :current_user, :action_name, :params
207
206
 
@@ -236,6 +235,18 @@ class WikiPolicy
236
235
  end
237
236
  end
238
237
 
238
+ class Thread
239
+ def self.all; end
240
+ end
241
+ class ThreadPolicy < Struct.new(:user, :thread)
242
+ class Scope < Struct.new(:user, :scope)
243
+ def resolve
244
+ # deliberate wrong useage of the method
245
+ scope.all(:unvalid, :parameters)
246
+ end
247
+ end
248
+ end
249
+
239
250
  class PostFourFiveSix < Struct.new(:user); end
240
251
 
241
252
  class CommentFourFiveSix; extend ActiveModel::Naming; end
metadata CHANGED
@@ -1,15 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pundit
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0
4
+ version: 2.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jonas Nicklas
8
- - Elabs AB
8
+ - Varvet AB
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2018-07-21 00:00:00.000000000 Z
12
+ date: 2021-08-13 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activesupport
@@ -25,6 +25,132 @@ dependencies:
25
25
  - - ">="
26
26
  - !ruby/object:Gem::Version
27
27
  version: 3.0.0
28
+ - !ruby/object:Gem::Dependency
29
+ name: actionpack
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - ">="
33
+ - !ruby/object:Gem::Version
34
+ version: 3.0.0
35
+ type: :development
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - ">="
40
+ - !ruby/object:Gem::Version
41
+ version: 3.0.0
42
+ - !ruby/object:Gem::Dependency
43
+ name: activemodel
44
+ requirement: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - ">="
47
+ - !ruby/object:Gem::Version
48
+ version: 3.0.0
49
+ type: :development
50
+ prerelease: false
51
+ version_requirements: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ version: 3.0.0
56
+ - !ruby/object:Gem::Dependency
57
+ name: bundler
58
+ requirement: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: '0'
63
+ type: :development
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ - !ruby/object:Gem::Dependency
71
+ name: pry
72
+ requirement: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ version: '0'
77
+ type: :development
78
+ prerelease: false
79
+ version_requirements: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - ">="
82
+ - !ruby/object:Gem::Version
83
+ version: '0'
84
+ - !ruby/object:Gem::Dependency
85
+ name: rake
86
+ requirement: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - ">="
89
+ - !ruby/object:Gem::Version
90
+ version: '0'
91
+ type: :development
92
+ prerelease: false
93
+ version_requirements: !ruby/object:Gem::Requirement
94
+ requirements:
95
+ - - ">="
96
+ - !ruby/object:Gem::Version
97
+ version: '0'
98
+ - !ruby/object:Gem::Dependency
99
+ name: rspec
100
+ requirement: !ruby/object:Gem::Requirement
101
+ requirements:
102
+ - - ">="
103
+ - !ruby/object:Gem::Version
104
+ version: 3.0.0
105
+ type: :development
106
+ prerelease: false
107
+ version_requirements: !ruby/object:Gem::Requirement
108
+ requirements:
109
+ - - ">="
110
+ - !ruby/object:Gem::Version
111
+ version: 3.0.0
112
+ - !ruby/object:Gem::Dependency
113
+ name: rubocop
114
+ requirement: !ruby/object:Gem::Requirement
115
+ requirements:
116
+ - - '='
117
+ - !ruby/object:Gem::Version
118
+ version: 0.74.0
119
+ type: :development
120
+ prerelease: false
121
+ version_requirements: !ruby/object:Gem::Requirement
122
+ requirements:
123
+ - - '='
124
+ - !ruby/object:Gem::Version
125
+ version: 0.74.0
126
+ - !ruby/object:Gem::Dependency
127
+ name: simplecov
128
+ requirement: !ruby/object:Gem::Requirement
129
+ requirements:
130
+ - - ">="
131
+ - !ruby/object:Gem::Version
132
+ version: 0.17.0
133
+ type: :development
134
+ prerelease: false
135
+ version_requirements: !ruby/object:Gem::Requirement
136
+ requirements:
137
+ - - ">="
138
+ - !ruby/object:Gem::Version
139
+ version: 0.17.0
140
+ - !ruby/object:Gem::Dependency
141
+ name: yard
142
+ requirement: !ruby/object:Gem::Requirement
143
+ requirements:
144
+ - - ">="
145
+ - !ruby/object:Gem::Version
146
+ version: '0'
147
+ type: :development
148
+ prerelease: false
149
+ version_requirements: !ruby/object:Gem::Requirement
150
+ requirements:
151
+ - - ">="
152
+ - !ruby/object:Gem::Version
153
+ version: '0'
28
154
  description: Object oriented authorization for Rails applications
29
155
  email:
30
156
  - jonas.nicklas@gmail.com
@@ -82,8 +208,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
82
208
  - !ruby/object:Gem::Version
83
209
  version: '0'
84
210
  requirements: []
85
- rubyforge_project:
86
- rubygems_version: 2.7.6
211
+ rubygems_version: 3.2.25
87
212
  signing_key:
88
213
  specification_version: 4
89
214
  summary: OO authorization for Rails