action_policy 0.3.4 → 0.4.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/ISSUE_TEMPLATE.md +4 -1
- data/.github/bug_report_template.rb +175 -0
- data/.travis.yml +4 -4
- data/CHANGELOG.md +54 -0
- data/LICENSE.txt +1 -1
- data/README.md +7 -10
- data/benchmarks/namespaced_lookup_cache.rb +8 -5
- data/benchmarks/pre_checks.rb +73 -0
- data/docs/README.md +2 -0
- data/docs/caching.md +22 -4
- data/docs/instrumentation.md +11 -0
- data/docs/lookup_chain.md +6 -1
- data/docs/namespaces.md +2 -2
- data/docs/quick_start.md +5 -3
- data/docs/rails.md +7 -0
- data/docs/testing.md +63 -0
- data/docs/writing_policies.md +1 -1
- data/gemfiles/rails42.gemfile +1 -0
- data/lib/action_policy.rb +1 -1
- data/lib/action_policy/behaviour.rb +4 -0
- data/lib/action_policy/behaviours/memoized.rb +3 -7
- data/lib/action_policy/behaviours/policy_for.rb +13 -4
- data/lib/action_policy/behaviours/scoping.rb +2 -0
- data/lib/action_policy/behaviours/thread_memoized.rb +3 -7
- data/lib/action_policy/ext/policy_cache_key.rb +8 -6
- data/lib/action_policy/ext/{symbol_classify.rb → symbol_camelize.rb} +6 -6
- data/lib/action_policy/lookup_chain.rb +19 -13
- data/lib/action_policy/policy/authorization.rb +7 -11
- data/lib/action_policy/policy/cache.rb +26 -4
- data/lib/action_policy/policy/core.rb +2 -2
- data/lib/action_policy/policy/pre_check.rb +2 -2
- data/lib/action_policy/policy/reasons.rb +2 -2
- data/lib/action_policy/rails/ext/active_record.rb +7 -0
- data/lib/action_policy/rails/policy/instrumentation.rb +9 -2
- data/lib/action_policy/rspec/dsl.rb +6 -6
- data/lib/action_policy/rspec/have_authorized_scope.rb +2 -2
- data/lib/action_policy/testing.rb +3 -3
- data/lib/action_policy/version.rb +1 -1
- data/lib/generators/action_policy/install/templates/application_policy.rb +1 -1
- metadata +5 -4
- data/.github/FUNDING.yml +0 -1
@@ -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,9 @@ 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)
|
70
66
|
ids.each do |id|
|
71
|
-
authorization_targets[id] =
|
67
|
+
authorization_targets[id] = {allow_nil: allow_nil || optional, optional: optional}
|
72
68
|
end
|
73
69
|
|
74
70
|
attr_reader(*ids)
|
@@ -30,9 +30,20 @@ module ActionPolicy # :nodoc:
|
|
30
30
|
ActionPolicy::CACHE_NAMESPACE
|
31
31
|
end
|
32
32
|
|
33
|
-
def cache_key(
|
34
|
-
|
35
|
-
|
33
|
+
def cache_key(*parts)
|
34
|
+
[
|
35
|
+
cache_namespace,
|
36
|
+
*parts
|
37
|
+
].map { |part| part._policy_cache_key }.join("/")
|
38
|
+
end
|
39
|
+
|
40
|
+
def rule_cache_key(rule)
|
41
|
+
cache_key(
|
42
|
+
context_cache_key,
|
43
|
+
record,
|
44
|
+
self.class,
|
45
|
+
rule
|
46
|
+
)
|
36
47
|
end
|
37
48
|
|
38
49
|
def context_cache_key
|
@@ -41,7 +52,7 @@ module ActionPolicy # :nodoc:
|
|
41
52
|
|
42
53
|
def apply_with_cache(rule)
|
43
54
|
options = self.class.cached_rules.fetch(rule)
|
44
|
-
key =
|
55
|
+
key = rule_cache_key(rule)
|
45
56
|
|
46
57
|
ActionPolicy.cache_store.then do |store|
|
47
58
|
@result = store.read(key)
|
@@ -62,6 +73,17 @@ module ActionPolicy # :nodoc:
|
|
62
73
|
apply_with_cache(rule) { super }
|
63
74
|
end
|
64
75
|
|
76
|
+
def cache(*parts, **options)
|
77
|
+
key = cache_key(*parts)
|
78
|
+
ActionPolicy.cache_store.then do |store|
|
79
|
+
res = store.read(key)
|
80
|
+
next res unless res.nil?
|
81
|
+
res = yield
|
82
|
+
store.write(key, res, **options)
|
83
|
+
res
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
65
87
|
module ClassMethods # :nodoc:
|
66
88
|
def cache(*rules, **options)
|
67
89
|
rules.each do |rule|
|
@@ -66,7 +66,7 @@ module ActionPolicy
|
|
66
66
|
attr_reader :record, :result
|
67
67
|
|
68
68
|
# NEXT_RELEASE: deprecate `record` arg, migrate to `record: nil`
|
69
|
-
def initialize(record = nil,
|
69
|
+
def initialize(record = nil, *)
|
70
70
|
@record = record
|
71
71
|
end
|
72
72
|
|
@@ -101,7 +101,7 @@ module ActionPolicy
|
|
101
101
|
#
|
102
102
|
# If record is `nil` then we uses the current policy.
|
103
103
|
def allowed_to?(rule, record = :__undef__, **options)
|
104
|
-
if record == :__undef__ && options.empty?
|
104
|
+
if (record == :__undef__ || record == self.record) && options.empty?
|
105
105
|
__apply__(rule)
|
106
106
|
else
|
107
107
|
policy_for(record: record, **options).apply(rule)
|
@@ -168,7 +168,7 @@ module ActionPolicy
|
|
168
168
|
check = pre_checks.find { |c| c.name == name }
|
169
169
|
raise "Pre-check already defined: #{name}" unless check.nil?
|
170
170
|
|
171
|
-
pre_checks << Check.new(self, name, options)
|
171
|
+
pre_checks << Check.new(self, name, **options)
|
172
172
|
end
|
173
173
|
end
|
174
174
|
|
@@ -181,7 +181,7 @@ module ActionPolicy
|
|
181
181
|
next pre_checks.delete(check) if options.empty?
|
182
182
|
|
183
183
|
# otherwise duplicate and apply skip options
|
184
|
-
pre_checks[pre_checks.index(check)] = check.dup.tap { |c| c.skip!
|
184
|
+
pre_checks[pre_checks.index(check)] = check.dup.tap { |c| c.skip!(**options) }
|
185
185
|
end
|
186
186
|
end
|
187
187
|
|
@@ -179,7 +179,7 @@ module ActionPolicy
|
|
179
179
|
|
180
180
|
def allowed_to?(rule, record = :__undef__, **options)
|
181
181
|
res =
|
182
|
-
if record == :__undef__
|
182
|
+
if (record == :__undef__ || record == self.record) && options.empty?
|
183
183
|
policy = self
|
184
184
|
with_clean_result { apply(rule) }
|
185
185
|
else
|
@@ -189,7 +189,7 @@ module ActionPolicy
|
|
189
189
|
policy.result
|
190
190
|
end
|
191
191
|
|
192
|
-
result
|
192
|
+
result&.reasons&.add(policy, rule, res.details) if res.fail?
|
193
193
|
|
194
194
|
res.clear_details
|
195
195
|
|
@@ -7,4 +7,11 @@ ActiveRecord::Relation.include(Module.new do
|
|
7
7
|
def policy_cache_key
|
8
8
|
object_id
|
9
9
|
end
|
10
|
+
|
11
|
+
# Explicitly define the policy_class method to avoid
|
12
|
+
# making Relation immutable (by initializing `arel` in `#respond_to_missing?).
|
13
|
+
# See https://github.com/palkan/action_policy/issues/101
|
14
|
+
def policy_class
|
15
|
+
nil
|
16
|
+
end
|
10
17
|
end)
|
@@ -6,12 +6,19 @@ module ActionPolicy # :nodoc:
|
|
6
6
|
# Add ActiveSupport::Notifications support.
|
7
7
|
#
|
8
8
|
# Fires `action_policy.apply_rule` event on every `#apply` call.
|
9
|
+
# Fires `action_policy.init` event on every policy initialization.
|
9
10
|
module Instrumentation
|
10
|
-
|
11
|
+
INIT_EVENT_NAME = "action_policy.init"
|
12
|
+
APPLY_EVENT_NAME = "action_policy.apply_rule"
|
13
|
+
|
14
|
+
def initialize(*)
|
15
|
+
event = {policy: self.class.name}
|
16
|
+
ActiveSupport::Notifications.instrument(INIT_EVENT_NAME, event) { super }
|
17
|
+
end
|
11
18
|
|
12
19
|
def apply(rule)
|
13
20
|
event = {policy: self.class.name, rule: rule.to_s}
|
14
|
-
ActiveSupport::Notifications.instrument(
|
21
|
+
ActiveSupport::Notifications.instrument(APPLY_EVENT_NAME, event) do
|
15
22
|
res = super
|
16
23
|
event[:cached] = result.cached?
|
17
24
|
event[:value] = result.value
|
@@ -13,18 +13,18 @@ module ActionPolicy
|
|
13
13
|
|
14
14
|
["", "f", "x"].each do |prefix|
|
15
15
|
class_eval <<~CODE, __FILE__, __LINE__ + 1
|
16
|
-
def #{prefix}succeed(msg = "succeeds", *args, **kwargs)
|
16
|
+
def #{prefix}succeed(msg = "succeeds", *args, **kwargs, &block)
|
17
17
|
the_caller = caller
|
18
18
|
#{prefix}context(msg, *args, **kwargs) do
|
19
|
-
instance_eval(&
|
19
|
+
instance_eval(&block) if block_given?
|
20
20
|
find_and_eval_shared("examples", "action_policy:policy_rule_example", the_caller.first, true, the_caller)
|
21
21
|
end
|
22
22
|
end
|
23
23
|
|
24
|
-
def #{prefix}failed(msg = "fails", *args, **kwargs)
|
24
|
+
def #{prefix}failed(msg = "fails", *args, **kwargs, &block)
|
25
25
|
the_caller = caller
|
26
26
|
#{prefix}context(msg, *args, **kwargs) do
|
27
|
-
instance_eval(&
|
27
|
+
instance_eval(&block) if block_given?
|
28
28
|
find_and_eval_shared("examples", "action_policy:policy_rule_example", the_caller.first, false, the_caller)
|
29
29
|
end
|
30
30
|
end
|
@@ -68,7 +68,7 @@ if defined?(::RSpec)
|
|
68
68
|
|
69
69
|
::RSpec.shared_examples_for "action_policy:policy_rule_example" do |success, the_caller|
|
70
70
|
if success
|
71
|
-
specify do
|
71
|
+
specify "is allowed" do
|
72
72
|
next if subject.success?
|
73
73
|
raise(
|
74
74
|
RSpec::Expectations::ExpectationNotMetError,
|
@@ -77,7 +77,7 @@ if defined?(::RSpec)
|
|
77
77
|
)
|
78
78
|
end
|
79
79
|
else
|
80
|
-
specify do
|
80
|
+
specify "is denied" do
|
81
81
|
next if subject.fail?
|
82
82
|
raise(
|
83
83
|
RSpec::Expectations::ExpectationNotMetError,
|
@@ -15,7 +15,7 @@ module ActionPolicy
|
|
15
15
|
|
16
16
|
def matches?(policy_class, actual_rule, target)
|
17
17
|
policy_class == policy.class &&
|
18
|
-
target == policy.record &&
|
18
|
+
(target.is_a?(Class) ? target == policy.record : target === policy.record) &&
|
19
19
|
rule == actual_rule
|
20
20
|
end
|
21
21
|
|
@@ -107,8 +107,8 @@ module ActionPolicy
|
|
107
107
|
super
|
108
108
|
end
|
109
109
|
|
110
|
-
def scopify(*args)
|
111
|
-
AuthorizeTracker.track_scope(*args)
|
110
|
+
def scopify(*args, **kwargs)
|
111
|
+
AuthorizeTracker.track_scope(*args, **kwargs)
|
112
112
|
super
|
113
113
|
end
|
114
114
|
end
|
@@ -5,7 +5,7 @@ class ApplicationPolicy < ActionPolicy::Base
|
|
5
5
|
#
|
6
6
|
# authorize :account, optional: true
|
7
7
|
#
|
8
|
-
# Read more about
|
8
|
+
# Read more about authorization context: https://actionpolicy.evilmartians.io/#/authorization_context
|
9
9
|
|
10
10
|
private
|
11
11
|
|
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.
|
4
|
+
version: 0.4.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Vladimir Dementyev
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2020-07-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: ammeter
|
@@ -158,9 +158,9 @@ extensions: []
|
|
158
158
|
extra_rdoc_files: []
|
159
159
|
files:
|
160
160
|
- ".gitattributes"
|
161
|
-
- ".github/FUNDING.yml"
|
162
161
|
- ".github/ISSUE_TEMPLATE.md"
|
163
162
|
- ".github/PULL_REQUEST_TEMPLATE.md"
|
163
|
+
- ".github/bug_report_template.rb"
|
164
164
|
- ".gitignore"
|
165
165
|
- ".rubocop.yml"
|
166
166
|
- ".tidelift.yml"
|
@@ -172,6 +172,7 @@ files:
|
|
172
172
|
- Rakefile
|
173
173
|
- action_policy.gemspec
|
174
174
|
- benchmarks/namespaced_lookup_cache.rb
|
175
|
+
- benchmarks/pre_checks.rb
|
175
176
|
- bin/console
|
176
177
|
- bin/setup
|
177
178
|
- docs/.nojekyll
|
@@ -235,7 +236,7 @@ files:
|
|
235
236
|
- lib/action_policy/ext/string_constantize.rb
|
236
237
|
- lib/action_policy/ext/string_match.rb
|
237
238
|
- lib/action_policy/ext/string_underscore.rb
|
238
|
-
- lib/action_policy/ext/
|
239
|
+
- lib/action_policy/ext/symbol_camelize.rb
|
239
240
|
- lib/action_policy/ext/yield_self_then.rb
|
240
241
|
- lib/action_policy/i18n.rb
|
241
242
|
- lib/action_policy/lookup_chain.rb
|
data/.github/FUNDING.yml
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
tidelift: "rubygems/action_policy"
|