pundit 2.3.1 → 2.3.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b5c9e118c59bc3a683734817ac6fb9036a2b909df7abce2dbbdc00fc16aebdf7
4
- data.tar.gz: 843cc1b7652e88d598a37a28f93bf13c41710bf3dddefeb96acf74e659279581
3
+ metadata.gz: aa554bffd828649aeac4e79a802070d4e68948beacbc9c991fddab7141a965c9
4
+ data.tar.gz: edf9be8366e5dfcb541eff929e99a04c2bfb23b800214bc39d68c790b32d7365
5
5
  SHA512:
6
- metadata.gz: f2430ece33471f7a321a124aeafab7dbc3be4688fbda581b758d90649f4ae06d0cbaf86df768e881d0a2f1c0ab55581cb5dc0f1d3012ab7611b2fd81b8a0f321
7
- data.tar.gz: 3432cc545ca5139cfcd7e1fc26a17d0882c11de71a3c6949c1d1da232183eba495de18aa06b64462c1233b820099788b43feeb0e9b439911bc9761dc7bd1e141
6
+ metadata.gz: 555ccc09f0cc62c3e1da52a7eafb2c3e4805a303c884da39c2ed1c8fc13583727d3e060381ed761f5dd06fdcc71cc3f98c4c991e64db8ac3ff5ff5a460f64aac
7
+ data.tar.gz: be290f6d6253367e0911525969fc8bb8972db670626bd9803ccd6e7fc1a1504afd1c921a5aac506ee9cfe559a9863bfb469dca3a656909b7ffa1d74aa4c6ea36
@@ -0,0 +1,8 @@
1
+ ## To do
2
+
3
+ - [ ] Commit changes:
4
+ - [ ] Bump `Pundit::VERSION` in `lib/pundit/version.rb`.
5
+ - [ ] Update `CHANGELOG.md`.
6
+ - [ ] Run `rake release`.
7
+ - [ ] Open pull request 🚀
8
+ - [ ] Make an announcement in [Pundit discussions](https://github.com/varvet/pundit/discussions/categories/announcements).
@@ -0,0 +1,107 @@
1
+ name: Main
2
+
3
+ on:
4
+ push:
5
+ branches: [ "main" ]
6
+ pull_request:
7
+ branches: [ "main" ]
8
+ workflow_dispatch:
9
+
10
+ permissions:
11
+ contents: read
12
+
13
+ env:
14
+ CC_TEST_REPORTER_ID: "ac477089fe20ab4fc7e0d304cab75f72d73d58a7596d366935d18fcc7d51f8f9"
15
+
16
+ # `github.ref` points to the *merge commit* when running tests on a pull request, which will be a commit
17
+ # that doesn't exists in our code base. Since this workflow triggers from a PR, we use the HEAD SHA instead.
18
+ #
19
+ # NOTE: These are both used by Code Climate (cc-test-reporter).
20
+ GIT_COMMIT_SHA: ${{ github.event.pull_request.head.sha }}
21
+ GIT_BRANCH: ${{ github.head_ref }}
22
+
23
+ jobs:
24
+ matrix-test:
25
+ runs-on: ubuntu-latest
26
+ continue-on-error: ${{ matrix.allow-failure || false }}
27
+ strategy:
28
+ fail-fast: false
29
+ matrix:
30
+ ruby-version:
31
+ - '3.1'
32
+ - '3.2'
33
+ - '3.3'
34
+ - 'jruby-9.3.10' # oldest supported jruby
35
+ - 'jruby'
36
+ include: # HEAD-versions
37
+ - ruby-version: 'head'
38
+ allow-failure: true
39
+ - ruby-version: 'jruby-head'
40
+ allow-failure: true
41
+ - ruby-version: 'truffleruby-head'
42
+ allow-failure: true
43
+
44
+ steps:
45
+ - uses: actions/checkout@v3
46
+ - name: Set up Ruby
47
+ uses: ruby/setup-ruby@v1
48
+ with:
49
+ rubygems: latest
50
+ ruby-version: ${{ matrix.ruby-version }}
51
+ bundler-cache: true
52
+ - name: Run tests
53
+ run: bundle exec rspec
54
+
55
+ test:
56
+ runs-on: ubuntu-latest
57
+ steps:
58
+ - uses: actions/checkout@v3
59
+ - name: Set up Ruby
60
+ uses: ruby/setup-ruby@v1
61
+ with:
62
+ rubygems: latest
63
+ ruby-version: 'ruby'
64
+ bundler-cache: true
65
+ - name: "Download cc-test-reporter from codeclimate.com"
66
+ run: |
67
+ curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter
68
+ chmod +x ./cc-test-reporter
69
+ - name: "Report to Code Climate that we will send a coverage report."
70
+ run: ./cc-test-reporter before-build
71
+ - name: Run tests
72
+ run: bundle exec rspec
73
+ env:
74
+ COVERAGE: 1
75
+ - name: Upload code coverage to Code Climate
76
+ run: |
77
+ ./cc-test-reporter after-build \
78
+ --coverage-input-type simplecov \
79
+ ./coverage/.resultset.json
80
+
81
+ rubocop:
82
+ runs-on: ubuntu-latest
83
+ steps:
84
+ - uses: actions/checkout@v3
85
+ - name: Set up Ruby
86
+ uses: ruby/setup-ruby@v1
87
+ with:
88
+ rubygems: default
89
+ ruby-version: 'ruby'
90
+ bundler-cache: false
91
+ - run: bundle install
92
+ - name: Run RuboCop
93
+ run: bundle exec rubocop
94
+
95
+ required-checks:
96
+ runs-on: ubuntu-latest
97
+ if: ${{ always() }}
98
+ needs:
99
+ - test
100
+ - matrix-test
101
+ - rubocop
102
+ steps:
103
+ - name: failure
104
+ if: ${{ failure() || contains(needs.*.result, 'failure') }}
105
+ run: exit 1
106
+ - name: success
107
+ run: exit 0
@@ -0,0 +1,33 @@
1
+ name: Push Gem
2
+
3
+ on:
4
+ workflow_dispatch:
5
+
6
+ permissions:
7
+ contents: read
8
+
9
+ jobs:
10
+ push:
11
+ if: github.repository == 'varvet/pundit'
12
+ runs-on: ubuntu-latest
13
+
14
+ permissions:
15
+ contents: write
16
+ id-token: write
17
+
18
+ steps:
19
+ # Set up
20
+ - name: Harden Runner
21
+ uses: step-security/harden-runner@a4aa98b93cab29d9b1101a6143fb8bce00e2eac4 # v2.7.1
22
+ with:
23
+ egress-policy: audit
24
+
25
+ - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4
26
+ - name: Set up Ruby
27
+ uses: ruby/setup-ruby@cacc9f1c0b3f4eb8a16a6bb0ed10897b43b9de49 # v1.176.0
28
+ with:
29
+ bundler-cache: true
30
+ ruby-version: ruby
31
+
32
+ # Release
33
+ - uses: rubygems/release-gem@612653d273a73bdae1df8453e090060bb4db5f31 # v1
data/.rubocop.yml CHANGED
@@ -1,7 +1,10 @@
1
1
  AllCops:
2
- TargetRubyVersion: 2.6
2
+ TargetRubyVersion: 3.1
3
3
  Exclude:
4
4
  - "lib/generators/**/templates/**/*"
5
+ <% `git status --ignored --porcelain`.lines.grep(/^!! /).each do |path| %>
6
+ - <%= path.sub(/^!! /, '').sub(/\/$/, '/**/*') %>
7
+ <% end %>
5
8
  SuggestExtensions: false
6
9
  NewCops: disable
7
10
 
@@ -20,15 +23,6 @@ Metrics/ModuleLength:
20
23
  Layout/LineLength:
21
24
  Max: 120
22
25
 
23
- Metrics/AbcSize:
24
- Enabled: false
25
-
26
- Metrics/CyclomaticComplexity:
27
- Enabled: false
28
-
29
- Metrics/PerceivedComplexity:
30
- Enabled: false
31
-
32
26
  Gemspec/RequiredRubyVersion:
33
27
  Enabled: false
34
28
 
@@ -59,14 +53,11 @@ Style/StringLiteralsInInterpolation:
59
53
  Style/StructInheritance:
60
54
  Enabled: false
61
55
 
62
- Style/AndOr:
63
- Enabled: false
64
-
65
- Style/Not:
66
- Enabled: false
67
-
68
56
  Style/DoubleNegation:
69
57
  Enabled: false
70
58
 
71
59
  Style/Documentation:
72
60
  Enabled: false # TODO: Enable again once we have more docs
61
+
62
+ Style/HashSyntax:
63
+ EnforcedShorthandSyntax: never
data/CHANGELOG.md CHANGED
@@ -2,7 +2,18 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
- Nothing.
5
+ ## 2.3.2 (2024-05-08)
6
+
7
+ - Refactor: First pass of Pundit::Context (#797)
8
+
9
+ ## Changed
10
+
11
+ - Update `ApplicationPolicy` generator to qualify the `Scope` class name (#792)
12
+ - Policy generator uses `NoMethodError` to indicate `#resolve` is not implemented (#776)
13
+
14
+ ## Deprecated
15
+
16
+ - Dropped support for Ruby 3.0 (#796)
6
17
 
7
18
  ## 2.3.1 (2023-07-17)
8
19
 
data/CONTRIBUTING.md CHANGED
@@ -20,7 +20,7 @@ Pundit version, OS version and any stack traces you have are very valuable.
20
20
  - **Document any change in behaviour**. Make sure the README and any other
21
21
  relevant documentation are kept up-to-date.
22
22
 
23
- - **Create topic branches**. Please don't ask us to pull from your master branch.
23
+ - **Create topic branches**. Please don't ask us to pull from your main branch.
24
24
 
25
25
  - **One pull request per feature**. If you want to do more than one thing, send
26
26
  multiple pull requests.
data/Gemfile CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  source "https://rubygems.org"
4
4
 
5
- ruby RUBY_VERSION
6
-
7
5
  gemspec
6
+
7
+ # https://github.com/ruby/psych/issues/655
8
+ gem "psych", "!= 5.1.1", platforms: %i[jruby]
data/README.md CHANGED
@@ -1,24 +1,22 @@
1
1
  # Pundit
2
2
 
3
- [![Build Status](https://app.travis-ci.com/varvet/pundit.svg?branch=main)](https://app.travis-ci.com/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)
3
+ [![Main](https://github.com/varvet/pundit/actions/workflows/main.yml/badge.svg)](https://github.com/varvet/pundit/actions/workflows/main.yml)
4
+ [![Code Climate](https://api.codeclimate.com/v1/badges/a940030f96c9fb43046a/maintainability)](https://codeclimate.com/github/varvet/pundit/maintainability)
5
+ [![Inline docs](http://inch-ci.org/github/varvet/pundit.svg?branch=main)](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
9
9
  classes and object oriented design patterns to build a straightforward, robust, and
10
10
  scalable authorization system.
11
11
 
12
- Links:
12
+ ## Links:
13
13
 
14
14
  - [API documentation for the most recent version](http://www.rubydoc.info/gems/pundit)
15
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)
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
18
 
19
- Sponsored by:
20
-
21
- [<img src="https://www.varvet.com/images/wordmark-red.svg" alt="Varvet" height="50px"/>](https://www.varvet.com)
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>
22
20
 
23
21
  ## Installation
24
22
 
@@ -279,7 +277,7 @@ generator, or create your own base class to inherit from:
279
277
 
280
278
  ``` ruby
281
279
  class PostPolicy < ApplicationPolicy
282
- class Scope < Scope
280
+ class Scope < ApplicationPolicy::Scope
283
281
  def resolve
284
282
  if user.admin?
285
283
  scope.all
@@ -476,7 +474,7 @@ example, associations which might be `nil`.
476
474
 
477
475
  ```ruby
478
476
  class NilClassPolicy < ApplicationPolicy
479
- class Scope < Scope
477
+ class Scope < ApplicationPolicy::Scope
480
478
  def resolve
481
479
  raise Pundit::NotDefinedError, "Cannot scope NilClass"
482
480
  end
@@ -43,7 +43,7 @@ class ApplicationPolicy
43
43
  end
44
44
 
45
45
  def resolve
46
- raise NotImplementedError, "You must define #resolve in #{self.class}"
46
+ raise NoMethodError, "You must define #resolve in #{self.class}"
47
47
  end
48
48
 
49
49
  private
@@ -1,6 +1,12 @@
1
1
  <% module_namespacing do -%>
2
2
  class <%= class_name %>Policy < ApplicationPolicy
3
- class Scope < Scope
3
+ # NOTE: Up to Pundit v2.3.1, the inheritance was declared as
4
+ # `Scope < Scope` rather than `Scope < ApplicationPolicy::Scope`.
5
+ # In most cases the behavior will be identical, but if updating existing
6
+ # code, beware of possible changes to the ancestors:
7
+ # https://gist.github.com/Burgestrand/4b4bc22f31c8a95c425fc0e30d7ef1f5
8
+
9
+ class Scope < ApplicationPolicy::Scope
4
10
  # NOTE: Be explicit about which records you allow access to!
5
11
  # def resolve
6
12
  # scope.all
@@ -15,6 +15,14 @@ module Pundit
15
15
 
16
16
  protected
17
17
 
18
+ # @return [Pundit::Context] a new instance of {Pundit::Context} with the current user
19
+ def pundit
20
+ @pundit ||= Pundit::Context.new(
21
+ user: pundit_user,
22
+ policy_cache: Pundit::CacheStore::LegacyStore.new(policies)
23
+ )
24
+ end
25
+
18
26
  # @return [Boolean] whether authorization has been performed, i.e. whether
19
27
  # one {#authorize} or {#skip_authorization} has been called
20
28
  def pundit_policy_authorized?
@@ -64,7 +72,7 @@ module Pundit
64
72
 
65
73
  @_pundit_policy_authorized = true
66
74
 
67
- Pundit.authorize(pundit_user, record, query, policy_class: policy_class, cache: policies)
75
+ pundit.authorize(record, query: query, policy_class: policy_class)
68
76
  end
69
77
 
70
78
  # Allow this action not to perform authorization.
@@ -98,9 +106,9 @@ module Pundit
98
106
  #
99
107
  # @see https://github.com/varvet/pundit#policies
100
108
  # @param record [Object] the object we're retrieving the policy for
101
- # @return [Object, nil] instance of policy class with query methods
109
+ # @return [Object] instance of policy class with query methods
102
110
  def policy(record)
103
- policies[record] ||= Pundit.policy!(pundit_user, record)
111
+ pundit.policy!(record)
104
112
  end
105
113
 
106
114
  # Retrieves a set of permitted attributes from the policy by instantiating
@@ -162,7 +170,7 @@ module Pundit
162
170
  private
163
171
 
164
172
  def pundit_policy_scope(scope)
165
- policy_scopes[scope] ||= Pundit.policy_scope!(pundit_user, scope)
173
+ policy_scopes[scope] ||= pundit.policy_scope!(scope)
166
174
  end
167
175
  end
168
176
  end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pundit
4
+ module CacheStore
5
+ # @api private
6
+ class LegacyStore
7
+ def initialize(hash = {})
8
+ @store = hash
9
+ end
10
+
11
+ def fetch(user:, record:)
12
+ _ = user
13
+ @store[record] ||= yield
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pundit
4
+ module CacheStore
5
+ # @api private
6
+ class NullStore
7
+ @instance = new
8
+
9
+ class << self
10
+ attr_reader :instance
11
+ end
12
+
13
+ def fetch(*, **)
14
+ yield
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,127 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pundit
4
+ class Context
5
+ def initialize(user:, policy_cache: CacheStore::NullStore.instance)
6
+ @user = user
7
+ @policy_cache = policy_cache
8
+ end
9
+
10
+ attr_reader :user
11
+
12
+ # @api private
13
+ attr_reader :policy_cache
14
+
15
+ # Retrieves the policy for the given record, initializing it with the
16
+ # record and user and finally throwing an error if the user is not
17
+ # authorized to perform the given action.
18
+ #
19
+ # @param user [Object] the user that initiated the action
20
+ # @param possibly_namespaced_record [Object, Array] the object we're checking permissions of
21
+ # @param query [Symbol, String] the predicate method to check on the policy (e.g. `:show?`)
22
+ # @param policy_class [Class] the policy class we want to force use of
23
+ # @raise [NotAuthorizedError] if the given query method returned false
24
+ # @return [Object] Always returns the passed object record
25
+ def authorize(possibly_namespaced_record, query:, policy_class:)
26
+ record = pundit_model(possibly_namespaced_record)
27
+ policy = if policy_class
28
+ policy_class.new(user, record)
29
+ else
30
+ policy!(possibly_namespaced_record)
31
+ end
32
+
33
+ raise NotAuthorizedError, query: query, record: record, policy: policy unless policy.public_send(query)
34
+
35
+ record
36
+ end
37
+
38
+ # Retrieves the policy scope for the given record.
39
+ #
40
+ # @see https://github.com/varvet/pundit#scopes
41
+ # @param user [Object] the user that initiated the action
42
+ # @param scope [Object] the object we're retrieving the policy scope for
43
+ # @raise [InvalidConstructorError] if the policy constructor called incorrectly
44
+ # @return [Scope{#resolve}, nil] instance of scope class which can resolve to a scope
45
+ def policy_scope(scope)
46
+ policy_scope_class = policy_finder(scope).scope
47
+ return unless policy_scope_class
48
+
49
+ begin
50
+ policy_scope = policy_scope_class.new(user, pundit_model(scope))
51
+ rescue ArgumentError
52
+ raise InvalidConstructorError, "Invalid #<#{policy_scope_class}> constructor is called"
53
+ end
54
+
55
+ policy_scope.resolve
56
+ end
57
+
58
+ # Retrieves the policy scope for the given record. Raises if not found.
59
+ #
60
+ # @see https://github.com/varvet/pundit#scopes
61
+ # @param user [Object] the user that initiated the action
62
+ # @param scope [Object] the object we're retrieving the policy scope for
63
+ # @raise [NotDefinedError] if the policy scope cannot be found
64
+ # @raise [InvalidConstructorError] if the policy constructor called incorrectly
65
+ # @return [Scope{#resolve}] instance of scope class which can resolve to a scope
66
+ def policy_scope!(scope)
67
+ policy_scope_class = policy_finder(scope).scope!
68
+ return unless policy_scope_class
69
+
70
+ begin
71
+ policy_scope = policy_scope_class.new(user, pundit_model(scope))
72
+ rescue ArgumentError
73
+ raise InvalidConstructorError, "Invalid #<#{policy_scope_class}> constructor is called"
74
+ end
75
+
76
+ policy_scope.resolve
77
+ end
78
+
79
+ # Retrieves the policy for the given record.
80
+ #
81
+ # @see https://github.com/varvet/pundit#policies
82
+ # @param user [Object] the user that initiated the action
83
+ # @param record [Object] the object we're retrieving the policy for
84
+ # @raise [InvalidConstructorError] if the policy constructor called incorrectly
85
+ # @return [Object, nil] instance of policy class with query methods
86
+ def policy(record)
87
+ cached_find(record, &:policy)
88
+ end
89
+
90
+ # Retrieves the policy for the given record. Raises if not found.
91
+ #
92
+ # @see https://github.com/varvet/pundit#policies
93
+ # @param user [Object] the user that initiated the action
94
+ # @param record [Object] the object we're retrieving the policy for
95
+ # @raise [NotDefinedError] if the policy cannot be found
96
+ # @raise [InvalidConstructorError] if the policy constructor called incorrectly
97
+ # @return [Object] instance of policy class with query methods
98
+ def policy!(record)
99
+ cached_find(record, &:policy!)
100
+ end
101
+
102
+ private
103
+
104
+ def cached_find(record)
105
+ policy_cache.fetch(user: user, record: record) do
106
+ klass = yield policy_finder(record)
107
+ next unless klass
108
+
109
+ model = pundit_model(record)
110
+
111
+ begin
112
+ klass.new(user, model)
113
+ rescue ArgumentError
114
+ raise InvalidConstructorError, "Invalid #<#{klass}> constructor is called"
115
+ end
116
+ end
117
+ end
118
+
119
+ def policy_finder(record)
120
+ PolicyFinder.new(record)
121
+ end
122
+
123
+ def pundit_model(record)
124
+ record.is_a?(Array) ? record.last : record
125
+ end
126
+ end
127
+ end
@@ -56,7 +56,7 @@ module Pundit
56
56
 
57
57
  # @return [String] the name of the key this object would have in a params hash
58
58
  #
59
- def param_key
59
+ def param_key # rubocop:disable Metrics/AbcSize
60
60
  model = object.is_a?(Array) ? object.last : object
61
61
 
62
62
  if model.respond_to?(:model_name)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Pundit
4
- VERSION = "2.3.1"
4
+ VERSION = "2.3.2"
5
5
  end
data/lib/pundit.rb CHANGED
@@ -8,6 +8,9 @@ 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
  require "pundit/authorization"
11
+ require "pundit/context"
12
+ require "pundit/cache_store/null_store"
13
+ require "pundit/cache_store/legacy_store"
11
14
 
12
15
  # @api private
13
16
  # To avoid name clashes with common Error naming when mixing in Pundit,
@@ -64,104 +67,35 @@ module Pundit
64
67
  end
65
68
 
66
69
  class << self
67
- # Retrieves the policy for the given record, initializing it with the
68
- # record and user and finally throwing an error if the user is not
69
- # authorized to perform the given action.
70
- #
71
- # @param user [Object] the user that initiated the action
72
- # @param possibly_namespaced_record [Object, Array] the object we're checking permissions of
73
- # @param query [Symbol, String] the predicate method to check on the policy (e.g. `:show?`)
74
- # @param policy_class [Class] the policy class we want to force use of
75
- # @param cache [#[], #[]=] a Hash-like object to cache the found policy instance in
76
- # @raise [NotAuthorizedError] if the given query method returned false
77
- # @return [Object] Always returns the passed object record
78
- def authorize(user, possibly_namespaced_record, query, policy_class: nil, cache: {})
79
- record = pundit_model(possibly_namespaced_record)
80
- policy = if policy_class
81
- policy_class.new(user, record)
70
+ # @see [Pundit::Context#authorize]
71
+ def authorize(user, record, query, policy_class: nil, cache: nil)
72
+ context = if cache
73
+ Context.new(user: user, policy_cache: cache)
82
74
  else
83
- cache[possibly_namespaced_record] ||= policy!(user, possibly_namespaced_record)
75
+ Context.new(user: user)
84
76
  end
85
77
 
86
- raise NotAuthorizedError, query: query, record: record, policy: policy unless policy.public_send(query)
87
-
88
- record
89
- end
90
-
91
- # Retrieves the policy scope for the given record.
92
- #
93
- # @see https://github.com/varvet/pundit#scopes
94
- # @param user [Object] the user that initiated the action
95
- # @param scope [Object] the object we're retrieving the policy scope for
96
- # @raise [InvalidConstructorError] if the policy constructor called incorrectly
97
- # @return [Scope{#resolve}, nil] instance of scope class which can resolve to a scope
98
- def policy_scope(user, scope)
99
- policy_scope_class = PolicyFinder.new(scope).scope
100
- return unless policy_scope_class
101
-
102
- begin
103
- policy_scope = policy_scope_class.new(user, pundit_model(scope))
104
- rescue ArgumentError
105
- raise InvalidConstructorError, "Invalid #<#{policy_scope_class}> constructor is called"
106
- end
107
-
108
- policy_scope.resolve
78
+ context.authorize(record, query: query, policy_class: policy_class)
109
79
  end
110
80
 
111
- # Retrieves the policy scope for the given record.
112
- #
113
- # @see https://github.com/varvet/pundit#scopes
114
- # @param user [Object] the user that initiated the action
115
- # @param scope [Object] the object we're retrieving the policy scope for
116
- # @raise [NotDefinedError] if the policy scope cannot be found
117
- # @raise [InvalidConstructorError] if the policy constructor called incorrectly
118
- # @return [Scope{#resolve}] instance of scope class which can resolve to a scope
119
- def policy_scope!(user, scope)
120
- policy_scope_class = PolicyFinder.new(scope).scope!
121
- return unless policy_scope_class
122
-
123
- begin
124
- policy_scope = policy_scope_class.new(user, pundit_model(scope))
125
- rescue ArgumentError
126
- raise InvalidConstructorError, "Invalid #<#{policy_scope_class}> constructor is called"
127
- end
128
-
129
- policy_scope.resolve
81
+ # @see [Pundit::Context#policy_scope]
82
+ def policy_scope(user, *args, **kwargs, &block)
83
+ Context.new(user: user).policy_scope(*args, **kwargs, &block)
130
84
  end
131
85
 
132
- # Retrieves the policy for the given record.
133
- #
134
- # @see https://github.com/varvet/pundit#policies
135
- # @param user [Object] the user that initiated the action
136
- # @param record [Object] the object we're retrieving the policy for
137
- # @raise [InvalidConstructorError] if the policy constructor called incorrectly
138
- # @return [Object, nil] instance of policy class with query methods
139
- def policy(user, record)
140
- policy = PolicyFinder.new(record).policy
141
- policy&.new(user, pundit_model(record))
142
- rescue ArgumentError
143
- raise InvalidConstructorError, "Invalid #<#{policy}> constructor is called"
86
+ # @see [Pundit::Context#policy_scope!]
87
+ def policy_scope!(user, *args, **kwargs, &block)
88
+ Context.new(user: user).policy_scope!(*args, **kwargs, &block)
144
89
  end
145
90
 
146
- # Retrieves the policy for the given record.
147
- #
148
- # @see https://github.com/varvet/pundit#policies
149
- # @param user [Object] the user that initiated the action
150
- # @param record [Object] the object we're retrieving the policy for
151
- # @raise [NotDefinedError] if the policy cannot be found
152
- # @raise [InvalidConstructorError] if the policy constructor called incorrectly
153
- # @return [Object] instance of policy class with query methods
154
- def policy!(user, record)
155
- policy = PolicyFinder.new(record).policy!
156
- policy.new(user, pundit_model(record))
157
- rescue ArgumentError
158
- raise InvalidConstructorError, "Invalid #<#{policy}> constructor is called"
91
+ # @see [Pundit::Context#policy]
92
+ def policy(user, *args, **kwargs, &block)
93
+ Context.new(user: user).policy(*args, **kwargs, &block)
159
94
  end
160
95
 
161
- private
162
-
163
- def pundit_model(record)
164
- record.is_a?(Array) ? record.last : record
96
+ # @see [Pundit::Context#policy!]
97
+ def policy!(user, *args, **kwargs, &block)
98
+ Context.new(user: user).policy!(*args, **kwargs, &block)
165
99
  end
166
100
  end
167
101
 
data/pundit.gemspec CHANGED
@@ -29,7 +29,7 @@ Gem::Specification.new do |gem|
29
29
  gem.add_development_dependency "railties", ">= 3.0.0"
30
30
  gem.add_development_dependency "rake"
31
31
  gem.add_development_dependency "rspec", ">= 3.0.0"
32
- gem.add_development_dependency "rubocop", "1.24.0"
32
+ gem.add_development_dependency "rubocop"
33
33
  gem.add_development_dependency "simplecov", ">= 0.17.0"
34
34
  gem.add_development_dependency "yard"
35
35
  end
@@ -3,10 +3,13 @@
3
3
  require "spec_helper"
4
4
 
5
5
  describe Pundit::Authorization do
6
- let(:controller) { Controller.new(user, "update", {}) }
6
+ def to_params(*args, **kwargs, &block)
7
+ ActionController::Parameters.new(*args, **kwargs, &block)
8
+ end
9
+
10
+ let(:controller) { Controller.new(user, "update", to_params({})) }
7
11
  let(:user) { double }
8
12
  let(:post) { Post.new(user) }
9
- let(:customer_post) { Customer::Post.new(user) }
10
13
  let(:comment) { Comment.new }
11
14
  let(:article) { Article.new }
12
15
  let(:article_tag) { ArticleTag.new }
@@ -188,7 +191,7 @@ describe Pundit::Authorization do
188
191
 
189
192
  describe "#permitted_attributes" do
190
193
  it "checks policy for permitted attributes" do
191
- params = ActionController::Parameters.new(
194
+ params = to_params(
192
195
  post: {
193
196
  title: "Hello",
194
197
  votes: 5,
@@ -206,7 +209,8 @@ describe Pundit::Authorization do
206
209
  end
207
210
 
208
211
  it "checks policy for permitted attributes for record of a ActiveModel type" do
209
- params = ActionController::Parameters.new(
212
+ customer_post = Customer::Post.new(user)
213
+ params = to_params(
210
214
  customer_post: {
211
215
  title: "Hello",
212
216
  votes: 5,
@@ -224,11 +228,23 @@ describe Pundit::Authorization do
224
228
  "votes" => 5
225
229
  )
226
230
  end
231
+
232
+ it "goes through the policy cache" do
233
+ params = to_params(post: { title: "Hello" })
234
+ user = double
235
+ post = Post.new(user)
236
+ controller = Controller.new(user, "update", params)
237
+
238
+ expect do
239
+ expect(controller.permitted_attributes(post)).to be_truthy
240
+ expect(controller.permitted_attributes(post)).to be_truthy
241
+ end.to change { PostPolicy.instances }.by(1)
242
+ end
227
243
  end
228
244
 
229
245
  describe "#permitted_attributes_for_action" do
230
246
  it "is checked if it is defined in the policy" do
231
- params = ActionController::Parameters.new(
247
+ params = to_params(
232
248
  post: {
233
249
  title: "Hello",
234
250
  body: "blah",
@@ -242,7 +258,7 @@ describe Pundit::Authorization do
242
258
  end
243
259
 
244
260
  it "can be explicitly set" do
245
- params = ActionController::Parameters.new(
261
+ params = to_params(
246
262
  post: {
247
263
  title: "Hello",
248
264
  body: "blah",
@@ -35,7 +35,7 @@ RSpec.describe "generators" do
35
35
  describe "#resolve" do
36
36
  it "raises a descriptive error" do
37
37
  scope = WidgetPolicy::Scope.new(double("User"), double("User.all"))
38
- expect { scope.resolve }.to raise_error(NotImplementedError, /WidgetPolicy::Scope/)
38
+ expect { scope.resolve }.to raise_error(NoMethodError, /WidgetPolicy::Scope/)
39
39
  end
40
40
  end
41
41
  end
data/spec/pundit_spec.rb CHANGED
@@ -64,7 +64,11 @@ RSpec.describe Pundit do
64
64
  end.to raise_error(Pundit::NotAuthorizedError, "not allowed to destroy? this Post") do |error|
65
65
  expect(error.query).to eq :destroy?
66
66
  expect(error.record).to eq post
67
- expect(error.policy).to eq Pundit.policy(user, post)
67
+ expect(error.policy).to have_attributes(
68
+ user: user,
69
+ record: post
70
+ )
71
+ expect(error.policy).to be_a(PostPolicy)
68
72
  end
69
73
  # rubocop:enable Style/MultilineBlockChain
70
74
  end
@@ -76,7 +80,11 @@ RSpec.describe Pundit do
76
80
  end.to raise_error(Pundit::NotAuthorizedError, "not allowed to destroy? this Comment") do |error|
77
81
  expect(error.query).to eq :destroy?
78
82
  expect(error.record).to eq comment
79
- expect(error.policy).to eq Pundit.policy(user, [:project, :admin, comment])
83
+ expect(error.policy).to have_attributes(
84
+ user: user,
85
+ record: comment
86
+ )
87
+ expect(error.policy).to be_a(Project::Admin::CommentPolicy)
80
88
  end
81
89
  # rubocop:enable Style/MultilineBlockChain
82
90
  end
data/spec/spec_helper.rb CHANGED
@@ -1,8 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "simplecov"
4
- SimpleCov.start do
5
- add_filter "/spec/"
3
+ if ENV["COVERAGE"]
4
+ require "simplecov"
5
+ SimpleCov.start do
6
+ add_filter "/spec/"
7
+ end
6
8
  end
7
9
 
8
10
  require "pundit"
@@ -16,13 +18,56 @@ require "active_support/core_ext"
16
18
  require "active_model/naming"
17
19
  require "action_controller/metal/strong_parameters"
18
20
 
19
- class PostPolicy < Struct.new(:user, :post)
20
- class Scope < Struct.new(:user, :scope)
21
+ module InstanceTracking
22
+ module ClassMethods
23
+ def instances
24
+ @instances || 0
25
+ end
26
+
27
+ attr_writer :instances
28
+ end
29
+
30
+ def self.prepended(other)
31
+ other.extend(ClassMethods)
32
+ end
33
+
34
+ def initialize(*args, **kwargs, &block)
35
+ self.class.instances += 1
36
+ super(*args, **kwargs, &block)
37
+ end
38
+ end
39
+
40
+ class BasePolicy
41
+ prepend InstanceTracking
42
+
43
+ class BaseScope
44
+ prepend InstanceTracking
45
+
46
+ def initialize(user, scope)
47
+ @user = user
48
+ @scope = scope
49
+ end
50
+
51
+ attr_reader :user, :scope
52
+ end
53
+
54
+ def initialize(user, record)
55
+ @user = user
56
+ @record = record
57
+ end
58
+
59
+ attr_reader :user, :record
60
+ end
61
+
62
+ class PostPolicy < BasePolicy
63
+ class Scope < BaseScope
21
64
  def resolve
22
65
  scope.published
23
66
  end
24
67
  end
25
68
 
69
+ alias post record
70
+
26
71
  def update?
27
72
  post.user == user
28
73
  end
@@ -48,7 +93,13 @@ class PostPolicy < Struct.new(:user, :post)
48
93
  end
49
94
  end
50
95
 
51
- class Post < Struct.new(:user)
96
+ class Post
97
+ def initialize(user = nil)
98
+ @user = user
99
+ end
100
+
101
+ attr_reader :user
102
+
52
103
  def self.published
53
104
  :published
54
105
  end
@@ -67,7 +118,7 @@ class Post < Struct.new(:user)
67
118
  end
68
119
 
69
120
  module Customer
70
- class Post < Post
121
+ class Post < ::Post
71
122
  def model_name
72
123
  OpenStruct.new(param_key: "customer_post")
73
124
  end
@@ -90,16 +141,18 @@ class CommentScope
90
141
  end
91
142
  end
92
143
 
93
- class CommentPolicy < Struct.new(:user, :comment)
94
- class Scope < Struct.new(:user, :scope)
144
+ class CommentPolicy < BasePolicy
145
+ class Scope < BaseScope
95
146
  def resolve
96
147
  CommentScope.new(scope)
97
148
  end
98
149
  end
150
+
151
+ alias comment record
99
152
  end
100
153
 
101
- class PublicationPolicy < Struct.new(:user, :publication)
102
- class Scope < Struct.new(:user, :scope)
154
+ class PublicationPolicy < BasePolicy
155
+ class Scope < BaseScope
103
156
  def resolve
104
157
  scope.published
105
158
  end
@@ -130,7 +183,9 @@ end
130
183
 
131
184
  class Article; end
132
185
 
133
- class BlogPolicy < Struct.new(:user, :blog); end
186
+ class BlogPolicy < BasePolicy
187
+ alias blog record
188
+ end
134
189
 
135
190
  class Blog; end
136
191
 
@@ -140,7 +195,7 @@ class ArtificialBlog < Blog
140
195
  end
141
196
  end
142
197
 
143
- class ArticleTagOtherNamePolicy < Struct.new(:user, :tag)
198
+ class ArticleTagOtherNamePolicy < BasePolicy
144
199
  def show?
145
200
  true
146
201
  end
@@ -148,6 +203,8 @@ class ArticleTagOtherNamePolicy < Struct.new(:user, :tag)
148
203
  def destroy?
149
204
  false
150
205
  end
206
+
207
+ alias tag record
151
208
  end
152
209
 
153
210
  class ArticleTag
@@ -156,33 +213,41 @@ class ArticleTag
156
213
  end
157
214
  end
158
215
 
159
- class CriteriaPolicy < Struct.new(:user, :criteria); end
216
+ class CriteriaPolicy < BasePolicy
217
+ alias criteria record
218
+ end
160
219
 
161
220
  module Project
162
- class CommentPolicy < Struct.new(:user, :comment)
163
- def update?
164
- true
165
- end
166
-
167
- class Scope < Struct.new(:user, :scope)
221
+ class CommentPolicy < BasePolicy
222
+ class Scope < BaseScope
168
223
  def resolve
169
224
  scope
170
225
  end
171
226
  end
227
+
228
+ def update?
229
+ true
230
+ end
231
+
232
+ alias comment record
172
233
  end
173
234
 
174
- class CriteriaPolicy < Struct.new(:user, :criteria); end
235
+ class CriteriaPolicy < BasePolicy
236
+ alias criteria record
237
+ end
175
238
 
176
- class PostPolicy < Struct.new(:user, :post)
177
- class Scope < Struct.new(:user, :scope)
239
+ class PostPolicy < BasePolicy
240
+ class Scope < BaseScope
178
241
  def resolve
179
242
  scope.read
180
243
  end
181
244
  end
245
+
246
+ alias post record
182
247
  end
183
248
 
184
249
  module Admin
185
- class CommentPolicy < Struct.new(:user, :comment)
250
+ class CommentPolicy < BasePolicy
186
251
  def update?
187
252
  true
188
253
  end
@@ -194,7 +259,7 @@ module Project
194
259
  end
195
260
  end
196
261
 
197
- class DenierPolicy < Struct.new(:user, :record)
262
+ class DenierPolicy < BasePolicy
198
263
  def update?
199
264
  false
200
265
  end
@@ -216,7 +281,7 @@ class Controller
216
281
  end
217
282
  end
218
283
 
219
- class NilClassPolicy < Struct.new(:user, :record)
284
+ class NilClassPolicy < BasePolicy
220
285
  class Scope
221
286
  def initialize(*)
222
287
  raise Pundit::NotDefinedError, "Cannot scope NilClass"
@@ -245,8 +310,8 @@ class Thread
245
310
  def self.all; end
246
311
  end
247
312
 
248
- class ThreadPolicy < Struct.new(:user, :thread)
249
- class Scope < Struct.new(:user, :scope)
313
+ class ThreadPolicy < BasePolicy
314
+ class Scope < BaseScope
250
315
  def resolve
251
316
  # deliberate wrong useage of the method
252
317
  scope.all(:unvalid, :parameters)
@@ -254,22 +319,34 @@ class ThreadPolicy < Struct.new(:user, :thread)
254
319
  end
255
320
  end
256
321
 
257
- class PostFourFiveSix < Struct.new(:user); end
322
+ class PostFourFiveSix
323
+ def initialize(user)
324
+ @user = user
325
+ end
326
+
327
+ attr_reader(:user)
328
+ end
258
329
 
259
330
  class CommentFourFiveSix; extend ActiveModel::Naming; end
260
331
 
261
332
  module ProjectOneTwoThree
262
- class CommentFourFiveSixPolicy < Struct.new(:user, :post); end
333
+ class CommentFourFiveSixPolicy < BasePolicy; end
263
334
 
264
- class CriteriaFourFiveSixPolicy < Struct.new(:user, :criteria); end
335
+ class CriteriaFourFiveSixPolicy < BasePolicy; end
265
336
 
266
- class PostFourFiveSixPolicy < Struct.new(:user, :post); end
337
+ class PostFourFiveSixPolicy < BasePolicy; end
267
338
 
268
- class TagFourFiveSix < Struct.new(:user); end
339
+ class TagFourFiveSix
340
+ def initialize(user)
341
+ @user = user
342
+ end
343
+
344
+ attr_reader(:user)
345
+ end
269
346
 
270
- class TagFourFiveSixPolicy < Struct.new(:user, :tag); end
347
+ class TagFourFiveSixPolicy < BasePolicy; end
271
348
 
272
349
  class AvatarFourFiveSix; extend ActiveModel::Naming; end
273
350
 
274
- class AvatarFourFiveSixPolicy < Struct.new(:user, :avatar); end
351
+ class AvatarFourFiveSixPolicy < BasePolicy; end
275
352
  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.3.1
4
+ version: 2.3.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jonas Nicklas
8
8
  - Varvet AB
9
- autorequire:
9
+ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2023-07-17 00:00:00.000000000 Z
12
+ date: 2024-05-08 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activesupport
@@ -127,16 +127,16 @@ dependencies:
127
127
  name: rubocop
128
128
  requirement: !ruby/object:Gem::Requirement
129
129
  requirements:
130
- - - '='
130
+ - - ">="
131
131
  - !ruby/object:Gem::Version
132
- version: 1.24.0
132
+ version: '0'
133
133
  type: :development
134
134
  prerelease: false
135
135
  version_requirements: !ruby/object:Gem::Requirement
136
136
  requirements:
137
- - - '='
137
+ - - ">="
138
138
  - !ruby/object:Gem::Version
139
- version: 1.24.0
139
+ version: '0'
140
140
  - !ruby/object:Gem::Dependency
141
141
  name: simplecov
142
142
  requirement: !ruby/object:Gem::Requirement
@@ -173,10 +173,12 @@ executables: []
173
173
  extensions: []
174
174
  extra_rdoc_files: []
175
175
  files:
176
- - ".github/pull_request_template.md"
176
+ - ".github/PULL_REQUEST_TEMPLATE/gem_release_template.md"
177
+ - ".github/PULL_REQUEST_TEMPLATE/pull_request_template.md"
178
+ - ".github/workflows/main.yml"
179
+ - ".github/workflows/push_gem.yml"
177
180
  - ".gitignore"
178
181
  - ".rubocop.yml"
179
- - ".travis.yml"
180
182
  - ".yardopts"
181
183
  - CHANGELOG.md
182
184
  - CODE_OF_CONDUCT.md
@@ -199,6 +201,9 @@ files:
199
201
  - lib/generators/test_unit/templates/policy_test.rb
200
202
  - lib/pundit.rb
201
203
  - lib/pundit/authorization.rb
204
+ - lib/pundit/cache_store/legacy_store.rb
205
+ - lib/pundit/cache_store/null_store.rb
206
+ - lib/pundit/context.rb
202
207
  - lib/pundit/policy_finder.rb
203
208
  - lib/pundit/rspec.rb
204
209
  - lib/pundit/version.rb
@@ -214,7 +219,7 @@ licenses:
214
219
  - MIT
215
220
  metadata:
216
221
  rubygems_mfa_required: 'true'
217
- post_install_message:
222
+ post_install_message:
218
223
  rdoc_options: []
219
224
  require_paths:
220
225
  - lib
@@ -229,8 +234,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
229
234
  - !ruby/object:Gem::Version
230
235
  version: '0'
231
236
  requirements: []
232
- rubygems_version: 3.4.10
233
- signing_key:
237
+ rubygems_version: 3.5.9
238
+ signing_key:
234
239
  specification_version: 4
235
240
  summary: OO authorization for Rails
236
241
  test_files:
data/.travis.yml DELETED
@@ -1,27 +0,0 @@
1
- language: ruby
2
- dist: focal
3
-
4
- matrix:
5
- include:
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
- script: bundle exec rake rubocop # ONLY lint once, first
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: 3.1.0
21
- - rvm: 3.2.0
22
- - rvm: jruby-9.3.10.0
23
- env:
24
- - JRUBY_OPTS="--debug"
25
- - rvm: truffleruby-head
26
- allow_failures:
27
- - rvm: truffleruby-head