piko-pro-gem 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/piko-pro-gem.gemspec +12 -0
- data/pundit-2.5.2/CHANGELOG.md +196 -0
- data/pundit-2.5.2/CONTRIBUTING.md +31 -0
- data/pundit-2.5.2/LICENSE.txt +22 -0
- data/pundit-2.5.2/README.md +891 -0
- data/pundit-2.5.2/SECURITY.md +19 -0
- data/pundit-2.5.2/config/rubocop-rspec.yml +5 -0
- data/pundit-2.5.2/lib/generators/pundit/install/USAGE +2 -0
- data/pundit-2.5.2/lib/generators/pundit/install/install_generator.rb +15 -0
- data/pundit-2.5.2/lib/generators/pundit/install/templates/application_policy.rb.tt +53 -0
- data/pundit-2.5.2/lib/generators/pundit/policy/USAGE +8 -0
- data/pundit-2.5.2/lib/generators/pundit/policy/policy_generator.rb +17 -0
- data/pundit-2.5.2/lib/generators/pundit/policy/templates/policy.rb.tt +16 -0
- data/pundit-2.5.2/lib/generators/rspec/policy_generator.rb +16 -0
- data/pundit-2.5.2/lib/generators/rspec/templates/policy_spec.rb.tt +27 -0
- data/pundit-2.5.2/lib/generators/test_unit/policy_generator.rb +16 -0
- data/pundit-2.5.2/lib/generators/test_unit/templates/policy_test.rb.tt +18 -0
- data/pundit-2.5.2/lib/pundit/authorization.rb +269 -0
- data/pundit-2.5.2/lib/pundit/cache_store/legacy_store.rb +27 -0
- data/pundit-2.5.2/lib/pundit/cache_store/null_store.rb +30 -0
- data/pundit-2.5.2/lib/pundit/cache_store.rb +24 -0
- data/pundit-2.5.2/lib/pundit/context.rb +190 -0
- data/pundit-2.5.2/lib/pundit/error.rb +71 -0
- data/pundit-2.5.2/lib/pundit/helper.rb +16 -0
- data/pundit-2.5.2/lib/pundit/policy_finder.rb +135 -0
- data/pundit-2.5.2/lib/pundit/railtie.rb +20 -0
- data/pundit-2.5.2/lib/pundit/rspec.rb +165 -0
- data/pundit-2.5.2/lib/pundit/version.rb +6 -0
- data/pundit-2.5.2/lib/pundit.rb +77 -0
- metadata +70 -0
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pundit
|
|
4
|
+
# {Pundit::Context} is intended to be created once per request and user, and
|
|
5
|
+
# it is then used to perform authorization checks throughout the request.
|
|
6
|
+
#
|
|
7
|
+
# @example Using Sinatra
|
|
8
|
+
# helpers do
|
|
9
|
+
# def current_user = ...
|
|
10
|
+
#
|
|
11
|
+
# def pundit
|
|
12
|
+
# @pundit ||= Pundit::Context.new(user: current_user)
|
|
13
|
+
# end
|
|
14
|
+
# end
|
|
15
|
+
#
|
|
16
|
+
# get "/posts/:id" do |id|
|
|
17
|
+
# pundit.authorize(Post.find(id), query: :show?)
|
|
18
|
+
# end
|
|
19
|
+
#
|
|
20
|
+
# @example Using [Roda](https://roda.jeremyevans.net/index.html)
|
|
21
|
+
# route do |r|
|
|
22
|
+
# context = Pundit::Context.new(user:)
|
|
23
|
+
#
|
|
24
|
+
# r.get "posts", Integer do |id|
|
|
25
|
+
# context.authorize(Post.find(id), query: :show?)
|
|
26
|
+
# end
|
|
27
|
+
# end
|
|
28
|
+
#
|
|
29
|
+
# @since v2.3.2
|
|
30
|
+
class Context
|
|
31
|
+
# @see Pundit::Authorization#pundit
|
|
32
|
+
# @param user later passed to policies and scopes
|
|
33
|
+
# @param policy_cache [#fetch] cache store for policies (see e.g. {CacheStore::NullStore})
|
|
34
|
+
# @since v2.3.2
|
|
35
|
+
def initialize(user:, policy_cache: CacheStore::NullStore.instance)
|
|
36
|
+
@user = user
|
|
37
|
+
@policy_cache = policy_cache
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# @api public
|
|
41
|
+
# @see #initialize
|
|
42
|
+
# @since v2.3.2
|
|
43
|
+
attr_reader :user
|
|
44
|
+
|
|
45
|
+
# @api private
|
|
46
|
+
# @see #initialize
|
|
47
|
+
# @since v2.3.2
|
|
48
|
+
attr_reader :policy_cache
|
|
49
|
+
|
|
50
|
+
# @!group Policies
|
|
51
|
+
|
|
52
|
+
# Retrieves the policy for the given record, initializing it with the
|
|
53
|
+
# record and user and finally throwing an error if the user is not
|
|
54
|
+
# authorized to perform the given action.
|
|
55
|
+
#
|
|
56
|
+
# @param possibly_namespaced_record [Object, Array] the object we're checking permissions of
|
|
57
|
+
# @param query [Symbol, String] the predicate method to check on the policy (e.g. `:show?`)
|
|
58
|
+
# @param policy_class [Class] the policy class we want to force use of
|
|
59
|
+
# @raise [NotAuthorizedError] if the given query method returned false
|
|
60
|
+
# @return [Object] Always returns the passed object record
|
|
61
|
+
# @since v2.3.2
|
|
62
|
+
def authorize(possibly_namespaced_record, query:, policy_class:)
|
|
63
|
+
record = pundit_model(possibly_namespaced_record)
|
|
64
|
+
policy = if policy_class
|
|
65
|
+
policy_class.new(user, record)
|
|
66
|
+
else
|
|
67
|
+
policy!(possibly_namespaced_record)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
raise NotAuthorizedError, query: query, record: record, policy: policy unless policy.public_send(query)
|
|
71
|
+
|
|
72
|
+
record
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Retrieves the policy for the given record.
|
|
76
|
+
#
|
|
77
|
+
# @see https://github.com/varvet/pundit#policies
|
|
78
|
+
# @param record [Object] the object we're retrieving the policy for
|
|
79
|
+
# @raise [InvalidConstructorError] if the policy constructor called incorrectly
|
|
80
|
+
# @return [Object, nil] instance of policy class with query methods
|
|
81
|
+
# @since v2.3.2
|
|
82
|
+
def policy(record)
|
|
83
|
+
cached_find(record, &:policy)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Retrieves the policy for the given record, or raises if not found.
|
|
87
|
+
#
|
|
88
|
+
# @see https://github.com/varvet/pundit#policies
|
|
89
|
+
# @param record [Object] the object we're retrieving the policy for
|
|
90
|
+
# @raise [NotDefinedError] if the policy cannot be found
|
|
91
|
+
# @raise [InvalidConstructorError] if the policy constructor called incorrectly
|
|
92
|
+
# @return [Object] instance of policy class with query methods
|
|
93
|
+
# @since v2.3.2
|
|
94
|
+
def policy!(record)
|
|
95
|
+
cached_find(record, &:policy!)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# @!endgroup
|
|
99
|
+
|
|
100
|
+
# @!group Scopes
|
|
101
|
+
|
|
102
|
+
# Retrieves the policy scope for the given record.
|
|
103
|
+
#
|
|
104
|
+
# @see https://github.com/varvet/pundit#scopes
|
|
105
|
+
# @param scope [Object] the object we're retrieving the policy scope for
|
|
106
|
+
# @raise [InvalidConstructorError] if the policy constructor called incorrectly
|
|
107
|
+
# @return [Scope{#resolve}, nil] instance of scope class which can resolve to a scope
|
|
108
|
+
# @since v2.3.2
|
|
109
|
+
def policy_scope(scope)
|
|
110
|
+
policy_scope_class = policy_finder(scope).scope
|
|
111
|
+
return unless policy_scope_class
|
|
112
|
+
|
|
113
|
+
begin
|
|
114
|
+
policy_scope = policy_scope_class.new(user, pundit_model(scope))
|
|
115
|
+
rescue ArgumentError
|
|
116
|
+
raise InvalidConstructorError, "Invalid #<#{policy_scope_class}> constructor is called"
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
policy_scope.resolve
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# Retrieves the policy scope for the given record. Raises if not found.
|
|
123
|
+
#
|
|
124
|
+
# @see https://github.com/varvet/pundit#scopes
|
|
125
|
+
# @param scope [Object] the object we're retrieving the policy scope for
|
|
126
|
+
# @raise [NotDefinedError] if the policy scope cannot be found
|
|
127
|
+
# @raise [InvalidConstructorError] if the policy constructor called incorrectly
|
|
128
|
+
# @return [Scope{#resolve}] instance of scope class which can resolve to a scope
|
|
129
|
+
# @since v2.3.2
|
|
130
|
+
def policy_scope!(scope)
|
|
131
|
+
policy_scope_class = policy_finder(scope).scope!
|
|
132
|
+
|
|
133
|
+
begin
|
|
134
|
+
policy_scope = policy_scope_class.new(user, pundit_model(scope))
|
|
135
|
+
rescue ArgumentError
|
|
136
|
+
raise InvalidConstructorError, "Invalid #<#{policy_scope_class}> constructor is called"
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
policy_scope.resolve
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# @!endgroup
|
|
143
|
+
|
|
144
|
+
private
|
|
145
|
+
|
|
146
|
+
# @!group Private Helpers
|
|
147
|
+
|
|
148
|
+
# Finds a cached policy for the given record, or yields to find one.
|
|
149
|
+
#
|
|
150
|
+
# @api private
|
|
151
|
+
# @param record [Object] the object we're retrieving the policy for
|
|
152
|
+
# @yield a policy finder if no policy was cached
|
|
153
|
+
# @yieldparam [PolicyFinder] policy_finder
|
|
154
|
+
# @yieldreturn [#new(user, model)]
|
|
155
|
+
# @return [Policy, nil] an instantiated policy
|
|
156
|
+
# @raise [InvalidConstructorError] if policy can't be instantated
|
|
157
|
+
# @since v2.3.2
|
|
158
|
+
def cached_find(record)
|
|
159
|
+
policy_cache.fetch(user: user, record: record) do
|
|
160
|
+
klass = yield policy_finder(record)
|
|
161
|
+
next unless klass
|
|
162
|
+
|
|
163
|
+
model = pundit_model(record)
|
|
164
|
+
|
|
165
|
+
begin
|
|
166
|
+
klass.new(user, model)
|
|
167
|
+
rescue ArgumentError
|
|
168
|
+
raise InvalidConstructorError, "Invalid #<#{klass}> constructor is called"
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
# Return a policy finder for the given record.
|
|
174
|
+
#
|
|
175
|
+
# @api private
|
|
176
|
+
# @return [PolicyFinder]
|
|
177
|
+
# @since v2.3.2
|
|
178
|
+
def policy_finder(record)
|
|
179
|
+
PolicyFinder.new(record)
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
# Given a possibly namespaced record, return the actual record.
|
|
183
|
+
#
|
|
184
|
+
# @api private
|
|
185
|
+
# @since v2.3.2
|
|
186
|
+
def pundit_model(record)
|
|
187
|
+
record.is_a?(Array) ? record.last : record
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
end
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pundit
|
|
4
|
+
# @api private
|
|
5
|
+
# @since v1.0.0
|
|
6
|
+
# To avoid name clashes with common Error naming when mixing in Pundit,
|
|
7
|
+
# keep it here with compact class style definition.
|
|
8
|
+
class Error < StandardError; end
|
|
9
|
+
|
|
10
|
+
# Error that will be raised when authorization has failed
|
|
11
|
+
# @since v0.1.0
|
|
12
|
+
class NotAuthorizedError < Error
|
|
13
|
+
# @see #initialize
|
|
14
|
+
# @since v0.2.3
|
|
15
|
+
attr_reader :query
|
|
16
|
+
# @see #initialize
|
|
17
|
+
# @since v0.2.3
|
|
18
|
+
attr_reader :record
|
|
19
|
+
# @see #initialize
|
|
20
|
+
# @since v0.2.3
|
|
21
|
+
attr_reader :policy
|
|
22
|
+
|
|
23
|
+
# @since v1.0.0
|
|
24
|
+
#
|
|
25
|
+
# @overload initialize(message)
|
|
26
|
+
# Create an error with a simple error message.
|
|
27
|
+
# @param [String] message A simple error message string.
|
|
28
|
+
#
|
|
29
|
+
# @overload initialize(options)
|
|
30
|
+
# Create an error with the specified attributes.
|
|
31
|
+
# @param [Hash] options The error options.
|
|
32
|
+
# @option options [String] :message Optional custom error message. Will default to a generalized message.
|
|
33
|
+
# @option options [Symbol] :query The name of the policy method that was checked.
|
|
34
|
+
# @option options [Object] :record The object that was being checked with the policy.
|
|
35
|
+
# @option options [Class] :policy The class of policy that was used for the check.
|
|
36
|
+
def initialize(options = {})
|
|
37
|
+
if options.is_a? String
|
|
38
|
+
message = options
|
|
39
|
+
else
|
|
40
|
+
@query = options[:query]
|
|
41
|
+
@record = options[:record]
|
|
42
|
+
@policy = options[:policy]
|
|
43
|
+
|
|
44
|
+
message = options.fetch(:message) do
|
|
45
|
+
record_name = record.is_a?(Class) ? record.to_s : "this #{record.class}"
|
|
46
|
+
"not allowed to #{policy.class}##{query} #{record_name}"
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
super(message)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Error that will be raised if a policy or policy scope constructor is not called correctly.
|
|
55
|
+
# @since v2.0.0
|
|
56
|
+
class InvalidConstructorError < Error; end
|
|
57
|
+
|
|
58
|
+
# Error that will be raised if a controller action has not called the
|
|
59
|
+
# `authorize` or `skip_authorization` methods.
|
|
60
|
+
# @since v0.2.3
|
|
61
|
+
class AuthorizationNotPerformedError < Error; end
|
|
62
|
+
|
|
63
|
+
# Error that will be raised if a controller action has not called the
|
|
64
|
+
# `policy_scope` or `skip_policy_scope` methods.
|
|
65
|
+
# @since v0.3.0
|
|
66
|
+
class PolicyScopingNotPerformedError < AuthorizationNotPerformedError; end
|
|
67
|
+
|
|
68
|
+
# Error that will be raised if a policy or policy scope is not defined.
|
|
69
|
+
# @since v0.1.0
|
|
70
|
+
class NotDefinedError < Error; end
|
|
71
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pundit
|
|
4
|
+
# Rails view helpers, to allow a slightly different view-specific
|
|
5
|
+
# implementation of the methods in {Pundit::Authorization}.
|
|
6
|
+
#
|
|
7
|
+
# @api private
|
|
8
|
+
# @since v1.0.0
|
|
9
|
+
module Helper
|
|
10
|
+
# @see Pundit::Authorization#pundit_policy_scope
|
|
11
|
+
# @since v1.0.0
|
|
12
|
+
def policy_scope(scope)
|
|
13
|
+
pundit_policy_scope(scope)
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# String#safe_constantize, String#demodulize, String#underscore, String#camelize
|
|
4
|
+
require "active_support/core_ext/string/inflections"
|
|
5
|
+
|
|
6
|
+
module Pundit
|
|
7
|
+
# Finds policy and scope classes for given object.
|
|
8
|
+
# @since v0.1.0
|
|
9
|
+
# @api public
|
|
10
|
+
# @example
|
|
11
|
+
# user = User.find(params[:id])
|
|
12
|
+
# finder = PolicyFinder.new(user)
|
|
13
|
+
# finder.policy #=> UserPolicy
|
|
14
|
+
# finder.scope #=> UserPolicy::Scope
|
|
15
|
+
#
|
|
16
|
+
class PolicyFinder
|
|
17
|
+
# A constant applied to the end of the class name to find the policy class.
|
|
18
|
+
#
|
|
19
|
+
# @api private
|
|
20
|
+
# @since v2.5.0
|
|
21
|
+
SUFFIX = "Policy"
|
|
22
|
+
|
|
23
|
+
# @see #initialize
|
|
24
|
+
# @since v0.1.0
|
|
25
|
+
attr_reader :object
|
|
26
|
+
|
|
27
|
+
# @param object [any] the object to find policy and scope classes for
|
|
28
|
+
# @since v0.1.0
|
|
29
|
+
def initialize(object)
|
|
30
|
+
@object = object
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# @return [nil, Scope{#resolve}] scope class which can resolve to a scope
|
|
34
|
+
# @see https://github.com/varvet/pundit#scopes
|
|
35
|
+
# @example
|
|
36
|
+
# scope = finder.scope #=> UserPolicy::Scope
|
|
37
|
+
# scope.resolve #=> <#ActiveRecord::Relation ...>
|
|
38
|
+
#
|
|
39
|
+
# @since v0.1.0
|
|
40
|
+
def scope
|
|
41
|
+
"#{policy}::Scope".safe_constantize
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# @return [nil, Class] policy class with query methods
|
|
45
|
+
# @see https://github.com/varvet/pundit#policies
|
|
46
|
+
# @example
|
|
47
|
+
# policy = finder.policy #=> UserPolicy
|
|
48
|
+
# policy.show? #=> true
|
|
49
|
+
# policy.update? #=> false
|
|
50
|
+
#
|
|
51
|
+
# @since v0.1.0
|
|
52
|
+
def policy
|
|
53
|
+
klass = find(object)
|
|
54
|
+
klass.is_a?(String) ? klass.safe_constantize : klass
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# @return [Scope{#resolve}] scope class which can resolve to a scope
|
|
58
|
+
# @raise [NotDefinedError] if scope could not be determined
|
|
59
|
+
#
|
|
60
|
+
# @since v0.1.0
|
|
61
|
+
def scope!
|
|
62
|
+
scope or raise NotDefinedError, "unable to find scope `#{find(object)}::Scope` for `#{object.inspect}`"
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# @return [Class] policy class with query methods
|
|
66
|
+
# @raise [NotDefinedError] if policy could not be determined
|
|
67
|
+
#
|
|
68
|
+
# @since v0.1.0
|
|
69
|
+
def policy!
|
|
70
|
+
policy or raise NotDefinedError, "unable to find policy `#{find(object)}` for `#{object.inspect}`"
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# @return [String] the name of the key this object would have in a params hash
|
|
74
|
+
#
|
|
75
|
+
# @since v1.1.0
|
|
76
|
+
def param_key # rubocop:disable Metrics/AbcSize
|
|
77
|
+
model = object.is_a?(Array) ? object.last : object
|
|
78
|
+
|
|
79
|
+
if model.respond_to?(:model_name)
|
|
80
|
+
model.model_name.param_key.to_s
|
|
81
|
+
elsif model.is_a?(Class)
|
|
82
|
+
model.to_s.demodulize.underscore
|
|
83
|
+
else
|
|
84
|
+
model.class.to_s.demodulize.underscore
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
private
|
|
89
|
+
|
|
90
|
+
# Given an object, find the policy class name.
|
|
91
|
+
#
|
|
92
|
+
# Uses recursion to handle namespaces.
|
|
93
|
+
#
|
|
94
|
+
# @return [String, Class] the policy class, or its name.
|
|
95
|
+
# @since v0.2.0
|
|
96
|
+
def find(subject)
|
|
97
|
+
if subject.is_a?(Array)
|
|
98
|
+
modules = subject.dup
|
|
99
|
+
last = modules.pop
|
|
100
|
+
context = modules.map { |x| find_class_name(x) }.join("::")
|
|
101
|
+
[context, find(last)].join("::")
|
|
102
|
+
elsif subject.respond_to?(:policy_class)
|
|
103
|
+
subject.policy_class
|
|
104
|
+
elsif subject.class.respond_to?(:policy_class)
|
|
105
|
+
subject.class.policy_class
|
|
106
|
+
else
|
|
107
|
+
klass = find_class_name(subject)
|
|
108
|
+
"#{klass}#{SUFFIX}"
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Given an object, find its' class name.
|
|
113
|
+
#
|
|
114
|
+
# - Supports ActiveModel.
|
|
115
|
+
# - Supports regular classes.
|
|
116
|
+
# - Supports symbols.
|
|
117
|
+
# - Supports object instances.
|
|
118
|
+
#
|
|
119
|
+
# @return [String, Class] the class, or its name.
|
|
120
|
+
# @since v1.1.0
|
|
121
|
+
def find_class_name(subject)
|
|
122
|
+
if subject.respond_to?(:model_name)
|
|
123
|
+
subject.model_name
|
|
124
|
+
elsif subject.class.respond_to?(:model_name)
|
|
125
|
+
subject.class.model_name
|
|
126
|
+
elsif subject.is_a?(Class)
|
|
127
|
+
subject
|
|
128
|
+
elsif subject.is_a?(Symbol)
|
|
129
|
+
subject.to_s.camelize
|
|
130
|
+
else
|
|
131
|
+
subject.class
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pundit
|
|
4
|
+
# @since v2.5.0
|
|
5
|
+
class Railtie < Rails::Railtie
|
|
6
|
+
if Rails.version.to_f >= 8.0
|
|
7
|
+
initializer "pundit.stats_directories" do
|
|
8
|
+
require "rails/code_statistics"
|
|
9
|
+
|
|
10
|
+
if Rails.root.join("app/policies").directory?
|
|
11
|
+
Rails::CodeStatistics.register_directory("Policies", "app/policies")
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
if Rails.root.join("test/policies").directory?
|
|
15
|
+
Rails::CodeStatistics.register_directory("Policy tests", "test/policies", test_directory: true)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "pundit"
|
|
4
|
+
# Array#to_sentence
|
|
5
|
+
require "active_support/core_ext/array/conversions"
|
|
6
|
+
|
|
7
|
+
module Pundit
|
|
8
|
+
# Namespace for Pundit's RSpec integration.
|
|
9
|
+
# @since v0.1.0
|
|
10
|
+
module RSpec
|
|
11
|
+
# Namespace for Pundit's RSpec matchers.
|
|
12
|
+
module Matchers
|
|
13
|
+
extend ::RSpec::Matchers::DSL
|
|
14
|
+
|
|
15
|
+
# @!method description=(description)
|
|
16
|
+
class << self
|
|
17
|
+
# Used to build a suitable description for the Pundit `permit` matcher.
|
|
18
|
+
# @api public
|
|
19
|
+
# @param value [String, Proc]
|
|
20
|
+
# @example
|
|
21
|
+
# Pundit::RSpec::Matchers.description = ->(user, record) do
|
|
22
|
+
# "permit user with role #{user.role} to access record with ID #{record.id}"
|
|
23
|
+
# end
|
|
24
|
+
attr_writer :description
|
|
25
|
+
|
|
26
|
+
# Used to retrieve a suitable description for the Pundit `permit` matcher.
|
|
27
|
+
# @api private
|
|
28
|
+
# @private
|
|
29
|
+
def description(user, record)
|
|
30
|
+
return @description.call(user, record) if defined?(@description) && @description.respond_to?(:call)
|
|
31
|
+
|
|
32
|
+
@description
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# rubocop:disable Metrics/BlockLength
|
|
37
|
+
matcher :permit do |user, record|
|
|
38
|
+
match_proc = lambda do |policy|
|
|
39
|
+
@violating_permissions = permissions.find_all do |permission|
|
|
40
|
+
!policy.new(user, record).public_send(permission)
|
|
41
|
+
end
|
|
42
|
+
@violating_permissions.empty?
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
match_when_negated_proc = lambda do |policy|
|
|
46
|
+
@violating_permissions = permissions.find_all do |permission|
|
|
47
|
+
policy.new(user, record).public_send(permission)
|
|
48
|
+
end
|
|
49
|
+
@violating_permissions.empty?
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
failure_message_proc = lambda do |policy|
|
|
53
|
+
"Expected #{policy} to grant #{permissions.to_sentence} on " \
|
|
54
|
+
"#{record} but #{@violating_permissions.to_sentence} #{was_or_were} not granted"
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
failure_message_when_negated_proc = lambda do |policy|
|
|
58
|
+
"Expected #{policy} not to grant #{permissions.to_sentence} on " \
|
|
59
|
+
"#{record} but #{@violating_permissions.to_sentence} #{was_or_were} granted"
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def was_or_were
|
|
63
|
+
if @violating_permissions.count > 1
|
|
64
|
+
"were"
|
|
65
|
+
else
|
|
66
|
+
"was"
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
description do
|
|
71
|
+
Pundit::RSpec::Matchers.description(user, record) || super()
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
if respond_to?(:match_when_negated)
|
|
75
|
+
match(&match_proc)
|
|
76
|
+
match_when_negated(&match_when_negated_proc)
|
|
77
|
+
failure_message(&failure_message_proc)
|
|
78
|
+
failure_message_when_negated(&failure_message_when_negated_proc)
|
|
79
|
+
else
|
|
80
|
+
# :nocov:
|
|
81
|
+
# Compatibility with RSpec < 3.0, released 2014-06-01.
|
|
82
|
+
match_for_should(&match_proc)
|
|
83
|
+
match_for_should_not(&match_when_negated_proc)
|
|
84
|
+
failure_message_for_should(&failure_message_proc)
|
|
85
|
+
failure_message_for_should_not(&failure_message_when_negated_proc)
|
|
86
|
+
# :nocov:
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
if ::RSpec.respond_to?(:current_example)
|
|
90
|
+
def current_example
|
|
91
|
+
::RSpec.current_example
|
|
92
|
+
end
|
|
93
|
+
else
|
|
94
|
+
# :nocov:
|
|
95
|
+
# Compatibility with RSpec < 3.0, released 2014-06-01.
|
|
96
|
+
def current_example
|
|
97
|
+
example
|
|
98
|
+
end
|
|
99
|
+
# :nocov:
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def permissions
|
|
103
|
+
current_example.metadata.fetch(:permissions) do
|
|
104
|
+
raise KeyError, <<~ERROR.strip
|
|
105
|
+
No permissions in example metadata, did you forget to wrap with `permissions :show?, ...`?
|
|
106
|
+
ERROR
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
# rubocop:enable Metrics/BlockLength
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# Mixed in to all policy example groups to provide a DSL.
|
|
114
|
+
module DSL
|
|
115
|
+
# @example
|
|
116
|
+
# describe PostPolicy do
|
|
117
|
+
# permissions :show?, :update? do
|
|
118
|
+
# it { is_expected.to permit(user, own_post) }
|
|
119
|
+
# end
|
|
120
|
+
# end
|
|
121
|
+
#
|
|
122
|
+
# @example focused example group
|
|
123
|
+
# describe PostPolicy do
|
|
124
|
+
# permissions :show?, :update?, :focus do
|
|
125
|
+
# it { is_expected.to permit(user, own_post) }
|
|
126
|
+
# end
|
|
127
|
+
# end
|
|
128
|
+
#
|
|
129
|
+
# @param list [Symbol, Array<Symbol>] a permission to describe
|
|
130
|
+
# @return [void]
|
|
131
|
+
def permissions(*list, &block)
|
|
132
|
+
metadata = { permissions: list, caller: caller }
|
|
133
|
+
|
|
134
|
+
if list.last == :focus
|
|
135
|
+
list.pop
|
|
136
|
+
metadata[:focus] = true
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
description = list.to_sentence
|
|
140
|
+
describe(description, metadata) { instance_eval(&block) }
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# Mixed in to all policy example groups.
|
|
145
|
+
#
|
|
146
|
+
# @private not useful
|
|
147
|
+
module PolicyExampleGroup
|
|
148
|
+
include Pundit::RSpec::Matchers
|
|
149
|
+
|
|
150
|
+
def self.included(base)
|
|
151
|
+
base.metadata[:type] = :policy
|
|
152
|
+
base.extend Pundit::RSpec::DSL
|
|
153
|
+
super
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
RSpec.configure do |config|
|
|
160
|
+
config.include(
|
|
161
|
+
Pundit::RSpec::PolicyExampleGroup,
|
|
162
|
+
type: :policy,
|
|
163
|
+
file_path: %r{spec/policies}
|
|
164
|
+
)
|
|
165
|
+
end
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "active_support"
|
|
4
|
+
|
|
5
|
+
require "pundit/version"
|
|
6
|
+
require "pundit/error"
|
|
7
|
+
require "pundit/policy_finder"
|
|
8
|
+
require "pundit/context"
|
|
9
|
+
require "pundit/authorization"
|
|
10
|
+
require "pundit/helper"
|
|
11
|
+
require "pundit/cache_store"
|
|
12
|
+
require "pundit/cache_store/null_store"
|
|
13
|
+
require "pundit/cache_store/legacy_store"
|
|
14
|
+
require "pundit/railtie" if defined?(Rails)
|
|
15
|
+
|
|
16
|
+
# Hello? Yes, this is Pundit.
|
|
17
|
+
#
|
|
18
|
+
# @api public
|
|
19
|
+
module Pundit
|
|
20
|
+
# @api private
|
|
21
|
+
# @since v1.0.0
|
|
22
|
+
# @deprecated See {Pundit::PolicyFinder}
|
|
23
|
+
SUFFIX = Pundit::PolicyFinder::SUFFIX
|
|
24
|
+
|
|
25
|
+
# @api private
|
|
26
|
+
# @private
|
|
27
|
+
# @since v0.1.0
|
|
28
|
+
module Generators; end
|
|
29
|
+
|
|
30
|
+
def self.included(base)
|
|
31
|
+
location = caller_locations(1, 1).first
|
|
32
|
+
warn <<~WARNING
|
|
33
|
+
'include Pundit' is deprecated. Please use 'include Pundit::Authorization' instead.
|
|
34
|
+
(called from #{location.label} at #{location.path}:#{location.lineno})
|
|
35
|
+
WARNING
|
|
36
|
+
base.include Authorization
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
class << self
|
|
40
|
+
# @see Pundit::Context#authorize
|
|
41
|
+
# @since v1.0.0
|
|
42
|
+
def authorize(user, record, query, policy_class: nil, cache: nil)
|
|
43
|
+
context = if cache
|
|
44
|
+
policy_cache = CacheStore::LegacyStore.new(cache)
|
|
45
|
+
Context.new(user: user, policy_cache: policy_cache)
|
|
46
|
+
else
|
|
47
|
+
Context.new(user: user)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
context.authorize(record, query: query, policy_class: policy_class)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# @see Pundit::Context#policy_scope
|
|
54
|
+
# @since v0.1.0
|
|
55
|
+
def policy_scope(user, *args, **kwargs, &block)
|
|
56
|
+
Context.new(user: user).policy_scope(*args, **kwargs, &block)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# @see Pundit::Context#policy_scope!
|
|
60
|
+
# @since v0.1.0
|
|
61
|
+
def policy_scope!(user, *args, **kwargs, &block)
|
|
62
|
+
Context.new(user: user).policy_scope!(*args, **kwargs, &block)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# @see Pundit::Context#policy
|
|
66
|
+
# @since v0.1.0
|
|
67
|
+
def policy(user, *args, **kwargs, &block)
|
|
68
|
+
Context.new(user: user).policy(*args, **kwargs, &block)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# @see Pundit::Context#policy!
|
|
72
|
+
# @since v0.1.0
|
|
73
|
+
def policy!(user, *args, **kwargs, &block)
|
|
74
|
+
Context.new(user: user).policy!(*args, **kwargs, &block)
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|