pundit 2.2.0 → 2.4.0
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 +4 -4
- data/.github/ISSUE_TEMPLATE/bug_report.md +20 -0
- data/.github/ISSUE_TEMPLATE/feature_request.md +26 -0
- data/.github/PULL_REQUEST_TEMPLATE/gem_release_template.md +8 -0
- data/.github/pull_request_template.md +9 -0
- data/.github/workflows/main.yml +112 -0
- data/.github/workflows/push_gem.yml +33 -0
- data/.rubocop.yml +7 -16
- data/CHANGELOG.md +40 -0
- data/CODE_OF_CONDUCT.md +1 -1
- data/CONTRIBUTING.md +3 -5
- data/Gemfile +3 -2
- data/README.md +95 -54
- data/SECURITY.md +19 -0
- data/config/rubocop-rspec.yml +5 -0
- data/lib/generators/pundit/install/templates/application_policy.rb +1 -1
- data/lib/generators/pundit/policy/templates/policy.rb +7 -1
- data/lib/generators/rspec/templates/policy_spec.rb +1 -1
- data/lib/pundit/authorization.rb +12 -4
- data/lib/pundit/cache_store/legacy_store.rb +17 -0
- data/lib/pundit/cache_store/null_store.rb +18 -0
- data/lib/pundit/context.rb +127 -0
- data/lib/pundit/policy_finder.rb +1 -1
- data/lib/pundit/rspec.rb +23 -1
- data/lib/pundit/version.rb +1 -1
- data/lib/pundit.rb +28 -89
- data/pundit.gemspec +4 -2
- data/spec/authorization_spec.rb +23 -7
- data/spec/dsl_spec.rb +30 -0
- data/spec/generators_spec.rb +1 -1
- data/spec/policies/post_policy_spec.rb +27 -0
- data/spec/pundit_spec.rb +35 -14
- data/spec/spec_helper.rb +113 -36
- metadata +26 -13
- data/.travis.yml +0 -26
data/SECURITY.md
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
# Security Policy
|
2
|
+
|
3
|
+
Please do not file an issue on GitHub, or send a PR addressing the issue.
|
4
|
+
|
5
|
+
## Supported versions
|
6
|
+
|
7
|
+
Most recent major version only.
|
8
|
+
|
9
|
+
## Reporting a vulnerability
|
10
|
+
|
11
|
+
Contact one of the maintainers directly:
|
12
|
+
|
13
|
+
* [@Burgestrand](https://github.com/Burgestrand)
|
14
|
+
* [@dgmstuart](https://github.com/dgmstuart)
|
15
|
+
* [@varvet](https://github.com/varvet)
|
16
|
+
|
17
|
+
You can report vulnerabilities on GitHub too: https://github.com/varvet/pundit/security
|
18
|
+
|
19
|
+
Thank you!
|
@@ -1,6 +1,12 @@
|
|
1
1
|
<% module_namespacing do -%>
|
2
2
|
class <%= class_name %>Policy < ApplicationPolicy
|
3
|
-
|
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
|
data/lib/pundit/authorization.rb
CHANGED
@@ -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
|
-
|
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
|
109
|
+
# @return [Object] instance of policy class with query methods
|
102
110
|
def policy(record)
|
103
|
-
|
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] ||=
|
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,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
|
data/lib/pundit/policy_finder.rb
CHANGED
@@ -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)
|
data/lib/pundit/rspec.rb
CHANGED
@@ -5,6 +5,16 @@ module Pundit
|
|
5
5
|
module Matchers
|
6
6
|
extend ::RSpec::Matchers::DSL
|
7
7
|
|
8
|
+
class << self
|
9
|
+
attr_writer :description
|
10
|
+
|
11
|
+
def description(user, record)
|
12
|
+
return @description.call(user, record) if defined?(@description) && @description.respond_to?(:call)
|
13
|
+
|
14
|
+
@description
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
8
18
|
# rubocop:disable Metrics/BlockLength
|
9
19
|
matcher :permit do |user, record|
|
10
20
|
match_proc = lambda do |policy|
|
@@ -33,6 +43,10 @@ module Pundit
|
|
33
43
|
"#{record} but #{@violating_permissions.to_sentence} #{was_were} granted"
|
34
44
|
end
|
35
45
|
|
46
|
+
description do
|
47
|
+
Pundit::RSpec::Matchers.description(user, record) || super()
|
48
|
+
end
|
49
|
+
|
36
50
|
if respond_to?(:match_when_negated)
|
37
51
|
match(&match_proc)
|
38
52
|
match_when_negated(&match_when_negated_proc)
|
@@ -55,7 +69,15 @@ module Pundit
|
|
55
69
|
|
56
70
|
module DSL
|
57
71
|
def permissions(*list, &block)
|
58
|
-
|
72
|
+
metadata = { permissions: list, caller: caller }
|
73
|
+
|
74
|
+
if list.last == :focus
|
75
|
+
list.pop
|
76
|
+
metadata[:focus] = true
|
77
|
+
end
|
78
|
+
|
79
|
+
description = list.to_sentence
|
80
|
+
describe(description, metadata) { instance_eval(&block) }
|
59
81
|
end
|
60
82
|
end
|
61
83
|
|
data/lib/pundit/version.rb
CHANGED
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,
|
@@ -33,7 +36,10 @@ module Pundit
|
|
33
36
|
@record = options[:record]
|
34
37
|
@policy = options[:policy]
|
35
38
|
|
36
|
-
message = options.fetch(:message)
|
39
|
+
message = options.fetch(:message) do
|
40
|
+
record_name = record.is_a?(Class) ? record.to_s : "this #{record.class}"
|
41
|
+
"not allowed to #{policy.class}##{query} #{record_name}"
|
42
|
+
end
|
37
43
|
end
|
38
44
|
|
39
45
|
super(message)
|
@@ -55,111 +61,44 @@ module Pundit
|
|
55
61
|
class NotDefinedError < Error; end
|
56
62
|
|
57
63
|
def self.included(base)
|
58
|
-
|
64
|
+
location = caller_locations(1, 1).first
|
65
|
+
warn <<~WARNING
|
59
66
|
'include Pundit' is deprecated. Please use 'include Pundit::Authorization' instead.
|
67
|
+
(called from #{location.label} at #{location.path}:#{location.lineno})
|
60
68
|
WARNING
|
61
69
|
base.include Authorization
|
62
70
|
end
|
63
71
|
|
64
72
|
class << self
|
65
|
-
#
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
# @param user [Object] the user that initiated the action
|
70
|
-
# @param possibly_namespaced_record [Object, Array] the object we're checking permissions of
|
71
|
-
# @param query [Symbol, String] the predicate method to check on the policy (e.g. `:show?`)
|
72
|
-
# @param policy_class [Class] the policy class we want to force use of
|
73
|
-
# @param cache [#[], #[]=] a Hash-like object to cache the found policy instance in
|
74
|
-
# @raise [NotAuthorizedError] if the given query method returned false
|
75
|
-
# @return [Object] Always returns the passed object record
|
76
|
-
def authorize(user, possibly_namespaced_record, query, policy_class: nil, cache: {})
|
77
|
-
record = pundit_model(possibly_namespaced_record)
|
78
|
-
policy = if policy_class
|
79
|
-
policy_class.new(user, record)
|
73
|
+
# @see [Pundit::Context#authorize]
|
74
|
+
def authorize(user, record, query, policy_class: nil, cache: nil)
|
75
|
+
context = if cache
|
76
|
+
Context.new(user: user, policy_cache: cache)
|
80
77
|
else
|
81
|
-
|
78
|
+
Context.new(user: user)
|
82
79
|
end
|
83
80
|
|
84
|
-
|
85
|
-
|
86
|
-
record
|
87
|
-
end
|
88
|
-
|
89
|
-
# Retrieves the policy scope for the given record.
|
90
|
-
#
|
91
|
-
# @see https://github.com/varvet/pundit#scopes
|
92
|
-
# @param user [Object] the user that initiated the action
|
93
|
-
# @param scope [Object] the object we're retrieving the policy scope for
|
94
|
-
# @raise [InvalidConstructorError] if the policy constructor called incorrectly
|
95
|
-
# @return [Scope{#resolve}, nil] instance of scope class which can resolve to a scope
|
96
|
-
def policy_scope(user, scope)
|
97
|
-
policy_scope_class = PolicyFinder.new(scope).scope
|
98
|
-
return unless policy_scope_class
|
99
|
-
|
100
|
-
begin
|
101
|
-
policy_scope = policy_scope_class.new(user, pundit_model(scope))
|
102
|
-
rescue ArgumentError
|
103
|
-
raise InvalidConstructorError, "Invalid #<#{policy_scope_class}> constructor is called"
|
104
|
-
end
|
105
|
-
|
106
|
-
policy_scope.resolve
|
81
|
+
context.authorize(record, query: query, policy_class: policy_class)
|
107
82
|
end
|
108
83
|
|
109
|
-
#
|
110
|
-
|
111
|
-
|
112
|
-
# @param user [Object] the user that initiated the action
|
113
|
-
# @param scope [Object] the object we're retrieving the policy scope for
|
114
|
-
# @raise [NotDefinedError] if the policy scope cannot be found
|
115
|
-
# @raise [InvalidConstructorError] if the policy constructor called incorrectly
|
116
|
-
# @return [Scope{#resolve}] instance of scope class which can resolve to a scope
|
117
|
-
def policy_scope!(user, scope)
|
118
|
-
policy_scope_class = PolicyFinder.new(scope).scope!
|
119
|
-
return unless policy_scope_class
|
120
|
-
|
121
|
-
begin
|
122
|
-
policy_scope = policy_scope_class.new(user, pundit_model(scope))
|
123
|
-
rescue ArgumentError
|
124
|
-
raise InvalidConstructorError, "Invalid #<#{policy_scope_class}> constructor is called"
|
125
|
-
end
|
126
|
-
|
127
|
-
policy_scope.resolve
|
84
|
+
# @see [Pundit::Context#policy_scope]
|
85
|
+
def policy_scope(user, *args, **kwargs, &block)
|
86
|
+
Context.new(user: user).policy_scope(*args, **kwargs, &block)
|
128
87
|
end
|
129
88
|
|
130
|
-
#
|
131
|
-
|
132
|
-
|
133
|
-
# @param user [Object] the user that initiated the action
|
134
|
-
# @param record [Object] the object we're retrieving the policy for
|
135
|
-
# @raise [InvalidConstructorError] if the policy constructor called incorrectly
|
136
|
-
# @return [Object, nil] instance of policy class with query methods
|
137
|
-
def policy(user, record)
|
138
|
-
policy = PolicyFinder.new(record).policy
|
139
|
-
policy&.new(user, pundit_model(record))
|
140
|
-
rescue ArgumentError
|
141
|
-
raise InvalidConstructorError, "Invalid #<#{policy}> constructor is called"
|
89
|
+
# @see [Pundit::Context#policy_scope!]
|
90
|
+
def policy_scope!(user, *args, **kwargs, &block)
|
91
|
+
Context.new(user: user).policy_scope!(*args, **kwargs, &block)
|
142
92
|
end
|
143
93
|
|
144
|
-
#
|
145
|
-
|
146
|
-
|
147
|
-
# @param user [Object] the user that initiated the action
|
148
|
-
# @param record [Object] the object we're retrieving the policy for
|
149
|
-
# @raise [NotDefinedError] if the policy cannot be found
|
150
|
-
# @raise [InvalidConstructorError] if the policy constructor called incorrectly
|
151
|
-
# @return [Object] instance of policy class with query methods
|
152
|
-
def policy!(user, record)
|
153
|
-
policy = PolicyFinder.new(record).policy!
|
154
|
-
policy.new(user, pundit_model(record))
|
155
|
-
rescue ArgumentError
|
156
|
-
raise InvalidConstructorError, "Invalid #<#{policy}> constructor is called"
|
94
|
+
# @see [Pundit::Context#policy]
|
95
|
+
def policy(user, *args, **kwargs, &block)
|
96
|
+
Context.new(user: user).policy(*args, **kwargs, &block)
|
157
97
|
end
|
158
98
|
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
record.is_a?(Array) ? record.last : record
|
99
|
+
# @see [Pundit::Context#policy!]
|
100
|
+
def policy!(user, *args, **kwargs, &block)
|
101
|
+
Context.new(user: user).policy!(*args, **kwargs, &block)
|
163
102
|
end
|
164
103
|
end
|
165
104
|
|
data/pundit.gemspec
CHANGED
@@ -8,7 +8,7 @@ Gem::Specification.new do |gem|
|
|
8
8
|
gem.name = "pundit"
|
9
9
|
gem.version = Pundit::VERSION
|
10
10
|
gem.authors = ["Jonas Nicklas", "Varvet AB"]
|
11
|
-
gem.email = ["jonas.nicklas@gmail.com", "
|
11
|
+
gem.email = ["jonas.nicklas@gmail.com", "info@varvet.com"]
|
12
12
|
gem.description = "Object oriented authorization for Rails applications"
|
13
13
|
gem.summary = "OO authorization for Rails"
|
14
14
|
gem.homepage = "https://github.com/varvet/pundit"
|
@@ -19,6 +19,8 @@ Gem::Specification.new do |gem|
|
|
19
19
|
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
20
20
|
gem.require_paths = ["lib"]
|
21
21
|
|
22
|
+
gem.metadata = { "rubygems_mfa_required" => "true" }
|
23
|
+
|
22
24
|
gem.add_dependency "activesupport", ">= 3.0.0"
|
23
25
|
gem.add_development_dependency "actionpack", ">= 3.0.0"
|
24
26
|
gem.add_development_dependency "activemodel", ">= 3.0.0"
|
@@ -27,7 +29,7 @@ Gem::Specification.new do |gem|
|
|
27
29
|
gem.add_development_dependency "railties", ">= 3.0.0"
|
28
30
|
gem.add_development_dependency "rake"
|
29
31
|
gem.add_development_dependency "rspec", ">= 3.0.0"
|
30
|
-
gem.add_development_dependency "rubocop"
|
32
|
+
gem.add_development_dependency "rubocop"
|
31
33
|
gem.add_development_dependency "simplecov", ">= 0.17.0"
|
32
34
|
gem.add_development_dependency "yard"
|
33
35
|
end
|
data/spec/authorization_spec.rb
CHANGED
@@ -3,10 +3,13 @@
|
|
3
3
|
require "spec_helper"
|
4
4
|
|
5
5
|
describe Pundit::Authorization do
|
6
|
-
|
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 }
|
@@ -166,7 +169,7 @@ describe Pundit::Authorization do
|
|
166
169
|
expect(controller.policy_scope(Post)).to eq :published
|
167
170
|
end
|
168
171
|
|
169
|
-
it "allows policy scope class to be
|
172
|
+
it "allows policy scope class to be overridden" do
|
170
173
|
expect(controller.policy_scope(Post, policy_scope_class: PublicationPolicy::Scope)).to eq :published
|
171
174
|
end
|
172
175
|
|
@@ -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 =
|
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
|
-
|
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 =
|
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 =
|
261
|
+
params = to_params(
|
246
262
|
post: {
|
247
263
|
title: "Hello",
|
248
264
|
body: "blah",
|
data/spec/dsl_spec.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "spec_helper"
|
4
|
+
|
5
|
+
RSpec.describe "Pundit RSpec DSL" do
|
6
|
+
let(:fake_rspec) do
|
7
|
+
double = class_double(RSpec::ExampleGroups)
|
8
|
+
double.extend(::Pundit::RSpec::DSL)
|
9
|
+
double
|
10
|
+
end
|
11
|
+
let(:block) { proc { "block content" } }
|
12
|
+
|
13
|
+
it "calls describe with the correct metadata and without :focus" do
|
14
|
+
expected_metadata = { permissions: %i[item1 item2], caller: instance_of(Array) }
|
15
|
+
expect(fake_rspec).to receive(:describe).with("item1 and item2", match(expected_metadata)) do |&block|
|
16
|
+
expect(block.call).to eq("block content")
|
17
|
+
end
|
18
|
+
|
19
|
+
fake_rspec.permissions(:item1, :item2, &block)
|
20
|
+
end
|
21
|
+
|
22
|
+
it "calls describe with the correct metadata and with :focus" do
|
23
|
+
expected_metadata = { permissions: %i[item1 item2], caller: instance_of(Array), focus: true }
|
24
|
+
expect(fake_rspec).to receive(:describe).with("item1 and item2", match(expected_metadata)) do |&block|
|
25
|
+
expect(block.call).to eq("block content")
|
26
|
+
end
|
27
|
+
|
28
|
+
fake_rspec.permissions(:item1, :item2, :focus, &block)
|
29
|
+
end
|
30
|
+
end
|
data/spec/generators_spec.rb
CHANGED
@@ -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(
|
38
|
+
expect { scope.resolve }.to raise_error(NoMethodError, /WidgetPolicy::Scope/)
|
39
39
|
end
|
40
40
|
end
|
41
41
|
end
|
@@ -18,5 +18,32 @@ RSpec.describe PostPolicy do
|
|
18
18
|
should permit(user, other_post)
|
19
19
|
end.to raise_error(RSpec::Expectations::ExpectationNotMetError)
|
20
20
|
end
|
21
|
+
|
22
|
+
it "uses the default description if not overridden" do
|
23
|
+
expect(permit(user, own_post).description).to eq("permit #{user.inspect} and #{own_post.inspect}")
|
24
|
+
end
|
25
|
+
|
26
|
+
context "when the matcher description is overridden" do
|
27
|
+
after do
|
28
|
+
Pundit::RSpec::Matchers.description = nil
|
29
|
+
end
|
30
|
+
|
31
|
+
it "sets a custom matcher description with a Proc" do
|
32
|
+
allow(user).to receive(:role).and_return("default_role")
|
33
|
+
allow(own_post).to receive(:id).and_return(1)
|
34
|
+
|
35
|
+
Pundit::RSpec::Matchers.description = lambda { |user, record|
|
36
|
+
"permit user with role #{user.role} to access record with ID #{record.id}"
|
37
|
+
}
|
38
|
+
|
39
|
+
description = permit(user, own_post).description
|
40
|
+
expect(description).to eq("permit user with role default_role to access record with ID 1")
|
41
|
+
end
|
42
|
+
|
43
|
+
it "sets a custom matcher description with a string" do
|
44
|
+
Pundit::RSpec::Matchers.description = "permit user"
|
45
|
+
expect(permit(user, own_post).description).to eq("permit user")
|
46
|
+
end
|
47
|
+
end
|
21
48
|
end
|
22
49
|
end
|