v5.3.0 - Run as the calling user, plural aliases, pointer fixes#14
Merged
Conversation
Introduce run_test_files! helper that runs each test file in its own Ruby process, prints PASS/FAIL + duration per file, and appends progress to a tail-able log. Add environment knobs: TEST_PATTERN to filter files and CONTINUE_ON_FAILURE to stop on first failure. Wire this helper into test:integration and test:unit (replacing their inline loops), create per-suite logs under tmp/, and ensure the task exits non-zero when any file fails. Task descriptions were updated to document the knobs.
There was a problem hiding this comment.
Pull request overview
This PR introduces first-class support for “run webhook handlers as the calling user” by capturing the caller’s live session token from Parse Server trigger payloads, exposing safe opt-in handles (user_client / user_agent), and locking in the non-leak/scrub invariants with both unit and Docker integration coverage.
Changes:
- Add
Parse::Webhooks::Payload#session_token,#session_token?,#user_client, and#user_agent, capturinguser.sessionTokenbefore credential scrubbing. - Add unit + end-to-end integration tests to ensure ACL/CLP enforcement under session-token scope and to prevent token leakage into handler-visible objects/serialization/log surfaces.
- Improve test rake tasks with per-file process isolation, progress logging, and filtering knobs; bump version/docs/changelog to 5.2.2.
Reviewed changes
Copilot reviewed 8 out of 9 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
lib/parse/webhooks/payload.rb |
Captures and exposes caller session token safely; provides user-scoped client/agent helpers. |
lib/parse/client.rb |
Adds session_token: option to bind a token to a client and apply it as lowest-priority auth fallback per request. |
test/lib/parse/webhook_session_token_capture_test.rb |
Unit regression tests to ensure token capture works and token never leaks into payload serialization/objects. |
test/lib/parse/webhook_session_token_as_user_integration_test.rb |
Docker integration tests proving scoped reads enforce ACL vs master contrast across route/DSL/MCP-style agent cases. |
Rakefile |
Adds shared per-test-file runner with progress logs and filtering knobs; updates test:unit / test:integration tasks to use it. |
docs/mcp_guide.md |
Documents webhook client-mode usage via captured session token (v5.2.2). |
CHANGELOG.md |
Changelog entry describing the new webhook caller-scoped capability and client binding. |
lib/parse/stack/version.rb |
Bumps gem version to 5.2.2. |
Gemfile.lock |
Updates locked gem version to 5.2.2. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+175
to
+181
| def self.extract_session_token(user_hash) | ||
| return nil unless user_hash.is_a?(Hash) | ||
| token = user_hash["sessionToken"] || user_hash[:sessionToken] || | ||
| user_hash["session_token"] || user_hash[:session_token] | ||
| token = token.to_s | ||
| token.empty? ? nil : token | ||
| end |
Comment on lines
+409
to
+412
| def with_session(&block) | ||
| raise ArgumentError, "Parse::Client#with_session requires a client with a bound session_token" if @session_token.nil? | ||
| Parse.with_session(@session_token, &block) | ||
| end |
Comment on lines
1131
to
1135
| if token.nil? && !(explicit_master && opts[:use_master_key] == true) | ||
| ambient = Parse.current_session_token | ||
| token = ambient if ambient.is_a?(String) && !ambient.empty? | ||
| token = @session_token if (token.nil? || token.to_s.empty?) && @session_token | ||
| end |
1bcf54c to
e16b604
Compare
Comment on lines
+38
to
+42
| # Resolve the identity for `rake client:console`. Returns a session-token String, | ||
| # or nil to mean "anonymous" (no token, no master). Order: PARSE_SESSION_TOKEN, | ||
| # PARSE_CLIENT_ANONYMOUS=true, PARSE_LOGIN_USER (+PARSE_LOGIN_PASSWORD), else | ||
| # prompt for login / token / anon. +client+ is the master client used to log in. | ||
| def client_console_token!(client) |
063a307 to
92dab08
Compare
Add support for binding a session token to clients and running operations as a user: Parse::Client gains a session_token option, become/anonymous helpers, with_session block scoping, and redacted inspect; request token resolution prefers ambient session, then bound token. Webhook payloads expose session_token, user_agent and user_client so handlers can opt in to acting as the calling user. Add Parse::User#session_client and client console (rake client:console) to open an IRB with a non-master user-scoped default client. Delegate pointer-style property declarations to belongs_to and enforce declaration-time guards: reject scalar `as:` usages, surface pointer metadata, and honor strict_property_redefinition to avoid silent type changes. Introduce pluralized class-name aliases (automatic const_missing hook + pluralized_alias! macro) so plurals like Posts resolve to Post. Update docs, changelog and add tests/snapshots covering associations, webhooks, pluralized aliases and property pointer behavior.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
v5.3.0
Three things this release: webhook handlers and clients can run as the calling
user, plural model-constant aliases, and
property … as:pointer associationsnow serialize correctly (breaking for one narrow case).
Breaking Changes
property … as:now serializes as aPointer (
{__type: "Pointer", …}) instead of a String. Previouslyas:was asilent no-op and the field defaulted to
:string. If a deployed app usedproperty … as:and saved records, Parse Server pinned that column asString, and a Pointer write is now rejected with error 111 (schema typemismatch) — drop or migrate that column to a Pointer type before deploying.
Apps that never used
property … as:are unaffected.Run webhook handlers (and clients) as the calling user
Parse Server includes the caller's live session token in every trigger/function
webhook fired by a logged-in user. A handler — or any client-side caller — can
opt into acting on the server as that user, with full ACL / CLP /
protectedFieldsenforcement, instead of the application master key.Parse::Webhooks::Payload#session_token/#session_token?— thecaller's token, captured before credential scrubbing;
nilfor master-keyrequests; never present in
as_json,inspect, or the request log.Parse::Webhooks::Payload#user_agent— a non-master, client-modeParse::Agentscoped to the caller (ACL/CLP enforced).Parse::Webhooks::Payload#user_client— a non-masterParse::Clientwith the caller's token bound, so even raw REST calls authorize as the user.
Parse::Client.new(session_token:)binds a token to a client,applied as the lowest-priority auth fallback (explicit per-call
session_token:,Parse.with_session, anduse_master_key: trueall win).Parse::Client#become(session_token)derives a non-master clientmirroring the receiver's connection;
Parse::Client#anonymousderives thesame with no token (unauthenticated REST).
Parse::User#session_client— a user-scoped client straight from alogged-in user (
Parse::User.login(u, p).session_client).Parse::Client#with_session { ... }runs a block with the client'sbound token as the ambient session, scoping REST-routed operations
(
find/get/count/save) as the user (the client-receiver flavor ofParse.with_session/Parse::User#with_session). Mongo-direct queries stayexplicitly scoped.
rake client:consoleopens an interactive console whose defaultclient is bound to a user (session token, login, or anonymous), so every query
runs with that user's ACL/CLP rather than the master key.
Parse::Client#inspectandParse::Webhooks::Payload#inspectredact the master key and session token (and the payload's pre-scrub raw
credentials) instead of printing them in cleartext.
master_key: nilno longer re-inherits the processmaster key from the environment, and a whitespace-only
session_token:istreated as no token — so a client built for user scoping stays non-master.
Pluralized class-name aliases
the plural reads as a query entry point —
Posts.where(...).countworks for aclass
Post. The alias is lazy and the same class object as the singular(
Posts.equal?(Post)), so it adds nodescendantsentry and registers noseparate Parse schema class.
pluralized_alias!macro for an explicit/custom plural (or anamespaced model), and
Parse.pluralized_aliasesconfiguration (default on;opt out with
Parse.pluralized_aliases = false/PARSE_PLURALIZED_ALIASES=false).Pointer associations declared with
property … as:property <name>, as: <class>andproperty <name>, :pointernowdeclare a pointer association identical to the equivalent
belongs_to— samefield type, remote column, target-class reference, dirty tracking, and
{__type: "Pointer", …}wire form (see Breaking Changes above).belongs_to/property … as:raiseArgumentErrorat declarationwhen
as:names a scalar type such asas: :string.Parse.validate_associations!verifies everybelongs_to/property … as:pointer target andhas_many … through: :relationtargetresolves to a known Parse class, for a once-after-load (boot/CI) check.
belongs_to(andproperty … as:) now retain_description:/_enum:agent metadata that previously onlypropertykept.