parse-stack-next 5.6.0 → 5.7.0
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 +409 -0
- data/README.md +378 -6
- data/docs/caching.md +748 -0
- data/examples/basic_client.rb +3 -3
- data/examples/basic_server.rb +3 -3
- data/examples/live_query_listener.rb +12 -12
- data/examples/rag_chatbot.rb +14 -14
- data/examples/transaction_example.rb +44 -45
- data/examples/webhook_server.rb +3 -3
- data/lib/parse/access.rb +493 -0
- data/lib/parse/acl_scope.rb +125 -52
- data/lib/parse/agent/approval_gate.rb +0 -0
- data/lib/parse/agent/cancellation_token.rb +3 -3
- data/lib/parse/agent/constraint_translator.rb +4 -4
- data/lib/parse/agent/describe.rb +34 -34
- data/lib/parse/agent/errors.rb +9 -9
- data/lib/parse/agent/mcp_client.rb +61 -59
- data/lib/parse/agent/mcp_dispatcher.rb +89 -101
- data/lib/parse/agent/mcp_rack_app.rb +92 -93
- data/lib/parse/agent/mcp_server.rb +1 -1
- data/lib/parse/agent/mcp_subscriptions.rb +22 -22
- data/lib/parse/agent/metadata_audit.rb +1 -2
- data/lib/parse/agent/metadata_dsl.rb +7 -7
- data/lib/parse/agent/metadata_registry.rb +12 -12
- data/lib/parse/agent/prompt_hardening.rb +4 -4
- data/lib/parse/agent/prompts.rb +10 -10
- data/lib/parse/agent/result_formatter.rb +4 -4
- data/lib/parse/agent/tools.rb +493 -400
- data/lib/parse/agent.rb +157 -116
- data/lib/parse/api/hooks.rb +1 -1
- data/lib/parse/api/server.rb +2 -2
- data/lib/parse/api/users.rb +2 -2
- data/lib/parse/atlas_search/index_manager.rb +1 -1
- data/lib/parse/atlas_search/session.rb +40 -218
- data/lib/parse/atlas_search.rb +181 -60
- data/lib/parse/authorization.rb +466 -0
- data/lib/parse/cache/invalidation.rb +167 -0
- data/lib/parse/cache/keyspace.rb +306 -0
- data/lib/parse/cache/moneta_surface.rb +126 -0
- data/lib/parse/cache/pool.rb +48 -5
- data/lib/parse/cache/redis.rb +425 -10
- data/lib/parse/cache/scoped_view.rb +443 -0
- data/lib/parse/cache/sub_cache.rb +264 -0
- data/lib/parse/cache/upstream_roles.rb +230 -0
- data/lib/parse/client/authentication.rb +1 -1
- data/lib/parse/client/body_builder.rb +1 -2
- data/lib/parse/client/caching.rb +84 -5
- data/lib/parse/client.rb +327 -189
- data/lib/parse/clp_scope.rb +225 -28
- data/lib/parse/console.rb +3 -3
- data/lib/parse/embeddings/batch_embedder.rb +3 -3
- data/lib/parse/embeddings/cache.rb +17 -17
- data/lib/parse/embeddings/cohere.rb +21 -22
- data/lib/parse/embeddings/image_fetch.rb +22 -21
- data/lib/parse/embeddings/jina.rb +21 -21
- data/lib/parse/embeddings/local_http.rb +6 -7
- data/lib/parse/embeddings/media_file.rb +5 -4
- data/lib/parse/embeddings/openai.rb +5 -5
- data/lib/parse/embeddings/provider.rb +1 -1
- data/lib/parse/embeddings/qwen.rb +10 -10
- data/lib/parse/embeddings/spend_cap.rb +2 -3
- data/lib/parse/embeddings/streaming_body.rb +11 -11
- data/lib/parse/embeddings/video_source.rb +7 -6
- data/lib/parse/embeddings/voyage.rb +59 -61
- data/lib/parse/embeddings.rb +28 -27
- data/lib/parse/graphql/type_generator.rb +1 -1
- data/lib/parse/graphql.rb +5 -5
- data/lib/parse/live_query/client.rb +1 -1
- data/lib/parse/live_query.rb +1 -1
- data/lib/parse/lock.rb +15 -18
- data/lib/parse/lock_backend.rb +1 -1
- data/lib/parse/lookup_rewriter.rb +6 -6
- data/lib/parse/model/classes/role.rb +227 -28
- data/lib/parse/model/classes/user.rb +60 -1
- data/lib/parse/model/clp.rb +4 -4
- data/lib/parse/model/core/actions.rb +97 -15
- data/lib/parse/model/core/create_lock.rb +0 -2
- data/lib/parse/model/core/describe.rb +57 -57
- data/lib/parse/model/core/embed_managed.rb +21 -23
- data/lib/parse/model/core/fetching.rb +2 -0
- data/lib/parse/model/core/indexing.rb +14 -14
- data/lib/parse/model/core/parse_reference.rb +1 -1
- data/lib/parse/model/core/properties.rb +2 -2
- data/lib/parse/model/core/querying.rb +1 -1
- data/lib/parse/model/core/schema.rb +2 -2
- data/lib/parse/model/core/search_indexing.rb +2 -2
- data/lib/parse/model/core/vector_searchable.rb +4 -5
- data/lib/parse/model/file.rb +21 -15
- data/lib/parse/model/geojson.rb +2 -2
- data/lib/parse/model/geopoint.rb +1 -0
- data/lib/parse/model/object.rb +132 -66
- data/lib/parse/model/pointer.rb +2 -0
- data/lib/parse/model/polygon.rb +3 -6
- data/lib/parse/model/push.rb +2 -2
- data/lib/parse/model/vector.rb +3 -1
- data/lib/parse/mongodb.rb +512 -282
- data/lib/parse/pipeline_security.rb +3 -0
- data/lib/parse/query/constraints.rb +46 -46
- data/lib/parse/query.rb +146 -59
- data/lib/parse/retrieval/agent_tool.rb +34 -20
- data/lib/parse/retrieval/chunk.rb +1 -0
- data/lib/parse/retrieval/reranker/cohere.rb +11 -11
- data/lib/parse/retrieval/reranker.rb +3 -4
- data/lib/parse/retrieval/retriever.rb +4 -6
- data/lib/parse/schema/index_migrator.rb +26 -26
- data/lib/parse/schema/search_index_migrator.rb +19 -19
- data/lib/parse/stack/tasks.rb +6 -6
- data/lib/parse/stack/version.rb +1 -1
- data/lib/parse/stack.rb +10 -10
- data/lib/parse/vector_search/hybrid.rb +66 -41
- data/lib/parse/vector_search.rb +14 -11
- data/lib/parse/webhooks/payload.rb +1 -0
- data/lib/parse/webhooks/registration.rb +4 -4
- data/lib/parse/webhooks/trigger_audit.rb +50 -49
- data/lib/parse/webhooks.rb +22 -4
- data/parse-stack-next.gemspec +6 -6
- metadata +10 -1
data/lib/parse/api/hooks.rb
CHANGED
|
@@ -44,7 +44,7 @@ module Parse
|
|
|
44
44
|
def _verify_trigger(triggerName)
|
|
45
45
|
camel = triggerName.to_s.camelize(:lower).to_sym
|
|
46
46
|
if %i[beforeCreate afterCreate].include?(camel)
|
|
47
|
-
save
|
|
47
|
+
save = camel == :beforeCreate ? "beforeSave" : "afterSave"
|
|
48
48
|
callback = camel == :beforeCreate ? "before_create" : "after_create"
|
|
49
49
|
raise ArgumentError,
|
|
50
50
|
"Parse Server has no #{camel} webhook trigger. Register a " \
|
data/lib/parse/api/server.rb
CHANGED
|
@@ -192,10 +192,10 @@ module Parse
|
|
|
192
192
|
# unparseable input so a wire-format surprise never raises.
|
|
193
193
|
def server_version_below?(actual, floor)
|
|
194
194
|
actual_parts = actual.scan(/\d+/).first(2).map(&:to_i)
|
|
195
|
-
floor_parts
|
|
195
|
+
floor_parts = floor.scan(/\d+/).first(2).map(&:to_i)
|
|
196
196
|
return false if actual_parts.empty? || floor_parts.empty?
|
|
197
197
|
actual_parts << 0 while actual_parts.length < 2
|
|
198
|
-
floor_parts
|
|
198
|
+
floor_parts << 0 while floor_parts.length < 2
|
|
199
199
|
(actual_parts <=> floor_parts) < 0
|
|
200
200
|
end
|
|
201
201
|
end
|
data/lib/parse/api/users.rb
CHANGED
|
@@ -271,6 +271,7 @@ module Parse
|
|
|
271
271
|
body[:email] = email || body[:email]
|
|
272
272
|
create_user(body, **opts)
|
|
273
273
|
end
|
|
274
|
+
|
|
274
275
|
private
|
|
275
276
|
|
|
276
277
|
# @!visibility private
|
|
@@ -315,7 +316,7 @@ module Parse
|
|
|
315
316
|
entry = login_rate_limits[username] || { failures: 0, locked_until: nil }
|
|
316
317
|
entry[:failures] += 1
|
|
317
318
|
if entry[:failures] >= LOGIN_MAX_FAILURES
|
|
318
|
-
delay = LOGIN_BASE_DELAY**(entry[:failures] - LOGIN_MAX_FAILURES + 1)
|
|
319
|
+
delay = LOGIN_BASE_DELAY ** (entry[:failures] - LOGIN_MAX_FAILURES + 1)
|
|
319
320
|
delay = [delay, 300].min # cap at 5 minutes
|
|
320
321
|
entry[:locked_until] = Time.now + delay
|
|
321
322
|
end
|
|
@@ -337,7 +338,6 @@ module Parse
|
|
|
337
338
|
entry[:locked_until] && (now - entry[:locked_until]) > LOGIN_RATE_LIMIT_TTL
|
|
338
339
|
end
|
|
339
340
|
end
|
|
340
|
-
|
|
341
341
|
end # Users
|
|
342
342
|
end #API
|
|
343
343
|
end #Parse
|
|
@@ -220,7 +220,7 @@ module Parse
|
|
|
220
220
|
def drop_index(collection_name, index_name, confirm:, allow_system_classes: false)
|
|
221
221
|
result = Parse::MongoDB.drop_search_index(
|
|
222
222
|
collection_name, index_name, confirm: confirm,
|
|
223
|
-
|
|
223
|
+
allow_system_classes: allow_system_classes,
|
|
224
224
|
)
|
|
225
225
|
clear_cache(collection_name)
|
|
226
226
|
result
|
|
@@ -3,249 +3,71 @@
|
|
|
3
3
|
|
|
4
4
|
require "set"
|
|
5
5
|
require_relative "../clp_scope"
|
|
6
|
+
require_relative "../authorization"
|
|
6
7
|
|
|
7
8
|
module Parse
|
|
8
9
|
module AtlasSearch
|
|
9
|
-
#
|
|
10
|
-
# sets for ACL-scoped Atlas Search queries.
|
|
10
|
+
# Compatibility shim over {Parse::Authorization}.
|
|
11
11
|
#
|
|
12
|
-
#
|
|
13
|
-
#
|
|
14
|
-
#
|
|
15
|
-
#
|
|
16
|
-
#
|
|
12
|
+
# This module used to own session-token resolution, role-closure
|
|
13
|
+
# expansion, and both caches. It no longer does. Deciding who a caller is
|
|
14
|
+
# and what they may read is authorization infrastructure that Atlas
|
|
15
|
+
# Search consumes, not infrastructure Atlas Search owns, and the old
|
|
16
|
+
# arrangement meant `Parse::Query#results_direct` on a query with no
|
|
17
|
+
# `$search` in it went through `Parse::ACLScope` into this namespace to
|
|
18
|
+
# find out who was asking. See {Parse::Authorization} for the reasoning
|
|
19
|
+
# and for the real implementation.
|
|
17
20
|
#
|
|
18
|
-
#
|
|
19
|
-
#
|
|
20
|
-
#
|
|
21
|
+
# Everything here now delegates to the default client's authorization
|
|
22
|
+
# context. That is the one behavior difference worth knowing: these
|
|
23
|
+
# methods take no `client:`, so they can only ever address
|
|
24
|
+
# `Parse.client`. Code running against a secondary application should
|
|
25
|
+
# call `other_client.authorization` directly.
|
|
21
26
|
#
|
|
22
|
-
#
|
|
23
|
-
# `/users/me` round-trip, and user → roles can require multiple
|
|
24
|
-
# `_Role` queries to walk the inheritance graph. Both are cached
|
|
25
|
-
# separately so a single agent turn that runs several Atlas Search
|
|
26
|
-
# tools amortizes the cost.
|
|
27
|
-
#
|
|
28
|
-
# Two distinct caches:
|
|
29
|
-
#
|
|
30
|
-
# * `session_cache`: maps `session_token` to `user_id`. Long
|
|
31
|
-
# TTL (1 hour default), invalidation profile is logout. Apps
|
|
32
|
-
# that need sub-TTL revocation must call {.invalidate}
|
|
33
|
-
# explicitly from their logout path.
|
|
34
|
-
#
|
|
35
|
-
# * `role_cache`: maps `user_id` to a `Set` of role names. Short
|
|
36
|
-
# TTL (2 minutes default), invalidation profile is role-graph
|
|
37
|
-
# mutation. Stale role data here yields incorrect ACL
|
|
38
|
-
# decisions, so the default is conservatively short.
|
|
39
|
-
#
|
|
40
|
-
# The default cache implementation is process-local
|
|
41
|
-
# ({MemoryCache}) and guarded by a `Mutex`. Apps that need shared
|
|
42
|
-
# cross-process caching (Redis, Memcached) may install a
|
|
43
|
-
# replacement via {AtlasSearch.session_cache=} /
|
|
44
|
-
# {AtlasSearch.role_cache=}; the replacement must respond to
|
|
45
|
-
# `get(key)`, `set(key, value, ttl:)`, and `invalidate(key)`.
|
|
27
|
+
# Slated for removal in 6.0.
|
|
46
28
|
module Session
|
|
47
|
-
#
|
|
48
|
-
#
|
|
49
|
-
#
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
# Default cache: in-memory hash with per-entry TTL, guarded by a
|
|
53
|
-
# `Mutex`. Suitable for single-process apps. Apps running
|
|
54
|
-
# multi-process (Puma workers, Sidekiq processes) get a per-
|
|
55
|
-
# process cache — install a shared cache through
|
|
56
|
-
# {AtlasSearch.session_cache=} for cross-process sharing.
|
|
57
|
-
class MemoryCache
|
|
58
|
-
def initialize
|
|
59
|
-
@data = {}
|
|
60
|
-
@mutex = Mutex.new
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
# @param key [String]
|
|
64
|
-
# @return [Object, nil] the cached value, or `nil` when the key
|
|
65
|
-
# is missing or its TTL has elapsed. Expired entries are
|
|
66
|
-
# evicted lazily on read.
|
|
67
|
-
def get(key)
|
|
68
|
-
@mutex.synchronize do
|
|
69
|
-
entry = @data[key]
|
|
70
|
-
return nil if entry.nil?
|
|
71
|
-
if entry[:expires_at] < Time.now
|
|
72
|
-
@data.delete(key)
|
|
73
|
-
return nil
|
|
74
|
-
end
|
|
75
|
-
entry[:value]
|
|
76
|
-
end
|
|
77
|
-
end
|
|
78
|
-
|
|
79
|
-
# @param key [String]
|
|
80
|
-
# @param value [Object]
|
|
81
|
-
# @param ttl [Numeric] seconds until the entry expires.
|
|
82
|
-
def set(key, value, ttl:)
|
|
83
|
-
@mutex.synchronize do
|
|
84
|
-
@data[key] = { value: value, expires_at: Time.now + ttl }
|
|
85
|
-
end
|
|
86
|
-
end
|
|
87
|
-
|
|
88
|
-
# @param key [String] cache key to forget.
|
|
89
|
-
def invalidate(key)
|
|
90
|
-
@mutex.synchronize { @data.delete(key) }
|
|
91
|
-
end
|
|
92
|
-
|
|
93
|
-
# Drop every entry. Used by {Session.reset_caches!} and by
|
|
94
|
-
# tests that need a clean slate.
|
|
95
|
-
def clear
|
|
96
|
-
@mutex.synchronize { @data.clear }
|
|
97
|
-
end
|
|
98
|
-
end
|
|
29
|
+
# @deprecated Use {Parse::Authorization::InvalidSession}. Kept as the
|
|
30
|
+
# same object rather than a subclass so existing `rescue
|
|
31
|
+
# Parse::AtlasSearch::Session::InvalidSession` clauses still catch
|
|
32
|
+
# what the resolver actually raises.
|
|
33
|
+
InvalidSession = Parse::Authorization::InvalidSession
|
|
99
34
|
|
|
100
|
-
#
|
|
101
|
-
|
|
102
|
-
# caller. `role_names` is a `Set` of role names (no `role:`
|
|
103
|
-
# prefix) the user inherits permissions from, computed via
|
|
104
|
-
# {Parse::Role.all_for_user}.
|
|
105
|
-
Resolved = Struct.new(:user_id, :role_names) do
|
|
106
|
-
# Build the canonical `_rperm`/`_wperm` permission-string set
|
|
107
|
-
# for this session. Always includes `"*"` (public). Includes
|
|
108
|
-
# `user_id` when present. Includes `"role:#{name}"` for each
|
|
109
|
-
# inherited role.
|
|
110
|
-
# @return [Array<String>]
|
|
111
|
-
def permission_strings
|
|
112
|
-
out = ["*"]
|
|
113
|
-
out << user_id if user_id && !user_id.empty?
|
|
114
|
-
role_names.each { |name| out << "role:#{name}" if name && !name.empty? }
|
|
115
|
-
out.uniq
|
|
116
|
-
end
|
|
35
|
+
# @deprecated Use {Parse::Authorization::MemoryCache}.
|
|
36
|
+
MemoryCache = Parse::Authorization::MemoryCache
|
|
117
37
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
user_id.nil? || user_id.empty?
|
|
121
|
-
end
|
|
122
|
-
end
|
|
38
|
+
# @deprecated Use {Parse::Authorization::Resolved}.
|
|
39
|
+
Resolved = Parse::Authorization::Resolved
|
|
123
40
|
|
|
124
41
|
class << self
|
|
125
|
-
#
|
|
126
|
-
#
|
|
127
|
-
#
|
|
128
|
-
#
|
|
129
|
-
# `nil` or empty `session_token` → anonymous {Resolved} with
|
|
130
|
-
# `user_id: nil` and an empty `role_names` set. The caller
|
|
131
|
-
# decides whether to refuse the request (the
|
|
132
|
-
# `require_session_token` toggle on {Parse::AtlasSearch}) or
|
|
133
|
-
# treat as public-only.
|
|
134
|
-
#
|
|
135
|
-
# Cache layering: token-to-user_id is checked first; on hit
|
|
136
|
-
# the slower `/users/me` round-trip is skipped. User-to-roles
|
|
137
|
-
# is then checked independently (a single user shared across
|
|
138
|
-
# sessions amortizes the role lookup).
|
|
139
|
-
#
|
|
140
|
-
# @param session_token [String, nil] the `X-Parse-Session-Token`
|
|
141
|
-
# value from the requesting session.
|
|
142
|
-
# @return [Resolved]
|
|
143
|
-
# @raise [InvalidSession] when the token cannot be resolved by
|
|
144
|
-
# `/users/me` (404 / 209 invalid session token / 401).
|
|
42
|
+
# @deprecated Use `client.authorization.resolve(token)`, or
|
|
43
|
+
# {Parse::Authorization.resolve} with an explicit `client:`.
|
|
44
|
+
# @return [Parse::Authorization::Resolved]
|
|
145
45
|
def resolve(session_token)
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
user_id = lookup_user_id(session_token.to_s)
|
|
149
|
-
role_names = lookup_role_names(user_id)
|
|
150
|
-
Resolved.new(user_id, role_names)
|
|
46
|
+
context.resolve(session_token)
|
|
151
47
|
end
|
|
152
48
|
|
|
153
|
-
#
|
|
154
|
-
#
|
|
155
|
-
#
|
|
156
|
-
#
|
|
157
|
-
# `user_id` mapping. The `role_names` cache is keyed on
|
|
158
|
-
# `user_id` and is not affected — call {.invalidate_user_roles}
|
|
159
|
-
# to clear that separately.
|
|
160
|
-
# @param session_token [String]
|
|
49
|
+
# @deprecated Use `client.authorization.invalidate(token)`. Note that
|
|
50
|
+
# `Parse::Cache::Invalidation` now does this automatically from the
|
|
51
|
+
# `_Session` `after_logout` trigger, so an application no longer has
|
|
52
|
+
# to remember to call it from its own logout path.
|
|
161
53
|
def invalidate(session_token)
|
|
162
|
-
|
|
163
|
-
Parse::AtlasSearch.session_cache.invalidate(session_token.to_s)
|
|
54
|
+
context.invalidate(session_token)
|
|
164
55
|
end
|
|
165
56
|
|
|
166
|
-
#
|
|
167
|
-
# `_Role.users` mutation that affects this user (role grant /
|
|
168
|
-
# revoke, role-graph reshape).
|
|
169
|
-
# @param user_id [String]
|
|
57
|
+
# @deprecated Use `client.authorization.invalidate_user_roles(id)`.
|
|
170
58
|
def invalidate_user_roles(user_id)
|
|
171
|
-
|
|
172
|
-
Parse::AtlasSearch.role_cache.invalidate(user_id.to_s)
|
|
59
|
+
context.invalidate_user_roles(user_id)
|
|
173
60
|
end
|
|
174
61
|
|
|
175
|
-
#
|
|
176
|
-
# and in startup hooks for processes that fork after warming
|
|
177
|
-
# the cache.
|
|
62
|
+
# @deprecated Use `client.authorization.reset_caches!`.
|
|
178
63
|
def reset_caches!
|
|
179
|
-
|
|
180
|
-
Parse::AtlasSearch.role_cache.clear if Parse::AtlasSearch.role_cache.respond_to?(:clear)
|
|
64
|
+
context.reset_caches!
|
|
181
65
|
end
|
|
182
66
|
|
|
183
67
|
private
|
|
184
68
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
# to `/users/me`. Raises {InvalidSession} on lookup failure;
|
|
188
|
-
# the caller is responsible for refusing the request.
|
|
189
|
-
def lookup_user_id(session_token)
|
|
190
|
-
cache = Parse::AtlasSearch.session_cache
|
|
191
|
-
cached = cache.get(session_token)
|
|
192
|
-
return cached if cached
|
|
193
|
-
|
|
194
|
-
response = begin
|
|
195
|
-
Parse.client.current_user(session_token)
|
|
196
|
-
rescue => e
|
|
197
|
-
raise InvalidSession, "session token lookup failed: #{e.class}: #{e.message}"
|
|
198
|
-
end
|
|
199
|
-
raise InvalidSession, "session token invalid or expired" if response.nil? || response.error?
|
|
200
|
-
|
|
201
|
-
result = response.result
|
|
202
|
-
user_id = result.is_a?(Hash) ? (result["objectId"] || result[:objectId]) : nil
|
|
203
|
-
raise InvalidSession, "session token resolved no user objectId" if user_id.nil? || user_id.to_s.empty?
|
|
204
|
-
|
|
205
|
-
user_id = user_id.to_s
|
|
206
|
-
cache.set(session_token, user_id, ttl: Parse::AtlasSearch.session_cache_ttl)
|
|
207
|
-
user_id
|
|
208
|
-
end
|
|
209
|
-
|
|
210
|
-
# @!visibility private
|
|
211
|
-
# Resolve user_id → Set<role_name> via cache, falling through
|
|
212
|
-
# to {Parse::Role.all_for_user}. Failures degrade silently to
|
|
213
|
-
# an empty set rather than raising — a Parse Server hiccup
|
|
214
|
-
# during the role walk must not turn every search call into a
|
|
215
|
-
# 500, and the worst case is a query that misses some
|
|
216
|
-
# role-restricted documents.
|
|
217
|
-
#
|
|
218
|
-
# ATLAS-7: explicitly re-raise the exceptions that signal
|
|
219
|
-
# attacks or policy denials BEFORE the generic rescue. Without
|
|
220
|
-
# this, a denied-operator probe (DeniedOperator), a timeout
|
|
221
|
-
# exhaustion (ExecutionTimeout), or a CLP denial during role
|
|
222
|
-
# graph traversal would silently downgrade to an empty role
|
|
223
|
-
# set — the caller would then run with public-only perms,
|
|
224
|
-
# missing role-restricted rows but also masking the attack
|
|
225
|
-
# signal from the operator. These exception classes are SDK
|
|
226
|
-
# contracts the caller must surface upward.
|
|
227
|
-
def lookup_role_names(user_id)
|
|
228
|
-
return Set.new if user_id.nil? || user_id.empty?
|
|
229
|
-
|
|
230
|
-
cache = Parse::AtlasSearch.role_cache
|
|
231
|
-
cached = cache.get(user_id)
|
|
232
|
-
return cached if cached.is_a?(Set)
|
|
233
|
-
|
|
234
|
-
pointer = Parse::Pointer.new(Parse::Model::CLASS_USER, user_id)
|
|
235
|
-
names = begin
|
|
236
|
-
Parse::Role.all_for_user(pointer, max_depth: 10)
|
|
237
|
-
rescue Parse::MongoDB::DeniedOperator,
|
|
238
|
-
Parse::MongoDB::ExecutionTimeout,
|
|
239
|
-
Parse::CLPScope::Denied
|
|
240
|
-
# Re-raise: these are attack signals or explicit policy
|
|
241
|
-
# denials and must NOT be swallowed into a fail-open
|
|
242
|
-
# public-only ACL state.
|
|
243
|
-
raise
|
|
244
|
-
rescue
|
|
245
|
-
Set.new
|
|
246
|
-
end
|
|
247
|
-
cache.set(user_id, names, ttl: Parse::AtlasSearch.role_cache_ttl)
|
|
248
|
-
names
|
|
69
|
+
def context
|
|
70
|
+
Parse.client.authorization
|
|
249
71
|
end
|
|
250
72
|
end
|
|
251
73
|
end
|