action_policy 0.7.3 → 0.7.4
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/CHANGELOG.md +10 -0
- data/lib/.rbnext/3.0/action_policy/policy/pre_check.rb +0 -2
- data/lib/.rbnext/3.0/action_policy/policy/reasons.rb +2 -0
- data/lib/action_policy/behaviour.rb +6 -4
- data/lib/action_policy/policy/pre_check.rb +0 -2
- data/lib/action_policy/policy/reasons.rb +2 -0
- data/lib/action_policy/rails/controller.rb +4 -1
- data/lib/action_policy/version.rb +1 -1
- metadata +7 -14
- data/lib/.rbnext/2.7/action_policy/behaviours/policy_for.rb +0 -70
- data/lib/.rbnext/2.7/action_policy/i18n.rb +0 -56
- data/lib/.rbnext/2.7/action_policy/policy/cache.rb +0 -101
- data/lib/.rbnext/2.7/action_policy/policy/pre_check.rb +0 -162
- data/lib/.rbnext/2.7/action_policy/rspec/be_authorized_to.rb +0 -96
- data/lib/.rbnext/2.7/action_policy/rspec/have_authorized_scope.rb +0 -130
- data/lib/.rbnext/2.7/action_policy/utils/pretty_print.rb +0 -155
- /data/lib/.rbnext/{2.7 → 3.0}/action_policy/rails/scope_matchers/action_controller_params.rb +0 -0
- /data/lib/.rbnext/{2.7 → 3.0}/action_policy/rails/scope_matchers/active_record.rb +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c2ca6d3ab4293001cc4b07e7fe5503a8c06a8b72684c0f7a9c4e7a4ef8a32c89
|
4
|
+
data.tar.gz: f1e726704611a3bc9ade5d3466fd64c4a5c9faddf5aa8338f3179ec4da2381e5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: aa35a3efefa37fc64066a78899b9b03a2283e2458471425a2e7cc2bc0b4ff829e87beda744057866f661e37fc54f14d094ba0fc446ea155537042478109576a7
|
7
|
+
data.tar.gz: d90069b7a1b3bea22c2d5e6dd990231f1bf24f85fd50081a92a54bcd5c271faa42f40c9c526a8434abd793e6f4c928b5cb28d4dad3fbaeb2bf3701f67fd4fd21
|
data/CHANGELOG.md
CHANGED
@@ -2,6 +2,14 @@
|
|
2
2
|
|
3
3
|
## master
|
4
4
|
|
5
|
+
## 0.7.4 (2025-03-12)
|
6
|
+
|
7
|
+
- Let authorize! return the policy record ([@sedubois][])
|
8
|
+
|
9
|
+
- Enable `allowance_to` as a helper method by default ([@stephannv][])
|
10
|
+
|
11
|
+
- Allow the `:through` option of `authorize` to be passed a proc ([@brendon][])
|
12
|
+
|
5
13
|
## 0.7.3 (2024-12-18)
|
6
14
|
|
7
15
|
- Fix keeping the result object in concurrent (Fiber-ed) execution environments. ([@palkan][])
|
@@ -539,3 +547,5 @@ This value is now stored in a cache (if any) instead of just the call result (`t
|
|
539
547
|
[@matsales28]: https://github.com/matsales28
|
540
548
|
[@killondark]: https://github.com/killondark
|
541
549
|
[@Spone]: https://github.com/Spone
|
550
|
+
[@stephannv]: https://github.com/stephannv
|
551
|
+
[@sedubois]: https://github.com/sedubois
|
@@ -33,11 +33,13 @@ module ActionPolicy
|
|
33
33
|
# Policy is inferred from record
|
34
34
|
# (unless explicitly specified through `with` option).
|
35
35
|
#
|
36
|
+
# @return the policy record
|
36
37
|
# Raises `ActionPolicy::Unauthorized` if check failed.
|
37
38
|
def authorize!(record = :__undef__, to:, **options)
|
38
39
|
policy = lookup_authorization_policy(record, **options)
|
39
40
|
|
40
41
|
Authorizer.call(policy, authorization_rule_for(policy, to))
|
42
|
+
policy.record
|
41
43
|
end
|
42
44
|
|
43
45
|
# Checks that an activity is allowed for the current context (e.g. user).
|
@@ -62,8 +64,8 @@ module ActionPolicy
|
|
62
64
|
|
63
65
|
private def build_authorization_context
|
64
66
|
self.class.authorization_targets
|
65
|
-
.each_with_object({}) do |(key,
|
66
|
-
obj[key] = send(
|
67
|
+
.each_with_object({}) do |(key, method_or_proc), obj|
|
68
|
+
obj[key] = method_or_proc.is_a?(Proc) ? method_or_proc.call : send(method_or_proc)
|
67
69
|
end
|
68
70
|
end
|
69
71
|
|
@@ -103,8 +105,8 @@ module ActionPolicy
|
|
103
105
|
# authorize :user
|
104
106
|
# end
|
105
107
|
def authorize(key, through: nil)
|
106
|
-
|
107
|
-
authorization_targets[key] =
|
108
|
+
method_or_proc = through || key
|
109
|
+
authorization_targets[key] = method_or_proc
|
108
110
|
end
|
109
111
|
|
110
112
|
def authorization_targets
|
@@ -26,6 +26,7 @@ module ActionPolicy
|
|
26
26
|
if respond_to?(:helper_method)
|
27
27
|
helper_method :allowed_to?
|
28
28
|
helper_method :authorized_scope
|
29
|
+
helper_method :allowance_to
|
29
30
|
end
|
30
31
|
|
31
32
|
attr_writer :authorize_count
|
@@ -44,13 +45,15 @@ module ActionPolicy
|
|
44
45
|
# If record is not provided, tries to infer the resource class
|
45
46
|
# from controller name (i.e. `controller_name.classify.safe_constantize`).
|
46
47
|
#
|
48
|
+
# @return the policy record
|
47
49
|
# Raises `ActionPolicy::Unauthorized` if check failed.
|
48
50
|
def authorize!(record = :__undef__, to: nil, **options)
|
49
51
|
to ||= :"#{action_name}?"
|
50
52
|
|
51
|
-
super
|
53
|
+
policy_record = super
|
52
54
|
|
53
55
|
self.authorize_count += 1
|
56
|
+
policy_record
|
54
57
|
end
|
55
58
|
|
56
59
|
# Tries to infer the resource class from controller name
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: action_policy
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.7.
|
4
|
+
version: 0.7.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Vladimir Dementyev
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2025-03-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: ruby-next-core
|
@@ -133,15 +133,6 @@ files:
|
|
133
133
|
- LICENSE.txt
|
134
134
|
- README.md
|
135
135
|
- config/rubocop-rspec.yml
|
136
|
-
- lib/.rbnext/2.7/action_policy/behaviours/policy_for.rb
|
137
|
-
- lib/.rbnext/2.7/action_policy/i18n.rb
|
138
|
-
- lib/.rbnext/2.7/action_policy/policy/cache.rb
|
139
|
-
- lib/.rbnext/2.7/action_policy/policy/pre_check.rb
|
140
|
-
- lib/.rbnext/2.7/action_policy/rails/scope_matchers/action_controller_params.rb
|
141
|
-
- lib/.rbnext/2.7/action_policy/rails/scope_matchers/active_record.rb
|
142
|
-
- lib/.rbnext/2.7/action_policy/rspec/be_authorized_to.rb
|
143
|
-
- lib/.rbnext/2.7/action_policy/rspec/have_authorized_scope.rb
|
144
|
-
- lib/.rbnext/2.7/action_policy/utils/pretty_print.rb
|
145
136
|
- lib/.rbnext/3.0/action_policy/behaviours/policy_for.rb
|
146
137
|
- lib/.rbnext/3.0/action_policy/behaviours/thread_memoized.rb
|
147
138
|
- lib/.rbnext/3.0/action_policy/ext/policy_cache_key.rb
|
@@ -152,6 +143,8 @@ files:
|
|
152
143
|
- lib/.rbnext/3.0/action_policy/policy/execution_result.rb
|
153
144
|
- lib/.rbnext/3.0/action_policy/policy/pre_check.rb
|
154
145
|
- lib/.rbnext/3.0/action_policy/policy/reasons.rb
|
146
|
+
- lib/.rbnext/3.0/action_policy/rails/scope_matchers/action_controller_params.rb
|
147
|
+
- lib/.rbnext/3.0/action_policy/rails/scope_matchers/active_record.rb
|
155
148
|
- lib/.rbnext/3.0/action_policy/rspec/be_an_alias_of.rb
|
156
149
|
- lib/.rbnext/3.0/action_policy/rspec/be_authorized_to.rb
|
157
150
|
- lib/.rbnext/3.0/action_policy/rspec/have_authorized_scope.rb
|
@@ -234,7 +227,7 @@ metadata:
|
|
234
227
|
documentation_uri: https://actionpolicy.evilmartians.io/
|
235
228
|
homepage_uri: https://actionpolicy.evilmartians.io/
|
236
229
|
source_code_uri: http://github.com/palkan/action_policy
|
237
|
-
post_install_message:
|
230
|
+
post_install_message:
|
238
231
|
rdoc_options: []
|
239
232
|
require_paths:
|
240
233
|
- lib
|
@@ -250,7 +243,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
250
243
|
version: '0'
|
251
244
|
requirements: []
|
252
245
|
rubygems_version: 3.4.19
|
253
|
-
signing_key:
|
246
|
+
signing_key:
|
254
247
|
specification_version: 4
|
255
248
|
summary: Authorization framework for Ruby/Rails application
|
256
249
|
test_files: []
|
@@ -1,70 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module ActionPolicy
|
4
|
-
module Behaviours
|
5
|
-
# Adds `policy_for` method
|
6
|
-
module PolicyFor
|
7
|
-
require "action_policy/ext/policy_cache_key"
|
8
|
-
using ActionPolicy::Ext::PolicyCacheKey
|
9
|
-
|
10
|
-
# Returns policy instance for the record.
|
11
|
-
def policy_for(record:, with: nil, namespace: authorization_namespace, context: nil, allow_nil: false, default: default_authorization_policy_class, strict_namespace: authorization_strict_namespace)
|
12
|
-
context = context ? build_authorization_context.merge(context) : authorization_context
|
13
|
-
|
14
|
-
policy_class = with || ::ActionPolicy.lookup(
|
15
|
-
record,
|
16
|
-
namespace: namespace, context: context, allow_nil: allow_nil, default: default, strict_namespace: strict_namespace
|
17
|
-
)
|
18
|
-
policy_class&.new(record, **context)
|
19
|
-
end
|
20
|
-
|
21
|
-
def authorization_context ; @authorization_context ||= build_authorization_context; end
|
22
|
-
|
23
|
-
def build_authorization_context
|
24
|
-
Kernel.raise NotImplementedError, "Please, define `build_authorization_context` method!"
|
25
|
-
end
|
26
|
-
|
27
|
-
def authorization_namespace
|
28
|
-
# override to provide specific authorization namespace
|
29
|
-
end
|
30
|
-
|
31
|
-
def default_authorization_policy_class
|
32
|
-
# override to provide a policy class use when no policy found
|
33
|
-
end
|
34
|
-
|
35
|
-
def authorization_strict_namespace
|
36
|
-
# override to provide strict namespace lookup option
|
37
|
-
end
|
38
|
-
|
39
|
-
# Override this method to provide implicit authorization target
|
40
|
-
# that would be used in case `record` is not specified in
|
41
|
-
# `authorize!` and `allowed_to?` call.
|
42
|
-
#
|
43
|
-
# It is also used to infer a policy for scoping (in `authorized_scope` method).
|
44
|
-
def implicit_authorization_target
|
45
|
-
# no-op
|
46
|
-
end
|
47
|
-
|
48
|
-
# Return implicit authorization target or raises an exception if it's nil
|
49
|
-
def implicit_authorization_target!
|
50
|
-
implicit_authorization_target || Kernel.raise(
|
51
|
-
NotFound,
|
52
|
-
[
|
53
|
-
self,
|
54
|
-
"Couldn't find implicit authorization target " \
|
55
|
-
"for #{self.class}. " \
|
56
|
-
"Please, provide policy class explicitly using `with` option or " \
|
57
|
-
"define the `implicit_authorization_target` method."
|
58
|
-
]
|
59
|
-
)
|
60
|
-
end
|
61
|
-
|
62
|
-
def policy_for_cache_key(record:, with: nil, namespace: nil, context: authorization_context, **__kwrest__)
|
63
|
-
record_key = record._policy_cache_key(use_object_id: true)
|
64
|
-
context_key = context.values.map { |_1| _1._policy_cache_key(use_object_id: true) }.join(".")
|
65
|
-
|
66
|
-
"#{namespace}/#{with}/#{context_key}/#{record_key}"
|
67
|
-
end
|
68
|
-
end
|
69
|
-
end
|
70
|
-
end
|
@@ -1,56 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module ActionPolicy
|
4
|
-
module I18n # :nodoc:
|
5
|
-
DEFAULT_UNAUTHORIZED_MESSAGE = "You are not authorized to perform this action"
|
6
|
-
|
7
|
-
class << self
|
8
|
-
def full_message(policy_class, rule, details = nil)
|
9
|
-
candidates = candidates_for(policy_class, rule)
|
10
|
-
|
11
|
-
options = {scope: :action_policy}
|
12
|
-
options.merge!(details) unless details.nil?
|
13
|
-
|
14
|
-
::I18n.t(
|
15
|
-
candidates.shift,
|
16
|
-
default: candidates,
|
17
|
-
**options
|
18
|
-
)
|
19
|
-
end
|
20
|
-
|
21
|
-
private
|
22
|
-
|
23
|
-
def candidates_for(policy_class, rule)
|
24
|
-
policy_hierarchy = policy_class.ancestors.select { |_1| _1.respond_to?(:identifier) }
|
25
|
-
[
|
26
|
-
*policy_hierarchy.map { |klass| :"policy.#{klass.identifier}.#{rule}" },
|
27
|
-
:"policy.#{rule}",
|
28
|
-
:unauthorized,
|
29
|
-
DEFAULT_UNAUTHORIZED_MESSAGE
|
30
|
-
]
|
31
|
-
end
|
32
|
-
end
|
33
|
-
|
34
|
-
ActionPolicy::Policy::FailureReasons.prepend(Module.new do
|
35
|
-
def full_messages
|
36
|
-
reasons.flat_map do |policy_klass, rules|
|
37
|
-
rules.flat_map do |rule|
|
38
|
-
if rule.is_a?(::Hash)
|
39
|
-
rule.map do |key, details|
|
40
|
-
ActionPolicy::I18n.full_message(policy_klass, key, details)
|
41
|
-
end
|
42
|
-
else
|
43
|
-
ActionPolicy::I18n.full_message(policy_klass, rule)
|
44
|
-
end
|
45
|
-
end
|
46
|
-
end
|
47
|
-
end
|
48
|
-
end)
|
49
|
-
|
50
|
-
ActionPolicy::Policy::ExecutionResult.prepend(Module.new do
|
51
|
-
def message
|
52
|
-
ActionPolicy::I18n.full_message(policy, rule, details)
|
53
|
-
end
|
54
|
-
end)
|
55
|
-
end
|
56
|
-
end
|
@@ -1,101 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "action_policy/version"
|
4
|
-
|
5
|
-
module ActionPolicy # :nodoc:
|
6
|
-
using RubyNext
|
7
|
-
|
8
|
-
# By default cache namespace (or prefix) contains major and minor version of the gem
|
9
|
-
CACHE_NAMESPACE = "acp:#{ActionPolicy::VERSION.split(".").take(2).join(".")}"
|
10
|
-
|
11
|
-
require "action_policy/ext/policy_cache_key"
|
12
|
-
|
13
|
-
using ActionPolicy::Ext::PolicyCacheKey
|
14
|
-
|
15
|
-
module Policy
|
16
|
-
# Provides long-lived cache through ActionPolicy.cache_store.
|
17
|
-
#
|
18
|
-
# NOTE: if cache_store is nil then we silently skip all the caching.
|
19
|
-
module Cache
|
20
|
-
class << self
|
21
|
-
def included(base)
|
22
|
-
base.extend ClassMethods
|
23
|
-
end
|
24
|
-
end
|
25
|
-
|
26
|
-
def cache_namespace() ; ActionPolicy::CACHE_NAMESPACE; end
|
27
|
-
|
28
|
-
def cache_key(*parts)
|
29
|
-
[
|
30
|
-
cache_namespace,
|
31
|
-
*parts
|
32
|
-
].map { |_1| _1._policy_cache_key }.join("/")
|
33
|
-
end
|
34
|
-
|
35
|
-
def rule_cache_key(rule)
|
36
|
-
cache_key(
|
37
|
-
context_cache_key,
|
38
|
-
record,
|
39
|
-
self.class,
|
40
|
-
rule
|
41
|
-
)
|
42
|
-
end
|
43
|
-
|
44
|
-
def context_cache_key
|
45
|
-
authorization_context.map { |_1, _2| _2._policy_cache_key.to_s }.join("/")
|
46
|
-
end
|
47
|
-
|
48
|
-
def apply_with_cache(rule)
|
49
|
-
options = self.class.cached_rules.fetch(rule)
|
50
|
-
key = rule_cache_key(rule)
|
51
|
-
|
52
|
-
ActionPolicy.cache_store.then do |store|
|
53
|
-
result = store.read(key)
|
54
|
-
unless result.nil?
|
55
|
-
result.cached!
|
56
|
-
next result
|
57
|
-
end
|
58
|
-
yield.tap do |result|
|
59
|
-
store.write(key, result, options)
|
60
|
-
end
|
61
|
-
end
|
62
|
-
end
|
63
|
-
|
64
|
-
def apply_r(rule)
|
65
|
-
return super if ActionPolicy.cache_store.nil? ||
|
66
|
-
!self.class.cached_rules.key?(rule)
|
67
|
-
|
68
|
-
apply_with_cache(rule) { super }
|
69
|
-
end
|
70
|
-
|
71
|
-
def cache(*parts, **options)
|
72
|
-
key = cache_key(*parts)
|
73
|
-
ActionPolicy.cache_store.then do |store|
|
74
|
-
res = store.read(key)
|
75
|
-
next res unless res.nil?
|
76
|
-
res = yield
|
77
|
-
store.write(key, res, options)
|
78
|
-
res
|
79
|
-
end
|
80
|
-
end
|
81
|
-
|
82
|
-
module ClassMethods # :nodoc:
|
83
|
-
def cache(*rules, **options)
|
84
|
-
rules.each do |rule|
|
85
|
-
cached_rules[rule] = options
|
86
|
-
end
|
87
|
-
end
|
88
|
-
|
89
|
-
def cached_rules
|
90
|
-
return @cached_rules if instance_variable_defined?(:@cached_rules)
|
91
|
-
|
92
|
-
@cached_rules = if superclass.respond_to?(:cached_rules)
|
93
|
-
superclass.cached_rules.dup
|
94
|
-
else
|
95
|
-
{}
|
96
|
-
end
|
97
|
-
end
|
98
|
-
end
|
99
|
-
end
|
100
|
-
end
|
101
|
-
end
|
@@ -1,162 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module ActionPolicy
|
4
|
-
module Policy
|
5
|
-
# Adds callback-style checks to policies to
|
6
|
-
# extract common checks from rules.
|
7
|
-
#
|
8
|
-
# class ApplicationPolicy < ActionPolicy::Base
|
9
|
-
# authorize :user
|
10
|
-
# pre_check :allow_admins
|
11
|
-
#
|
12
|
-
# private
|
13
|
-
# # Allow every action for admins
|
14
|
-
# def allow_admins
|
15
|
-
# allow! if user.admin?
|
16
|
-
# end
|
17
|
-
# end
|
18
|
-
#
|
19
|
-
# You can specify conditional pre-checks (through `except` / `only`) options
|
20
|
-
# and skip already defined pre-checks if necessary.
|
21
|
-
#
|
22
|
-
# class UserPolicy < ApplicationPolicy
|
23
|
-
# skip_pre_check :allow_admins, only: :destroy?
|
24
|
-
#
|
25
|
-
# def destroy?
|
26
|
-
# user.admin? && !record.admin?
|
27
|
-
# end
|
28
|
-
# end
|
29
|
-
module PreCheck
|
30
|
-
# Single pre-check instance.
|
31
|
-
#
|
32
|
-
# Implements filtering logic.
|
33
|
-
class Check
|
34
|
-
attr_reader :name, :policy_class
|
35
|
-
|
36
|
-
def initialize(policy, name, except: nil, only: nil)
|
37
|
-
if !except.nil? && !only.nil?
|
38
|
-
raise ArgumentError,
|
39
|
-
"Only one of `except` and `only` may be specified for pre-check"
|
40
|
-
end
|
41
|
-
|
42
|
-
@policy_class = policy
|
43
|
-
@name = name
|
44
|
-
@blacklist = Array(except) unless except.nil?
|
45
|
-
@whitelist = Array(only) unless only.nil?
|
46
|
-
|
47
|
-
rebuild_filter
|
48
|
-
end
|
49
|
-
|
50
|
-
def applicable?(rule)
|
51
|
-
return true if filter.nil?
|
52
|
-
filter.call(rule)
|
53
|
-
end
|
54
|
-
|
55
|
-
def call(policy) ; policy.send(name); end
|
56
|
-
|
57
|
-
def skip!(except: nil, only: nil)
|
58
|
-
if !except.nil? && !only.nil?
|
59
|
-
raise ArgumentError,
|
60
|
-
"Only one of `except` and `only` may be specified when skipping pre-check"
|
61
|
-
end
|
62
|
-
|
63
|
-
if except.nil? && only.nil?
|
64
|
-
raise ArgumentError,
|
65
|
-
"At least one of `except` and `only` must be specified when skipping pre-check"
|
66
|
-
end
|
67
|
-
|
68
|
-
if except
|
69
|
-
@whitelist = Array(except)
|
70
|
-
@whitelist -= blacklist if blacklist
|
71
|
-
@blacklist = nil
|
72
|
-
else
|
73
|
-
# only
|
74
|
-
@blacklist += Array(only) if blacklist
|
75
|
-
@whitelist -= Array(only) if whitelist
|
76
|
-
@blacklist = Array(only) if filter.nil?
|
77
|
-
end
|
78
|
-
|
79
|
-
rebuild_filter
|
80
|
-
end
|
81
|
-
# rubocop: enable
|
82
|
-
# rubocop: enable
|
83
|
-
|
84
|
-
def dup
|
85
|
-
self.class.new(
|
86
|
-
policy_class,
|
87
|
-
name,
|
88
|
-
except: blacklist&.dup,
|
89
|
-
only: whitelist&.dup
|
90
|
-
)
|
91
|
-
end
|
92
|
-
|
93
|
-
private
|
94
|
-
|
95
|
-
attr_reader :whitelist, :blacklist, :filter
|
96
|
-
|
97
|
-
def rebuild_filter
|
98
|
-
@filter =
|
99
|
-
if whitelist
|
100
|
-
proc { |rule| whitelist.include?(rule) }
|
101
|
-
elsif blacklist
|
102
|
-
proc { |rule| !blacklist.include?(rule) }
|
103
|
-
end
|
104
|
-
end
|
105
|
-
end
|
106
|
-
|
107
|
-
class << self
|
108
|
-
def included(base)
|
109
|
-
base.extend ClassMethods
|
110
|
-
end
|
111
|
-
end
|
112
|
-
|
113
|
-
def run_pre_checks(rule)
|
114
|
-
self.class.pre_checks.each do |check|
|
115
|
-
next unless check.applicable?(rule)
|
116
|
-
check.call(self)
|
117
|
-
end
|
118
|
-
|
119
|
-
yield if block_given?
|
120
|
-
end
|
121
|
-
|
122
|
-
def __apply__(rule)
|
123
|
-
run_pre_checks(rule) { super }
|
124
|
-
end
|
125
|
-
|
126
|
-
module ClassMethods # :nodoc:
|
127
|
-
def pre_check(*names, **options)
|
128
|
-
names.each do |name|
|
129
|
-
# do not allow pre-check override
|
130
|
-
check = pre_checks.find { |_1| _1.name == name }
|
131
|
-
raise "Pre-check already defined: #{name}" unless check.nil?
|
132
|
-
|
133
|
-
pre_checks << Check.new(self, name, **options)
|
134
|
-
end
|
135
|
-
end
|
136
|
-
|
137
|
-
def skip_pre_check(*names, **options)
|
138
|
-
names.each do |name|
|
139
|
-
check = pre_checks.find { |_1| _1.name == name }
|
140
|
-
raise "Pre-check not found: #{name}" if check.nil?
|
141
|
-
|
142
|
-
# when no options provided we remove this check completely
|
143
|
-
next pre_checks.delete(check) if options.empty?
|
144
|
-
|
145
|
-
# otherwise duplicate and apply skip options
|
146
|
-
pre_checks[pre_checks.index(check)] = check.dup.tap { |_1| _1.skip!(**options) }
|
147
|
-
end
|
148
|
-
end
|
149
|
-
|
150
|
-
def pre_checks
|
151
|
-
return @pre_checks if instance_variable_defined?(:@pre_checks)
|
152
|
-
|
153
|
-
@pre_checks = if superclass.respond_to?(:pre_checks)
|
154
|
-
superclass.pre_checks.dup
|
155
|
-
else
|
156
|
-
[]
|
157
|
-
end
|
158
|
-
end
|
159
|
-
end
|
160
|
-
end
|
161
|
-
end
|
162
|
-
end
|
@@ -1,96 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "action_policy/testing"
|
4
|
-
|
5
|
-
module ActionPolicy
|
6
|
-
module RSpec
|
7
|
-
# Authorization matcher `be_authorized_to`.
|
8
|
-
#
|
9
|
-
# Verifies that a block of code has been authorized using specific policy.
|
10
|
-
#
|
11
|
-
# Example:
|
12
|
-
#
|
13
|
-
# # in controller/request specs
|
14
|
-
# subject { patch :update, id: product.id }
|
15
|
-
#
|
16
|
-
# it "is authorized" do
|
17
|
-
# expect { subject }
|
18
|
-
# .to be_authorized_to(:manage?, product)
|
19
|
-
# .with(ProductPolicy)
|
20
|
-
# end
|
21
|
-
#
|
22
|
-
class BeAuthorizedTo < ::RSpec::Matchers::BuiltIn::BaseMatcher
|
23
|
-
attr_reader :rule, :target, :policy, :actual_calls, :context
|
24
|
-
|
25
|
-
def initialize(rule, target)
|
26
|
-
@rule = rule
|
27
|
-
@target = target
|
28
|
-
end
|
29
|
-
|
30
|
-
def with(policy)
|
31
|
-
@policy = policy
|
32
|
-
self
|
33
|
-
end
|
34
|
-
|
35
|
-
def with_context(context)
|
36
|
-
@context = context
|
37
|
-
self
|
38
|
-
end
|
39
|
-
|
40
|
-
def match(_expected, actual)
|
41
|
-
raise "This matcher only supports block expectations" unless actual.is_a?(Proc)
|
42
|
-
|
43
|
-
@policy ||= ::ActionPolicy.lookup(target)
|
44
|
-
@context ||= nil
|
45
|
-
|
46
|
-
begin
|
47
|
-
ActionPolicy::Testing::AuthorizeTracker.tracking { actual.call }
|
48
|
-
rescue ActionPolicy::Unauthorized
|
49
|
-
# we don't want to care about authorization result
|
50
|
-
end
|
51
|
-
|
52
|
-
@actual_calls = ActionPolicy::Testing::AuthorizeTracker.calls
|
53
|
-
|
54
|
-
actual_calls.any? { |_1| _1.matches?(policy, rule, target, context) }
|
55
|
-
end
|
56
|
-
|
57
|
-
def does_not_match?(*__rest__)
|
58
|
-
raise "This matcher doesn't support negation"
|
59
|
-
end
|
60
|
-
|
61
|
-
def supports_block_expectations?() ; true; end
|
62
|
-
|
63
|
-
def failure_message
|
64
|
-
"expected #{formatted_record} " \
|
65
|
-
"to be authorized with #{policy}##{rule}, " \
|
66
|
-
"#{context ? "and context #{context.inspect}, " : ""}" \
|
67
|
-
"but #{actual_calls_message}"
|
68
|
-
end
|
69
|
-
|
70
|
-
def actual_calls_message
|
71
|
-
if actual_calls.empty?
|
72
|
-
"no authorization calls have been made"
|
73
|
-
else
|
74
|
-
"the following calls were encountered:\n" \
|
75
|
-
"#{formatted_calls}"
|
76
|
-
end
|
77
|
-
end
|
78
|
-
|
79
|
-
def formatted_calls
|
80
|
-
actual_calls.map do |_1|
|
81
|
-
" - #{_1.inspect}"
|
82
|
-
end.join("\n")
|
83
|
-
end
|
84
|
-
|
85
|
-
def formatted_record(record = target) ; ::RSpec::Support::ObjectFormatter.format(record); end
|
86
|
-
end
|
87
|
-
end
|
88
|
-
end
|
89
|
-
|
90
|
-
RSpec.configure do |config|
|
91
|
-
config.include(Module.new do
|
92
|
-
def be_authorized_to(rule, target)
|
93
|
-
ActionPolicy::RSpec::BeAuthorizedTo.new(rule, target)
|
94
|
-
end
|
95
|
-
end)
|
96
|
-
end
|
@@ -1,130 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "action_policy/testing"
|
4
|
-
|
5
|
-
module ActionPolicy
|
6
|
-
module RSpec
|
7
|
-
# Implements `have_authorized_scope` matcher.
|
8
|
-
#
|
9
|
-
# Verifies that a block of code applies authorization scoping using specific policy.
|
10
|
-
#
|
11
|
-
# Example:
|
12
|
-
#
|
13
|
-
# # in controller/request specs
|
14
|
-
# subject { get :index }
|
15
|
-
#
|
16
|
-
# it "has authorized scope" do
|
17
|
-
# expect { subject }
|
18
|
-
# .to have_authorized_scope(:active_record_relation)
|
19
|
-
# .with(ProductPolicy)
|
20
|
-
# end
|
21
|
-
#
|
22
|
-
class HaveAuthorizedScope < ::RSpec::Matchers::BuiltIn::BaseMatcher
|
23
|
-
attr_reader :type, :name, :policy, :scope_options, :actual_scopes,
|
24
|
-
:target_expectations, :context
|
25
|
-
|
26
|
-
def initialize(type)
|
27
|
-
@type = type
|
28
|
-
@name = :default
|
29
|
-
@scope_options = nil
|
30
|
-
end
|
31
|
-
|
32
|
-
def with(policy)
|
33
|
-
@policy = policy
|
34
|
-
self
|
35
|
-
end
|
36
|
-
|
37
|
-
def as(name)
|
38
|
-
@name = name
|
39
|
-
self
|
40
|
-
end
|
41
|
-
|
42
|
-
def with_scope_options(scope_options)
|
43
|
-
@scope_options = scope_options
|
44
|
-
self
|
45
|
-
end
|
46
|
-
|
47
|
-
def with_target(&block)
|
48
|
-
@target_expectations = block
|
49
|
-
self
|
50
|
-
end
|
51
|
-
|
52
|
-
def with_context(context)
|
53
|
-
@context = context
|
54
|
-
self
|
55
|
-
end
|
56
|
-
|
57
|
-
def match(_expected, actual)
|
58
|
-
raise "This matcher only supports block expectations" unless actual.is_a?(Proc)
|
59
|
-
|
60
|
-
ActionPolicy::Testing::AuthorizeTracker.tracking { actual.call }
|
61
|
-
|
62
|
-
@actual_scopes = ActionPolicy::Testing::AuthorizeTracker.scopings
|
63
|
-
|
64
|
-
matching_scopes = actual_scopes.select { |_1| _1.matches?(policy, type, name, scope_options, context) }
|
65
|
-
|
66
|
-
return false if matching_scopes.empty?
|
67
|
-
|
68
|
-
return true unless target_expectations
|
69
|
-
|
70
|
-
if matching_scopes.size > 1
|
71
|
-
raise "Too many matching scopings (#{matching_scopes.size}), " \
|
72
|
-
"you can run `.with_target` only when there is the only one match"
|
73
|
-
end
|
74
|
-
|
75
|
-
target_expectations.call(matching_scopes.first.target)
|
76
|
-
true
|
77
|
-
end
|
78
|
-
|
79
|
-
def does_not_match?(*__rest__)
|
80
|
-
raise "This matcher doesn't support negation"
|
81
|
-
end
|
82
|
-
|
83
|
-
def supports_block_expectations?() ; true; end
|
84
|
-
|
85
|
-
def failure_message
|
86
|
-
"expected a scoping named :#{name} for type :#{type} " \
|
87
|
-
"#{scope_options_message} " \
|
88
|
-
"#{context ? "and context #{context.inspect} " : ""}" \
|
89
|
-
"from #{policy} to have been applied, " \
|
90
|
-
"but #{actual_scopes_message}"
|
91
|
-
end
|
92
|
-
|
93
|
-
def scope_options_message
|
94
|
-
if scope_options
|
95
|
-
if defined?(::RSpec::Matchers::Composable) &&
|
96
|
-
scope_options.is_a?(::RSpec::Matchers::Composable)
|
97
|
-
"with scope options #{scope_options.description}"
|
98
|
-
else
|
99
|
-
"with scope options #{scope_options}"
|
100
|
-
end
|
101
|
-
else
|
102
|
-
"without scope options"
|
103
|
-
end
|
104
|
-
end
|
105
|
-
|
106
|
-
def actual_scopes_message
|
107
|
-
if actual_scopes.empty?
|
108
|
-
"no scopings have been made"
|
109
|
-
else
|
110
|
-
"the following scopings were encountered:\n" \
|
111
|
-
"#{formatted_scopings}"
|
112
|
-
end
|
113
|
-
end
|
114
|
-
|
115
|
-
def formatted_scopings
|
116
|
-
actual_scopes.map do |_1|
|
117
|
-
" - #{_1.inspect}"
|
118
|
-
end.join("\n")
|
119
|
-
end
|
120
|
-
end
|
121
|
-
end
|
122
|
-
end
|
123
|
-
|
124
|
-
RSpec.configure do |config|
|
125
|
-
config.include(Module.new do
|
126
|
-
def have_authorized_scope(type)
|
127
|
-
ActionPolicy::RSpec::HaveAuthorizedScope.new(type)
|
128
|
-
end
|
129
|
-
end)
|
130
|
-
end
|
@@ -1,155 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
old_verbose = $VERBOSE
|
4
|
-
|
5
|
-
begin
|
6
|
-
require "method_source"
|
7
|
-
# Ignore parse warnings when patch
|
8
|
-
# Ruby version mismatches
|
9
|
-
$VERBOSE = nil
|
10
|
-
require "prism"
|
11
|
-
rescue LoadError
|
12
|
-
# do nothing
|
13
|
-
ensure
|
14
|
-
$VERBOSE = old_verbose
|
15
|
-
end
|
16
|
-
|
17
|
-
module ActionPolicy
|
18
|
-
using RubyNext
|
19
|
-
|
20
|
-
# Takes the object and a method name,
|
21
|
-
# and returns the "annotated" source code for the method:
|
22
|
-
# code is split into parts by logical operators and each
|
23
|
-
# part is evaluated separately.
|
24
|
-
#
|
25
|
-
# Example:
|
26
|
-
#
|
27
|
-
# class MyClass
|
28
|
-
# def access?
|
29
|
-
# admin? && access_feed?
|
30
|
-
# end
|
31
|
-
# end
|
32
|
-
#
|
33
|
-
# puts PrettyPrint.format_method(MyClass.new, :access?)
|
34
|
-
#
|
35
|
-
# #=> MyClass#access?
|
36
|
-
# #=> ↳ admin? #=> false
|
37
|
-
# #=> AND
|
38
|
-
# #=> access_feed? #=> true
|
39
|
-
module PrettyPrint
|
40
|
-
TRUE = "\e[32mtrue\e[0m"
|
41
|
-
FALSE = "\e[31mfalse\e[0m"
|
42
|
-
|
43
|
-
class Visitor
|
44
|
-
attr_reader :lines, :object, :source
|
45
|
-
attr_accessor :indent
|
46
|
-
|
47
|
-
def initialize(object)
|
48
|
-
@object = object
|
49
|
-
end
|
50
|
-
|
51
|
-
def collect(ast)
|
52
|
-
@lines = []
|
53
|
-
@indent = 0
|
54
|
-
@source = ast.source.source
|
55
|
-
ast = ast.value.child_nodes[0].child_nodes[0].body
|
56
|
-
|
57
|
-
visit_node(ast)
|
58
|
-
|
59
|
-
lines.join("\n")
|
60
|
-
end
|
61
|
-
|
62
|
-
def visit_node(ast)
|
63
|
-
if respond_to?("visit_#{ast.type}")
|
64
|
-
send("visit_#{ast.type}", ast)
|
65
|
-
else
|
66
|
-
visit_missing ast
|
67
|
-
end
|
68
|
-
end
|
69
|
-
|
70
|
-
def expression_with_result(sexp)
|
71
|
-
expression = source[sexp.location.start_offset...sexp.location.end_offset]
|
72
|
-
"#{expression} #=> #{PrettyPrint.colorize(eval_exp(expression))}"
|
73
|
-
end
|
74
|
-
|
75
|
-
def eval_exp(exp)
|
76
|
-
return "<skipped>" if ignore_exp?(exp)
|
77
|
-
object.instance_eval(exp)
|
78
|
-
rescue => e
|
79
|
-
"Failed: #{e.message}"
|
80
|
-
end
|
81
|
-
|
82
|
-
def visit_and_node(ast)
|
83
|
-
visit_node(ast.left)
|
84
|
-
lines << indented("AND")
|
85
|
-
visit_node(ast.right)
|
86
|
-
end
|
87
|
-
|
88
|
-
def visit_or_node(ast)
|
89
|
-
visit_node(ast.left)
|
90
|
-
lines << indented("OR")
|
91
|
-
visit_node(ast.right)
|
92
|
-
end
|
93
|
-
|
94
|
-
def visit_statements_node(ast)
|
95
|
-
ast.child_nodes.each do |node|
|
96
|
-
visit_node(node)
|
97
|
-
# restore indent after each expression
|
98
|
-
self.indent -= 2
|
99
|
-
end
|
100
|
-
end
|
101
|
-
|
102
|
-
def visit_parentheses_node(ast)
|
103
|
-
lines << indented("(")
|
104
|
-
self.indent += 2
|
105
|
-
visit_node(ast.child_nodes[0])
|
106
|
-
lines << indented(")")
|
107
|
-
end
|
108
|
-
|
109
|
-
def visit_missing(ast)
|
110
|
-
lines << indented(expression_with_result(ast))
|
111
|
-
end
|
112
|
-
|
113
|
-
def indented(str)
|
114
|
-
"#{indent.zero? ? "↳ " : ""}#{" " * indent}#{str}".tap do
|
115
|
-
# increase indent after the first expression
|
116
|
-
self.indent += 2 if indent.zero?
|
117
|
-
end
|
118
|
-
end
|
119
|
-
|
120
|
-
# Some lines should not be evaled
|
121
|
-
def ignore_exp?(exp)
|
122
|
-
PrettyPrint.ignore_expressions.any? { |_1| exp.match?(_1) }
|
123
|
-
end
|
124
|
-
end
|
125
|
-
|
126
|
-
class << self
|
127
|
-
attr_accessor :ignore_expressions
|
128
|
-
|
129
|
-
if defined?(::Prism) && defined?(::MethodSource)
|
130
|
-
def available?() ; true; end
|
131
|
-
|
132
|
-
def print_method(object, method_name)
|
133
|
-
ast = Prism.parse(object.method(method_name).source)
|
134
|
-
|
135
|
-
Visitor.new(object).collect(ast)
|
136
|
-
end
|
137
|
-
else
|
138
|
-
def available?() ; false; end
|
139
|
-
|
140
|
-
def print_method(_, _) ; ""; end
|
141
|
-
end
|
142
|
-
|
143
|
-
def colorize(val)
|
144
|
-
return val unless $stdout.isatty
|
145
|
-
return TRUE if val.eql?(true) # rubocop:disable Lint/DeprecatedConstants
|
146
|
-
return FALSE if val.eql?(false) # rubocop:disable Lint/DeprecatedConstants
|
147
|
-
val
|
148
|
-
end
|
149
|
-
end
|
150
|
-
|
151
|
-
self.ignore_expressions = [
|
152
|
-
/^\s*binding\.(pry|irb)\s*$/s
|
153
|
-
]
|
154
|
-
end
|
155
|
-
end
|
/data/lib/.rbnext/{2.7 → 3.0}/action_policy/rails/scope_matchers/action_controller_params.rb
RENAMED
File without changes
|
File without changes
|