action_policy 0.5.4 → 0.5.5
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 +5 -1
- 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/3.0/action_policy/behaviours/thread_memoized.rb +1 -1
- 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 +1 -1
- data/lib/action_policy/policy/aliases.rb +2 -2
- data/lib/action_policy/policy/authorization.rb +2 -2
- data/lib/action_policy/policy/cache.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/policy/scoping.rb +2 -2
- data/lib/action_policy/test_helper.rb +1 -0
- data/lib/action_policy/version.rb +1 -1
- metadata +8 -9
- 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: 939f6e73eca2c33b4f9d3575fde063ac346e46bcda7ab05c062ea9a35edf7252
|
4
|
+
data.tar.gz: 8c200d093dd9efa19d6cabd811c4ce29f7c4420efd3e8052c863adbf43c2a632
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 380f476a6716d6fc096d1b6abb6ef68eba62c4ad5fb761427754870582550a25394f24c0b29bab711785ab4806812b9c20ca0ec26f0f4efe7ddca11b762a9585
|
7
|
+
data.tar.gz: 0a63c058b49e2063af867ee74a78066e1e39f5a22d226748ee5780543e40287d672bfc177bedaf91f7036168ac72746a9128dc222a4d8a7bfe0690cdda7b5b49
|
data/CHANGELOG.md
CHANGED
@@ -2,6 +2,10 @@
|
|
2
2
|
|
3
3
|
## master
|
4
4
|
|
5
|
+
## 0.5.5 (2020-12-28)
|
6
|
+
|
7
|
+
- Upgrade to Ruby 3.0. ([@palkan][])
|
8
|
+
|
5
9
|
## 0.5.4 (2020-12-09)
|
6
10
|
|
7
11
|
- Add support for RSpec aliases detection when linting policy specs with `rubocop-rspec` 2.0 ([@pirj][])
|
@@ -431,7 +435,7 @@ This value is now stored in a cache (if any) instead of just the call result (`t
|
|
431
435
|
[@ilyasgaraev]: https://github.com/ilyasgaraev
|
432
436
|
[@brendon]: https://github.com/brendon
|
433
437
|
[@DmitryTsepelev]: https://github.com/DmitryTsepelev
|
434
|
-
[@korolvs]: https://github.com/
|
438
|
+
[@korolvs]: https://github.com/slavadev
|
435
439
|
[@nicolas-brousse]: https://github.com/nicolas-brousse
|
436
440
|
[@somenugget]: https://github.com/somenugget
|
437
441
|
[@Be-ngt-oH]: https://github.com/Be-ngt-oH
|
@@ -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)
|
150
|
+
return FALSE if val.eql?(false)
|
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
|
@@ -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.
|
@@ -52,7 +52,7 @@ module ActionPolicy
|
|
52
52
|
class << self
|
53
53
|
attr_accessor :chain, :namespace_cache_enabled
|
54
54
|
|
55
|
-
|
55
|
+
alias_method :namespace_cache_enabled?, :namespace_cache_enabled
|
56
56
|
|
57
57
|
def call(record, **opts)
|
58
58
|
chain.each do |probe|
|
@@ -53,11 +53,11 @@ module ActionPolicy
|
|
53
53
|
def rules_aliases
|
54
54
|
return @rules_aliases if instance_variable_defined?(:@rules_aliases)
|
55
55
|
|
56
|
-
if superclass.respond_to?(:rules_aliases)
|
56
|
+
@rules_aliases = if superclass.respond_to?(:rules_aliases)
|
57
57
|
superclass.rules_aliases.dup
|
58
58
|
else
|
59
59
|
{}
|
60
|
-
end
|
60
|
+
end
|
61
61
|
end
|
62
62
|
|
63
63
|
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
|
@@ -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
|
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.5.
|
4
|
+
version: 0.5.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Vladimir Dementyev
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-12-
|
11
|
+
date: 2020-12-28 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: 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
|
@@ -133,6 +133,10 @@ files:
|
|
133
133
|
- LICENSE.txt
|
134
134
|
- README.md
|
135
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
|
136
140
|
- lib/.rbnext/2.7/action_policy/behaviours/policy_for.rb
|
137
141
|
- lib/.rbnext/2.7/action_policy/i18n.rb
|
138
142
|
- lib/.rbnext/2.7/action_policy/policy/cache.rb
|
@@ -140,20 +144,15 @@ files:
|
|
140
144
|
- lib/.rbnext/2.7/action_policy/rspec/be_authorized_to.rb
|
141
145
|
- lib/.rbnext/2.7/action_policy/rspec/have_authorized_scope.rb
|
142
146
|
- lib/.rbnext/2.7/action_policy/utils/pretty_print.rb
|
143
|
-
- lib/.rbnext/3.0/action_policy/behaviour.rb
|
144
|
-
- lib/.rbnext/3.0/action_policy/behaviours/policy_for.rb
|
145
|
-
- lib/.rbnext/3.0/action_policy/behaviours/scoping.rb
|
146
147
|
- lib/.rbnext/3.0/action_policy/behaviours/thread_memoized.rb
|
147
148
|
- lib/.rbnext/3.0/action_policy/ext/policy_cache_key.rb
|
148
149
|
- lib/.rbnext/3.0/action_policy/policy/aliases.rb
|
149
|
-
- lib/.rbnext/3.0/action_policy/policy/authorization.rb
|
150
150
|
- lib/.rbnext/3.0/action_policy/policy/cache.rb
|
151
151
|
- lib/.rbnext/3.0/action_policy/policy/core.rb
|
152
152
|
- lib/.rbnext/3.0/action_policy/policy/defaults.rb
|
153
153
|
- lib/.rbnext/3.0/action_policy/policy/execution_result.rb
|
154
154
|
- lib/.rbnext/3.0/action_policy/policy/pre_check.rb
|
155
155
|
- lib/.rbnext/3.0/action_policy/policy/reasons.rb
|
156
|
-
- lib/.rbnext/3.0/action_policy/policy/scoping.rb
|
157
156
|
- lib/.rbnext/3.0/action_policy/rspec/be_authorized_to.rb
|
158
157
|
- lib/.rbnext/3.0/action_policy/rspec/have_authorized_scope.rb
|
159
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
|