action_policy 0.3.4 → 0.4.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/.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"
|