sequel-privacy 0.2.1 → 0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +47 -32
- data/lib/sequel/plugins/privacy.rb +8 -0
- data/lib/sequel/privacy/built_in_policies.rb +1 -1
- data/lib/sequel/privacy/enforcer.rb +23 -8
- data/lib/sequel/privacy/policy.rb +60 -8
- data/lib/sequel/privacy/policy_dsl.rb +12 -3
- data/lib/sequel/privacy/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 651aa5392073590a594f91df25458c4e2594ae6de3d8157b53525bfe210ebeef
|
|
4
|
+
data.tar.gz: 6addd80f151b6a1b272e5f70376ac41e18fd84e7a60cc77d68017d3c6b646dc6
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: cb083193065e4d0929beb64dc3f51029e375861d42f5800da2218a3699b1adcd37c16aea5dc55f3cce0d250322967bed98f3c2e53119a693d8e7e9f0111bdab6
|
|
7
|
+
data.tar.gz: 83840dfe5cded9036995df6371c083c9ab72e983f0c3de1eee69ff7a6b56b0b4e00c3ede0c6225e71669ce3bb14d981f08095c49153c5e75bacf6ee6896c072f
|
data/README.md
CHANGED
|
@@ -31,23 +31,26 @@ module P
|
|
|
31
31
|
AlwaysAllow = Sequel::Privacy::BuiltInPolicies::AlwaysAllow
|
|
32
32
|
PassAndLog = Sequel::Privacy::BuiltInPolicies::PassAndLog
|
|
33
33
|
|
|
34
|
-
|
|
34
|
+
# State-gate policies examine only the subject. Declare
|
|
35
|
+
# `allow_anonymous: true` so logged-out viewers can still pass.
|
|
36
|
+
policy :AllowIfPublished, ->(_actor, subject) {
|
|
35
37
|
allow if subject.published
|
|
36
|
-
}
|
|
38
|
+
}, allow_anonymous: true, cache_by: :subject
|
|
37
39
|
|
|
38
|
-
|
|
40
|
+
# Actor-only role checks — cheap because they cache per-actor.
|
|
41
|
+
policy :AllowAdmins, ->(actor) {
|
|
39
42
|
allow if actor.is_role?(:admin)
|
|
40
43
|
}, 'Allow admin users', cacheable: true
|
|
41
|
-
|
|
42
|
-
policy :AllowMembers, ->(
|
|
44
|
+
|
|
45
|
+
policy :AllowMembers, ->(actor) {
|
|
43
46
|
allow if actor.is_role?(:member)
|
|
44
47
|
}, cacheable: true
|
|
45
48
|
|
|
46
|
-
policy :AllowSelf, ->(
|
|
49
|
+
policy :AllowSelf, ->(actor, subject) {
|
|
47
50
|
allow if subject == actor
|
|
48
|
-
}, 'Allow if subject is the actor', single_match: true
|
|
49
|
-
|
|
50
|
-
policy :AllowFriendsOfSubject, ->(
|
|
51
|
+
}, 'Allow if subject is the actor', single_match: true
|
|
52
|
+
|
|
53
|
+
policy :AllowFriendsOfSubject, ->(actor, subject) {
|
|
51
54
|
allow if subject.includes_friend?(actor)
|
|
52
55
|
}
|
|
53
56
|
end
|
|
@@ -107,28 +110,34 @@ member.phone # => nil if :view_phone denies
|
|
|
107
110
|
|
|
108
111
|
## Policy Definition
|
|
109
112
|
|
|
110
|
-
Policies are lambdas that execute in the context of an `Actions` struct, giving access to `allow`, `deny`, and `pass` outcome methods, as well as the `all` combinator. `allow` and `deny` will end evaluation of the chain of policies, whereas `pass` will continue to the next policy in the chain.
|
|
113
|
+
Policies are lambdas that execute in the context of an `Actions` struct, giving access to `allow`, `deny`, and `pass` outcome methods, as well as the `all` combinator. `allow` and `deny` will end evaluation of the chain of policies, whereas `pass` will continue to the next policy in the chain.
|
|
114
|
+
|
|
115
|
+
Policies are **actor-first**. Arities map to:
|
|
116
|
+
- 0 args — global decision (`-> { allow if Time.now.sunday? }`)
|
|
117
|
+
- 1 arg — `(actor)`: role / identity checks
|
|
118
|
+
- 2 args — `(actor, subject)`: ownership, membership
|
|
119
|
+
- 3 args — `(actor, subject, direct_object)`: "can actor do X to subject with direct_object?"
|
|
111
120
|
|
|
112
|
-
Policies
|
|
121
|
+
Policies of arity ≥ 1 auto-deny for anonymous viewers (nil actor). Use `allow_anonymous: true` to opt out — meant for state-gate policies that examine only the subject.
|
|
113
122
|
|
|
114
123
|
|
|
115
124
|
```ruby
|
|
116
125
|
|
|
117
126
|
policy :AlwaysAllow, -> { allow }
|
|
118
127
|
|
|
119
|
-
policy :AllowIfPublished, ->(subject) {
|
|
128
|
+
policy :AllowIfPublished, ->(_actor, subject) {
|
|
120
129
|
allow if subject.published
|
|
121
|
-
}
|
|
130
|
+
}, allow_anonymous: true, cache_by: :subject
|
|
122
131
|
|
|
123
|
-
policy :AllowAdmins, ->(
|
|
132
|
+
policy :AllowAdmins, ->(actor) {
|
|
124
133
|
allow if actor.is_role?(:admin)
|
|
125
134
|
}
|
|
126
135
|
|
|
127
|
-
policy :AllowOwner, ->(
|
|
136
|
+
policy :AllowOwner, ->(actor, subject) {
|
|
128
137
|
allow if subject.owner_id == actor.id
|
|
129
138
|
}
|
|
130
139
|
|
|
131
|
-
policy :AllowIfDirectObjectIsActor, ->(
|
|
140
|
+
policy :AllowIfDirectObjectIsActor, ->(actor, _subject, direct_object) {
|
|
132
141
|
allow if actor.id == direct_object.id
|
|
133
142
|
}
|
|
134
143
|
|
|
@@ -141,14 +150,14 @@ different modules.
|
|
|
141
150
|
module P
|
|
142
151
|
module Groups
|
|
143
152
|
extend Sequel::Privacy::PolicyDSL
|
|
144
|
-
|
|
145
|
-
policy :AllowIfOpen, ->
|
|
153
|
+
|
|
154
|
+
policy :AllowIfOpen, ->(_actor, subject) {
|
|
146
155
|
allow if subject.open?
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
policy :AllowIfMember, ->
|
|
156
|
+
}, allow_anonymous: true, cache_by: :subject
|
|
157
|
+
|
|
158
|
+
policy :AllowIfMember, ->(actor, subject) {
|
|
150
159
|
allow if subject.includes_member? actor
|
|
151
|
-
}
|
|
160
|
+
}
|
|
152
161
|
end
|
|
153
162
|
end
|
|
154
163
|
```
|
|
@@ -159,25 +168,31 @@ end
|
|
|
159
168
|
policy :MyPolicy, ->() { ... },
|
|
160
169
|
'Human-readable description', # For logging
|
|
161
170
|
cacheable: true, # Cache results (default: true)
|
|
162
|
-
single_match: false
|
|
171
|
+
single_match: false, # Only one subject can match
|
|
172
|
+
cache_by: :actor, # Override cache-key dimensions
|
|
173
|
+
allow_anonymous: false # Allow nil actor (opts out of auto-deny)
|
|
163
174
|
```
|
|
164
175
|
|
|
165
176
|
**`cacheable: true`** (default): Results are cached for the duration of the request, keyed by policy + arguments. Use for policies that don't depend on mutable state.
|
|
166
177
|
|
|
167
|
-
**`single_match: true`**: Optimization for policies for which there is only one matching Actor possible for a given Subject. For example in `AllowAuthors`, since a `Post` can have only one other, it's not worth a potentially expensive check on other combinations once you've found the winner.
|
|
178
|
+
**`single_match: true`**: Optimization for policies for which there is only one matching Actor possible for a given Subject. For example in `AllowAuthors`, since a `Post` can have only one other, it's not worth a potentially expensive check on other combinations once you've found the winner.
|
|
179
|
+
|
|
180
|
+
**`cache_by:`** (Symbol or Array of `:actor`, `:subject`, `:direct_object`): Override the cache-key dimensions. By default the key uses every input the policy receives. Pass a subset when the policy ignores some of its inputs — e.g. `AllowAdmins` takes `(actor, subject)` but only examines actor, so `cache_by: :actor` shares one entry across subjects.
|
|
181
|
+
|
|
182
|
+
**`allow_anonymous: true`**: Skip the auto-deny for nil actor. Use for state-gate policies that examine only the subject (e.g. "post is published").
|
|
168
183
|
|
|
169
184
|
### Policy Combinators
|
|
170
185
|
|
|
171
186
|
Use `all()` to require multiple conditions:
|
|
172
187
|
|
|
173
188
|
```ruby
|
|
174
|
-
policy :AllowAddSelfToOpenGroup, ->(
|
|
189
|
+
policy :AllowAddSelfToOpenGroup, ->(actor, subject, direct_object) {
|
|
175
190
|
all(
|
|
176
|
-
P::AllowIfGroupIsOpen
|
|
191
|
+
P::AllowIfGroupIsOpen,
|
|
177
192
|
P::AllowIfDirectObjectIsActor
|
|
178
193
|
)
|
|
179
194
|
}
|
|
180
|
-
policy :AllowRemoveSelf, ->(
|
|
195
|
+
policy :AllowRemoveSelf, ->(actor, subject, direct_object) {
|
|
181
196
|
all(
|
|
182
197
|
P::AllowIfIncludesMember,
|
|
183
198
|
P::AllowIfDirectObjectIsActor
|
|
@@ -308,25 +323,25 @@ The `association` block supports three actions:
|
|
|
308
323
|
- `:remove` - Wraps `remove_*` method (e.g., `remove_member`)
|
|
309
324
|
- `:remove_all` - Wraps `remove_all_*` method (e.g., `remove_all_members`)
|
|
310
325
|
|
|
311
|
-
Association policies receive `(
|
|
312
|
-
- `subject` - The model instance (e.g., the group)
|
|
326
|
+
Association policies receive `(actor, subject, direct_object)`:
|
|
313
327
|
- `actor` - The current user from the viewer context
|
|
328
|
+
- `subject` - The model instance (e.g., the group)
|
|
314
329
|
- `direct_object` - The object being added/removed (e.g., the user being added to the group)
|
|
315
330
|
|
|
316
331
|
For `remove_all`, the direct object is `nil` since there's no specific target.
|
|
317
332
|
|
|
318
333
|
```ruby
|
|
319
334
|
# Allow users to add/remove themselves
|
|
320
|
-
policy :AllowSelfJoin, ->(
|
|
335
|
+
policy :AllowSelfJoin, ->(actor, _subject, direct_object) {
|
|
321
336
|
allow if actor.id == direct_object.id
|
|
322
337
|
}, single_match: true
|
|
323
338
|
|
|
324
|
-
policy :AllowSelfRemove, ->(
|
|
339
|
+
policy :AllowSelfRemove, ->(actor, _subject, direct_object) {
|
|
325
340
|
allow if actor.id == direct_object.id
|
|
326
341
|
}, single_match: true
|
|
327
342
|
|
|
328
343
|
# Allow group admins to add/remove anyone
|
|
329
|
-
policy :AllowGroupAdmin, ->(
|
|
344
|
+
policy :AllowGroupAdmin, ->(actor, subject, _direct_object) {
|
|
330
345
|
allow if subject.includes_admin?(actor)
|
|
331
346
|
}
|
|
332
347
|
```
|
|
@@ -302,6 +302,8 @@ module Sequel
|
|
|
302
302
|
vc = instance_variable_get(:@viewer_context)
|
|
303
303
|
|
|
304
304
|
unless vc
|
|
305
|
+
return original_method.bind(self).() if T.unsafe(self.class).allow_unsafe_access?
|
|
306
|
+
|
|
305
307
|
Kernel.raise Sequel::Privacy::MissingViewerContext,
|
|
306
308
|
"#{self.class}##{field} requires a ViewerContext"
|
|
307
309
|
end
|
|
@@ -518,6 +520,8 @@ module Sequel
|
|
|
518
520
|
vc = instance_variable_get(:@viewer_context)
|
|
519
521
|
|
|
520
522
|
unless vc
|
|
523
|
+
return original.bind(self).(obj) if T.unsafe(self.class).allow_unsafe_access?
|
|
524
|
+
|
|
521
525
|
Kernel.raise Sequel::Privacy::MissingViewerContext,
|
|
522
526
|
"Cannot #{method_name} without a viewer context"
|
|
523
527
|
end
|
|
@@ -548,6 +552,8 @@ module Sequel
|
|
|
548
552
|
vc = instance_variable_get(:@viewer_context)
|
|
549
553
|
|
|
550
554
|
unless vc
|
|
555
|
+
return original.bind(self).(obj) if T.unsafe(self.class).allow_unsafe_access?
|
|
556
|
+
|
|
551
557
|
Kernel.raise Sequel::Privacy::MissingViewerContext,
|
|
552
558
|
"Cannot #{method_name} without a viewer context"
|
|
553
559
|
end
|
|
@@ -578,6 +584,8 @@ module Sequel
|
|
|
578
584
|
vc = instance_variable_get(:@viewer_context)
|
|
579
585
|
|
|
580
586
|
unless vc
|
|
587
|
+
return original.bind(self).() if T.unsafe(self.class).allow_unsafe_access?
|
|
588
|
+
|
|
581
589
|
Kernel.raise Sequel::Privacy::MissingViewerContext,
|
|
582
590
|
"Cannot #{method_name} without a viewer context"
|
|
583
591
|
end
|
|
@@ -25,7 +25,7 @@ module Sequel
|
|
|
25
25
|
# Pass and log - useful for debugging policy chains.
|
|
26
26
|
PassAndLog = Policy.create(
|
|
27
27
|
:PassAndLog,
|
|
28
|
-
->(
|
|
28
|
+
->(actor, subject) {
|
|
29
29
|
Sequel::Privacy::Enforcer.logger&.info("PassAndLog: #{subject.class} for actor #{actor.id}")
|
|
30
30
|
:pass
|
|
31
31
|
},
|
|
@@ -97,15 +97,27 @@ module Sequel
|
|
|
97
97
|
).returns(Integer)
|
|
98
98
|
end
|
|
99
99
|
def self.compute_cache_key(policy, subject, actor, viewer_context, direct_object)
|
|
100
|
+
if (keys = policy.cache_by)
|
|
101
|
+
parts = T.let([policy, viewer_context], T::Array[T.untyped])
|
|
102
|
+
keys.each do |k|
|
|
103
|
+
parts << case k
|
|
104
|
+
when :actor then actor
|
|
105
|
+
when :subject then subject
|
|
106
|
+
when :direct_object then direct_object
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
return parts.hash
|
|
110
|
+
end
|
|
111
|
+
|
|
100
112
|
case policy.arity
|
|
101
113
|
when 0
|
|
102
114
|
[policy, viewer_context].hash
|
|
103
115
|
when 1
|
|
104
|
-
[policy,
|
|
116
|
+
[policy, actor, viewer_context].hash
|
|
105
117
|
when 2
|
|
106
|
-
[policy,
|
|
118
|
+
[policy, actor, subject, viewer_context].hash
|
|
107
119
|
else
|
|
108
|
-
[policy,
|
|
120
|
+
[policy, actor, subject, direct_object, viewer_context].hash
|
|
109
121
|
end
|
|
110
122
|
end
|
|
111
123
|
|
|
@@ -211,8 +223,11 @@ module Sequel
|
|
|
211
223
|
).returns(T.untyped)
|
|
212
224
|
end
|
|
213
225
|
def self.execute_policy(policy, subject, actor, direct_object)
|
|
214
|
-
#
|
|
215
|
-
|
|
226
|
+
# Policies with arity >= 1 expect an actor as the first arg.
|
|
227
|
+
# Anonymous viewers (no actor) auto-deny unless the policy opts in
|
|
228
|
+
# with allow_anonymous: true (for state-gate policies that examine
|
|
229
|
+
# only the subject).
|
|
230
|
+
if !actor && policy.arity >= 1 && !policy.allow_anonymous?
|
|
216
231
|
return :deny
|
|
217
232
|
end
|
|
218
233
|
|
|
@@ -220,11 +235,11 @@ module Sequel
|
|
|
220
235
|
when 0
|
|
221
236
|
Actions.evaluate(&policy)
|
|
222
237
|
when 1
|
|
223
|
-
Actions.evaluate(
|
|
238
|
+
Actions.evaluate(actor, &policy)
|
|
224
239
|
when 2
|
|
225
|
-
Actions.evaluate(
|
|
240
|
+
Actions.evaluate(actor, subject, &policy)
|
|
226
241
|
else
|
|
227
|
-
Actions.evaluate(
|
|
242
|
+
Actions.evaluate(actor, subject, direct_object, &policy)
|
|
228
243
|
end
|
|
229
244
|
end
|
|
230
245
|
|
|
@@ -5,11 +5,15 @@ module Sequel
|
|
|
5
5
|
module Privacy
|
|
6
6
|
# A Policy wraps a Proc/lambda with metadata about how it should be evaluated.
|
|
7
7
|
#
|
|
8
|
-
# Policies
|
|
9
|
-
# - 0 args: -> { allow }
|
|
8
|
+
# Policies are actor-first. Arities map to:
|
|
9
|
+
# - 0 args: -> { allow if Time.now.sunday? } # Global decision
|
|
10
10
|
# - 1 arg: ->(actor) { allow if actor.is_role?(:admin) }
|
|
11
|
-
# - 2 args: ->(
|
|
12
|
-
# - 3 args: ->(
|
|
11
|
+
# - 2 args: ->(actor, subject) { allow if subject.owner_id == actor.id }
|
|
12
|
+
# - 3 args: ->(actor, subject, direct_object) { ... }
|
|
13
|
+
#
|
|
14
|
+
# Any policy with arity >= 1 auto-denies for anonymous viewers (nil actor)
|
|
15
|
+
# unless declared with `allow_anonymous: true`. That flag is for state-gate
|
|
16
|
+
# policies that deliberately ignore actor — e.g. "post is published."
|
|
13
17
|
#
|
|
14
18
|
# Policies must return :allow, :deny, :pass, or an array of policies (for combinators).
|
|
15
19
|
class Policy < Proc
|
|
@@ -21,6 +25,8 @@ module Sequel
|
|
|
21
25
|
sig { returns(T.nilable(String)) }
|
|
22
26
|
attr_reader :comment
|
|
23
27
|
|
|
28
|
+
VALID_CACHE_BY = T.let(%i[actor subject direct_object].freeze, T::Array[Symbol])
|
|
29
|
+
|
|
24
30
|
# Factory method for creating policies. Accepts procs of any arity
|
|
25
31
|
# (0–3 args) returning :allow, :deny, :pass, or an Array of policies.
|
|
26
32
|
sig do
|
|
@@ -29,15 +35,20 @@ module Sequel
|
|
|
29
35
|
lam: Proc,
|
|
30
36
|
comment: T.nilable(String),
|
|
31
37
|
cacheable: T::Boolean,
|
|
32
|
-
single_match: T::Boolean
|
|
38
|
+
single_match: T::Boolean,
|
|
39
|
+
cache_by: T.nilable(T.any(Symbol, T::Array[Symbol])),
|
|
40
|
+
allow_anonymous: T::Boolean
|
|
33
41
|
).returns(T.self_type)
|
|
34
42
|
end
|
|
35
|
-
def self.create(policy_name, lam, comment = nil, cacheable: true, single_match: false
|
|
43
|
+
def self.create(policy_name, lam, comment = nil, cacheable: true, single_match: false, cache_by: nil,
|
|
44
|
+
allow_anonymous: false)
|
|
36
45
|
new(&lam).setup(
|
|
37
46
|
policy_name: policy_name,
|
|
38
47
|
comment: comment,
|
|
39
48
|
cacheable: cacheable,
|
|
40
|
-
single_match: single_match
|
|
49
|
+
single_match: single_match,
|
|
50
|
+
cache_by: cache_by,
|
|
51
|
+
allow_anonymous: allow_anonymous
|
|
41
52
|
)
|
|
42
53
|
end
|
|
43
54
|
|
|
@@ -47,7 +58,20 @@ module Sequel
|
|
|
47
58
|
# @param comment [String, nil] Description of what this policy does
|
|
48
59
|
# @param cacheable [Boolean] Whether results can be cached (default: true)
|
|
49
60
|
# @param single_match [Boolean] Whether only one subject/actor pair can match (default: false)
|
|
50
|
-
|
|
61
|
+
# @param cache_by [Symbol, Array<Symbol>, nil] Override the cache-key
|
|
62
|
+
# dimensions. By default the key is derived from the policy's arity
|
|
63
|
+
# (all inputs the policy receives). Pass a subset of
|
|
64
|
+
# `:actor, :subject, :direct_object` to cache by only those — useful
|
|
65
|
+
# when the policy ignores inputs it nominally receives (e.g. an
|
|
66
|
+
# "is-admin" check that takes `(actor, subject)` but only examines
|
|
67
|
+
# actor should use `cache_by: :actor` to share a single entry across
|
|
68
|
+
# subjects).
|
|
69
|
+
# @param allow_anonymous [Boolean] If true, skip the auto-deny that
|
|
70
|
+
# normally fires when a policy of arity >= 1 is evaluated for an
|
|
71
|
+
# anonymous viewer (nil actor). Use for state-gate policies that
|
|
72
|
+
# ignore the actor and decide purely on subject state.
|
|
73
|
+
def setup(policy_name: nil, comment: nil, cacheable: true, single_match: false, cache_by: nil,
|
|
74
|
+
allow_anonymous: false)
|
|
51
75
|
raise 'Privacy Policy is frozen' if @frozen
|
|
52
76
|
|
|
53
77
|
@cacheable = cacheable
|
|
@@ -55,6 +79,8 @@ module Sequel
|
|
|
55
79
|
@comment = comment
|
|
56
80
|
@frozen = true
|
|
57
81
|
@single_match = single_match
|
|
82
|
+
@cache_by = normalize_cache_by(cache_by)
|
|
83
|
+
@allow_anonymous = allow_anonymous
|
|
58
84
|
self
|
|
59
85
|
end
|
|
60
86
|
|
|
@@ -69,6 +95,32 @@ module Sequel
|
|
|
69
95
|
def single_match?
|
|
70
96
|
@single_match || false
|
|
71
97
|
end
|
|
98
|
+
|
|
99
|
+
sig { returns(T.nilable(T::Array[Symbol])) }
|
|
100
|
+
def cache_by
|
|
101
|
+
@cache_by
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
sig { returns(T::Boolean) }
|
|
105
|
+
def allow_anonymous?
|
|
106
|
+
@allow_anonymous || false
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
private
|
|
110
|
+
|
|
111
|
+
sig { params(val: T.nilable(T.any(Symbol, T::Array[Symbol]))).returns(T.nilable(T::Array[Symbol])) }
|
|
112
|
+
def normalize_cache_by(val)
|
|
113
|
+
return nil if val.nil?
|
|
114
|
+
|
|
115
|
+
keys = Array(val).map(&:to_sym)
|
|
116
|
+
invalid = keys - VALID_CACHE_BY
|
|
117
|
+
unless invalid.empty?
|
|
118
|
+
raise ArgumentError,
|
|
119
|
+
"Invalid cache_by key(s): #{invalid.inspect}. Valid keys: #{VALID_CACHE_BY.inspect}"
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
keys
|
|
123
|
+
end
|
|
72
124
|
end
|
|
73
125
|
end
|
|
74
126
|
end
|
|
@@ -31,21 +31,30 @@ module Sequel
|
|
|
31
31
|
# @param comment [String, nil] Human-readable description
|
|
32
32
|
# @param cacheable [Boolean] Whether results can be cached (default: true)
|
|
33
33
|
# @param single_match [Boolean] Whether only one subject/actor can match (default: false)
|
|
34
|
+
# @param cache_by [Symbol, Array<Symbol>, nil] Override cache-key
|
|
35
|
+
# dimensions. See Sequel::Privacy::Policy#setup for details.
|
|
36
|
+
# @param allow_anonymous [Boolean] Skip auto-deny for nil actor.
|
|
37
|
+
# See Sequel::Privacy::Policy#setup for details.
|
|
34
38
|
sig do
|
|
35
39
|
params(
|
|
36
40
|
name: Symbol,
|
|
37
41
|
lam: Proc,
|
|
38
42
|
comment: T.nilable(String),
|
|
39
43
|
cacheable: T::Boolean,
|
|
40
|
-
single_match: T::Boolean
|
|
44
|
+
single_match: T::Boolean,
|
|
45
|
+
cache_by: T.nilable(T.any(Symbol, T::Array[Symbol])),
|
|
46
|
+
allow_anonymous: T::Boolean
|
|
41
47
|
).void
|
|
42
48
|
end
|
|
43
|
-
def policy(name, lam, comment = nil, cacheable: true, single_match: false
|
|
49
|
+
def policy(name, lam, comment = nil, cacheable: true, single_match: false, cache_by: nil,
|
|
50
|
+
allow_anonymous: false)
|
|
44
51
|
p = Policy.new(&lam).setup(
|
|
45
52
|
policy_name: name,
|
|
46
53
|
comment: comment,
|
|
47
54
|
cacheable: cacheable,
|
|
48
|
-
single_match: single_match
|
|
55
|
+
single_match: single_match,
|
|
56
|
+
cache_by: cache_by,
|
|
57
|
+
allow_anonymous: allow_anonymous
|
|
49
58
|
)
|
|
50
59
|
const_set(name, p)
|
|
51
60
|
end
|