pundit 2.3.1 → 2.3.2

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: 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