action_policy 0.5.4 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +23 -1
- data/LICENSE.txt +1 -1
- data/README.md +1 -1
- data/lib/.rbnext/{3.0 → 1995.next}/action_policy/behaviours/policy_for.rb +10 -4
- 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 +10 -4
- 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 +10 -3
- data/lib/.rbnext/3.0/action_policy/policy/reasons.rb +18 -2
- data/lib/.rbnext/3.0/action_policy/utils/pretty_print.rb +2 -2
- data/lib/action_policy/behaviour.rb +4 -6
- 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 +10 -4
- 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 +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 +10 -3
- data/lib/action_policy/policy/pre_check.rb +2 -2
- data/lib/action_policy/policy/reasons.rb +20 -4
- data/lib/action_policy/policy/scoping.rb +2 -2
- data/lib/action_policy/rails/controller.rb +1 -1
- 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
- data/lib/action_policy.rb +2 -0
- metadata +13 -14
- 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: 0fe2b2b40f6a3bd85c312495209382b1ee72950072257e676be008e2ef8d77c5
|
4
|
+
data.tar.gz: f0c9b0cb38bc130cdb8c7f3f4cfba3adcc326f25c6435fe75723e8c0d3ba3fe9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f49f02d335942aa0a100e681f7570c1b6d042e68d1ed9d02012ec84b9f354b5c0a1d7a42ba15fbd3c84c84536e444af95057a09ef00cdc6ae3c2f5d0b24dd3a6
|
7
|
+
data.tar.gz: 2eced066c406feb11e8cdf99113fa8d27f8fe73ff015bcc1d2958c0a657540d195b25a8328dee02206b8539e55d16278f531dcde6b9567fef95ec1bead92cda0
|
data/CHANGELOG.md
CHANGED
@@ -2,6 +2,26 @@
|
|
2
2
|
|
3
3
|
## master
|
4
4
|
|
5
|
+
## 0.6.0 (2021-09-02)
|
6
|
+
|
7
|
+
- Drop Ruby 2.5 support.
|
8
|
+
- [Closes [#186](https://github.com/palkan/action_policy/issues/186)] Add `inline_reasons: true` option to `allowed_to?` to avoid wrapping reasons. ([@palkan][])
|
9
|
+
- [Fixes [#173](https://github.com/palkan/action_policy/issues/173)] Explicit context were not merged with implicit one within policy classes. ([@palkan][])
|
10
|
+
- Add `strict_namespace:` option to policy_for behaviour ([@kevynlebouille][])
|
11
|
+
- Prevent possible side effects in policy lookup ([@tomdalling][])
|
12
|
+
|
13
|
+
## 0.5.7 (2021-03-03)
|
14
|
+
|
15
|
+
The previous release had incorrect dependencies (due to the missing transpiled files).
|
16
|
+
|
17
|
+
## ~~0.5.6 (2021-03-03)~~
|
18
|
+
|
19
|
+
- Add `ActionPolicy.enforce_predicate_rules_naming` config to catch rule missing question mark ([@skojin][])
|
20
|
+
|
21
|
+
## 0.5.5 (2020-12-28)
|
22
|
+
|
23
|
+
- Upgrade to Ruby 3.0. ([@palkan][])
|
24
|
+
|
5
25
|
## 0.5.4 (2020-12-09)
|
6
26
|
|
7
27
|
- Add support for RSpec aliases detection when linting policy specs with `rubocop-rspec` 2.0 ([@pirj][])
|
@@ -431,8 +451,10 @@ This value is now stored in a cache (if any) instead of just the call result (`t
|
|
431
451
|
[@ilyasgaraev]: https://github.com/ilyasgaraev
|
432
452
|
[@brendon]: https://github.com/brendon
|
433
453
|
[@DmitryTsepelev]: https://github.com/DmitryTsepelev
|
434
|
-
[@korolvs]: https://github.com/
|
454
|
+
[@korolvs]: https://github.com/slavadev
|
435
455
|
[@nicolas-brousse]: https://github.com/nicolas-brousse
|
436
456
|
[@somenugget]: https://github.com/somenugget
|
437
457
|
[@Be-ngt-oH]: https://github.com/Be-ngt-oH
|
438
458
|
[@pirj]: https://github.com/pirj
|
459
|
+
[@skojin]: https://github.com/skojin
|
460
|
+
[@tomdalling]: https://github.com/tomdalling
|
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
@@ -8,16 +8,18 @@ module ActionPolicy
|
|
8
8
|
using ActionPolicy::Ext::PolicyCacheKey
|
9
9
|
|
10
10
|
# Returns policy instance for the record.
|
11
|
-
def policy_for(record:, with: nil, namespace: authorization_namespace, context:
|
11
|
+
def policy_for(record:, with: nil, namespace: authorization_namespace, context: nil, allow_nil: false, default: default_authorization_policy_class, strict_namespace: authorization_strict_namespace)
|
12
|
+
context = context ? authorization_context.merge(context) : authorization_context
|
13
|
+
|
12
14
|
policy_class = with || ::ActionPolicy.lookup(
|
13
15
|
record,
|
14
|
-
|
16
|
+
namespace: namespace, context: context, allow_nil: allow_nil, default: default, strict_namespace: strict_namespace
|
15
17
|
)
|
16
18
|
policy_class&.new(record, **context)
|
17
19
|
end
|
18
20
|
|
19
21
|
def authorization_context
|
20
|
-
raise NotImplementedError, "Please, define `authorization_context` method!"
|
22
|
+
Kernel.raise NotImplementedError, "Please, define `authorization_context` method!"
|
21
23
|
end
|
22
24
|
|
23
25
|
def authorization_namespace
|
@@ -28,6 +30,10 @@ module ActionPolicy
|
|
28
30
|
# override to provide a policy class use when no policy found
|
29
31
|
end
|
30
32
|
|
33
|
+
def authorization_strict_namespace
|
34
|
+
# override to provide strict namespace lookup option
|
35
|
+
end
|
36
|
+
|
31
37
|
# Override this method to provide implicit authorization target
|
32
38
|
# that would be used in case `record` is not specified in
|
33
39
|
# `authorize!` and `allowed_to?` call.
|
@@ -39,7 +45,7 @@ module ActionPolicy
|
|
39
45
|
|
40
46
|
# Return implicit authorization target or raises an exception if it's nil
|
41
47
|
def implicit_authorization_target!
|
42
|
-
implicit_authorization_target || raise(
|
48
|
+
implicit_authorization_target || Kernel.raise(
|
43
49
|
NotFound,
|
44
50
|
[
|
45
51
|
self,
|
@@ -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
|
@@ -8,16 +8,18 @@ module ActionPolicy
|
|
8
8
|
using ActionPolicy::Ext::PolicyCacheKey
|
9
9
|
|
10
10
|
# Returns policy instance for the record.
|
11
|
-
def policy_for(record:, with: nil, namespace: authorization_namespace, context:
|
11
|
+
def policy_for(record:, with: nil, namespace: authorization_namespace, context: nil, allow_nil: false, default: default_authorization_policy_class, strict_namespace: authorization_strict_namespace)
|
12
|
+
context = context ? authorization_context.merge(context) : authorization_context
|
13
|
+
|
12
14
|
policy_class = with || ::ActionPolicy.lookup(
|
13
15
|
record,
|
14
|
-
|
16
|
+
namespace: namespace, context: context, allow_nil: allow_nil, default: default, strict_namespace: strict_namespace
|
15
17
|
)
|
16
18
|
policy_class&.new(record, **context)
|
17
19
|
end
|
18
20
|
|
19
21
|
def authorization_context
|
20
|
-
raise NotImplementedError, "Please, define `authorization_context` method!"
|
22
|
+
Kernel.raise NotImplementedError, "Please, define `authorization_context` method!"
|
21
23
|
end
|
22
24
|
|
23
25
|
def authorization_namespace
|
@@ -28,6 +30,10 @@ module ActionPolicy
|
|
28
30
|
# override to provide a policy class use when no policy found
|
29
31
|
end
|
30
32
|
|
33
|
+
def authorization_strict_namespace
|
34
|
+
# override to provide strict namespace lookup option
|
35
|
+
end
|
36
|
+
|
31
37
|
# Override this method to provide implicit authorization target
|
32
38
|
# that would be used in case `record` is not specified in
|
33
39
|
# `authorize!` and `allowed_to?` call.
|
@@ -39,7 +45,7 @@ module ActionPolicy
|
|
39
45
|
|
40
46
|
# Return implicit authorization target or raises an exception if it's nil
|
41
47
|
def implicit_authorization_target!
|
42
|
-
implicit_authorization_target || raise(
|
48
|
+
implicit_authorization_target || Kernel.raise(
|
43
49
|
NotFound,
|
44
50
|
[
|
45
51
|
self,
|
@@ -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
|
@@ -126,7 +133,7 @@ module ActionPolicy
|
|
126
133
|
end
|
127
134
|
|
128
135
|
# An alias for readability purposes
|
129
|
-
def check?(*args) ; allowed_to?(*args); end
|
136
|
+
def check?(*args, **hargs) ; allowed_to?(*args, **hargs); end
|
130
137
|
|
131
138
|
# Returns a rule name (policy method name) for activity.
|
132
139
|
#
|
@@ -31,6 +31,20 @@ module ActionPolicy
|
|
31
31
|
|
32
32
|
def present?() ; !empty?; end
|
33
33
|
|
34
|
+
def merge(other)
|
35
|
+
other.reasons.each do |policy_class, rules|
|
36
|
+
reasons[policy_class] ||= []
|
37
|
+
|
38
|
+
rules.each do |rule|
|
39
|
+
if rule.is_a?(::Hash)
|
40
|
+
add_detailed_reason(reasons[policy_class], rule)
|
41
|
+
else
|
42
|
+
add_non_detailed_reason(reasons[policy_class], rule)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
34
48
|
private
|
35
49
|
|
36
50
|
def add_non_detailed_reason(store, rule)
|
@@ -182,7 +196,7 @@ module ActionPolicy
|
|
182
196
|
result.details ||= {}
|
183
197
|
end
|
184
198
|
|
185
|
-
def allowed_to?(rule, record = :__undef__, **options)
|
199
|
+
def allowed_to?(rule, record = :__undef__, inline_reasons: false, **options)
|
186
200
|
res =
|
187
201
|
if (record == :__undef__ || record == self.record) && options.empty?
|
188
202
|
rule = resolve_rule(rule)
|
@@ -196,7 +210,9 @@ module ActionPolicy
|
|
196
210
|
policy.result
|
197
211
|
end
|
198
212
|
|
199
|
-
|
213
|
+
if res.fail? && result&.reasons
|
214
|
+
inline_reasons ? result.reasons.merge(res.reasons) : result.reasons.add(policy, rule, res.details)
|
215
|
+
end
|
200
216
|
|
201
217
|
res.clear_details
|
202
218
|
|
@@ -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
|
@@ -74,10 +74,8 @@ module ActionPolicy
|
|
74
74
|
end
|
75
75
|
|
76
76
|
def lookup_authorization_policy(record, **options) # :nodoc:
|
77
|
-
record = implicit_authorization_target! if
|
78
|
-
raise ArgumentError, "Record must be specified" if record.nil?
|
79
|
-
|
80
|
-
options[:context] && (options[:context] = authorization_context.merge(options[:context]))
|
77
|
+
record = implicit_authorization_target! if :__undef__ == record # rubocop:disable Style/YodaCondition See https://github.com/palkan/action_policy/pull/180
|
78
|
+
Kernel.raise ArgumentError, "Record must be specified" if record.nil?
|
81
79
|
|
82
80
|
policy_for(record: record, **options)
|
83
81
|
end
|
@@ -104,11 +102,11 @@ module ActionPolicy
|
|
104
102
|
def authorization_targets
|
105
103
|
return @authorization_targets if instance_variable_defined?(:@authorization_targets)
|
106
104
|
|
107
|
-
if superclass.respond_to?(:authorization_targets)
|
105
|
+
@authorization_targets = if superclass.respond_to?(:authorization_targets)
|
108
106
|
superclass.authorization_targets.dup
|
109
107
|
else
|
110
108
|
{}
|
111
|
-
end
|
109
|
+
end
|
112
110
|
end
|
113
111
|
end
|
114
112
|
end
|
@@ -8,16 +8,18 @@ module ActionPolicy
|
|
8
8
|
using ActionPolicy::Ext::PolicyCacheKey
|
9
9
|
|
10
10
|
# Returns policy instance for the record.
|
11
|
-
def policy_for(record:, with: nil, namespace: authorization_namespace, context:
|
11
|
+
def policy_for(record:, with: nil, namespace: authorization_namespace, context: nil, allow_nil: false, default: default_authorization_policy_class, strict_namespace: authorization_strict_namespace)
|
12
|
+
context = context ? authorization_context.merge(context) : authorization_context
|
13
|
+
|
12
14
|
policy_class = with || ::ActionPolicy.lookup(
|
13
15
|
record,
|
14
|
-
|
16
|
+
namespace:, context:, allow_nil:, default:, strict_namespace:
|
15
17
|
)
|
16
18
|
policy_class&.new(record, **context)
|
17
19
|
end
|
18
20
|
|
19
21
|
def authorization_context
|
20
|
-
raise NotImplementedError, "Please, define `authorization_context` method!"
|
22
|
+
Kernel.raise NotImplementedError, "Please, define `authorization_context` method!"
|
21
23
|
end
|
22
24
|
|
23
25
|
def authorization_namespace
|
@@ -28,6 +30,10 @@ module ActionPolicy
|
|
28
30
|
# override to provide a policy class use when no policy found
|
29
31
|
end
|
30
32
|
|
33
|
+
def authorization_strict_namespace
|
34
|
+
# override to provide strict namespace lookup option
|
35
|
+
end
|
36
|
+
|
31
37
|
# Override this method to provide implicit authorization target
|
32
38
|
# that would be used in case `record` is not specified in
|
33
39
|
# `authorize!` and `allowed_to?` call.
|
@@ -39,7 +45,7 @@ module ActionPolicy
|
|
39
45
|
|
40
46
|
# Return implicit authorization target or raises an exception if it's nil
|
41
47
|
def implicit_authorization_target!
|
42
|
-
implicit_authorization_target || raise(
|
48
|
+
implicit_authorization_target || Kernel.raise(
|
43
49
|
NotFound,
|
44
50
|
[
|
45
51
|
self,
|
@@ -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|
|
@@ -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
|
@@ -126,7 +133,7 @@ module ActionPolicy
|
|
126
133
|
end
|
127
134
|
|
128
135
|
# An alias for readability purposes
|
129
|
-
def check?(*args) = allowed_to?(*args)
|
136
|
+
def check?(*args, **hargs) = allowed_to?(*args, **hargs)
|
130
137
|
|
131
138
|
# Returns a rule name (policy method name) for activity.
|
132
139
|
#
|
@@ -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
|
@@ -31,6 +31,20 @@ module ActionPolicy
|
|
31
31
|
|
32
32
|
def present?() = !empty?
|
33
33
|
|
34
|
+
def merge(other)
|
35
|
+
other.reasons.each do |policy_class, rules|
|
36
|
+
reasons[policy_class] ||= []
|
37
|
+
|
38
|
+
rules.each do |rule|
|
39
|
+
if rule.is_a?(::Hash)
|
40
|
+
add_detailed_reason(reasons[policy_class], rule)
|
41
|
+
else
|
42
|
+
add_non_detailed_reason(reasons[policy_class], rule)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
34
48
|
private
|
35
49
|
|
36
50
|
def add_non_detailed_reason(store, rule)
|
@@ -72,7 +86,7 @@ module ActionPolicy
|
|
72
86
|
def all_details
|
73
87
|
return @all_details if defined?(@all_details)
|
74
88
|
|
75
|
-
{}.tap do |all|
|
89
|
+
@all_details = {}.tap do |all|
|
76
90
|
next unless defined?(@reasons)
|
77
91
|
|
78
92
|
reasons.reasons.each_value do |rules|
|
@@ -84,7 +98,7 @@ module ActionPolicy
|
|
84
98
|
all.merge!(details)
|
85
99
|
end
|
86
100
|
end
|
87
|
-
end
|
101
|
+
end
|
88
102
|
end
|
89
103
|
|
90
104
|
# Add reasons to inspect
|
@@ -182,7 +196,7 @@ module ActionPolicy
|
|
182
196
|
result.details ||= {}
|
183
197
|
end
|
184
198
|
|
185
|
-
def allowed_to?(rule, record = :__undef__, **options)
|
199
|
+
def allowed_to?(rule, record = :__undef__, inline_reasons: false, **options)
|
186
200
|
res =
|
187
201
|
if (record == :__undef__ || record == self.record) && options.empty?
|
188
202
|
rule = resolve_rule(rule)
|
@@ -196,7 +210,9 @@ module ActionPolicy
|
|
196
210
|
policy.result
|
197
211
|
end
|
198
212
|
|
199
|
-
|
213
|
+
if res.fail? && result&.reasons
|
214
|
+
inline_reasons ? result.reasons.merge(res.reasons) : result.reasons.add(policy, rule, res.details)
|
215
|
+
end
|
200
216
|
|
201
217
|
res.clear_details
|
202
218
|
|
@@ -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
|
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) ||
|
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.6.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Vladimir Dementyev
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-09-02 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
|
@@ -223,7 +222,7 @@ metadata:
|
|
223
222
|
documentation_uri: https://actionpolicy.evilmartians.io/
|
224
223
|
homepage_uri: https://actionpolicy.evilmartians.io/
|
225
224
|
source_code_uri: http://github.com/palkan/action_policy
|
226
|
-
post_install_message:
|
225
|
+
post_install_message:
|
227
226
|
rdoc_options: []
|
228
227
|
require_paths:
|
229
228
|
- lib
|
@@ -231,15 +230,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
231
230
|
requirements:
|
232
231
|
- - ">="
|
233
232
|
- !ruby/object:Gem::Version
|
234
|
-
version: 2.
|
233
|
+
version: 2.6.0
|
235
234
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
236
235
|
requirements:
|
237
236
|
- - ">="
|
238
237
|
- !ruby/object:Gem::Version
|
239
238
|
version: '0'
|
240
239
|
requirements: []
|
241
|
-
rubygems_version: 3.
|
242
|
-
signing_key:
|
240
|
+
rubygems_version: 3.2.15
|
241
|
+
signing_key:
|
243
242
|
specification_version: 4
|
244
243
|
summary: Authorization framework for Ruby/Rails application
|
245
244
|
test_files: []
|
@@ -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
|