action_policy 0.6.7 → 0.6.9
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 +9 -0
- data/LICENSE.txt +1 -1
- data/lib/.rbnext/2.7/action_policy/behaviours/policy_for.rb +1 -1
- data/lib/.rbnext/2.7/action_policy/rails/scope_matchers/action_controller_params.rb +5 -3
- data/lib/.rbnext/2.7/action_policy/rails/scope_matchers/active_record.rb +13 -11
- data/lib/.rbnext/2.7/action_policy/rspec/be_authorized_to.rb +1 -1
- data/lib/.rbnext/2.7/action_policy/rspec/have_authorized_scope.rb +9 -3
- data/lib/.rbnext/3.0/action_policy/ext/policy_cache_key.rb +10 -10
- data/lib/.rbnext/3.0/action_policy/policy/core.rb +1 -1
- data/lib/.rbnext/3.0/action_policy/rspec/be_authorized_to.rb +1 -1
- data/lib/.rbnext/3.0/action_policy/rspec/have_authorized_scope.rb +9 -3
- data/lib/.rbnext/3.0/action_policy/utils/suggest_message.rb +1 -1
- data/lib/.rbnext/3.1/action_policy/behaviours/policy_for.rb +1 -1
- data/lib/.rbnext/3.1/action_policy/ext/policy_cache_key.rb +10 -10
- data/lib/.rbnext/3.2/action_policy/behaviours/policy_for.rb +68 -0
- data/lib/.rbnext/3.2/action_policy/ext/policy_cache_key.rb +72 -0
- data/lib/.rbnext/3.2/action_policy/lookup_chain.rb +145 -0
- data/lib/.rbnext/3.2/action_policy/policy/core.rb +168 -0
- data/lib/.rbnext/3.2/action_policy/rspec/be_authorized_to.rb +96 -0
- data/lib/.rbnext/3.2/action_policy/rspec/have_authorized_scope.rb +130 -0
- data/lib/.rbnext/3.2/action_policy/utils/suggest_message.rb +19 -0
- data/lib/action_policy/rails/scope_matchers/action_controller_params.rb +5 -3
- data/lib/action_policy/rails/scope_matchers/active_record.rb +13 -11
- data/lib/action_policy/railtie.rb +4 -15
- data/lib/action_policy/rspec/have_authorized_scope.rb +8 -2
- data/lib/action_policy/test_helper.rb +5 -2
- data/lib/action_policy/testing.rb +17 -10
- data/lib/action_policy/version.rb +1 -1
- metadata +15 -8
@@ -0,0 +1,168 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "action_policy/behaviours/policy_for"
|
4
|
+
require "action_policy/policy/execution_result"
|
5
|
+
require "action_policy/utils/suggest_message"
|
6
|
+
require "action_policy/utils/pretty_print"
|
7
|
+
|
8
|
+
unless "".respond_to?(:underscore)
|
9
|
+
require "action_policy/ext/string_underscore"
|
10
|
+
using ActionPolicy::Ext::StringUnderscore
|
11
|
+
end
|
12
|
+
|
13
|
+
module ActionPolicy
|
14
|
+
using RubyNext
|
15
|
+
|
16
|
+
# Raised when `resolve_rule` failed to find an approriate
|
17
|
+
# policy rule method for the activity
|
18
|
+
class UnknownRule < Error
|
19
|
+
include ActionPolicy::SuggestMessage
|
20
|
+
|
21
|
+
attr_reader :policy, :rule, :message
|
22
|
+
|
23
|
+
def initialize(policy, rule)
|
24
|
+
@policy = policy.class
|
25
|
+
@rule = rule
|
26
|
+
@message = "Couldn't find rule '#{@rule}' for #{@policy}" \
|
27
|
+
"#{suggest(@rule, @policy.instance_methods - Object.instance_methods)}"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
class NonPredicateRule < UnknownRule
|
32
|
+
def initialize(policy, rule)
|
33
|
+
@policy = policy.class
|
34
|
+
@rule = rule
|
35
|
+
@message = "The rule '#{@rule}' of '#{@policy}' must ends with ? (question mark)\nDid you mean? #{@rule}?"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
module Policy
|
40
|
+
# Core policy API
|
41
|
+
module Core
|
42
|
+
class << self
|
43
|
+
def included(base)
|
44
|
+
base.extend ClassMethods
|
45
|
+
|
46
|
+
# Generate a new class for each _policy chain_
|
47
|
+
# in order to extend it independently
|
48
|
+
base.module_eval do
|
49
|
+
@result_class = Class.new(ExecutionResult)
|
50
|
+
|
51
|
+
# we need to make this class _named_,
|
52
|
+
# 'cause anonymous classes couldn't be marshalled
|
53
|
+
base.const_set(:APR, @result_class)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
module ClassMethods # :nodoc:
|
59
|
+
attr_writer :identifier
|
60
|
+
|
61
|
+
def result_class
|
62
|
+
return @result_class if instance_variable_defined?(:@result_class)
|
63
|
+
@result_class = superclass.result_class
|
64
|
+
end
|
65
|
+
|
66
|
+
def identifier
|
67
|
+
return @identifier if instance_variable_defined?(:@identifier)
|
68
|
+
|
69
|
+
@identifier = name.sub(/Policy$/, "").underscore.to_sym
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
include ActionPolicy::Behaviours::PolicyFor
|
74
|
+
|
75
|
+
attr_reader :record, :result
|
76
|
+
|
77
|
+
# NEXT_RELEASE: deprecate `record` arg, migrate to `record: nil`
|
78
|
+
def initialize(record = nil, *__rest__)
|
79
|
+
@record = record
|
80
|
+
end
|
81
|
+
|
82
|
+
# Returns a result of applying the specified rule (true of false).
|
83
|
+
# Unlike simply calling a predicate rule (`policy.manage?`),
|
84
|
+
# `apply` also calls pre-checks.
|
85
|
+
def apply(rule)
|
86
|
+
@result = self.class.result_class.new(self.class, rule)
|
87
|
+
|
88
|
+
catch :policy_fulfilled do
|
89
|
+
result.load __apply__(resolve_rule(rule))
|
90
|
+
end
|
91
|
+
|
92
|
+
result.value
|
93
|
+
end
|
94
|
+
|
95
|
+
def deny!
|
96
|
+
result&.load false
|
97
|
+
throw :policy_fulfilled
|
98
|
+
end
|
99
|
+
|
100
|
+
def allow!
|
101
|
+
result&.load true
|
102
|
+
throw :policy_fulfilled
|
103
|
+
end
|
104
|
+
|
105
|
+
# This method performs the rule call.
|
106
|
+
# Override or extend it to provide custom functionality
|
107
|
+
# (such as caching, pre checks, etc.)
|
108
|
+
def __apply__(rule) = public_send(rule)
|
109
|
+
|
110
|
+
# Wrap code that could modify result
|
111
|
+
# to prevent the current result modification
|
112
|
+
def with_clean_result # :nodoc:
|
113
|
+
was_result = @result
|
114
|
+
yield
|
115
|
+
@result
|
116
|
+
ensure
|
117
|
+
@result = was_result
|
118
|
+
end
|
119
|
+
|
120
|
+
# Returns a result of applying the specified rule to the specified record.
|
121
|
+
# Under the hood a policy class for record is resolved
|
122
|
+
# (unless it's explicitly set through `with` option).
|
123
|
+
#
|
124
|
+
# If record is `nil` then we uses the current policy.
|
125
|
+
def allowed_to?(rule, record = :__undef__, **options)
|
126
|
+
if (record == :__undef__ || record == self.record) && options.empty?
|
127
|
+
__apply__(resolve_rule(rule))
|
128
|
+
else
|
129
|
+
policy_for(record: record, **options).then do |policy|
|
130
|
+
policy.apply(policy.resolve_rule(rule))
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
# An alias for readability purposes
|
136
|
+
def check?(*args, **hargs) = allowed_to?(*args, **hargs)
|
137
|
+
|
138
|
+
# Returns a rule name (policy method name) for activity.
|
139
|
+
#
|
140
|
+
# By default, rule name is equal to activity name.
|
141
|
+
#
|
142
|
+
# Raises ActionPolicy::UnknownRule when rule is not found in policy.
|
143
|
+
def resolve_rule(activity)
|
144
|
+
raise UnknownRule.new(self, activity) unless
|
145
|
+
respond_to?(activity)
|
146
|
+
activity
|
147
|
+
end
|
148
|
+
|
149
|
+
# Return annotated source code for the rule
|
150
|
+
# NOTE: require "method_source" and "unparser" gems to be installed.
|
151
|
+
# Otherwise returns empty string.
|
152
|
+
def inspect_rule(rule) = PrettyPrint.print_method(self, rule)
|
153
|
+
|
154
|
+
# Helper for printing the annotated rule source.
|
155
|
+
# Useful for debugging: type `pp :show?` within the context of the policy
|
156
|
+
# to preview the rule.
|
157
|
+
def pp(rule)
|
158
|
+
with_clean_result do
|
159
|
+
# We need result to exist for `allowed_to?` to work correctly
|
160
|
+
@result = self.class.result_class.new(self.class, rule)
|
161
|
+
header = "#{self.class.name}##{rule}"
|
162
|
+
source = inspect_rule(rule)
|
163
|
+
$stdout.puts "#{header}\n#{source}"
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
@@ -0,0 +1,96 @@
|
|
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.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
|
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
|
81
|
+
" - #{_1.inspect}"
|
82
|
+
end.join("\n")
|
83
|
+
end
|
84
|
+
|
85
|
+
def formatted_record(record = target) = ::RSpec::Support::ObjectFormatter.format(record)
|
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
|
@@ -0,0 +1,130 @@
|
|
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.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
|
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
|
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
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActionPolicy
|
4
|
+
# Adds `suggest` method which uses did_you_mean
|
5
|
+
# to generate a suggestion message
|
6
|
+
module SuggestMessage
|
7
|
+
if defined?(::DidYouMean::SpellChecker)
|
8
|
+
def suggest(needle, heystack)
|
9
|
+
suggestion = ::DidYouMean::SpellChecker.new(
|
10
|
+
dictionary: heystack
|
11
|
+
).correct(needle).first
|
12
|
+
|
13
|
+
suggestion ? "\nDid you mean? #{suggestion}" : ""
|
14
|
+
end
|
15
|
+
else
|
16
|
+
def suggest(*__rest__) = ""
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -12,8 +12,10 @@ module ActionPolicy
|
|
12
12
|
end
|
13
13
|
end
|
14
14
|
|
15
|
-
# Register params scope matcher
|
16
|
-
ActionPolicy::Base.scope_matcher :action_controller_params, ActionController::Parameters
|
17
|
-
|
18
15
|
# Add alias to base policy
|
19
16
|
ActionPolicy::Base.extend ActionPolicy::ScopeMatchers::ActionControllerParams
|
17
|
+
|
18
|
+
ActiveSupport.on_load(:action_controller) do
|
19
|
+
# Register params scope matcher
|
20
|
+
ActionPolicy::Base.scope_matcher :action_controller_params, ActionController::Parameters
|
21
|
+
end
|
@@ -12,18 +12,20 @@ module ActionPolicy
|
|
12
12
|
end
|
13
13
|
end
|
14
14
|
|
15
|
-
# Register relation scope matcher
|
16
|
-
ActionPolicy::Base.scope_matcher :active_record_relation, ActiveRecord::Relation
|
17
|
-
|
18
15
|
# Add alias to base policy
|
19
16
|
ActionPolicy::Base.extend ActionPolicy::ScopeMatchers::ActiveRecord
|
20
17
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
18
|
+
ActiveSupport.on_load(:active_record) do
|
19
|
+
# Register relation scope matcher
|
20
|
+
ActionPolicy::Base.scope_matcher :active_record_relation, ActiveRecord::Relation
|
21
|
+
|
22
|
+
ActiveRecord::Relation.include(Module.new do
|
23
|
+
def policy_name
|
24
|
+
if model.respond_to?(:policy_name)
|
25
|
+
model.policy_name.to_s
|
26
|
+
else
|
27
|
+
"#{model}Policy"
|
28
|
+
end
|
27
29
|
end
|
28
|
-
end
|
29
|
-
end
|
30
|
+
end)
|
31
|
+
end
|
@@ -74,8 +74,6 @@ module ActionPolicy # :nodoc:
|
|
74
74
|
app.config.action_policy.namespace_cache_enabled
|
75
75
|
|
76
76
|
ActiveSupport.on_load(:action_controller) do
|
77
|
-
require "action_policy/rails/scope_matchers/action_controller_params"
|
78
|
-
|
79
77
|
next unless app.config.action_policy.auto_inject_into_controller
|
80
78
|
|
81
79
|
ActionController::Base.include ActionPolicy::Controller
|
@@ -95,21 +93,12 @@ module ActionPolicy # :nodoc:
|
|
95
93
|
ActionCable::Channel::Base.authorize :user, through: :current_user
|
96
94
|
end
|
97
95
|
|
96
|
+
# Scope matchers
|
97
|
+
require "action_policy/rails/scope_matchers/action_controller_params"
|
98
|
+
require "action_policy/rails/scope_matchers/active_record"
|
99
|
+
|
98
100
|
ActiveSupport.on_load(:active_record) do
|
99
101
|
require "action_policy/rails/ext/active_record"
|
100
|
-
require "action_policy/rails/scope_matchers/active_record"
|
101
|
-
end
|
102
|
-
|
103
|
-
# Trigger load hooks of the components that extend ActionPolicy itself
|
104
|
-
# (e.g., scope matchers)
|
105
|
-
begin
|
106
|
-
::ActionController::Base
|
107
|
-
rescue NameError
|
108
|
-
end
|
109
|
-
|
110
|
-
begin
|
111
|
-
::ActiveRecord::Base
|
112
|
-
rescue NameError
|
113
102
|
end
|
114
103
|
end
|
115
104
|
end
|
@@ -21,7 +21,7 @@ module ActionPolicy
|
|
21
21
|
#
|
22
22
|
class HaveAuthorizedScope < ::RSpec::Matchers::BuiltIn::BaseMatcher
|
23
23
|
attr_reader :type, :name, :policy, :scope_options, :actual_scopes,
|
24
|
-
:target_expectations
|
24
|
+
:target_expectations, :context
|
25
25
|
|
26
26
|
def initialize(type)
|
27
27
|
@type = type
|
@@ -49,6 +49,11 @@ module ActionPolicy
|
|
49
49
|
self
|
50
50
|
end
|
51
51
|
|
52
|
+
def with_context(context)
|
53
|
+
@context = context
|
54
|
+
self
|
55
|
+
end
|
56
|
+
|
52
57
|
def match(_expected, actual)
|
53
58
|
raise "This matcher only supports block expectations" unless actual.is_a?(Proc)
|
54
59
|
|
@@ -56,7 +61,7 @@ module ActionPolicy
|
|
56
61
|
|
57
62
|
@actual_scopes = ActionPolicy::Testing::AuthorizeTracker.scopings
|
58
63
|
|
59
|
-
matching_scopes = actual_scopes.select { _1.matches?(policy, type, name, scope_options) }
|
64
|
+
matching_scopes = actual_scopes.select { _1.matches?(policy, type, name, scope_options, context) }
|
60
65
|
|
61
66
|
return false if matching_scopes.empty?
|
62
67
|
|
@@ -80,6 +85,7 @@ module ActionPolicy
|
|
80
85
|
def failure_message
|
81
86
|
"expected a scoping named :#{name} for type :#{type} " \
|
82
87
|
"#{scope_options_message} " \
|
88
|
+
"#{context ? "and context #{context.inspect} " : ""}" \
|
83
89
|
"from #{policy} to have been applied, " \
|
84
90
|
"but #{actual_scopes_message}"
|
85
91
|
end
|
@@ -82,7 +82,7 @@ module ActionPolicy
|
|
82
82
|
# end
|
83
83
|
# end
|
84
84
|
#
|
85
|
-
def assert_have_authorized_scope(type:, with:, as: :default, scope_options: nil)
|
85
|
+
def assert_have_authorized_scope(type:, with:, as: :default, scope_options: nil, context: {})
|
86
86
|
raise ArgumentError, "Block is required" unless block_given?
|
87
87
|
|
88
88
|
policy = with
|
@@ -97,10 +97,13 @@ module ActionPolicy
|
|
97
97
|
"without scope options"
|
98
98
|
end
|
99
99
|
|
100
|
+
context_message = context.empty? ? "without context" : "with context: #{context}"
|
101
|
+
|
100
102
|
assert(
|
101
|
-
actual_scopes.any? { |scope| scope.matches?(policy, type, as, scope_options) },
|
103
|
+
actual_scopes.any? { |scope| scope.matches?(policy, type, as, scope_options, context) },
|
102
104
|
"Expected a scoping named :#{as} for :#{type} type " \
|
103
105
|
"#{scope_options_message} " \
|
106
|
+
"and #{context_message} " \
|
104
107
|
"from #{policy} to have been applied, " \
|
105
108
|
"but no such scoping has been made.\n" \
|
106
109
|
"Registered scopings: " \
|
@@ -5,7 +5,19 @@ module ActionPolicy
|
|
5
5
|
module Testing
|
6
6
|
# Collects all Authorizer calls
|
7
7
|
module AuthorizeTracker
|
8
|
+
module Context
|
9
|
+
private
|
10
|
+
|
11
|
+
def context_matches?(context, actual)
|
12
|
+
return true unless context
|
13
|
+
|
14
|
+
context === actual || actual >= context
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
8
18
|
class Call # :nodoc:
|
19
|
+
include Context
|
20
|
+
|
9
21
|
attr_reader :policy, :rule
|
10
22
|
|
11
23
|
def initialize(policy, rule)
|
@@ -23,17 +35,11 @@ module ActionPolicy
|
|
23
35
|
"#{policy.record.inspect} was authorized with #{policy.class}##{rule} " \
|
24
36
|
"and context #{policy.authorization_context.inspect}"
|
25
37
|
end
|
26
|
-
|
27
|
-
private
|
28
|
-
|
29
|
-
def context_matches?(context, actual)
|
30
|
-
return true unless context
|
31
|
-
|
32
|
-
context === actual || actual >= context
|
33
|
-
end
|
34
38
|
end
|
35
39
|
|
36
40
|
class Scoping # :nodoc:
|
41
|
+
include Context
|
42
|
+
|
37
43
|
attr_reader :policy, :target, :type, :name, :scope_options
|
38
44
|
|
39
45
|
def initialize(policy, target, type, name, scope_options)
|
@@ -44,11 +50,12 @@ module ActionPolicy
|
|
44
50
|
@scope_options = scope_options
|
45
51
|
end
|
46
52
|
|
47
|
-
def matches?(policy_class, actual_type, actual_name, actual_scope_options)
|
53
|
+
def matches?(policy_class, actual_type, actual_name, actual_scope_options, actual_context)
|
48
54
|
policy_class == policy.class &&
|
49
55
|
type == actual_type &&
|
50
56
|
name == actual_name &&
|
51
|
-
actual_scope_options === scope_options
|
57
|
+
actual_scope_options === scope_options &&
|
58
|
+
context_matches?(actual_context, policy.authorization_context)
|
52
59
|
end
|
53
60
|
|
54
61
|
def inspect
|
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.6.
|
4
|
+
version: 0.6.9
|
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: 2024-04-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: ruby-next-core
|
@@ -16,14 +16,14 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version:
|
19
|
+
version: '1.0'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version:
|
26
|
+
version: '1.0'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: ammeter
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -162,6 +162,13 @@ files:
|
|
162
162
|
- lib/.rbnext/3.1/action_policy/ext/module_namespace.rb
|
163
163
|
- lib/.rbnext/3.1/action_policy/ext/policy_cache_key.rb
|
164
164
|
- lib/.rbnext/3.1/action_policy/policy/authorization.rb
|
165
|
+
- lib/.rbnext/3.2/action_policy/behaviours/policy_for.rb
|
166
|
+
- lib/.rbnext/3.2/action_policy/ext/policy_cache_key.rb
|
167
|
+
- lib/.rbnext/3.2/action_policy/lookup_chain.rb
|
168
|
+
- lib/.rbnext/3.2/action_policy/policy/core.rb
|
169
|
+
- lib/.rbnext/3.2/action_policy/rspec/be_authorized_to.rb
|
170
|
+
- lib/.rbnext/3.2/action_policy/rspec/have_authorized_scope.rb
|
171
|
+
- lib/.rbnext/3.2/action_policy/utils/suggest_message.rb
|
165
172
|
- lib/action_policy.rb
|
166
173
|
- lib/action_policy/authorizer.rb
|
167
174
|
- lib/action_policy/base.rb
|
@@ -227,7 +234,7 @@ metadata:
|
|
227
234
|
documentation_uri: https://actionpolicy.evilmartians.io/
|
228
235
|
homepage_uri: https://actionpolicy.evilmartians.io/
|
229
236
|
source_code_uri: http://github.com/palkan/action_policy
|
230
|
-
post_install_message:
|
237
|
+
post_install_message:
|
231
238
|
rdoc_options: []
|
232
239
|
require_paths:
|
233
240
|
- lib
|
@@ -242,8 +249,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
242
249
|
- !ruby/object:Gem::Version
|
243
250
|
version: '0'
|
244
251
|
requirements: []
|
245
|
-
rubygems_version: 3.4.
|
246
|
-
signing_key:
|
252
|
+
rubygems_version: 3.4.19
|
253
|
+
signing_key:
|
247
254
|
specification_version: 4
|
248
255
|
summary: Authorization framework for Ruby/Rails application
|
249
256
|
test_files: []
|