parse-stack-next 5.6.0 → 5.7.1
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 +489 -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 +219 -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/associations/belongs_to.rb +4 -0
- data/lib/parse/model/associations/collection_proxy.rb +3 -0
- data/lib/parse/model/associations/has_many.rb +4 -0
- 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 +366 -113
- 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 +6 -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 +6 -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 +136 -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 +120 -5
- data/parse-stack-next.gemspec +6 -6
- metadata +10 -1
data/lib/parse/model/file.rb
CHANGED
|
@@ -43,7 +43,6 @@ module Parse
|
|
|
43
43
|
# in logs or a CDN access trail.
|
|
44
44
|
class SignedUrlError < Parse::Error; end
|
|
45
45
|
|
|
46
|
-
|
|
47
46
|
# Regular expression that matches the old legacy Parse hosted file name
|
|
48
47
|
LEGACY_FILE_RX = /^tfss-[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}-/
|
|
49
48
|
# The default attributes in a Parse File hash. Matches the Parse
|
|
@@ -86,7 +85,7 @@ module Parse
|
|
|
86
85
|
# Alibaba Cloud metadata service (public-IP-space but well-known
|
|
87
86
|
# cloud-metadata endpoint that must not be reachable from SDK fetches).
|
|
88
87
|
"100.100.100.200/32",
|
|
89
|
-
"::/128", "::1/128", "fc00::/7", "fe80::/10", "ff00::/8", "::ffff:0:0/96"
|
|
88
|
+
"::/128", "::1/128", "fc00::/7", "fe80::/10", "ff00::/8", "::ffff:0:0/96",
|
|
90
89
|
].map { |c| IPAddr.new(c) }.freeze
|
|
91
90
|
# Restrictive port allowlist for Parse::File URL fetches. By default
|
|
92
91
|
# only the standard HTTP/HTTPS ports are permitted. Operators may
|
|
@@ -166,12 +165,14 @@ module Parse
|
|
|
166
165
|
# @return [Integer] Maximum byte size for a remote URL fetch via
|
|
167
166
|
# `Parse::File.create` / `Parse::File.new(url)`.
|
|
168
167
|
attr_writer :max_remote_size
|
|
168
|
+
|
|
169
169
|
def max_remote_size
|
|
170
170
|
@max_remote_size ||= DEFAULT_MAX_REMOTE_SIZE
|
|
171
171
|
end
|
|
172
172
|
|
|
173
173
|
# @return [Integer] Read/open timeout (seconds) for remote URL fetches.
|
|
174
174
|
attr_writer :remote_timeout
|
|
175
|
+
|
|
175
176
|
def remote_timeout
|
|
176
177
|
@remote_timeout ||= DEFAULT_REMOTE_TIMEOUT
|
|
177
178
|
end
|
|
@@ -182,6 +183,7 @@ module Parse
|
|
|
182
183
|
# ".example.com" matches "files.example.com"). Default: empty (any
|
|
183
184
|
# public host is allowed; private hosts are always denied).
|
|
184
185
|
attr_writer :allowed_remote_hosts
|
|
186
|
+
|
|
185
187
|
def allowed_remote_hosts
|
|
186
188
|
@allowed_remote_hosts ||= []
|
|
187
189
|
end
|
|
@@ -202,6 +204,7 @@ module Parse
|
|
|
202
204
|
# entries via leading "." (e.g. `".cdn.example.com"`) match any
|
|
203
205
|
# subdomain.
|
|
204
206
|
attr_writer :trusted_url_hosts
|
|
207
|
+
|
|
205
208
|
def trusted_url_hosts
|
|
206
209
|
@trusted_url_hosts ||= ["files.parsetfss.com"]
|
|
207
210
|
end
|
|
@@ -226,6 +229,7 @@ module Parse
|
|
|
226
229
|
# hydration `attributes=` — asymmetric writer behavior was an
|
|
227
230
|
# explicit anti-goal of the design.
|
|
228
231
|
attr_writer :signed_url_policy
|
|
232
|
+
|
|
229
233
|
def signed_url_policy
|
|
230
234
|
@signed_url_policy ||= :strip
|
|
231
235
|
end
|
|
@@ -244,12 +248,14 @@ module Parse
|
|
|
244
248
|
# The default is intentionally non-breaking; integrators ready to
|
|
245
249
|
# enforce flip the policy explicitly.
|
|
246
250
|
attr_writer :untrusted_url_policy
|
|
251
|
+
|
|
247
252
|
def untrusted_url_policy
|
|
248
253
|
@untrusted_url_policy ||= :warn
|
|
249
254
|
end
|
|
250
255
|
|
|
251
256
|
# @return [Array<Integer>] Allowed remote ports for URL fetches.
|
|
252
257
|
attr_writer :allowed_remote_ports
|
|
258
|
+
|
|
253
259
|
def allowed_remote_ports
|
|
254
260
|
@allowed_remote_ports ||= DEFAULT_ALLOWED_REMOTE_PORTS.dup
|
|
255
261
|
end
|
|
@@ -543,10 +549,10 @@ module Parse
|
|
|
543
549
|
# "exceeds". Fail fast with a clear message before any DNS/host work.
|
|
544
550
|
max_bytes = coerce_positive_max_bytes(max_bytes)
|
|
545
551
|
uri = begin
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
552
|
+
URI.parse(url_string)
|
|
553
|
+
rescue URI::InvalidURIError => e
|
|
554
|
+
raise ArgumentError, "Invalid URL: #{e.message}"
|
|
555
|
+
end
|
|
550
556
|
unless %w[http https].include?(uri.scheme)
|
|
551
557
|
raise ArgumentError, "Parse::File only supports http(s) URLs (got #{uri.scheme.inspect})"
|
|
552
558
|
end
|
|
@@ -603,8 +609,7 @@ module Parse
|
|
|
603
609
|
# @return [Integer, nil]
|
|
604
610
|
def coerce_positive_max_bytes(max_bytes)
|
|
605
611
|
return nil if max_bytes.nil?
|
|
606
|
-
requested =
|
|
607
|
-
begin
|
|
612
|
+
requested = begin
|
|
608
613
|
Integer(max_bytes)
|
|
609
614
|
rescue ArgumentError, TypeError
|
|
610
615
|
raise ArgumentError, "max_bytes must be a positive integer (got #{max_bytes.inspect})"
|
|
@@ -931,6 +936,7 @@ module Parse
|
|
|
931
936
|
@url = Parse::File.sanitize_hydrated_url(value, fallback: @url, name: @name)
|
|
932
937
|
end
|
|
933
938
|
end
|
|
939
|
+
|
|
934
940
|
private :normalize_and_store_url
|
|
935
941
|
|
|
936
942
|
# @!visibility private
|
|
@@ -953,10 +959,10 @@ module Parse
|
|
|
953
959
|
return raw unless raw.start_with?("http://") || raw.start_with?("https://")
|
|
954
960
|
|
|
955
961
|
uri = begin
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
962
|
+
URI.parse(raw)
|
|
963
|
+
rescue URI::InvalidURIError
|
|
964
|
+
return raw # malformed URL — leave it alone; downstream code already handles
|
|
965
|
+
end
|
|
960
966
|
host = uri.host.to_s.downcase
|
|
961
967
|
return raw if host.empty?
|
|
962
968
|
|
|
@@ -975,7 +981,7 @@ module Parse
|
|
|
975
981
|
when :strip
|
|
976
982
|
warn_untrusted_url_host_once(host, action: "stripped")
|
|
977
983
|
fallback
|
|
978
|
-
else
|
|
984
|
+
else # :warn (default)
|
|
979
985
|
warn_untrusted_url_host_once(host, action: "accepted")
|
|
980
986
|
raw
|
|
981
987
|
end
|
|
@@ -1034,8 +1040,8 @@ module Parse
|
|
|
1034
1040
|
def save(session_token: nil, use_master_key: nil)
|
|
1035
1041
|
unless saved? || @contents.nil? || @name.nil?
|
|
1036
1042
|
opts = {}
|
|
1037
|
-
opts[:session_token]
|
|
1038
|
-
opts[:use_master_key]
|
|
1043
|
+
opts[:session_token] = session_token unless session_token.nil?
|
|
1044
|
+
opts[:use_master_key] = use_master_key unless use_master_key.nil?
|
|
1039
1045
|
response = client.create_file(@name, @contents, @mime_type, **opts)
|
|
1040
1046
|
unless response.error?
|
|
1041
1047
|
result = response.result
|
data/lib/parse/model/geojson.rb
CHANGED
|
@@ -49,8 +49,7 @@ module Parse
|
|
|
49
49
|
end
|
|
50
50
|
|
|
51
51
|
def coordinates=(value)
|
|
52
|
-
coords =
|
|
53
|
-
case value
|
|
52
|
+
coords = case value
|
|
54
53
|
when self.class
|
|
55
54
|
deep_copy_array(value.coordinates)
|
|
56
55
|
when Hash
|
|
@@ -76,6 +75,7 @@ module Parse
|
|
|
76
75
|
def to_geojson
|
|
77
76
|
{ "type" => geojson_type, "coordinates" => deep_copy_array(@coordinates) }
|
|
78
77
|
end
|
|
78
|
+
|
|
79
79
|
alias_method :as_json, :to_geojson
|
|
80
80
|
|
|
81
81
|
# @return [String] the JSON form, suitable for direct shipment to
|
data/lib/parse/model/geopoint.rb
CHANGED
data/lib/parse/model/object.rb
CHANGED
|
@@ -403,11 +403,11 @@ module Parse
|
|
|
403
403
|
# @return [Parse::ACL] the current default ACLs for this class.
|
|
404
404
|
def default_acls
|
|
405
405
|
@default_acls ||= case acl_policy_setting
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
406
|
+
when :public, :owner_else_public then Parse::ACL.everyone
|
|
407
|
+
when :public_read, :owner_but_public_read then Parse::ACL.everyone(true, false)
|
|
408
|
+
when :private, :owner_else_private then Parse::ACL.private
|
|
409
|
+
else Parse::ACL.everyone
|
|
410
|
+
end
|
|
411
411
|
end
|
|
412
412
|
|
|
413
413
|
# A method to set default ACLs to be applied for newly created
|
|
@@ -541,10 +541,10 @@ module Parse
|
|
|
541
541
|
end
|
|
542
542
|
if owner.nil? && policy.to_s.start_with?("owner_")
|
|
543
543
|
fallback = case policy
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
544
|
+
when :owner_else_public then "public R/W"
|
|
545
|
+
when :owner_but_public_read then "public read only"
|
|
546
|
+
else "master-key-only"
|
|
547
|
+
end
|
|
548
548
|
warn "[#{self}] acl_policy #{policy.inspect} declared without `owner:` field; ACL resolution will always use the fallback (#{fallback}). Pass `as:` at construction to override."
|
|
549
549
|
end
|
|
550
550
|
@acl_policy_setting = policy
|
|
@@ -698,7 +698,7 @@ module Parse
|
|
|
698
698
|
class_permissions.set_default_permission(
|
|
699
699
|
public_access: public,
|
|
700
700
|
roles: Array(roles),
|
|
701
|
-
requires_authentication: requires_authentication
|
|
701
|
+
requires_authentication: requires_authentication,
|
|
702
702
|
)
|
|
703
703
|
|
|
704
704
|
# Also explicitly set all operations to ensure they're included
|
|
@@ -789,7 +789,7 @@ module Parse
|
|
|
789
789
|
roles: Array(roles),
|
|
790
790
|
users: Array(users),
|
|
791
791
|
pointer_fields: converted_pointer_fields,
|
|
792
|
-
requires_authentication: requires_authentication
|
|
792
|
+
requires_authentication: requires_authentication,
|
|
793
793
|
)
|
|
794
794
|
end
|
|
795
795
|
|
|
@@ -1043,8 +1043,8 @@ module Parse
|
|
|
1043
1043
|
|
|
1044
1044
|
per_field[local_sym] = {
|
|
1045
1045
|
write: guards_map[local_sym] || :open,
|
|
1046
|
-
read:
|
|
1047
|
-
type:
|
|
1046
|
+
read: hidden_from.empty? ? :open : { hidden_from: hidden_from },
|
|
1047
|
+
type: data_type,
|
|
1048
1048
|
}
|
|
1049
1049
|
end
|
|
1050
1050
|
|
|
@@ -1057,10 +1057,10 @@ module Parse
|
|
|
1057
1057
|
end
|
|
1058
1058
|
|
|
1059
1059
|
{
|
|
1060
|
-
operations:
|
|
1061
|
-
read_user_fields:
|
|
1060
|
+
operations: operations,
|
|
1061
|
+
read_user_fields: perms.respond_to?(:read_user_fields) ? perms.read_user_fields : [],
|
|
1062
1062
|
write_user_fields: perms.respond_to?(:write_user_fields) ? perms.write_user_fields : [],
|
|
1063
|
-
fields:
|
|
1063
|
+
fields: per_field,
|
|
1064
1064
|
}
|
|
1065
1065
|
end
|
|
1066
1066
|
|
|
@@ -1241,8 +1241,7 @@ module Parse
|
|
|
1241
1241
|
# for tests or internal mongo-direct bulk writes). A class may flip
|
|
1242
1242
|
# the per-class default with `vector_visibility :public`; an explicit
|
|
1243
1243
|
# `include_vectors:` in the call always wins over the class default.
|
|
1244
|
-
include_vectors =
|
|
1245
|
-
if opts.key?(:include_vectors)
|
|
1244
|
+
include_vectors = if opts.key?(:include_vectors)
|
|
1246
1245
|
opts[:include_vectors] == true
|
|
1247
1246
|
else
|
|
1248
1247
|
self.class.respond_to?(:vectors_public_by_default?) && self.class.vectors_public_by_default?
|
|
@@ -1338,6 +1337,8 @@ module Parse
|
|
|
1338
1337
|
# for trusted hydration from server JSON; it bypasses the filter.
|
|
1339
1338
|
# @return [Parse::Object] a the corresponding Parse::Object or subclass.
|
|
1340
1339
|
def initialize(opts = {})
|
|
1340
|
+
Parse::Core::Actions.mark_transaction_object_created(self)
|
|
1341
|
+
|
|
1341
1342
|
# Trusted hydration is signalled by the `@_trusted_init` instance
|
|
1342
1343
|
# variable rather than by a `trusted:` keyword argument. Using a
|
|
1343
1344
|
# keyword would break subclasses that override `initialize(*args)`
|
|
@@ -1351,6 +1352,12 @@ module Parse
|
|
|
1351
1352
|
# the trust signal here.
|
|
1352
1353
|
trusted = @_trusted_init == true
|
|
1353
1354
|
@_trusted_init = nil
|
|
1355
|
+
input_hash = opts.is_a?(Hash) ? opts : nil
|
|
1356
|
+
input_had_acl = input_hash && %w[ACL acl].any? do |key|
|
|
1357
|
+
input_hash.key?(key) || input_hash.key?(key.to_sym)
|
|
1358
|
+
end
|
|
1359
|
+
input_had_id = input_hash && (input_hash.key?(Parse::Model::OBJECT_ID) || input_hash.key?(:objectId) ||
|
|
1360
|
+
input_hash.key?(:id) || input_hash.key?(Parse::Model::ID))
|
|
1354
1361
|
acl_owner_override = nil
|
|
1355
1362
|
if opts.is_a?(String) #then it's the objectId
|
|
1356
1363
|
@id = opts.to_s
|
|
@@ -1398,6 +1405,31 @@ module Parse
|
|
|
1398
1405
|
@_acl_pristine = !acl_was_user_supplied
|
|
1399
1406
|
@_acl_owner_override = acl_owner_override
|
|
1400
1407
|
|
|
1408
|
+
# Record where our ACL knowledge came from. `acl.nil?` alone is not
|
|
1409
|
+
# enough: it can mean a genuinely ACL-less (therefore public) server
|
|
1410
|
+
# row, but it also describes an id-only pointer or a selective response
|
|
1411
|
+
# that never fetched ACL. Conversely, a custom class may stamp its local
|
|
1412
|
+
# default ACL while hydrating a full server row whose ACL was absent.
|
|
1413
|
+
# Access inspection uses this evidence to distinguish those cases.
|
|
1414
|
+
@_authorization_acl_state = if trusted
|
|
1415
|
+
if has_selective_keys? && !input_had_acl
|
|
1416
|
+
:unknown
|
|
1417
|
+
elsif input_had_acl
|
|
1418
|
+
self.acl.nil? ? :absent : :present
|
|
1419
|
+
else
|
|
1420
|
+
:absent
|
|
1421
|
+
end
|
|
1422
|
+
elsif opts.is_a?(String) || input_had_id
|
|
1423
|
+
:unknown
|
|
1424
|
+
elsif input_had_acl
|
|
1425
|
+
self.acl.nil? ? :unknown : :local
|
|
1426
|
+
elsif self.class.builtin_acl_default_active?
|
|
1427
|
+
:unknown
|
|
1428
|
+
else
|
|
1429
|
+
:local
|
|
1430
|
+
end
|
|
1431
|
+
@_authorization_acl_tracking_ready = true
|
|
1432
|
+
|
|
1401
1433
|
# One-time per-class permissive-default warning. Fires only when the
|
|
1402
1434
|
# effective policy is :public or :owner_else_public.
|
|
1403
1435
|
self.class._warn_permissive_acl_default_once
|
|
@@ -1617,6 +1649,39 @@ module Parse
|
|
|
1617
1649
|
@_fetched_keys.include?(key) || (remote_key && @_fetched_keys.include?(remote_key))
|
|
1618
1650
|
end
|
|
1619
1651
|
|
|
1652
|
+
# Describes whether the in-memory ACL is authoritative enough for local
|
|
1653
|
+
# access inspection. `:absent` means a full trusted server response omitted
|
|
1654
|
+
# ACL (Parse Server's public default); `:present` and `:local` have a usable
|
|
1655
|
+
# ACL value; `:unknown` covers pointers and incomplete hydration.
|
|
1656
|
+
#
|
|
1657
|
+
# @return [Symbol] `:present`, `:absent`, `:local`, or `:unknown`.
|
|
1658
|
+
# @api private
|
|
1659
|
+
def authorization_acl_state
|
|
1660
|
+
@_authorization_acl_state || :unknown
|
|
1661
|
+
end
|
|
1662
|
+
|
|
1663
|
+
# Update ACL evidence after `fetch!` applies a server response to an
|
|
1664
|
+
# existing instance. Save/update responses are deliberately excluded: they
|
|
1665
|
+
# are deltas and omission there says nothing about the stored ACL.
|
|
1666
|
+
# @param attributes [Hash] trusted server response.
|
|
1667
|
+
# @param partial [Boolean] whether a selective key fetch produced it.
|
|
1668
|
+
# @api private
|
|
1669
|
+
def record_authorization_hydration!(attributes, partial: false)
|
|
1670
|
+
return unless attributes.is_a?(Hash)
|
|
1671
|
+
has_acl = %w[ACL acl].any? do |key|
|
|
1672
|
+
attributes.key?(key) || attributes.key?(key.to_sym)
|
|
1673
|
+
end
|
|
1674
|
+
@_authorization_acl_state = if has_acl
|
|
1675
|
+
self.acl.nil? ? :absent : :present
|
|
1676
|
+
elsif partial
|
|
1677
|
+
:unknown
|
|
1678
|
+
else
|
|
1679
|
+
:absent
|
|
1680
|
+
end
|
|
1681
|
+
@_authorization_acl_tracking_ready = true
|
|
1682
|
+
@_authorization_acl_state
|
|
1683
|
+
end
|
|
1684
|
+
|
|
1620
1685
|
# Returns the nested fetched keys map for building nested objects.
|
|
1621
1686
|
# @return [Hash] map of field names to their fetched keys
|
|
1622
1687
|
def nested_fetched_keys
|
|
@@ -1884,48 +1949,48 @@ module Parse
|
|
|
1884
1949
|
owner = @_acl_owner_override if defined?(@_acl_owner_override)
|
|
1885
1950
|
if owner.nil? && (field = self.class.acl_owner_field)
|
|
1886
1951
|
owner = if field == :self
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1952
|
+
# Self-referential ownership (Parse::User only — enforced at
|
|
1953
|
+
# declaration time). Pre-generate a Parse-compatible objectId
|
|
1954
|
+
# client-side so the ACL grant can reference the record's own
|
|
1955
|
+
# id in the same POST body that creates it. Skipped when the id
|
|
1956
|
+
# is already set (e.g. when re-saving an existing user, or when
|
|
1957
|
+
# parse_reference precompute already ran).
|
|
1958
|
+
@id = Parse::Core::ParseReference.generate_object_id if @id.blank?
|
|
1959
|
+
@id
|
|
1960
|
+
elsif respond_to?(field)
|
|
1961
|
+
send(field)
|
|
1962
|
+
end
|
|
1898
1963
|
end
|
|
1899
1964
|
owner_id = _resolve_acl_owner_id(owner)
|
|
1900
1965
|
|
|
1901
1966
|
target_acl = case policy
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1967
|
+
when :public
|
|
1968
|
+
Parse::ACL.everyone(true, true)
|
|
1969
|
+
when :public_read
|
|
1970
|
+
Parse::ACL.everyone(true, false)
|
|
1971
|
+
when :private
|
|
1972
|
+
Parse::ACL.private
|
|
1973
|
+
when :owner_else_public
|
|
1974
|
+
if owner_id
|
|
1975
|
+
acl = Parse::ACL.new
|
|
1976
|
+
acl.apply(owner_id, true, true)
|
|
1977
|
+
acl
|
|
1978
|
+
else
|
|
1979
|
+
Parse::ACL.everyone(true, true)
|
|
1980
|
+
end
|
|
1981
|
+
when :owner_else_private
|
|
1982
|
+
if owner_id
|
|
1983
|
+
acl = Parse::ACL.new
|
|
1984
|
+
acl.apply(owner_id, true, true)
|
|
1985
|
+
acl
|
|
1986
|
+
else
|
|
1987
|
+
Parse::ACL.private
|
|
1988
|
+
end
|
|
1989
|
+
when :owner_but_public_read
|
|
1990
|
+
acl = Parse::ACL.everyone(true, false)
|
|
1991
|
+
acl.apply(owner_id, true, true) if owner_id
|
|
1992
|
+
acl
|
|
1993
|
+
end
|
|
1929
1994
|
|
|
1930
1995
|
# Only re-stamp if the resolved ACL differs from the init-time stamp;
|
|
1931
1996
|
# this avoids an unnecessary dirty mark on the acl field for `:public`
|
|
@@ -1979,12 +2044,17 @@ module Parse
|
|
|
1979
2044
|
# caller intent to override.
|
|
1980
2045
|
# @api private
|
|
1981
2046
|
def acl_will_change!
|
|
2047
|
+
_capture_transaction_state!
|
|
2048
|
+
|
|
1982
2049
|
# Only capture snapshot on the first change (before any modifications)
|
|
1983
2050
|
unless defined?(@_acl_snapshot_before_change) && @_acl_snapshot_before_change
|
|
1984
2051
|
# Deep copy the ACL by creating a new one from its JSON representation
|
|
1985
2052
|
@_acl_snapshot_before_change = @acl ? Parse::ACL.new(@acl.as_json) : Parse::ACL.new
|
|
1986
2053
|
end
|
|
1987
2054
|
@_acl_pristine = false if defined?(@_acl_pristine)
|
|
2055
|
+
if defined?(@_authorization_acl_tracking_ready) && @_authorization_acl_tracking_ready
|
|
2056
|
+
@_authorization_acl_state = :local
|
|
2057
|
+
end
|
|
1988
2058
|
super
|
|
1989
2059
|
end
|
|
1990
2060
|
|
|
@@ -2156,16 +2226,16 @@ class Array
|
|
|
2156
2226
|
next m if m.is_a?(Parse::Pointer)
|
|
2157
2227
|
if m.is_a?(Hash)
|
|
2158
2228
|
resolved = if className
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2229
|
+
# Caller knows the type; warn on mismatch but always
|
|
2230
|
+
# use the declared className.
|
|
2231
|
+
incoming = m[f] || m[:className]
|
|
2232
|
+
if incoming && !Parse::Model.same_parse_class?(incoming, className)
|
|
2233
|
+
warn "[Parse::Array#parse_objects] expected className=#{className.inspect}, ignoring incoming className=#{incoming.inspect}"
|
|
2234
|
+
end
|
|
2235
|
+
className
|
|
2236
|
+
else
|
|
2237
|
+
m[f] || m[:className]
|
|
2238
|
+
end
|
|
2169
2239
|
next Parse::Object.build(m, resolved) if resolved
|
|
2170
2240
|
end
|
|
2171
2241
|
nil
|
data/lib/parse/model/pointer.rb
CHANGED
|
@@ -118,8 +118,10 @@ module Parse
|
|
|
118
118
|
# nil unless the instance came from the corresponding search path.
|
|
119
119
|
# @return [Float, nil]
|
|
120
120
|
def vector_score; @_vector_score; end
|
|
121
|
+
|
|
121
122
|
# @return [Float, nil]
|
|
122
123
|
def search_score; @_search_score; end
|
|
124
|
+
|
|
123
125
|
# @return [Hash, nil]
|
|
124
126
|
def search_highlights; @_search_highlights; end
|
|
125
127
|
|
data/lib/parse/model/polygon.rb
CHANGED
|
@@ -99,8 +99,7 @@ module Parse
|
|
|
99
99
|
end
|
|
100
100
|
outer = rings.first
|
|
101
101
|
pairs = outer.map do |(lng, lat)|
|
|
102
|
-
raise ArgumentError, "[Parse::Polygon] GeoJSON ring entries must be [lng, lat] numeric pairs."
|
|
103
|
-
unless lng.is_a?(Numeric) && lat.is_a?(Numeric)
|
|
102
|
+
raise ArgumentError, "[Parse::Polygon] GeoJSON ring entries must be [lng, lat] numeric pairs." unless lng.is_a?(Numeric) && lat.is_a?(Numeric)
|
|
104
103
|
[lat.to_f, lng.to_f]
|
|
105
104
|
end
|
|
106
105
|
new(pairs)
|
|
@@ -120,8 +119,7 @@ module Parse
|
|
|
120
119
|
# - a Hash with a `coordinates` key (the Parse REST wire shape)
|
|
121
120
|
# - another {Parse::Polygon}
|
|
122
121
|
def coordinates=(value)
|
|
123
|
-
coords =
|
|
124
|
-
case value
|
|
122
|
+
coords = case value
|
|
125
123
|
when Parse::Polygon
|
|
126
124
|
# Duplicate so external mutation of the source doesn't leak in.
|
|
127
125
|
value.coordinates.map { |pair| pair.dup }
|
|
@@ -270,8 +268,7 @@ module Parse
|
|
|
270
268
|
# @param point [Parse::GeoPoint, Array<Numeric>] the point to test.
|
|
271
269
|
# @return [Boolean]
|
|
272
270
|
def contains_point?(point)
|
|
273
|
-
lat, lng =
|
|
274
|
-
case point
|
|
271
|
+
lat, lng = case point
|
|
275
272
|
when Parse::GeoPoint then [point.latitude, point.longitude]
|
|
276
273
|
when Array then [point[0].to_f, point[1].to_f]
|
|
277
274
|
else
|
data/lib/parse/model/push.rb
CHANGED
|
@@ -963,11 +963,11 @@ module Parse
|
|
|
963
963
|
if device_type.present? && !SUPPORTED_PUSH_DEVICE_TYPES.include?(device_type)
|
|
964
964
|
if UNSUPPORTED_PUSH_DEVICE_TYPES.include?(device_type)
|
|
965
965
|
warn "[Parse::Push] Warning: device_type '#{device_type}' may not be supported for push notifications. " \
|
|
966
|
-
"Supported types: #{SUPPORTED_PUSH_DEVICE_TYPES.join(
|
|
966
|
+
"Supported types: #{SUPPORTED_PUSH_DEVICE_TYPES.join(", ")}"
|
|
967
967
|
else
|
|
968
968
|
warn "[Parse::Push] Warning: unknown device_type '#{device_type}' for installation #{installation_id}. " \
|
|
969
969
|
"This device type may not receive push notifications. " \
|
|
970
|
-
"Supported types: #{SUPPORTED_PUSH_DEVICE_TYPES.join(
|
|
970
|
+
"Supported types: #{SUPPORTED_PUSH_DEVICE_TYPES.join(", ")}"
|
|
971
971
|
end
|
|
972
972
|
end
|
|
973
973
|
end
|
data/lib/parse/model/vector.rb
CHANGED
|
@@ -57,6 +57,7 @@ module Parse
|
|
|
57
57
|
def dimensions
|
|
58
58
|
@values.length
|
|
59
59
|
end
|
|
60
|
+
|
|
60
61
|
alias_method :length, :dimensions
|
|
61
62
|
alias_method :size, :dimensions
|
|
62
63
|
|
|
@@ -84,10 +85,11 @@ module Parse
|
|
|
84
85
|
def ==(other)
|
|
85
86
|
case other
|
|
86
87
|
when Parse::Vector then @values == other.values
|
|
87
|
-
when Array
|
|
88
|
+
when Array then @values == other
|
|
88
89
|
else false
|
|
89
90
|
end
|
|
90
91
|
end
|
|
92
|
+
|
|
91
93
|
alias_method :eql?, :==
|
|
92
94
|
|
|
93
95
|
def hash
|