action_policy 0.4.0 → 0.5.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/CHANGELOG.md +233 -171
- data/LICENSE.txt +1 -1
- data/README.md +7 -11
- data/lib/action_policy.rb +7 -1
- data/lib/action_policy/behaviour.rb +22 -16
- data/lib/action_policy/behaviours/policy_for.rb +10 -3
- data/lib/action_policy/behaviours/scoping.rb +2 -1
- data/lib/action_policy/behaviours/thread_memoized.rb +1 -3
- data/lib/action_policy/ext/module_namespace.rb +1 -6
- data/lib/action_policy/ext/policy_cache_key.rb +15 -33
- data/lib/action_policy/ext/{symbol_classify.rb → symbol_camelize.rb} +6 -6
- data/lib/action_policy/i18n.rb +1 -1
- data/lib/action_policy/lookup_chain.rb +41 -21
- data/lib/action_policy/policy/aliases.rb +7 -12
- data/lib/action_policy/policy/authorization.rb +14 -17
- data/lib/action_policy/policy/cache.rb +34 -18
- data/lib/action_policy/policy/core.rb +25 -12
- data/lib/action_policy/policy/defaults.rb +3 -9
- data/lib/action_policy/policy/execution_result.rb +3 -9
- data/lib/action_policy/policy/pre_check.rb +19 -58
- data/lib/action_policy/policy/reasons.rb +30 -20
- data/lib/action_policy/policy/scoping.rb +5 -6
- data/lib/action_policy/rails/controller.rb +6 -1
- data/lib/action_policy/rails/ext/active_record.rb +7 -0
- data/lib/action_policy/rails/policy/instrumentation.rb +1 -1
- data/lib/action_policy/rspec/be_authorized_to.rb +5 -9
- data/lib/action_policy/rspec/dsl.rb +3 -3
- data/lib/action_policy/rspec/have_authorized_scope.rb +5 -7
- data/lib/action_policy/testing.rb +1 -1
- data/lib/action_policy/utils/pretty_print.rb +21 -24
- data/lib/action_policy/utils/suggest_message.rb +1 -3
- data/lib/action_policy/version.rb +1 -1
- data/lib/generators/action_policy/install/templates/{application_policy.rb → application_policy.rb.tt} +1 -1
- data/lib/generators/action_policy/policy/policy_generator.rb +4 -1
- data/lib/generators/action_policy/policy/templates/{policy.rb → policy.rb.tt} +0 -0
- data/lib/generators/rspec/templates/{policy_spec.rb → policy_spec.rb.tt} +0 -0
- data/lib/generators/test_unit/templates/{policy_test.rb → policy_test.rb.tt} +0 -0
- metadata +30 -119
- data/.gitattributes +0 -2
- data/.github/FUNDING.yml +0 -1
- data/.github/ISSUE_TEMPLATE.md +0 -18
- data/.github/PULL_REQUEST_TEMPLATE.md +0 -29
- data/.gitignore +0 -15
- data/.rubocop.yml +0 -54
- data/.tidelift.yml +0 -6
- data/.travis.yml +0 -31
- data/Gemfile +0 -22
- data/Rakefile +0 -27
- data/action_policy.gemspec +0 -44
- data/benchmarks/namespaced_lookup_cache.rb +0 -71
- data/bin/console +0 -14
- data/bin/setup +0 -8
- data/docs/.nojekyll +0 -0
- data/docs/CNAME +0 -1
- data/docs/README.md +0 -77
- data/docs/_sidebar.md +0 -27
- data/docs/aliases.md +0 -122
- data/docs/assets/docsify-search.js +0 -364
- data/docs/assets/docsify.min.js +0 -3
- data/docs/assets/fonts/FiraCode-Medium.woff +0 -0
- data/docs/assets/fonts/FiraCode-Regular.woff +0 -0
- data/docs/assets/images/banner.png +0 -0
- data/docs/assets/images/cache.png +0 -0
- data/docs/assets/images/cache.svg +0 -70
- data/docs/assets/images/layer.png +0 -0
- data/docs/assets/images/layer.svg +0 -35
- data/docs/assets/prism-ruby.min.js +0 -1
- data/docs/assets/styles.css +0 -347
- data/docs/assets/vue.min.css +0 -1
- data/docs/authorization_context.md +0 -92
- data/docs/behaviour.md +0 -113
- data/docs/caching.md +0 -273
- data/docs/controller_action_aliases.md +0 -109
- data/docs/custom_lookup_chain.md +0 -48
- data/docs/custom_policy.md +0 -53
- data/docs/debugging.md +0 -55
- data/docs/decorators.md +0 -27
- data/docs/favicon.ico +0 -0
- data/docs/graphql.md +0 -302
- data/docs/i18n.md +0 -44
- data/docs/index.html +0 -43
- data/docs/instrumentation.md +0 -84
- data/docs/lookup_chain.md +0 -17
- data/docs/namespaces.md +0 -77
- data/docs/non_rails.md +0 -28
- data/docs/pre_checks.md +0 -57
- data/docs/pundit_migration.md +0 -80
- data/docs/quick_start.md +0 -118
- data/docs/rails.md +0 -120
- data/docs/reasons.md +0 -120
- data/docs/scoping.md +0 -255
- data/docs/testing.md +0 -333
- data/docs/writing_policies.md +0 -107
- data/gemfiles/jruby.gemfile +0 -8
- data/gemfiles/rails42.gemfile +0 -8
- data/gemfiles/rails6.gemfile +0 -8
- data/gemfiles/railsmaster.gemfile +0 -6
- data/lib/action_policy/ext/string_match.rb +0 -14
- data/lib/action_policy/ext/yield_self_then.rb +0 -25
@@ -46,23 +46,18 @@ module ActionPolicy
|
|
46
46
|
end
|
47
47
|
end
|
48
48
|
|
49
|
-
def lookup_alias(rule)
|
50
|
-
rules_aliases[rule]
|
51
|
-
end
|
49
|
+
def lookup_alias(rule) = rules_aliases[rule]
|
52
50
|
|
53
|
-
def lookup_default_rule
|
54
|
-
rules_aliases[DEFAULT]
|
55
|
-
end
|
51
|
+
def lookup_default_rule() = rules_aliases[DEFAULT]
|
56
52
|
|
57
53
|
def rules_aliases
|
58
54
|
return @rules_aliases if instance_variable_defined?(:@rules_aliases)
|
59
55
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
end
|
56
|
+
if superclass.respond_to?(:rules_aliases)
|
57
|
+
superclass.rules_aliases.dup
|
58
|
+
else
|
59
|
+
{}
|
60
|
+
end => @rules_aliases
|
66
61
|
end
|
67
62
|
|
68
63
|
def method_added(name)
|
@@ -43,21 +43,17 @@ module ActionPolicy
|
|
43
43
|
|
44
44
|
attr_reader :authorization_context
|
45
45
|
|
46
|
-
def initialize(
|
47
|
-
super(
|
46
|
+
def initialize(record = nil, **params)
|
47
|
+
super(record)
|
48
48
|
|
49
49
|
@authorization_context = {}
|
50
50
|
|
51
51
|
self.class.authorization_targets.each do |id, opts|
|
52
|
-
|
53
|
-
val = params.fetch(id, nil)
|
54
|
-
else
|
55
|
-
raise AuthorizationContextMissing, id unless params.key?(id)
|
52
|
+
raise AuthorizationContextMissing, id unless params.key?(id) || opts[:optional]
|
56
53
|
|
57
|
-
|
54
|
+
val = params.fetch(id, nil)
|
58
55
|
|
59
|
-
|
60
|
-
end
|
56
|
+
raise AuthorizationContextMissing, id if val.nil? && opts[:allow_nil] != true
|
61
57
|
|
62
58
|
authorization_context[id] = instance_variable_set("@#{id}", val)
|
63
59
|
end
|
@@ -66,9 +62,11 @@ module ActionPolicy
|
|
66
62
|
end
|
67
63
|
|
68
64
|
module ClassMethods # :nodoc:
|
69
|
-
def authorize(*ids,
|
65
|
+
def authorize(*ids, allow_nil: false, optional: false)
|
66
|
+
allow_nil ||= optional
|
67
|
+
|
70
68
|
ids.each do |id|
|
71
|
-
authorization_targets[id] =
|
69
|
+
authorization_targets[id] = {allow_nil, optional}
|
72
70
|
end
|
73
71
|
|
74
72
|
attr_reader(*ids)
|
@@ -77,12 +75,11 @@ module ActionPolicy
|
|
77
75
|
def authorization_targets
|
78
76
|
return @authorization_targets if instance_variable_defined?(:@authorization_targets)
|
79
77
|
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
end
|
78
|
+
if superclass.respond_to?(:authorization_targets)
|
79
|
+
superclass.authorization_targets.dup
|
80
|
+
else
|
81
|
+
{}
|
82
|
+
end => @authorization_targets
|
86
83
|
end
|
87
84
|
end
|
88
85
|
end
|
@@ -3,16 +3,13 @@
|
|
3
3
|
require "action_policy/version"
|
4
4
|
|
5
5
|
module ActionPolicy # :nodoc:
|
6
|
+
using RubyNext
|
7
|
+
|
6
8
|
# By default cache namespace (or prefix) contains major and minor version of the gem
|
7
9
|
CACHE_NAMESPACE = "acp:#{ActionPolicy::VERSION.split(".").take(2).join(".")}"
|
8
10
|
|
9
|
-
require "action_policy/ext/yield_self_then"
|
10
11
|
require "action_policy/ext/policy_cache_key"
|
11
12
|
|
12
|
-
unless "".respond_to?(:then)
|
13
|
-
require "action_policy/ext/yield_self_then"
|
14
|
-
using ActionPolicy::Ext::YieldSelfThen
|
15
|
-
end
|
16
13
|
using ActionPolicy::Ext::PolicyCacheKey
|
17
14
|
|
18
15
|
module Policy
|
@@ -26,22 +23,31 @@ module ActionPolicy # :nodoc:
|
|
26
23
|
end
|
27
24
|
end
|
28
25
|
|
29
|
-
def cache_namespace
|
30
|
-
|
26
|
+
def cache_namespace() = ActionPolicy::CACHE_NAMESPACE
|
27
|
+
|
28
|
+
def cache_key(*parts)
|
29
|
+
[
|
30
|
+
cache_namespace,
|
31
|
+
*parts
|
32
|
+
].map { _1._policy_cache_key }.join("/")
|
31
33
|
end
|
32
34
|
|
33
|
-
def
|
34
|
-
|
35
|
-
|
35
|
+
def rule_cache_key(rule)
|
36
|
+
cache_key(
|
37
|
+
context_cache_key,
|
38
|
+
record,
|
39
|
+
self.class,
|
40
|
+
rule
|
41
|
+
)
|
36
42
|
end
|
37
43
|
|
38
44
|
def context_cache_key
|
39
|
-
authorization_context.map {
|
45
|
+
authorization_context.map { _2._policy_cache_key.to_s }.join("/")
|
40
46
|
end
|
41
47
|
|
42
48
|
def apply_with_cache(rule)
|
43
49
|
options = self.class.cached_rules.fetch(rule)
|
44
|
-
key =
|
50
|
+
key = rule_cache_key(rule)
|
45
51
|
|
46
52
|
ActionPolicy.cache_store.then do |store|
|
47
53
|
@result = store.read(key)
|
@@ -62,6 +68,17 @@ module ActionPolicy # :nodoc:
|
|
62
68
|
apply_with_cache(rule) { super }
|
63
69
|
end
|
64
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
|
+
|
65
82
|
module ClassMethods # :nodoc:
|
66
83
|
def cache(*rules, **options)
|
67
84
|
rules.each do |rule|
|
@@ -72,12 +89,11 @@ module ActionPolicy # :nodoc:
|
|
72
89
|
def cached_rules
|
73
90
|
return @cached_rules if instance_variable_defined?(:@cached_rules)
|
74
91
|
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
end
|
92
|
+
if superclass.respond_to?(:cached_rules)
|
93
|
+
superclass.cached_rules.dup
|
94
|
+
else
|
95
|
+
{}
|
96
|
+
end => @cached_rules
|
81
97
|
end
|
82
98
|
end
|
83
99
|
end
|
@@ -11,6 +11,8 @@ unless "".respond_to?(:underscore)
|
|
11
11
|
end
|
12
12
|
|
13
13
|
module ActionPolicy
|
14
|
+
using RubyNext
|
15
|
+
|
14
16
|
# Raised when `resolve_rule` failed to find an approriate
|
15
17
|
# policy rule method for the activity
|
16
18
|
class UnknownRule < Error
|
@@ -75,15 +77,28 @@ module ActionPolicy
|
|
75
77
|
# `apply` also calls pre-checks.
|
76
78
|
def apply(rule)
|
77
79
|
@result = self.class.result_class.new(self.class, rule)
|
78
|
-
|
80
|
+
|
81
|
+
catch :policy_fulfilled do
|
82
|
+
result.load __apply__(rule)
|
83
|
+
end
|
84
|
+
|
85
|
+
result.value
|
86
|
+
end
|
87
|
+
|
88
|
+
def deny!
|
89
|
+
result&.load false
|
90
|
+
throw :policy_fulfilled
|
91
|
+
end
|
92
|
+
|
93
|
+
def allow!
|
94
|
+
result&.load true
|
95
|
+
throw :policy_fulfilled
|
79
96
|
end
|
80
97
|
|
81
98
|
# This method performs the rule call.
|
82
99
|
# Override or extend it to provide custom functionality
|
83
100
|
# (such as caching, pre checks, etc.)
|
84
|
-
def __apply__(rule)
|
85
|
-
public_send(rule)
|
86
|
-
end
|
101
|
+
def __apply__(rule) = public_send(rule)
|
87
102
|
|
88
103
|
# Wrap code that could modify result
|
89
104
|
# to prevent the current result modification
|
@@ -102,16 +117,16 @@ module ActionPolicy
|
|
102
117
|
# If record is `nil` then we uses the current policy.
|
103
118
|
def allowed_to?(rule, record = :__undef__, **options)
|
104
119
|
if (record == :__undef__ || record == self.record) && options.empty?
|
105
|
-
__apply__(rule)
|
120
|
+
__apply__(resolve_rule(rule))
|
106
121
|
else
|
107
|
-
policy_for(record: record, **options).
|
122
|
+
policy_for(record: record, **options).then do |policy|
|
123
|
+
policy.apply(policy.resolve_rule(rule))
|
124
|
+
end
|
108
125
|
end
|
109
126
|
end
|
110
127
|
|
111
128
|
# An alias for readability purposes
|
112
|
-
def check?(*args)
|
113
|
-
allowed_to?(*args)
|
114
|
-
end
|
129
|
+
def check?(*args) = allowed_to?(*args)
|
115
130
|
|
116
131
|
# Returns a rule name (policy method name) for activity.
|
117
132
|
#
|
@@ -127,9 +142,7 @@ module ActionPolicy
|
|
127
142
|
# Return annotated source code for the rule
|
128
143
|
# NOTE: require "method_source" and "unparser" gems to be installed.
|
129
144
|
# Otherwise returns empty string.
|
130
|
-
def inspect_rule(rule)
|
131
|
-
PrettyPrint.print_method(self, rule)
|
132
|
-
end
|
145
|
+
def inspect_rule(rule) = PrettyPrint.print_method(self, rule)
|
133
146
|
|
134
147
|
# Helper for printing the annotated rule source.
|
135
148
|
# Useful for debugging: type `pp :show?` within the context of the policy
|
@@ -21,17 +21,11 @@ module ActionPolicy
|
|
21
21
|
base.authorize :user
|
22
22
|
end
|
23
23
|
|
24
|
-
def index?
|
25
|
-
false
|
26
|
-
end
|
24
|
+
def index?() = false
|
27
25
|
|
28
|
-
def create?
|
29
|
-
false
|
30
|
-
end
|
26
|
+
def create?() = false
|
31
27
|
|
32
|
-
def manage?
|
33
|
-
false
|
34
|
-
end
|
28
|
+
def manage?() = false
|
35
29
|
end
|
36
30
|
end
|
37
31
|
end
|
@@ -19,21 +19,15 @@ module ActionPolicy
|
|
19
19
|
@value = value
|
20
20
|
end
|
21
21
|
|
22
|
-
def success?
|
23
|
-
@value == true
|
24
|
-
end
|
22
|
+
def success?() = @value == true
|
25
23
|
|
26
|
-
def fail?
|
27
|
-
@value == false
|
28
|
-
end
|
24
|
+
def fail?() = @value == false
|
29
25
|
|
30
26
|
def cached!
|
31
27
|
@cached = true
|
32
28
|
end
|
33
29
|
|
34
|
-
def cached?
|
35
|
-
@cached == true
|
36
|
-
end
|
30
|
+
def cached?() = @cached == true
|
37
31
|
|
38
32
|
def inspect
|
39
33
|
"<#{policy}##{rule}: #{@value}>"
|
@@ -31,40 +31,12 @@ module ActionPolicy
|
|
31
31
|
#
|
32
32
|
# Implements filtering logic.
|
33
33
|
class Check
|
34
|
-
# Wrapper over check result
|
35
|
-
class Result
|
36
|
-
DENY = :__deny__
|
37
|
-
ALLOW = :__allow__
|
38
|
-
|
39
|
-
KINDS = {
|
40
|
-
DENY => false,
|
41
|
-
ALLOW => true
|
42
|
-
}.freeze
|
43
|
-
|
44
|
-
attr_reader :fulfilled
|
45
|
-
|
46
|
-
def initialize(val)
|
47
|
-
@fulfilled = KINDS.key?(val)
|
48
|
-
@value = val
|
49
|
-
end
|
50
|
-
|
51
|
-
alias fulfilled? fulfilled
|
52
|
-
|
53
|
-
def denied?
|
54
|
-
@value == DENY
|
55
|
-
end
|
56
|
-
|
57
|
-
def value
|
58
|
-
KINDS.fetch(@value)
|
59
|
-
end
|
60
|
-
end
|
61
|
-
|
62
34
|
attr_reader :name, :policy_class
|
63
35
|
|
64
36
|
def initialize(policy, name, except: nil, only: nil)
|
65
37
|
if !except.nil? && !only.nil?
|
66
38
|
raise ArgumentError,
|
67
|
-
|
39
|
+
"Only one of `except` and `only` may be specified for pre-check"
|
68
40
|
end
|
69
41
|
|
70
42
|
@policy_class = policy
|
@@ -80,23 +52,17 @@ module ActionPolicy
|
|
80
52
|
filter.call(rule)
|
81
53
|
end
|
82
54
|
|
83
|
-
def call(policy)
|
84
|
-
Result.new(policy.send(name)).tap do |res|
|
85
|
-
# add denial reason if Reasons included
|
86
|
-
policy.result.reasons.add(policy, name) if
|
87
|
-
res.denied? && policy.result.respond_to?(:reasons)
|
88
|
-
end
|
89
|
-
end
|
55
|
+
def call(policy) = policy.send(name)
|
90
56
|
|
91
57
|
def skip!(except: nil, only: nil)
|
92
58
|
if !except.nil? && !only.nil?
|
93
59
|
raise ArgumentError,
|
94
|
-
|
60
|
+
"Only one of `except` and `only` may be specified when skipping pre-check"
|
95
61
|
end
|
96
62
|
|
97
63
|
if except.nil? && only.nil?
|
98
64
|
raise ArgumentError,
|
99
|
-
|
65
|
+
"At least one of `except` and `only` must be specified when skipping pre-check"
|
100
66
|
end
|
101
67
|
|
102
68
|
if except
|
@@ -116,7 +82,12 @@ module ActionPolicy
|
|
116
82
|
# rubocop: enable
|
117
83
|
|
118
84
|
def dup
|
119
|
-
self.class.new(
|
85
|
+
self.class.new(
|
86
|
+
policy_class,
|
87
|
+
name,
|
88
|
+
except: blacklist&.dup,
|
89
|
+
only: whitelist&.dup
|
90
|
+
)
|
120
91
|
end
|
121
92
|
|
122
93
|
private
|
@@ -142,21 +113,12 @@ module ActionPolicy
|
|
142
113
|
def run_pre_checks(rule)
|
143
114
|
self.class.pre_checks.each do |check|
|
144
115
|
next unless check.applicable?(rule)
|
145
|
-
|
146
|
-
return res.value if res.fulfilled?
|
116
|
+
check.call(self)
|
147
117
|
end
|
148
118
|
|
149
119
|
yield if block_given?
|
150
120
|
end
|
151
121
|
|
152
|
-
def deny!
|
153
|
-
Check::Result::DENY
|
154
|
-
end
|
155
|
-
|
156
|
-
def allow!
|
157
|
-
Check::Result::ALLOW
|
158
|
-
end
|
159
|
-
|
160
122
|
def __apply__(rule)
|
161
123
|
run_pre_checks(rule) { super }
|
162
124
|
end
|
@@ -165,7 +127,7 @@ module ActionPolicy
|
|
165
127
|
def pre_check(*names, **options)
|
166
128
|
names.each do |name|
|
167
129
|
# do not allow pre-check override
|
168
|
-
check = pre_checks.find {
|
130
|
+
check = pre_checks.find { _1.name == name }
|
169
131
|
raise "Pre-check already defined: #{name}" unless check.nil?
|
170
132
|
|
171
133
|
pre_checks << Check.new(self, name, **options)
|
@@ -174,26 +136,25 @@ module ActionPolicy
|
|
174
136
|
|
175
137
|
def skip_pre_check(*names, **options)
|
176
138
|
names.each do |name|
|
177
|
-
check = pre_checks.find {
|
139
|
+
check = pre_checks.find { _1.name == name }
|
178
140
|
raise "Pre-check not found: #{name}" if check.nil?
|
179
141
|
|
180
142
|
# when no options provided we remove this check completely
|
181
143
|
next pre_checks.delete(check) if options.empty?
|
182
144
|
|
183
145
|
# otherwise duplicate and apply skip options
|
184
|
-
pre_checks[pre_checks.index(check)] = check.dup.tap {
|
146
|
+
pre_checks[pre_checks.index(check)] = check.dup.tap { _1.skip!(**options) }
|
185
147
|
end
|
186
148
|
end
|
187
149
|
|
188
150
|
def pre_checks
|
189
151
|
return @pre_checks if instance_variable_defined?(:@pre_checks)
|
190
152
|
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
end
|
153
|
+
if superclass.respond_to?(:pre_checks)
|
154
|
+
superclass.pre_checks.dup
|
155
|
+
else
|
156
|
+
[]
|
157
|
+
end => @pre_checks
|
197
158
|
end
|
198
159
|
end
|
199
160
|
end
|