parse-stack-next 5.5.4 → 5.5.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +8 -6
- data/README.md +26 -13
- data/bin/parse-console +9 -1
- data/docs/TEST_SERVER.md +115 -238
- data/docs/mcp_guide.md +1 -1
- data/docs/mongodb_index_optimization_guide.md +3 -2
- data/docs/usage_guide.md +1 -1
- data/docs/yard-template/default/fulldoc/html/css/common.css +52 -9
- data/docs/yard-template/default/fulldoc/html/css/full_list.css +40 -13
- data/lib/parse/agent/constraint_translator.rb +18 -18
- data/lib/parse/agent/errors.rb +29 -7
- data/lib/parse/agent/metadata_dsl.rb +6 -6
- data/lib/parse/agent/tools.rb +250 -59
- data/lib/parse/agent.rb +42 -30
- data/lib/parse/api/aggregate.rb +3 -3
- data/lib/parse/api/cloud_functions.rb +19 -10
- data/lib/parse/api/objects.rb +8 -8
- data/lib/parse/api/users.rb +9 -9
- data/lib/parse/atlas_search/session.rb +34 -34
- data/lib/parse/atlas_search.rb +243 -110
- data/lib/parse/client/body_builder.rb +10 -10
- data/lib/parse/client/logging.rb +5 -2
- data/lib/parse/client/profiling.rb +5 -2
- data/lib/parse/client/protocol.rb +1 -1
- data/lib/parse/client/url_redaction.rb +94 -0
- data/lib/parse/client.rb +43 -28
- data/lib/parse/embeddings/image_fetch.rb +6 -1
- data/lib/parse/embeddings/voyage.rb +16 -17
- data/lib/parse/live_query/client.rb +7 -7
- data/lib/parse/live_query/subscription.rb +1 -1
- data/lib/parse/lock.rb +1 -1
- data/lib/parse/lock_backend.rb +118 -2
- data/lib/parse/model/acl.rb +24 -24
- data/lib/parse/model/classes/job_schedule.rb +8 -8
- data/lib/parse/model/classes/job_status.rb +9 -9
- data/lib/parse/model/classes/role.rb +49 -49
- data/lib/parse/model/classes/session.rb +2 -2
- data/lib/parse/model/classes/user.rb +66 -66
- data/lib/parse/model/core/builder.rb +7 -7
- data/lib/parse/model/core/create_lock.rb +1 -1
- data/lib/parse/model/core/properties.rb +4 -4
- data/lib/parse/model/file.rb +57 -16
- data/lib/parse/model/model.rb +19 -19
- data/lib/parse/model/object.rb +38 -38
- data/lib/parse/model/pointer.rb +4 -4
- data/lib/parse/model/push.rb +5 -5
- data/lib/parse/mongodb.rb +84 -26
- data/lib/parse/pipeline_security.rb +2 -2
- data/lib/parse/query/constraints.rb +38 -38
- data/lib/parse/query.rb +151 -75
- data/lib/parse/retrieval/reranker/cohere.rb +30 -0
- data/lib/parse/schema.rb +1 -1
- data/lib/parse/stack/version.rb +1 -1
- data/lib/parse/stack.rb +23 -10
- data/lib/parse/two_factor_auth/user_extension.rb +25 -25
- data/lib/parse/webhooks/payload.rb +35 -35
- data/lib/parse/webhooks/registration.rb +2 -2
- data/lib/parse/webhooks/replay_protection.rb +16 -16
- data/lib/parse/webhooks.rb +11 -11
- data/parse-stack-next.gemspec +19 -1
- metadata +2 -38
- data/.bundle/config +0 -5
- data/.env.sample +0 -138
- data/.env.test +0 -10
- data/.github/ISSUE_TEMPLATE/bug_report.yml +0 -105
- data/.github/ISSUE_TEMPLATE/feature_request.yml +0 -67
- data/.github/dependabot.yml +0 -13
- data/.github/workflows/codeql.yml +0 -44
- data/.github/workflows/docs.yml +0 -39
- data/.github/workflows/release.yml +0 -43
- data/.github/workflows/ruby.yml +0 -38
- data/.gitignore +0 -56
- data/.ruby-version +0 -1
- data/.solargraph.yml +0 -22
- data/.vscode/settings.json +0 -3
- data/.yardopts +0 -19
- data/Gemfile +0 -43
- data/Gemfile.lock +0 -198
- data/Makefile +0 -63
- data/Rakefile +0 -825
- data/config/parse-config.json +0 -12
- data/scripts/debug-ips.js +0 -35
- data/scripts/docker/Dockerfile.parse +0 -17
- data/scripts/docker/atlas-init.js +0 -284
- data/scripts/docker/docker-compose.atlas.yml +0 -80
- data/scripts/docker/docker-compose.test.yml +0 -159
- data/scripts/docker/docker-compose.verifyemail.yml +0 -4
- data/scripts/docker/mongo-init.js +0 -21
- data/scripts/docker/preflight.sh +0 -76
- data/scripts/eval_mcp_with_lm_studio.rb +0 -274
- data/scripts/start-parse.sh +0 -154
- data/scripts/start_mcp_server.rb +0 -78
- data/scripts/test_server_connection.rb +0 -82
- data/scripts/vector_prototype/create_vector_index.js +0 -105
- data/scripts/vector_prototype/fetch_embeddings.py +0 -241
- data/scripts/vector_prototype/fixture_manifest.json +0 -9
- data/scripts/vector_prototype/query_prototype.rb +0 -84
- data/scripts/vector_prototype/run.sh +0 -34
data/lib/parse/agent.rb
CHANGED
|
@@ -739,7 +739,7 @@ module Parse
|
|
|
739
739
|
|
|
740
740
|
# Built-in mutation tools that route through session-token REST and
|
|
741
741
|
# are therefore enforceable by Parse Server's native ACL/CLP. Gated
|
|
742
|
-
# additionally by the per-agent
|
|
742
|
+
# additionally by the per-agent `allow_mutations:` kwarg in client
|
|
743
743
|
# mode (default false) and by the existing process-level env vars
|
|
744
744
|
# (PARSE_AGENT_ALLOW_WRITE_TOOLS + PARSE_AGENT_ALLOW_RAW_CRUD).
|
|
745
745
|
CLIENT_SAFE_MUTATION_TOOLS = %i[
|
|
@@ -806,7 +806,7 @@ module Parse
|
|
|
806
806
|
# configure this on load.
|
|
807
807
|
attr_accessor :allowed_llm_endpoints
|
|
808
808
|
|
|
809
|
-
# Validate
|
|
809
|
+
# Validate `endpoint` against {allowed_llm_endpoints}. No-op
|
|
810
810
|
# when the allowlist is unset. Raises `ArgumentError` on miss so
|
|
811
811
|
# the caller's `ask` / `ask_streaming` invocation fails before
|
|
812
812
|
# any HTTP request is sent.
|
|
@@ -887,29 +887,29 @@ module Parse
|
|
|
887
887
|
attr_reader :session_token
|
|
888
888
|
|
|
889
889
|
# @return [Parse::User, Parse::Pointer, nil] the User identity the
|
|
890
|
-
# agent was constructed with via
|
|
890
|
+
# agent was constructed with via `acl_user:`. The agent's
|
|
891
891
|
# {#acl_scope} resolves this user's permission_strings
|
|
892
892
|
# (objectId + roles, expanded) at construction. nil for
|
|
893
893
|
# session_token / acl_role / master-key construction.
|
|
894
894
|
attr_reader :acl_user_scope
|
|
895
895
|
|
|
896
896
|
# @return [Parse::Role, String, Symbol, nil] the Role identity the
|
|
897
|
-
# agent was constructed with via
|
|
897
|
+
# agent was constructed with via `acl_role:`. Used for
|
|
898
898
|
# service-account-style scoping ("see as if a user with this
|
|
899
899
|
# role were asking") without a specific user. nil for
|
|
900
900
|
# session_token / acl_user / master-key construction.
|
|
901
901
|
attr_reader :acl_role_scope
|
|
902
902
|
|
|
903
903
|
# @return [Parse::ACLScope::Resolution, nil] the resolved ACL scope
|
|
904
|
-
# for this agent. Frozen at construction.
|
|
904
|
+
# for this agent. Frozen at construction. `nil` means master-key
|
|
905
905
|
# posture — the agent runs every tool call with the application
|
|
906
906
|
# master key, bypassing per-row ACL/CLP enforcement. Non-nil
|
|
907
|
-
# carries a
|
|
907
|
+
# carries a `permission_strings` allow-set that built-in tools
|
|
908
908
|
# forward to mongo-direct / Atlas Search via {#acl_scope_kwargs}.
|
|
909
909
|
attr_reader :acl_scope
|
|
910
910
|
|
|
911
911
|
# @return [Boolean] whether this agent may run Atlas Search tools
|
|
912
|
-
# in master-key-equivalent mode when no
|
|
912
|
+
# in master-key-equivalent mode when no `session_token` is set.
|
|
913
913
|
# See {#master_atlas?} for the gate semantics applied by the
|
|
914
914
|
# Atlas Search tool handlers in {Parse::Agent::Tools}.
|
|
915
915
|
attr_reader :master_atlas
|
|
@@ -921,7 +921,7 @@ module Parse
|
|
|
921
921
|
# Parse::Client has no master_key). In client mode the dispatchable
|
|
922
922
|
# tool set is restricted to {CLIENT_SAFE_READ_TOOLS},
|
|
923
923
|
# {CLIENT_SAFE_MUTATION_TOOLS} (gated on {#allow_mutations?}), and
|
|
924
|
-
# any registered tool declared
|
|
924
|
+
# any registered tool declared `client_safe: true`.
|
|
925
925
|
def client_mode?
|
|
926
926
|
@client_mode == true
|
|
927
927
|
end
|
|
@@ -930,7 +930,7 @@ module Parse
|
|
|
930
930
|
# tools (create_object/update_object/delete_object). Layered with
|
|
931
931
|
# the process-level PARSE_AGENT_ALLOW_WRITE_TOOLS +
|
|
932
932
|
# PARSE_AGENT_ALLOW_RAW_CRUD env vars (all three must be true).
|
|
933
|
-
# Default:
|
|
933
|
+
# Default: `false` in client mode, `true` in master-key mode.
|
|
934
934
|
def allow_mutations?
|
|
935
935
|
@allow_mutations == true
|
|
936
936
|
end
|
|
@@ -1050,10 +1050,10 @@ module Parse
|
|
|
1050
1050
|
tok.cancelled?
|
|
1051
1051
|
end
|
|
1052
1052
|
|
|
1053
|
-
# @return [Boolean]
|
|
1054
|
-
# constructed with
|
|
1053
|
+
# @return [Boolean] `true` when this agent has been explicitly
|
|
1054
|
+
# constructed with `master_atlas: true`. Used by the Atlas
|
|
1055
1055
|
# Search tool handlers in {Parse::Agent::Tools} to gate calls
|
|
1056
|
-
# that would otherwise refuse because no
|
|
1056
|
+
# that would otherwise refuse because no `session_token` is
|
|
1057
1057
|
# available — see {Parse::AtlasSearch} for the reasoning behind
|
|
1058
1058
|
# the dedicated opt-in (Atlas Search bypasses Parse Server
|
|
1059
1059
|
# entirely, so the agent's normal master-key posture is not a
|
|
@@ -1098,7 +1098,7 @@ module Parse
|
|
|
1098
1098
|
# The agent's resolved identity claim set — the
|
|
1099
1099
|
# `["*", userObjectId, "role:Foo", ...]` array that gets matched
|
|
1100
1100
|
# against a document's `_rperm` (for read) or `_wperm` (for
|
|
1101
|
-
# write). Returns
|
|
1101
|
+
# write). Returns `nil` for master-key posture (unrestricted reach
|
|
1102
1102
|
# — no filtering applied).
|
|
1103
1103
|
#
|
|
1104
1104
|
# The set is identity-based and identical for read and write
|
|
@@ -1113,7 +1113,7 @@ module Parse
|
|
|
1113
1113
|
# A ready-to-prepend `$match` stage filtering an aggregation
|
|
1114
1114
|
# pipeline to documents the agent's scope is allowed to READ.
|
|
1115
1115
|
# Mirrors what the built-in read tools inject automatically via
|
|
1116
|
-
# {Parse::ACLScope.match_stage_for}. Returns
|
|
1116
|
+
# {Parse::ACLScope.match_stage_for}. Returns `nil` for master-key
|
|
1117
1117
|
# posture.
|
|
1118
1118
|
#
|
|
1119
1119
|
# @return [Hash, nil]
|
|
@@ -1129,7 +1129,7 @@ module Parse
|
|
|
1129
1129
|
# perform writes (e.g., a custom `agent_method` that batch-updates
|
|
1130
1130
|
# rows under the agent's scope) prepend this stage themselves so
|
|
1131
1131
|
# the update only sees rows whose `_wperm` includes the agent's
|
|
1132
|
-
# identity. Returns
|
|
1132
|
+
# identity. Returns `nil` for master-key posture.
|
|
1133
1133
|
#
|
|
1134
1134
|
# @return [Hash, nil]
|
|
1135
1135
|
def acl_write_match_stage
|
|
@@ -1138,11 +1138,11 @@ module Parse
|
|
|
1138
1138
|
{ "$match" => Parse::ACL.write_predicate(perms) }
|
|
1139
1139
|
end
|
|
1140
1140
|
|
|
1141
|
-
#
|
|
1141
|
+
# `true` when the agent carries any non-master-key scope
|
|
1142
1142
|
# (session_token, acl_user, or acl_role). Use this when deciding
|
|
1143
1143
|
# whether a Parse Server endpoint that DOES NOT enforce ACL
|
|
1144
1144
|
# (notably the REST `aggregate` endpoint) is safe to route through:
|
|
1145
|
-
# any
|
|
1145
|
+
# any `true` here means the REST path would silently bypass the
|
|
1146
1146
|
# agent's declared scope, so the tool must use the mongo-direct
|
|
1147
1147
|
# path (which runs Parse::ACLScope's `_rperm` injection).
|
|
1148
1148
|
#
|
|
@@ -1151,11 +1151,11 @@ module Parse
|
|
|
1151
1151
|
!@acl_scope.nil?
|
|
1152
1152
|
end
|
|
1153
1153
|
|
|
1154
|
-
#
|
|
1154
|
+
# `true` when the agent's ACL scope cannot be honored by Parse
|
|
1155
1155
|
# Server's REST surface at all (no "act as role" affordance) and
|
|
1156
1156
|
# the SDK must auto-route every built-in tool through mongo-direct
|
|
1157
1157
|
# (Parse::MongoDB.aggregate / Parse::Query#results_direct). Fires
|
|
1158
|
-
# ONLY for
|
|
1158
|
+
# ONLY for `acl_user:` and `acl_role:` scopes; session_token
|
|
1159
1159
|
# agents can keep the REST find_objects path because Parse Server
|
|
1160
1160
|
# validates the token natively for find / get endpoints.
|
|
1161
1161
|
#
|
|
@@ -1170,7 +1170,7 @@ module Parse
|
|
|
1170
1170
|
!(@acl_user_scope.nil? && @acl_role_scope.nil?)
|
|
1171
1171
|
end
|
|
1172
1172
|
|
|
1173
|
-
#
|
|
1173
|
+
# `true` when an AGGREGATE operation for this agent MUST run through
|
|
1174
1174
|
# the SDK's mongo-direct path (Parse::MongoDB.aggregate) instead of
|
|
1175
1175
|
# Parse Server's REST aggregate endpoint. The REST aggregate endpoint
|
|
1176
1176
|
# enforces NEITHER ACL, CLP, nor protectedFields and requires the
|
|
@@ -1183,7 +1183,7 @@ module Parse
|
|
|
1183
1183
|
# REST find/get DOES enforce a session token — but REST aggregate does
|
|
1184
1184
|
# not). Fires for acl_user / acl_role scopes AND for any session-token
|
|
1185
1185
|
# identity, including a runtime-impersonated agent whose @acl_scope has
|
|
1186
|
-
# been cleared. Master-key agents return
|
|
1186
|
+
# been cleared. Master-key agents return `false`.
|
|
1187
1187
|
#
|
|
1188
1188
|
# @return [Boolean]
|
|
1189
1189
|
def requires_mongo_direct?
|
|
@@ -1766,7 +1766,7 @@ module Parse
|
|
|
1766
1766
|
# false default would silently disable
|
|
1767
1767
|
# writes for every existing master-key
|
|
1768
1768
|
# agent).
|
|
1769
|
-
# When
|
|
1769
|
+
# When `parent:` is supplied, the child cannot widen the parent's
|
|
1770
1770
|
# gate: if parent.allow_mutations? is false, child must also be
|
|
1771
1771
|
# false. Default-on-nil inherits the parent's value verbatim so
|
|
1772
1772
|
# the safe path (omit kwarg) is trivially correct.
|
|
@@ -2543,6 +2543,18 @@ module Parse
|
|
|
2543
2543
|
details = e.respond_to?(:to_details) ? e.to_details : {}
|
|
2544
2544
|
response = error_response(e.message, error_code: :access_denied, details: details.any? ? details : nil)
|
|
2545
2545
|
|
|
2546
|
+
# Recognized-but-unimplemented tool (the built-in write/admin
|
|
2547
|
+
# CRUD tools ship without a handler). Surface the actionable
|
|
2548
|
+
# message rather than collapsing to the opaque internal-error
|
|
2549
|
+
# path, and tag a distinct :not_implemented code so callers can
|
|
2550
|
+
# branch on "tool doesn't exist here" vs a real failure.
|
|
2551
|
+
rescue Parse::Agent::NotImplemented => e
|
|
2552
|
+
trigger_callbacks(:on_error, e, { tool: tool_name, args: kwargs })
|
|
2553
|
+
payload[:success] = false
|
|
2554
|
+
payload[:error_class] = e.class.name
|
|
2555
|
+
payload[:error_code] = :not_implemented
|
|
2556
|
+
response = error_response(e.message, error_code: :not_implemented)
|
|
2557
|
+
|
|
2546
2558
|
# Validation errors (e.g. from registered tool handlers or get_objects)
|
|
2547
2559
|
rescue Parse::Agent::ValidationError => e
|
|
2548
2560
|
trigger_callbacks(:on_error, e, { tool: tool_name, args: kwargs })
|
|
@@ -3012,7 +3024,7 @@ module Parse
|
|
|
3012
3024
|
# {#import_conversation}.
|
|
3013
3025
|
IMPORT_MAX_CONTENT_LEN = 32 * 1024
|
|
3014
3026
|
# @!visibility private
|
|
3015
|
-
# Roles permitted on imported conversation entries.
|
|
3027
|
+
# Roles permitted on imported conversation entries. `system` and `tool`
|
|
3016
3028
|
# are explicitly excluded — without this guard, an attacker who
|
|
3017
3029
|
# controls a saved transcript can plant fabricated tool results
|
|
3018
3030
|
# (which the next LLM turn treats as authentic prior retrievals) or
|
|
@@ -3024,10 +3036,10 @@ module Parse
|
|
|
3024
3036
|
# conversation history and token usage. Permissions are NEVER
|
|
3025
3037
|
# restored from the export — they belong to the Agent constructor.
|
|
3026
3038
|
#
|
|
3027
|
-
# Only
|
|
3028
|
-
# String/nil
|
|
3039
|
+
# Only `role: "user"` and `role: "assistant"` entries with
|
|
3040
|
+
# String/nil `content` are accepted. Disallowed roles, oversized
|
|
3029
3041
|
# content, or message counts above {IMPORT_MAX_MESSAGES} raise
|
|
3030
|
-
#
|
|
3042
|
+
# `ArgumentError`; a malformed JSON payload returns `false` with a
|
|
3031
3043
|
# warning.
|
|
3032
3044
|
#
|
|
3033
3045
|
# @param json_string [String] JSON string from {#export_conversation}.
|
|
@@ -3761,11 +3773,11 @@ module Parse
|
|
|
3761
3773
|
|
|
3762
3774
|
# Get the current authentication context.
|
|
3763
3775
|
#
|
|
3764
|
-
# @return [Hash]
|
|
3765
|
-
#
|
|
3766
|
-
# ONLY for
|
|
3776
|
+
# @return [Hash] `:type` is one of `:session_token`, `:acl_user`,
|
|
3777
|
+
# `:acl_role`, or `:master_key`. `:using_master_key` is `true`
|
|
3778
|
+
# ONLY for `:master_key`; scoped agents (session_token / acl_user /
|
|
3767
3779
|
# acl_role) run with explicit ACL enforcement and never set the
|
|
3768
|
-
# master-key flag. The
|
|
3780
|
+
# master-key flag. The `:identity` slot carries a posture-specific
|
|
3769
3781
|
# identifier (user_id for session/acl_user, role name for
|
|
3770
3782
|
# acl_role, nil for master_key) so the AUDIT log can attribute
|
|
3771
3783
|
# tool calls accurately.
|
data/lib/parse/api/aggregate.rb
CHANGED
|
@@ -25,7 +25,7 @@ module Parse
|
|
|
25
25
|
module ClassMethods
|
|
26
26
|
# Get the aggregate API path for this class.
|
|
27
27
|
#
|
|
28
|
-
#
|
|
28
|
+
# `className` is validated to prevent path-smuggling — aggregate
|
|
29
29
|
# endpoints are master-key-only on Parse Server, so any traversal
|
|
30
30
|
# here pivots a master-key request to a different endpoint.
|
|
31
31
|
#
|
|
@@ -66,9 +66,9 @@ module Parse
|
|
|
66
66
|
# @param pipeline [Array] the MongoDB aggregation pipeline stages.
|
|
67
67
|
# @param opts [Hash] additional options to pass to the {Parse::Client} request.
|
|
68
68
|
# @param headers [Hash] additional HTTP headers to send with the request.
|
|
69
|
-
# @param raw_values [Boolean] when true, adds
|
|
69
|
+
# @param raw_values [Boolean] when true, adds `rawValues: true` to the request
|
|
70
70
|
# so Parse Server returns un-decoded field values (PS 9.9.0+, #10438).
|
|
71
|
-
# @param raw_field_names [Boolean] when true, adds
|
|
71
|
+
# @param raw_field_names [Boolean] when true, adds `rawFieldNames: true` to the
|
|
72
72
|
# request so Parse Server returns original (un-decoded) field names (PS 9.9.0+).
|
|
73
73
|
# @return [Parse::Response]
|
|
74
74
|
# @see Parse::Query
|
|
@@ -9,31 +9,40 @@ module Parse
|
|
|
9
9
|
# Call a cloud function.
|
|
10
10
|
# @param name [String] the name of the cloud function.
|
|
11
11
|
# @param body [Hash] the parameters to forward to the function.
|
|
12
|
-
# @param opts [Hash] additional options for the request.
|
|
12
|
+
# @param opts [Hash] additional options for the request. Accepts the
|
|
13
|
+
# explicit `opts:` hash for back-compat, but request options may
|
|
14
|
+
# also be passed as plain keyword arguments (e.g.
|
|
15
|
+
# `call_function("f", {}, session_token: t)`) to match the rest of
|
|
16
|
+
# the API surface — those are merged into `opts` (explicit `opts:`
|
|
17
|
+
# keys win on conflict).
|
|
13
18
|
# @option opts [String] :session_token The session token for authenticated requests.
|
|
14
19
|
# @option opts [String] :master_key Whether to use the master key for this request.
|
|
15
20
|
# @param context [Hash, nil] an optional caller context forwarded as the
|
|
16
|
-
#
|
|
17
|
-
#
|
|
18
|
-
# Omit or pass
|
|
21
|
+
# `X-Parse-Cloud-Context` header. Parse Server maps it to
|
|
22
|
+
# `req.info.context` in the cloud function handler.
|
|
23
|
+
# Omit or pass `nil` to leave behavior unchanged.
|
|
19
24
|
# @return [Parse::Response]
|
|
20
|
-
def call_function(name, body = {}, opts: {}, context: nil)
|
|
25
|
+
def call_function(name, body = {}, opts: {}, context: nil, **kwopts)
|
|
21
26
|
safe = Parse::API::PathSegment.identifier!(name, kind: "function name")
|
|
22
27
|
headers = {}
|
|
23
28
|
headers[Parse::Protocol::CLOUD_CONTEXT] = context.to_json unless context.nil?
|
|
24
|
-
|
|
29
|
+
merged = kwopts.empty? ? opts : kwopts.merge(opts)
|
|
30
|
+
request :post, "functions/#{safe}", body: body, headers: headers, opts: merged
|
|
25
31
|
end
|
|
26
32
|
|
|
27
33
|
# Trigger a job.
|
|
28
34
|
# @param name [String] the name of the job to trigger.
|
|
29
35
|
# @param body [Hash] the parameters to forward to the job.
|
|
30
|
-
# @param opts [Hash] additional options for the request.
|
|
36
|
+
# @param opts [Hash] additional options for the request. Like
|
|
37
|
+
# {#call_function}, request options may be passed either as the
|
|
38
|
+
# explicit `opts:` hash or as plain keyword arguments.
|
|
31
39
|
# @option opts [String] :session_token The session token for authenticated requests.
|
|
32
40
|
# @option opts [String] :master_key Whether to use the master key for this request.
|
|
33
41
|
# @return [Parse::Response]
|
|
34
|
-
def trigger_job(name, body = {}, opts: {})
|
|
42
|
+
def trigger_job(name, body = {}, opts: {}, **kwopts)
|
|
35
43
|
safe = Parse::API::PathSegment.identifier!(name, kind: "job name")
|
|
36
|
-
|
|
44
|
+
merged = kwopts.empty? ? opts : kwopts.merge(opts)
|
|
45
|
+
request :post, "jobs/#{safe}", body: body, opts: merged
|
|
37
46
|
end
|
|
38
47
|
|
|
39
48
|
# Call a cloud function with a specific session token.
|
|
@@ -42,7 +51,7 @@ module Parse
|
|
|
42
51
|
# @param body [Hash] the parameters to forward to the function.
|
|
43
52
|
# @param session_token [String] the session token for authenticated requests.
|
|
44
53
|
# @param context [Hash, nil] an optional caller context forwarded as the
|
|
45
|
-
#
|
|
54
|
+
# `X-Parse-Cloud-Context` header.
|
|
46
55
|
# @return [Parse::Response]
|
|
47
56
|
def call_function_with_session(name, body = {}, session_token, context: nil)
|
|
48
57
|
opts = {}
|
data/lib/parse/api/objects.rb
CHANGED
|
@@ -36,9 +36,9 @@ module Parse
|
|
|
36
36
|
module ClassMethods
|
|
37
37
|
# Get the API path for this class.
|
|
38
38
|
#
|
|
39
|
-
# Both
|
|
39
|
+
# Both `className` and `id` are validated to prevent path-smuggling
|
|
40
40
|
# attacks where an attacker-controlled string traverses to a
|
|
41
|
-
# different REST endpoint (e.g.
|
|
41
|
+
# different REST endpoint (e.g. `"../sessions/me"`) with whatever
|
|
42
42
|
# auth the outer request carries — typically the master key.
|
|
43
43
|
#
|
|
44
44
|
# @param className [String] the name of the Parse collection.
|
|
@@ -85,9 +85,9 @@ module Parse
|
|
|
85
85
|
# @param opts [Hash] additional options to pass to the {Parse::Client} request.
|
|
86
86
|
# @param headers [Hash] additional HTTP headers to send with the request.
|
|
87
87
|
# @param context [Hash, nil] an optional caller context forwarded as the
|
|
88
|
-
#
|
|
89
|
-
#
|
|
90
|
-
# Omit or pass
|
|
88
|
+
# `X-Parse-Cloud-Context` header. Parse Server maps it to
|
|
89
|
+
# `req.info.context` inside beforeSave/afterSave cloud triggers.
|
|
90
|
+
# Omit or pass `nil` to leave behavior unchanged.
|
|
91
91
|
# @return [Parse::Response]
|
|
92
92
|
def create_object(className, body = {}, headers: {}, context: nil, **opts)
|
|
93
93
|
unless context.nil?
|
|
@@ -143,9 +143,9 @@ module Parse
|
|
|
143
143
|
# @param opts [Hash] additional options to pass to the {Parse::Client} request.
|
|
144
144
|
# @param headers [Hash] additional HTTP headers to send with the request.
|
|
145
145
|
# @param context [Hash, nil] an optional caller context forwarded as the
|
|
146
|
-
#
|
|
147
|
-
#
|
|
148
|
-
# Omit or pass
|
|
146
|
+
# `X-Parse-Cloud-Context` header. Parse Server maps it to
|
|
147
|
+
# `req.info.context` inside beforeSave/afterSave cloud triggers.
|
|
148
|
+
# Omit or pass `nil` to leave behavior unchanged.
|
|
149
149
|
# @return [Parse::Response]
|
|
150
150
|
def update_object(className, id, body = {}, headers: {}, context: nil, **opts)
|
|
151
151
|
unless context.nil?
|
data/lib/parse/api/users.rb
CHANGED
|
@@ -108,9 +108,9 @@ module Parse
|
|
|
108
108
|
#
|
|
109
109
|
# Client-side rate limited on a per-email basis using the same
|
|
110
110
|
# tracker that backs {#login} (entries are namespaced under a
|
|
111
|
-
#
|
|
111
|
+
# `pwreset:` prefix so the two limiters don't collide on usernames
|
|
112
112
|
# that happen to equal an email). Every request counts toward the
|
|
113
|
-
# backoff — Parse Server's
|
|
113
|
+
# backoff — Parse Server's `requestPasswordReset` response does
|
|
114
114
|
# not differentiate "email exists" from "email does not exist"
|
|
115
115
|
# (and rightly so, to avoid account enumeration), so the SDK
|
|
116
116
|
# cannot distinguish a legitimate retry from an attacker probing
|
|
@@ -212,23 +212,23 @@ module Parse
|
|
|
212
212
|
# that the username + password combination is correct without producing a
|
|
213
213
|
# new session token on success.
|
|
214
214
|
#
|
|
215
|
-
# Uses the
|
|
216
|
-
# BODY, mirroring
|
|
215
|
+
# Uses the `POST /parse/verifyPassword` endpoint (credentials in the request
|
|
216
|
+
# BODY, mirroring `login`) rather than the `GET` form. Parse Server accepts
|
|
217
217
|
# both (same handler, neither master-key gated; the POST variant landed in
|
|
218
218
|
# 7.1.0), but POST keeps the plaintext password out of the URL — and
|
|
219
|
-
# therefore out of server access logs, reverse-proxy logs, the
|
|
219
|
+
# therefore out of server access logs, reverse-proxy logs, the `Referer`
|
|
220
220
|
# header, and the SDK's URL-keyed response cache.
|
|
221
221
|
#
|
|
222
222
|
# On success Parse Server returns the user object (HTTP 200) with the same
|
|
223
|
-
# shape as a login response (minus
|
|
223
|
+
# shape as a login response (minus `sessionToken`). On failure it returns a
|
|
224
224
|
# 4xx with an error body, most commonly:
|
|
225
|
-
# - code 101 (
|
|
226
|
-
# - code 205 (
|
|
225
|
+
# - code 101 (`ERROR_OBJECT_NOT_FOUND`) for an unknown username or wrong password.
|
|
226
|
+
# - code 205 (`ERROR_EMAIL_NOT_FOUND`) when `preventLoginWithUnverifiedEmail`
|
|
227
227
|
# is enabled and the account's email has not been verified.
|
|
228
228
|
#
|
|
229
229
|
# Client-side rate limited per username using the SAME bucket as {#login}
|
|
230
230
|
# (bare username, no namespace) — failures across both credential oracles
|
|
231
|
-
# accumulate, so an attacker cannot bypass a
|
|
231
|
+
# accumulate, so an attacker cannot bypass a `login` lockout by pivoting to
|
|
232
232
|
# this endpoint. The trade-off: a run of failed step-up re-auth calls counts
|
|
233
233
|
# toward (and can trigger) the primary login lockout for that username.
|
|
234
234
|
# Client-side limiting is a convenience, not a boundary — the server is the
|
|
@@ -11,46 +11,46 @@ module Parse
|
|
|
11
11
|
#
|
|
12
12
|
# Atlas Search runs aggregations directly against MongoDB and
|
|
13
13
|
# therefore bypasses Parse Server's per-request ACL enforcement.
|
|
14
|
-
# To compile a
|
|
14
|
+
# To compile a `_rperm` `$match` stage (see {Parse::ACL.read_predicate})
|
|
15
15
|
# the caller needs to know two things about the requesting
|
|
16
16
|
# session:
|
|
17
17
|
#
|
|
18
|
-
# 1. The
|
|
18
|
+
# 1. The `_User.objectId` that owns the session.
|
|
19
19
|
# 2. The transitive upward closure of role names that user
|
|
20
20
|
# inherits permissions from (cf. {Parse::Role.all_for_user}).
|
|
21
21
|
#
|
|
22
22
|
# Both lookups can be expensive — token → user requires a
|
|
23
|
-
#
|
|
24
|
-
#
|
|
23
|
+
# `/users/me` round-trip, and user → roles can require multiple
|
|
24
|
+
# `_Role` queries to walk the inheritance graph. Both are cached
|
|
25
25
|
# separately so a single agent turn that runs several Atlas Search
|
|
26
26
|
# tools amortizes the cost.
|
|
27
27
|
#
|
|
28
28
|
# Two distinct caches:
|
|
29
29
|
#
|
|
30
|
-
# *
|
|
30
|
+
# * `session_cache`: maps `session_token` to `user_id`. Long
|
|
31
31
|
# TTL (1 hour default), invalidation profile is logout. Apps
|
|
32
32
|
# that need sub-TTL revocation must call {.invalidate}
|
|
33
33
|
# explicitly from their logout path.
|
|
34
34
|
#
|
|
35
|
-
# *
|
|
35
|
+
# * `role_cache`: maps `user_id` to a `Set` of role names. Short
|
|
36
36
|
# TTL (2 minutes default), invalidation profile is role-graph
|
|
37
37
|
# mutation. Stale role data here yields incorrect ACL
|
|
38
38
|
# decisions, so the default is conservatively short.
|
|
39
39
|
#
|
|
40
40
|
# The default cache implementation is process-local
|
|
41
|
-
# ({MemoryCache}) and guarded by a
|
|
41
|
+
# ({MemoryCache}) and guarded by a `Mutex`. Apps that need shared
|
|
42
42
|
# cross-process caching (Redis, Memcached) may install a
|
|
43
43
|
# replacement via {AtlasSearch.session_cache=} /
|
|
44
44
|
# {AtlasSearch.role_cache=}; the replacement must respond to
|
|
45
|
-
#
|
|
45
|
+
# `get(key)`, `set(key, value, ttl:)`, and `invalidate(key)`.
|
|
46
46
|
module Session
|
|
47
|
-
# Raised when a
|
|
48
|
-
# token, expired session, or
|
|
47
|
+
# Raised when a `session_token` cannot be resolved — invalid
|
|
48
|
+
# token, expired session, or `/users/me` returned an error.
|
|
49
49
|
# Atlas Search callers should treat this as a 401-equivalent.
|
|
50
50
|
class InvalidSession < StandardError; end
|
|
51
51
|
|
|
52
52
|
# Default cache: in-memory hash with per-entry TTL, guarded by a
|
|
53
|
-
#
|
|
53
|
+
# `Mutex`. Suitable for single-process apps. Apps running
|
|
54
54
|
# multi-process (Puma workers, Sidekiq processes) get a per-
|
|
55
55
|
# process cache — install a shared cache through
|
|
56
56
|
# {AtlasSearch.session_cache=} for cross-process sharing.
|
|
@@ -61,7 +61,7 @@ module Parse
|
|
|
61
61
|
end
|
|
62
62
|
|
|
63
63
|
# @param key [String]
|
|
64
|
-
# @return [Object, nil] the cached value, or
|
|
64
|
+
# @return [Object, nil] the cached value, or `nil` when the key
|
|
65
65
|
# is missing or its TTL has elapsed. Expired entries are
|
|
66
66
|
# evicted lazily on read.
|
|
67
67
|
def get(key)
|
|
@@ -97,15 +97,15 @@ module Parse
|
|
|
97
97
|
end
|
|
98
98
|
end
|
|
99
99
|
|
|
100
|
-
# Value returned by {Session.resolve}.
|
|
101
|
-
#
|
|
102
|
-
# caller.
|
|
100
|
+
# Value returned by {Session.resolve}. `user_id` is the
|
|
101
|
+
# `_User.objectId` owning the session, or `nil` for an anonymous
|
|
102
|
+
# caller. `role_names` is a `Set` of role names (no `role:`
|
|
103
103
|
# prefix) the user inherits permissions from, computed via
|
|
104
104
|
# {Parse::Role.all_for_user}.
|
|
105
105
|
Resolved = Struct.new(:user_id, :role_names) do
|
|
106
|
-
# Build the canonical
|
|
107
|
-
# for this session. Always includes
|
|
108
|
-
#
|
|
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
109
|
# inherited role.
|
|
110
110
|
# @return [Array<String>]
|
|
111
111
|
def permission_strings
|
|
@@ -115,33 +115,33 @@ module Parse
|
|
|
115
115
|
out.uniq
|
|
116
116
|
end
|
|
117
117
|
|
|
118
|
-
# @return [Boolean]
|
|
118
|
+
# @return [Boolean] `true` for the anonymous-session case.
|
|
119
119
|
def anonymous?
|
|
120
120
|
user_id.nil? || user_id.empty?
|
|
121
121
|
end
|
|
122
122
|
end
|
|
123
123
|
|
|
124
124
|
class << self
|
|
125
|
-
# Resolve a
|
|
126
|
-
# transitive set of role names whose
|
|
127
|
-
# strings should be checked against
|
|
125
|
+
# Resolve a `session_token` to the requesting user and the
|
|
126
|
+
# transitive set of role names whose `role:NAME` permission
|
|
127
|
+
# strings should be checked against `_rperm`.
|
|
128
128
|
#
|
|
129
|
-
#
|
|
130
|
-
#
|
|
129
|
+
# `nil` or empty `session_token` → anonymous {Resolved} with
|
|
130
|
+
# `user_id: nil` and an empty `role_names` set. The caller
|
|
131
131
|
# decides whether to refuse the request (the
|
|
132
|
-
#
|
|
132
|
+
# `require_session_token` toggle on {Parse::AtlasSearch}) or
|
|
133
133
|
# treat as public-only.
|
|
134
134
|
#
|
|
135
135
|
# Cache layering: token-to-user_id is checked first; on hit
|
|
136
|
-
# the slower
|
|
136
|
+
# the slower `/users/me` round-trip is skipped. User-to-roles
|
|
137
137
|
# is then checked independently (a single user shared across
|
|
138
138
|
# sessions amortizes the role lookup).
|
|
139
139
|
#
|
|
140
|
-
# @param session_token [String, nil] the
|
|
140
|
+
# @param session_token [String, nil] the `X-Parse-Session-Token`
|
|
141
141
|
# value from the requesting session.
|
|
142
142
|
# @return [Resolved]
|
|
143
143
|
# @raise [InvalidSession] when the token cannot be resolved by
|
|
144
|
-
#
|
|
144
|
+
# `/users/me` (404 / 209 invalid session token / 401).
|
|
145
145
|
def resolve(session_token)
|
|
146
146
|
return Resolved.new(nil, Set.new) if session_token.nil? || session_token.to_s.empty?
|
|
147
147
|
|
|
@@ -150,12 +150,12 @@ module Parse
|
|
|
150
150
|
Resolved.new(user_id, role_names)
|
|
151
151
|
end
|
|
152
152
|
|
|
153
|
-
# Forget a
|
|
153
|
+
# Forget a `session_token` entry from the session-token cache.
|
|
154
154
|
# Apps that revoke sessions out-of-band (logout, password
|
|
155
155
|
# reset, admin revoke) should call this from the same path so
|
|
156
156
|
# subsequent Atlas Search requests don't act on the stale
|
|
157
|
-
#
|
|
158
|
-
#
|
|
157
|
+
# `user_id` mapping. The `role_names` cache is keyed on
|
|
158
|
+
# `user_id` and is not affected — call {.invalidate_user_roles}
|
|
159
159
|
# to clear that separately.
|
|
160
160
|
# @param session_token [String]
|
|
161
161
|
def invalidate(session_token)
|
|
@@ -163,8 +163,8 @@ module Parse
|
|
|
163
163
|
Parse::AtlasSearch.session_cache.invalidate(session_token.to_s)
|
|
164
164
|
end
|
|
165
165
|
|
|
166
|
-
# Forget cached role membership for a
|
|
167
|
-
#
|
|
166
|
+
# Forget cached role membership for a `user_id`. Call after any
|
|
167
|
+
# `_Role.users` mutation that affects this user (role grant /
|
|
168
168
|
# revoke, role-graph reshape).
|
|
169
169
|
# @param user_id [String]
|
|
170
170
|
def invalidate_user_roles(user_id)
|
|
@@ -184,7 +184,7 @@ module Parse
|
|
|
184
184
|
|
|
185
185
|
# @!visibility private
|
|
186
186
|
# Resolve session_token → user_id via cache, falling through
|
|
187
|
-
# to
|
|
187
|
+
# to `/users/me`. Raises {InvalidSession} on lookup failure;
|
|
188
188
|
# the caller is responsible for refusing the request.
|
|
189
189
|
def lookup_user_id(session_token)
|
|
190
190
|
cache = Parse::AtlasSearch.session_cache
|