action_policy 0.5.2 → 0.5.7
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 +22 -2
- data/LICENSE.txt +1 -1
- data/README.md +1 -1
- data/config/rubocop-rspec.yml +17 -0
- data/lib/.rbnext/{3.0 → 1995.next}/action_policy/behaviours/policy_for.rb +1 -1
- data/lib/.rbnext/{3.0 → 1995.next}/action_policy/behaviours/scoping.rb +2 -2
- data/lib/.rbnext/{3.0 → 1995.next}/action_policy/policy/authorization.rb +0 -0
- data/lib/.rbnext/1995.next/action_policy/utils/pretty_print.rb +159 -0
- data/lib/.rbnext/2.7/action_policy/behaviours/policy_for.rb +1 -1
- data/lib/.rbnext/2.7/action_policy/utils/pretty_print.rb +2 -2
- data/lib/.rbnext/3.0/action_policy/behaviours/thread_memoized.rb +1 -1
- data/lib/.rbnext/3.0/action_policy/policy/aliases.rb +8 -0
- data/lib/.rbnext/3.0/action_policy/policy/core.rb +9 -2
- data/lib/.rbnext/3.0/action_policy/policy/reasons.rb +2 -0
- data/lib/.rbnext/3.0/action_policy/utils/pretty_print.rb +2 -2
- data/lib/action_policy.rb +2 -0
- data/lib/action_policy/behaviour.rb +2 -2
- data/lib/action_policy/behaviours/memoized.rb +1 -1
- data/lib/action_policy/behaviours/namespaced.rb +1 -1
- data/lib/action_policy/behaviours/policy_for.rb +1 -1
- data/lib/action_policy/behaviours/scoping.rb +2 -2
- data/lib/action_policy/behaviours/thread_memoized.rb +1 -1
- data/lib/action_policy/lookup_chain.rb +11 -27
- data/lib/action_policy/policy/aliases.rb +10 -2
- data/lib/action_policy/policy/authorization.rb +2 -2
- data/lib/action_policy/policy/cache.rb +2 -2
- data/lib/action_policy/policy/core.rb +9 -2
- data/lib/action_policy/policy/pre_check.rb +2 -2
- data/lib/action_policy/policy/reasons.rb +2 -2
- data/lib/action_policy/policy/scoping.rb +2 -2
- data/lib/action_policy/test_helper.rb +1 -0
- data/lib/action_policy/utils/pretty_print.rb +2 -2
- data/lib/action_policy/version.rb +1 -1
- metadata +10 -10
- data/lib/.rbnext/3.0/action_policy/behaviour.rb +0 -115
- data/lib/.rbnext/3.0/action_policy/policy/scoping.rb +0 -160
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 021e6da5ccd46f76732cb52c0c04ae1a45ce3a74a2b64643e179ee16c40417e3
|
4
|
+
data.tar.gz: 96a978308fde0160f5b739d47abdd833005ba7109e97837ac0dd5721f1ec52a8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 69953fedcbaa8d007c3c922e17dd26618eda2ed840d59bac476111f62bb7aae7c20f2920606925eb4b3acab7e90f994a6e1a9a55b7de0a0740f3747bb4e85aa3
|
7
|
+
data.tar.gz: 23de3c82949db85f3a41f079187493087add27f88e0cd9315ccb492e925f4490390d5ae17fb493eb3ae290f5469f216ab258e21e7c155be9853de5728c8b3311
|
data/CHANGELOG.md
CHANGED
@@ -2,6 +2,24 @@
|
|
2
2
|
|
3
3
|
## master
|
4
4
|
|
5
|
+
## 0.5.7 (2021-03-03)
|
6
|
+
|
7
|
+
The previous release had incorrect dependencies (due to the missing transpiled files).
|
8
|
+
|
9
|
+
## ~~0.5.6 (2021-03-03)~~
|
10
|
+
|
11
|
+
- Add `ActionPolicy.enforce_predicate_rules_naming` config to catch rule missing question mark ([@skojin][])
|
12
|
+
|
13
|
+
## 0.5.5 (2020-12-28)
|
14
|
+
|
15
|
+
- Upgrade to Ruby 3.0. ([@palkan][])
|
16
|
+
|
17
|
+
## 0.5.4 (2020-12-09)
|
18
|
+
|
19
|
+
- Add support for RSpec aliases detection when linting policy specs with `rubocop-rspec` 2.0 ([@pirj][])
|
20
|
+
|
21
|
+
- Fix `strict_namespace: true` lookup option not finding policies in global namespace ([@Be-ngt-oH][])
|
22
|
+
|
5
23
|
## 0.5.0 (2020-09-29)
|
6
24
|
|
7
25
|
- Move `deny!` / `allow!` to core. ([@palkan][])
|
@@ -37,7 +55,7 @@ This method is similar to `allowed_to?` but returns an authorization result obje
|
|
37
55
|
|
38
56
|
Fixes [#122](https://github.com/palkan/action_policy/issues/122).
|
39
57
|
|
40
|
-
- Separated `#classify`-based and `#camelize`-based symbol lookups. ([Be-ngt-oH][])
|
58
|
+
- Separated `#classify`-based and `#camelize`-based symbol lookups. ([@Be-ngt-oH][])
|
41
59
|
|
42
60
|
Only affects Rails apps. Now lookup for `:users` tries to find `UsersPolicy` first (camelize),
|
43
61
|
and only then search for `UserPolicy` (classify).
|
@@ -425,7 +443,9 @@ This value is now stored in a cache (if any) instead of just the call result (`t
|
|
425
443
|
[@ilyasgaraev]: https://github.com/ilyasgaraev
|
426
444
|
[@brendon]: https://github.com/brendon
|
427
445
|
[@DmitryTsepelev]: https://github.com/DmitryTsepelev
|
428
|
-
[@korolvs]: https://github.com/
|
446
|
+
[@korolvs]: https://github.com/slavadev
|
429
447
|
[@nicolas-brousse]: https://github.com/nicolas-brousse
|
430
448
|
[@somenugget]: https://github.com/somenugget
|
431
449
|
[@Be-ngt-oH]: https://github.com/Be-ngt-oH
|
450
|
+
[@pirj]: https://github.com/pirj
|
451
|
+
[@skojin]: https://github.com/skojin
|
data/LICENSE.txt
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
The MIT License (MIT)
|
2
2
|
|
3
|
-
Copyright (c) 2018-
|
3
|
+
Copyright (c) 2018-2021 Vladimir Dementyev
|
4
4
|
|
5
5
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
6
|
of this software and associated documentation files (the "Software"), to deal
|
data/README.md
CHANGED
@@ -11,7 +11,7 @@ module ActionPolicy
|
|
11
11
|
def policy_for(record:, with: nil, namespace: authorization_namespace, context: authorization_context, allow_nil: false, default: default_authorization_policy_class)
|
12
12
|
policy_class = with || ::ActionPolicy.lookup(
|
13
13
|
record,
|
14
|
-
|
14
|
+
namespace: namespace, context: context, allow_nil: allow_nil, default: default
|
15
15
|
)
|
16
16
|
policy_class&.new(record, **context)
|
17
17
|
end
|
@@ -19,11 +19,11 @@ module ActionPolicy
|
|
19
19
|
type ||= authorization_scope_type_for(policy, target)
|
20
20
|
name = as
|
21
21
|
|
22
|
-
Authorizer.scopify(target, policy,
|
22
|
+
Authorizer.scopify(target, policy, type: type, name: name, scope_options: scope_options)
|
23
23
|
end
|
24
24
|
|
25
25
|
# For backward compatibility
|
26
|
-
|
26
|
+
alias_method :authorized, :authorized_scope
|
27
27
|
|
28
28
|
# Infer scope type for target if none provided.
|
29
29
|
# Raises an exception if type couldn't be inferred.
|
File without changes
|
@@ -0,0 +1,159 @@
|
|
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 "parser/current"
|
11
|
+
require "unparser"
|
12
|
+
rescue LoadError
|
13
|
+
# do nothing
|
14
|
+
ensure
|
15
|
+
$VERBOSE = old_verbose
|
16
|
+
end
|
17
|
+
|
18
|
+
module ActionPolicy
|
19
|
+
using RubyNext
|
20
|
+
|
21
|
+
# Takes the object and a method name,
|
22
|
+
# and returns the "annotated" source code for the method:
|
23
|
+
# code is split into parts by logical operators and each
|
24
|
+
# part is evaluated separately.
|
25
|
+
#
|
26
|
+
# Example:
|
27
|
+
#
|
28
|
+
# class MyClass
|
29
|
+
# def access?
|
30
|
+
# admin? && access_feed?
|
31
|
+
# end
|
32
|
+
# end
|
33
|
+
#
|
34
|
+
# puts PrettyPrint.format_method(MyClass.new, :access?)
|
35
|
+
#
|
36
|
+
# #=> MyClass#access?
|
37
|
+
# #=> ↳ admin? #=> false
|
38
|
+
# #=> AND
|
39
|
+
# #=> access_feed? #=> true
|
40
|
+
module PrettyPrint
|
41
|
+
TRUE = "\e[32mtrue\e[0m"
|
42
|
+
FALSE = "\e[31mfalse\e[0m"
|
43
|
+
|
44
|
+
class Visitor
|
45
|
+
attr_reader :lines, :object
|
46
|
+
attr_accessor :indent
|
47
|
+
|
48
|
+
def initialize(object)
|
49
|
+
@object = object
|
50
|
+
end
|
51
|
+
|
52
|
+
def collect(ast)
|
53
|
+
@lines = []
|
54
|
+
@indent = 0
|
55
|
+
|
56
|
+
visit_node(ast)
|
57
|
+
|
58
|
+
lines.join("\n")
|
59
|
+
end
|
60
|
+
|
61
|
+
def visit_node(ast)
|
62
|
+
if respond_to?("visit_#{ast.type}")
|
63
|
+
send("visit_#{ast.type}", ast)
|
64
|
+
else
|
65
|
+
visit_missing ast
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def expression_with_result(sexp)
|
70
|
+
expression = Unparser.unparse(sexp)
|
71
|
+
"#{expression} #=> #{PrettyPrint.colorize(eval_exp(expression))}"
|
72
|
+
end
|
73
|
+
|
74
|
+
def eval_exp(exp)
|
75
|
+
return "<skipped>" if ignore_exp?(exp)
|
76
|
+
object.instance_eval(exp)
|
77
|
+
rescue => e
|
78
|
+
"Failed: #{e.message}"
|
79
|
+
end
|
80
|
+
|
81
|
+
def visit_and(ast)
|
82
|
+
visit_node(ast.children[0])
|
83
|
+
lines << indented("AND")
|
84
|
+
visit_node(ast.children[1])
|
85
|
+
end
|
86
|
+
|
87
|
+
def visit_or(ast)
|
88
|
+
visit_node(ast.children[0])
|
89
|
+
lines << indented("OR")
|
90
|
+
visit_node(ast.children[1])
|
91
|
+
end
|
92
|
+
|
93
|
+
def visit_begin(ast)
|
94
|
+
# Parens
|
95
|
+
if ast.children.size == 1
|
96
|
+
lines << indented("(")
|
97
|
+
self.indent += 2
|
98
|
+
visit_node(ast.children[0])
|
99
|
+
self.indent -= 2
|
100
|
+
lines << indented(")")
|
101
|
+
else
|
102
|
+
# Multiple expressions
|
103
|
+
ast.children.each do |node|
|
104
|
+
visit_node(node)
|
105
|
+
# restore indent after each expression
|
106
|
+
self.indent -= 2
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def visit_missing(ast)
|
112
|
+
lines << indented(expression_with_result(ast))
|
113
|
+
end
|
114
|
+
|
115
|
+
def indented(str)
|
116
|
+
"#{indent.zero? ? "↳ " : ""}#{" " * indent}#{str}".tap do
|
117
|
+
# increase indent after the first expression
|
118
|
+
self.indent += 2 if indent.zero?
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
# Some lines should not be evaled
|
123
|
+
def ignore_exp?(exp)
|
124
|
+
PrettyPrint.ignore_expressions.any? { exp.match?(_1) }
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
class << self
|
129
|
+
attr_accessor :ignore_expressions
|
130
|
+
|
131
|
+
if defined?(::Unparser) && defined?(::MethodSource)
|
132
|
+
def available?() = true
|
133
|
+
|
134
|
+
def print_method(object, method_name)
|
135
|
+
ast = object.method(method_name).source.then(&Unparser.method(:parse))
|
136
|
+
# outer node is a method definition itself
|
137
|
+
body = ast.children[2]
|
138
|
+
|
139
|
+
Visitor.new(object).collect(body)
|
140
|
+
end
|
141
|
+
else
|
142
|
+
def available?() = false
|
143
|
+
|
144
|
+
def print_method(_, _) = ""
|
145
|
+
end
|
146
|
+
|
147
|
+
def colorize(val)
|
148
|
+
return val unless $stdout.isatty
|
149
|
+
return TRUE if val.eql?(true) # rubocop:disable Lint/DeprecatedConstants
|
150
|
+
return FALSE if val.eql?(false) # rubocop:disable Lint/DeprecatedConstants
|
151
|
+
val
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
self.ignore_expressions = [
|
156
|
+
/^\s*binding\.(pry|irb)\s*$/s
|
157
|
+
]
|
158
|
+
end
|
159
|
+
end
|
@@ -11,7 +11,7 @@ module ActionPolicy
|
|
11
11
|
def policy_for(record:, with: nil, namespace: authorization_namespace, context: authorization_context, allow_nil: false, default: default_authorization_policy_class)
|
12
12
|
policy_class = with || ::ActionPolicy.lookup(
|
13
13
|
record,
|
14
|
-
|
14
|
+
namespace: namespace, context: context, allow_nil: allow_nil, default: default
|
15
15
|
)
|
16
16
|
policy_class&.new(record, **context)
|
17
17
|
end
|
@@ -146,8 +146,8 @@ module ActionPolicy
|
|
146
146
|
|
147
147
|
def colorize(val)
|
148
148
|
return val unless $stdout.isatty
|
149
|
-
return TRUE if val.eql?(true)
|
150
|
-
return FALSE if val.eql?(false)
|
149
|
+
return TRUE if val.eql?(true) # rubocop:disable Lint/DeprecatedConstants
|
150
|
+
return FALSE if val.eql?(false) # rubocop:disable Lint/DeprecatedConstants
|
151
151
|
val
|
152
152
|
end
|
153
153
|
end
|
@@ -31,10 +31,18 @@ module ActionPolicy
|
|
31
31
|
def resolve_rule(activity)
|
32
32
|
self.class.lookup_alias(activity) ||
|
33
33
|
(activity if respond_to?(activity)) ||
|
34
|
+
(check_rule_naming(activity) if ActionPolicy.enforce_predicate_rules_naming) ||
|
34
35
|
self.class.lookup_default_rule ||
|
35
36
|
super
|
36
37
|
end
|
37
38
|
|
39
|
+
private def check_rule_naming(activity)
|
40
|
+
unless activity[-1] == "?"
|
41
|
+
raise NonPredicateRule.new(self, activity)
|
42
|
+
end
|
43
|
+
nil
|
44
|
+
end
|
45
|
+
|
38
46
|
module ClassMethods # :nodoc:
|
39
47
|
def default_rule(val)
|
40
48
|
rules_aliases[DEFAULT] = val
|
@@ -23,12 +23,19 @@ module ActionPolicy
|
|
23
23
|
def initialize(policy, rule)
|
24
24
|
@policy = policy.class
|
25
25
|
@rule = rule
|
26
|
-
@message =
|
27
|
-
"Couldn't find rule '#{@rule}' for #{@policy}" \
|
26
|
+
@message = "Couldn't find rule '#{@rule}' for #{@policy}" \
|
28
27
|
"#{suggest(@rule, @policy.instance_methods - Object.instance_methods)}"
|
29
28
|
end
|
30
29
|
end
|
31
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
|
+
|
32
39
|
module Policy
|
33
40
|
# Core policy API
|
34
41
|
module Core
|
@@ -185,10 +185,12 @@ module ActionPolicy
|
|
185
185
|
def allowed_to?(rule, record = :__undef__, **options)
|
186
186
|
res =
|
187
187
|
if (record == :__undef__ || record == self.record) && options.empty?
|
188
|
+
rule = resolve_rule(rule)
|
188
189
|
policy = self
|
189
190
|
with_clean_result { apply(rule) }
|
190
191
|
else
|
191
192
|
policy = policy_for(record: record, **options)
|
193
|
+
rule = policy.resolve_rule(rule)
|
192
194
|
|
193
195
|
policy.apply(rule)
|
194
196
|
policy.result
|
@@ -146,8 +146,8 @@ module ActionPolicy
|
|
146
146
|
|
147
147
|
def colorize(val)
|
148
148
|
return val unless $stdout.isatty
|
149
|
-
return TRUE if val.eql?(true)
|
150
|
-
return FALSE if val.eql?(false)
|
149
|
+
return TRUE if val.eql?(true) # rubocop:disable Lint/DeprecatedConstants
|
150
|
+
return FALSE if val.eql?(false) # rubocop:disable Lint/DeprecatedConstants
|
151
151
|
val
|
152
152
|
end
|
153
153
|
end
|
data/lib/action_policy.rb
CHANGED
@@ -34,6 +34,8 @@ module ActionPolicy
|
|
34
34
|
class << self
|
35
35
|
attr_accessor :cache_store
|
36
36
|
|
37
|
+
attr_accessor :enforce_predicate_rules_naming
|
38
|
+
|
37
39
|
# Find a policy class for a target
|
38
40
|
def lookup(target, allow_nil: false, default: nil, **options)
|
39
41
|
LookupChain.call(target, **options) ||
|
@@ -104,11 +104,11 @@ module ActionPolicy
|
|
104
104
|
def authorization_targets
|
105
105
|
return @authorization_targets if instance_variable_defined?(:@authorization_targets)
|
106
106
|
|
107
|
-
if superclass.respond_to?(:authorization_targets)
|
107
|
+
@authorization_targets = if superclass.respond_to?(:authorization_targets)
|
108
108
|
superclass.authorization_targets.dup
|
109
109
|
else
|
110
110
|
{}
|
111
|
-
end
|
111
|
+
end
|
112
112
|
end
|
113
113
|
end
|
114
114
|
end
|
@@ -11,7 +11,7 @@ module ActionPolicy
|
|
11
11
|
def policy_for(record:, with: nil, namespace: authorization_namespace, context: authorization_context, allow_nil: false, default: default_authorization_policy_class)
|
12
12
|
policy_class = with || ::ActionPolicy.lookup(
|
13
13
|
record,
|
14
|
-
|
14
|
+
namespace:, context:, allow_nil:, default:
|
15
15
|
)
|
16
16
|
policy_class&.new(record, **context)
|
17
17
|
end
|
@@ -19,11 +19,11 @@ module ActionPolicy
|
|
19
19
|
type ||= authorization_scope_type_for(policy, target)
|
20
20
|
name = as
|
21
21
|
|
22
|
-
Authorizer.scopify(target, policy,
|
22
|
+
Authorizer.scopify(target, policy, type:, name:, scope_options:)
|
23
23
|
end
|
24
24
|
|
25
25
|
# For backward compatibility
|
26
|
-
|
26
|
+
alias_method :authorized, :authorized_scope
|
27
27
|
|
28
28
|
# Infer scope type for target if none provided.
|
29
29
|
# Raises an exception if type couldn't be inferred.
|
@@ -36,8 +36,7 @@ module ActionPolicy
|
|
36
36
|
if strict
|
37
37
|
put_if_absent(:strict, namespace, policy, &block)
|
38
38
|
else
|
39
|
-
|
40
|
-
put_if_absent(:flexible, namespace, policy) { cached_policy }
|
39
|
+
put_if_absent(:flexible, namespace, policy, &block)
|
41
40
|
end
|
42
41
|
end
|
43
42
|
|
@@ -53,7 +52,7 @@ module ActionPolicy
|
|
53
52
|
class << self
|
54
53
|
attr_accessor :chain, :namespace_cache_enabled
|
55
54
|
|
56
|
-
|
55
|
+
alias_method :namespace_cache_enabled?, :namespace_cache_enabled
|
57
56
|
|
58
57
|
def call(record, **opts)
|
59
58
|
chain.each do |probe|
|
@@ -66,22 +65,17 @@ module ActionPolicy
|
|
66
65
|
private
|
67
66
|
|
68
67
|
def lookup_within_namespace(policy_name, namespace, strict: false)
|
69
|
-
|
70
|
-
NamespaceCache.fetch(namespace.name, policy_name, strict: strict) do
|
68
|
+
NamespaceCache.fetch(namespace&.name || "Kernel", policy_name, strict: strict) do
|
71
69
|
mod = namespace
|
72
70
|
loop do
|
73
|
-
policy =
|
74
|
-
break policy
|
71
|
+
policy = [mod&.name, policy_name].compact.join("::").safe_constantize
|
72
|
+
break policy if policy || mod.nil? || strict
|
73
|
+
|
75
74
|
mod = mod.namespace
|
76
|
-
break if mod.nil? || strict
|
77
75
|
end
|
78
76
|
end
|
79
77
|
end
|
80
78
|
|
81
|
-
def objectify_policy(policy_name, strict: false)
|
82
|
-
policy_name.safe_constantize unless strict
|
83
|
-
end
|
84
|
-
|
85
79
|
def policy_class_name_for(record)
|
86
80
|
return record.policy_name.to_s if record.respond_to?(:policy_name)
|
87
81
|
|
@@ -110,17 +104,10 @@ module ActionPolicy
|
|
110
104
|
record.class.policy_class if record.class.respond_to?(:policy_class)
|
111
105
|
}
|
112
106
|
|
113
|
-
# Lookup within namespace when provided
|
114
|
-
NAMESPACE_LOOKUP = ->(record, namespace: nil, **) {
|
115
|
-
next if namespace.nil?
|
116
|
-
|
117
|
-
policy_name = policy_class_name_for(record)
|
118
|
-
lookup_within_namespace(policy_name, namespace)
|
119
|
-
}
|
120
|
-
|
121
107
|
# Infer from class name
|
122
|
-
INFER_FROM_CLASS = ->(record, **) {
|
123
|
-
policy_class_name_for(record)
|
108
|
+
INFER_FROM_CLASS = ->(record, namespace: nil, strict_namespace: false, **) {
|
109
|
+
policy_name = policy_class_name_for(record)
|
110
|
+
lookup_within_namespace(policy_name, namespace, strict: strict_namespace)
|
124
111
|
}
|
125
112
|
|
126
113
|
# Infer from passed symbol
|
@@ -128,8 +115,7 @@ module ActionPolicy
|
|
128
115
|
next unless record.is_a?(Symbol)
|
129
116
|
|
130
117
|
policy_name = "#{record.camelize}Policy"
|
131
|
-
lookup_within_namespace(policy_name, namespace, strict: strict_namespace)
|
132
|
-
objectify_policy(policy_name, strict: strict_namespace)
|
118
|
+
lookup_within_namespace(policy_name, namespace, strict: strict_namespace)
|
133
119
|
}
|
134
120
|
|
135
121
|
# (Optional) Infer using String#classify if available
|
@@ -137,8 +123,7 @@ module ActionPolicy
|
|
137
123
|
next unless record.is_a?(Symbol)
|
138
124
|
|
139
125
|
policy_name = "#{record.to_s.classify}Policy"
|
140
|
-
lookup_within_namespace(policy_name, namespace, strict: strict_namespace)
|
141
|
-
objectify_policy(policy_name, strict: strict_namespace)
|
126
|
+
lookup_within_namespace(policy_name, namespace, strict: strict_namespace)
|
142
127
|
}
|
143
128
|
|
144
129
|
self.chain = [
|
@@ -146,7 +131,6 @@ module ActionPolicy
|
|
146
131
|
(CLASSIFY_SYMBOL_LOOKUP if String.method_defined?(:classify)),
|
147
132
|
INSTANCE_POLICY_CLASS,
|
148
133
|
CLASS_POLICY_CLASS,
|
149
|
-
NAMESPACE_LOOKUP,
|
150
134
|
INFER_FROM_CLASS
|
151
135
|
].compact
|
152
136
|
end
|
@@ -31,10 +31,18 @@ module ActionPolicy
|
|
31
31
|
def resolve_rule(activity)
|
32
32
|
self.class.lookup_alias(activity) ||
|
33
33
|
(activity if respond_to?(activity)) ||
|
34
|
+
(check_rule_naming(activity) if ActionPolicy.enforce_predicate_rules_naming) ||
|
34
35
|
self.class.lookup_default_rule ||
|
35
36
|
super
|
36
37
|
end
|
37
38
|
|
39
|
+
private def check_rule_naming(activity)
|
40
|
+
unless activity[-1] == "?"
|
41
|
+
raise NonPredicateRule.new(self, activity)
|
42
|
+
end
|
43
|
+
nil
|
44
|
+
end
|
45
|
+
|
38
46
|
module ClassMethods # :nodoc:
|
39
47
|
def default_rule(val)
|
40
48
|
rules_aliases[DEFAULT] = val
|
@@ -53,11 +61,11 @@ module ActionPolicy
|
|
53
61
|
def rules_aliases
|
54
62
|
return @rules_aliases if instance_variable_defined?(:@rules_aliases)
|
55
63
|
|
56
|
-
if superclass.respond_to?(:rules_aliases)
|
64
|
+
@rules_aliases = if superclass.respond_to?(:rules_aliases)
|
57
65
|
superclass.rules_aliases.dup
|
58
66
|
else
|
59
67
|
{}
|
60
|
-
end
|
68
|
+
end
|
61
69
|
end
|
62
70
|
|
63
71
|
def method_added(name)
|
@@ -75,11 +75,11 @@ module ActionPolicy
|
|
75
75
|
def authorization_targets
|
76
76
|
return @authorization_targets if instance_variable_defined?(:@authorization_targets)
|
77
77
|
|
78
|
-
if superclass.respond_to?(:authorization_targets)
|
78
|
+
@authorization_targets = if superclass.respond_to?(:authorization_targets)
|
79
79
|
superclass.authorization_targets.dup
|
80
80
|
else
|
81
81
|
{}
|
82
|
-
end
|
82
|
+
end
|
83
83
|
end
|
84
84
|
end
|
85
85
|
end
|
@@ -89,11 +89,11 @@ module ActionPolicy # :nodoc:
|
|
89
89
|
def cached_rules
|
90
90
|
return @cached_rules if instance_variable_defined?(:@cached_rules)
|
91
91
|
|
92
|
-
if superclass.respond_to?(:cached_rules)
|
92
|
+
@cached_rules = if superclass.respond_to?(:cached_rules)
|
93
93
|
superclass.cached_rules.dup
|
94
94
|
else
|
95
95
|
{}
|
96
|
-
end
|
96
|
+
end
|
97
97
|
end
|
98
98
|
end
|
99
99
|
end
|
@@ -23,12 +23,19 @@ module ActionPolicy
|
|
23
23
|
def initialize(policy, rule)
|
24
24
|
@policy = policy.class
|
25
25
|
@rule = rule
|
26
|
-
@message =
|
27
|
-
"Couldn't find rule '#{@rule}' for #{@policy}" \
|
26
|
+
@message = "Couldn't find rule '#{@rule}' for #{@policy}" \
|
28
27
|
"#{suggest(@rule, @policy.instance_methods - Object.instance_methods)}"
|
29
28
|
end
|
30
29
|
end
|
31
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
|
+
|
32
39
|
module Policy
|
33
40
|
# Core policy API
|
34
41
|
module Core
|
@@ -150,11 +150,11 @@ module ActionPolicy
|
|
150
150
|
def pre_checks
|
151
151
|
return @pre_checks if instance_variable_defined?(:@pre_checks)
|
152
152
|
|
153
|
-
if superclass.respond_to?(:pre_checks)
|
153
|
+
@pre_checks = if superclass.respond_to?(:pre_checks)
|
154
154
|
superclass.pre_checks.dup
|
155
155
|
else
|
156
156
|
[]
|
157
|
-
end
|
157
|
+
end
|
158
158
|
end
|
159
159
|
end
|
160
160
|
end
|
@@ -72,7 +72,7 @@ module ActionPolicy
|
|
72
72
|
def all_details
|
73
73
|
return @all_details if defined?(@all_details)
|
74
74
|
|
75
|
-
{}.tap do |all|
|
75
|
+
@all_details = {}.tap do |all|
|
76
76
|
next unless defined?(@reasons)
|
77
77
|
|
78
78
|
reasons.reasons.each_value do |rules|
|
@@ -84,7 +84,7 @@ module ActionPolicy
|
|
84
84
|
all.merge!(details)
|
85
85
|
end
|
86
86
|
end
|
87
|
-
end
|
87
|
+
end
|
88
88
|
end
|
89
89
|
|
90
90
|
# Add reasons to inspect
|
@@ -148,11 +148,11 @@ module ActionPolicy
|
|
148
148
|
def scope_matchers
|
149
149
|
return @scope_matchers if instance_variable_defined?(:@scope_matchers)
|
150
150
|
|
151
|
-
if superclass.respond_to?(:scope_matchers)
|
151
|
+
@scope_matchers = if superclass.respond_to?(:scope_matchers)
|
152
152
|
superclass.scope_matchers.dup
|
153
153
|
else
|
154
154
|
[]
|
155
|
-
end
|
155
|
+
end
|
156
156
|
end
|
157
157
|
end
|
158
158
|
end
|
@@ -146,8 +146,8 @@ module ActionPolicy
|
|
146
146
|
|
147
147
|
def colorize(val)
|
148
148
|
return val unless $stdout.isatty
|
149
|
-
return TRUE if val.eql?(true)
|
150
|
-
return FALSE if val.eql?(false)
|
149
|
+
return TRUE if val.eql?(true) # rubocop:disable Lint/DeprecatedConstants
|
150
|
+
return FALSE if val.eql?(false) # rubocop:disable Lint/DeprecatedConstants
|
151
151
|
val
|
152
152
|
end
|
153
153
|
end
|
metadata
CHANGED
@@ -1,29 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: action_policy
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.5.
|
4
|
+
version: 0.5.7
|
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: 2021-03-04 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
name: ruby-next
|
14
|
+
name: ruby-next-core
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
17
|
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: 0.
|
19
|
+
version: 0.11.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: 0.
|
26
|
+
version: 0.11.0
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: ammeter
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -132,6 +132,11 @@ files:
|
|
132
132
|
- CHANGELOG.md
|
133
133
|
- LICENSE.txt
|
134
134
|
- README.md
|
135
|
+
- config/rubocop-rspec.yml
|
136
|
+
- lib/.rbnext/1995.next/action_policy/behaviours/policy_for.rb
|
137
|
+
- lib/.rbnext/1995.next/action_policy/behaviours/scoping.rb
|
138
|
+
- lib/.rbnext/1995.next/action_policy/policy/authorization.rb
|
139
|
+
- lib/.rbnext/1995.next/action_policy/utils/pretty_print.rb
|
135
140
|
- lib/.rbnext/2.7/action_policy/behaviours/policy_for.rb
|
136
141
|
- lib/.rbnext/2.7/action_policy/i18n.rb
|
137
142
|
- lib/.rbnext/2.7/action_policy/policy/cache.rb
|
@@ -139,20 +144,15 @@ files:
|
|
139
144
|
- lib/.rbnext/2.7/action_policy/rspec/be_authorized_to.rb
|
140
145
|
- lib/.rbnext/2.7/action_policy/rspec/have_authorized_scope.rb
|
141
146
|
- lib/.rbnext/2.7/action_policy/utils/pretty_print.rb
|
142
|
-
- lib/.rbnext/3.0/action_policy/behaviour.rb
|
143
|
-
- lib/.rbnext/3.0/action_policy/behaviours/policy_for.rb
|
144
|
-
- lib/.rbnext/3.0/action_policy/behaviours/scoping.rb
|
145
147
|
- lib/.rbnext/3.0/action_policy/behaviours/thread_memoized.rb
|
146
148
|
- lib/.rbnext/3.0/action_policy/ext/policy_cache_key.rb
|
147
149
|
- lib/.rbnext/3.0/action_policy/policy/aliases.rb
|
148
|
-
- lib/.rbnext/3.0/action_policy/policy/authorization.rb
|
149
150
|
- lib/.rbnext/3.0/action_policy/policy/cache.rb
|
150
151
|
- lib/.rbnext/3.0/action_policy/policy/core.rb
|
151
152
|
- lib/.rbnext/3.0/action_policy/policy/defaults.rb
|
152
153
|
- lib/.rbnext/3.0/action_policy/policy/execution_result.rb
|
153
154
|
- lib/.rbnext/3.0/action_policy/policy/pre_check.rb
|
154
155
|
- lib/.rbnext/3.0/action_policy/policy/reasons.rb
|
155
|
-
- lib/.rbnext/3.0/action_policy/policy/scoping.rb
|
156
156
|
- lib/.rbnext/3.0/action_policy/rspec/be_authorized_to.rb
|
157
157
|
- lib/.rbnext/3.0/action_policy/rspec/have_authorized_scope.rb
|
158
158
|
- lib/.rbnext/3.0/action_policy/utils/pretty_print.rb
|
@@ -1,115 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "action_policy/behaviours/policy_for"
|
4
|
-
require "action_policy/behaviours/scoping"
|
5
|
-
require "action_policy/behaviours/memoized"
|
6
|
-
require "action_policy/behaviours/thread_memoized"
|
7
|
-
require "action_policy/behaviours/namespaced"
|
8
|
-
|
9
|
-
require "action_policy/authorizer"
|
10
|
-
|
11
|
-
module ActionPolicy
|
12
|
-
# Provides `authorize!` and `allowed_to?` methods and
|
13
|
-
# `authorize` class method to define authorization context.
|
14
|
-
#
|
15
|
-
# Could be included anywhere to perform authorization.
|
16
|
-
module Behaviour
|
17
|
-
include ActionPolicy::Behaviours::PolicyFor
|
18
|
-
include ActionPolicy::Behaviours::Scoping
|
19
|
-
|
20
|
-
def self.included(base)
|
21
|
-
# Handle ActiveSupport::Concern differently
|
22
|
-
if base.respond_to?(:class_methods)
|
23
|
-
base.class_methods do
|
24
|
-
include ClassMethods
|
25
|
-
end
|
26
|
-
else
|
27
|
-
base.extend ClassMethods
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
31
|
-
# Authorize action against a policy.
|
32
|
-
#
|
33
|
-
# Policy is inferred from record
|
34
|
-
# (unless explicitly specified through `with` option).
|
35
|
-
#
|
36
|
-
# Raises `ActionPolicy::Unauthorized` if check failed.
|
37
|
-
def authorize!(record = :__undef__, to:, **options)
|
38
|
-
policy = lookup_authorization_policy(record, **options)
|
39
|
-
|
40
|
-
Authorizer.call(policy, authorization_rule_for(policy, to))
|
41
|
-
end
|
42
|
-
|
43
|
-
# Checks that an activity is allowed for the current context (e.g. user).
|
44
|
-
#
|
45
|
-
# Returns true of false.
|
46
|
-
def allowed_to?(rule, record = :__undef__, **options)
|
47
|
-
policy = lookup_authorization_policy(record, **options)
|
48
|
-
|
49
|
-
policy.apply(authorization_rule_for(policy, rule))
|
50
|
-
end
|
51
|
-
|
52
|
-
# Returns the authorization result object after applying a specified rule to a record.
|
53
|
-
def allowance_to(rule, record = :__undef__, **options)
|
54
|
-
policy = lookup_authorization_policy(record, **options)
|
55
|
-
|
56
|
-
policy.apply(authorization_rule_for(policy, rule))
|
57
|
-
policy.result
|
58
|
-
end
|
59
|
-
|
60
|
-
def authorization_context
|
61
|
-
return @__authorization_context if
|
62
|
-
instance_variable_defined?(:@__authorization_context)
|
63
|
-
|
64
|
-
@__authorization_context = self.class.authorization_targets
|
65
|
-
.each_with_object({}) do |(key, meth), obj|
|
66
|
-
obj[key] = send(meth)
|
67
|
-
end
|
68
|
-
end
|
69
|
-
|
70
|
-
# Check that rule is defined for policy,
|
71
|
-
# otherwise fallback to :manage? rule.
|
72
|
-
def authorization_rule_for(policy, rule)
|
73
|
-
policy.resolve_rule(rule)
|
74
|
-
end
|
75
|
-
|
76
|
-
def lookup_authorization_policy(record, **options) # :nodoc:
|
77
|
-
record = implicit_authorization_target! if record == :__undef__
|
78
|
-
raise ArgumentError, "Record must be specified" if record.nil?
|
79
|
-
|
80
|
-
options[:context] && (options[:context] = authorization_context.merge(options[:context]))
|
81
|
-
|
82
|
-
policy_for(record: record, **options)
|
83
|
-
end
|
84
|
-
|
85
|
-
module ClassMethods # :nodoc:
|
86
|
-
# Configure authorization context.
|
87
|
-
#
|
88
|
-
# For example:
|
89
|
-
#
|
90
|
-
# class ApplicationController < ActionController::Base
|
91
|
-
# # Pass the value of `current_user` to authorization as `user`
|
92
|
-
# authorize :user, through: :current_user
|
93
|
-
# end
|
94
|
-
#
|
95
|
-
# # Assuming that in your ApplicationPolicy
|
96
|
-
# class ApplicationPolicy < ActionPolicy::Base
|
97
|
-
# authorize :user
|
98
|
-
# end
|
99
|
-
def authorize(key, through: nil)
|
100
|
-
meth = through || key
|
101
|
-
authorization_targets[key] = meth
|
102
|
-
end
|
103
|
-
|
104
|
-
def authorization_targets
|
105
|
-
return @authorization_targets if instance_variable_defined?(:@authorization_targets)
|
106
|
-
|
107
|
-
@authorization_targets = if superclass.respond_to?(:authorization_targets)
|
108
|
-
superclass.authorization_targets.dup
|
109
|
-
else
|
110
|
-
{}
|
111
|
-
end
|
112
|
-
end
|
113
|
-
end
|
114
|
-
end
|
115
|
-
end
|
@@ -1,160 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "action_policy/behaviours/scoping"
|
4
|
-
|
5
|
-
require "action_policy/utils/suggest_message"
|
6
|
-
|
7
|
-
module ActionPolicy
|
8
|
-
class UnknownScopeType < Error # :nodoc:
|
9
|
-
include ActionPolicy::SuggestMessage
|
10
|
-
|
11
|
-
MESSAGE_TEMPLATE = "Unknown policy scope type :%s for %s%s"
|
12
|
-
|
13
|
-
attr_reader :message
|
14
|
-
|
15
|
-
def initialize(policy_class, type)
|
16
|
-
@message = format(
|
17
|
-
MESSAGE_TEMPLATE,
|
18
|
-
type, policy_class,
|
19
|
-
suggest(type, policy_class.scoping_handlers.keys)
|
20
|
-
)
|
21
|
-
end
|
22
|
-
end
|
23
|
-
|
24
|
-
class UnknownNamedScope < Error # :nodoc:
|
25
|
-
include ActionPolicy::SuggestMessage
|
26
|
-
|
27
|
-
MESSAGE_TEMPLATE = "Unknown named scope :%s for type :%s for %s%s"
|
28
|
-
|
29
|
-
attr_reader :message
|
30
|
-
|
31
|
-
def initialize(policy_class, type, name)
|
32
|
-
@message = format(
|
33
|
-
MESSAGE_TEMPLATE, name, type, policy_class,
|
34
|
-
suggest(name, policy_class.scoping_handlers[type].keys)
|
35
|
-
)
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
39
|
-
class UnrecognizedScopeTarget < Error # :nodoc:
|
40
|
-
MESSAGE_TEMPLATE = "Couldn't infer scope type for %s instance"
|
41
|
-
|
42
|
-
attr_reader :message
|
43
|
-
|
44
|
-
def initialize(target)
|
45
|
-
target_class = target.is_a?(Module) ? target : target.class
|
46
|
-
|
47
|
-
@message = format(
|
48
|
-
MESSAGE_TEMPLATE, target_class
|
49
|
-
)
|
50
|
-
end
|
51
|
-
end
|
52
|
-
|
53
|
-
module Policy
|
54
|
-
# Scoping is used to modify the _object under authorization_.
|
55
|
-
#
|
56
|
-
# The most common situation is when you want to _scope_ the collection depending
|
57
|
-
# on the current user permissions.
|
58
|
-
#
|
59
|
-
# For example:
|
60
|
-
#
|
61
|
-
# class ApplicationPolicy < ActionPolicy::Base
|
62
|
-
# # Scoping only makes sense when you have the authorization context
|
63
|
-
# authorize :user
|
64
|
-
#
|
65
|
-
# # :relation here is a scoping type
|
66
|
-
# scope_for :relation do |relation|
|
67
|
-
# # authorization context is available within a scope
|
68
|
-
# if user.admin?
|
69
|
-
# relation
|
70
|
-
# else
|
71
|
-
# relation.publicly_visible
|
72
|
-
# end
|
73
|
-
# end
|
74
|
-
# end
|
75
|
-
#
|
76
|
-
# base_scope = User.all
|
77
|
-
# authorized_scope = ApplicantPolicy.new(user: user)
|
78
|
-
# .apply_scope(base_scope, type: :relation)
|
79
|
-
module Scoping
|
80
|
-
class << self
|
81
|
-
def included(base)
|
82
|
-
base.extend ClassMethods
|
83
|
-
end
|
84
|
-
end
|
85
|
-
|
86
|
-
include ActionPolicy::Behaviours::Scoping
|
87
|
-
|
88
|
-
# Pass target to the scope handler of the specified type and name.
|
89
|
-
# If `name` is not specified then `:default` name is used.
|
90
|
-
# If `type` is not specified then we try to infer the type from the
|
91
|
-
# target class.
|
92
|
-
def apply_scope(target, type:, name: :default, scope_options: nil)
|
93
|
-
raise ActionPolicy::UnknownScopeType.new(self.class, type) unless
|
94
|
-
self.class.scoping_handlers.key?(type)
|
95
|
-
|
96
|
-
raise ActionPolicy::UnknownNamedScope.new(self.class, type, name) unless
|
97
|
-
self.class.scoping_handlers[type].key?(name)
|
98
|
-
|
99
|
-
mid = :"__scoping__#{type}__#{name}"
|
100
|
-
scope_options ? send(mid, target, **scope_options) : send(mid, target)
|
101
|
-
end
|
102
|
-
|
103
|
-
def resolve_scope_type(target)
|
104
|
-
lookup_type_from_target(target) ||
|
105
|
-
raise(ActionPolicy::UnrecognizedScopeTarget, target)
|
106
|
-
end
|
107
|
-
|
108
|
-
def lookup_type_from_target(target)
|
109
|
-
self.class.scope_matchers.detect do |(_type, matcher)|
|
110
|
-
matcher === target
|
111
|
-
end&.first
|
112
|
-
end
|
113
|
-
|
114
|
-
module ClassMethods # :nodoc:
|
115
|
-
# Register a new scoping method for the `type`
|
116
|
-
def scope_for(type, name = :default, &block)
|
117
|
-
mid = :"__scoping__#{type}__#{name}"
|
118
|
-
|
119
|
-
define_method(mid, &block)
|
120
|
-
|
121
|
-
scoping_handlers[type][name] = mid
|
122
|
-
end
|
123
|
-
|
124
|
-
def scoping_handlers
|
125
|
-
return @scoping_handlers if instance_variable_defined?(:@scoping_handlers)
|
126
|
-
|
127
|
-
@scoping_handlers =
|
128
|
-
Hash.new { |h, k| h[k] = {} }.tap do |handlers|
|
129
|
-
if superclass.respond_to?(:scoping_handlers)
|
130
|
-
superclass.scoping_handlers.each do |k, v|
|
131
|
-
handlers[k] = v.dup
|
132
|
-
end
|
133
|
-
end
|
134
|
-
end
|
135
|
-
end
|
136
|
-
|
137
|
-
# Define scope type matcher.
|
138
|
-
#
|
139
|
-
# Scope matcher is an object that implements `#===` (_case equality_) or a Proc.
|
140
|
-
#
|
141
|
-
# When no type is provided when applying a scope we try to infer a type
|
142
|
-
# from the target object by calling matchers one by one until we find a matching
|
143
|
-
# type (i.e. there is a matcher which returns `true` when applying it to the target).
|
144
|
-
def scope_matcher(type, class_or_proc)
|
145
|
-
scope_matchers << [type, class_or_proc]
|
146
|
-
end
|
147
|
-
|
148
|
-
def scope_matchers
|
149
|
-
return @scope_matchers if instance_variable_defined?(:@scope_matchers)
|
150
|
-
|
151
|
-
@scope_matchers = if superclass.respond_to?(:scope_matchers)
|
152
|
-
superclass.scope_matchers.dup
|
153
|
-
else
|
154
|
-
[]
|
155
|
-
end
|
156
|
-
end
|
157
|
-
end
|
158
|
-
end
|
159
|
-
end
|
160
|
-
end
|