Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ Clause-②: yes (narrowing)
- At a measure's own `filter` the form is refused with `400 INVALID_FILTER`, as the engine refuses it at an aggregation's own `filter`: put the condition in the query's `where`.
- `POST /api/v1/analytics/sql` refuses a `where` carrying the form with `400 INVALID_FILTER`: no statement it could print reproduces a read of the related object as the caller. The query itself is answered by `POST /api/v1/analytics/query`.

**Why.** Measured on the base over one fixture with the real security layer (a related field the caller may not read, a related row scope, 1,001 matching related records). The native-SQL strategy flattened the form to a dotted member and joined the related table: through a dataset that `include`d the relationship it answered rows for a condition on a field the caller cannot read, answered a match past the engine's cap, and counted a measure filter carrying the form; without the declared join it named a table that does not exist (500), and a multi-valued relation was refused. The engine-aggregate strategy refused the form as a cross-object filter (400). The engine serves the form since the nested-relation filter landed in `where`.
**Why.** Measured on the base before the field-level gate (#20917) and the relationship-path admission (#20933) landed, over one fixture with the real security layer (a related field the caller may not read, a related row scope, 1,001 matching related records). The native-SQL strategy flattened the form to a dotted member and joined the related table: through a dataset that `include`d the relationship it answered rows for a condition on a field the caller cannot read, answered a match past the engine's cap, and counted a measure filter carrying the form; without the declared join it named a table that does not exist (500), and a multi-valued relation was refused. The engine-aggregate strategy refused the form as a cross-object filter (400). The engine serves the form since the nested-relation filter landed in `where`.

**How.** The native-SQL strategy declines a query in which the form appears in the `where`, the dataset's own `filter` or a requested measure's `filter`, so the query runs on the engine-aggregate path, which hands the form to the engine as written.

Expand Down
6 changes: 4 additions & 2 deletions .changeset/20917-analytics-field-permission-gate.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ Clause-②: yes (narrowing)

<!-- adr-0087: not-required (no-migration-prescription) No authorable key, export or stored shape is removed or renamed. The change refuses analytics queries that read a field the caller's field-level permissions hide, which the engine already refuses on the data API and on the ObjectQL strategy, so there is nothing for `objectstack migrate meta` to rewrite. The one public-surface addition is a new optional service hook. -->

**BREAKING for analytics queries on a SQL deployment that read a field the caller may not read.**
**BREAKING for analytics queries that read a field the caller may not read: on a SQL deployment, and on `POST /api/v1/analytics/sql` whichever strategy serves the cube.**

**What changed.** `POST /api/v1/analytics/query`, `POST /api/v1/analytics/sql`
and `POST /api/v1/analytics/dataset/query` now judge every field a query reads
Expand All @@ -19,7 +19,9 @@ filters. A member of an authored cube is judged by the field it resolves to,
not by its name in the cube. A field the caller may not read answers
`403 PERMISSION_DENIED`, in the words the engine uses for the same field. The
native-SQL strategy, the one a SQL driver serves first, answered such queries;
the ObjectQL strategy and the data API already refused them.
the ObjectQL strategy already refused them on `POST /api/v1/analytics/query`
and `POST /api/v1/analytics/dataset/query`, as the data API did, but printed
the statement on `POST /api/v1/analytics/sql`.

**What is not affected.** A query that reads only fields the caller may read
answers as before. A system context, and a caller with no permission sets, are
Expand Down
6 changes: 4 additions & 2 deletions .changeset/20933-analytics-relationship-path-admission.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ Clause-②: no (narrowing)

<!-- adr-0087: not-required (no-migration-prescription) No authorable key, export or stored shape is removed or renamed. The change refuses, or row-scopes, what the native-SQL strategy read from an object reached through an undeclared relationship path, the way a declared join to the same object already was; there is nothing for `objectstack migrate meta` to rewrite. -->

**BREAKING for analytics queries on a SQL deployment that read a related object through a relationship path the cube does not declare.**
**BREAKING for analytics queries that read a related object through a relationship path the cube does not declare: on a SQL deployment, and on `POST /api/v1/analytics/sql` whichever strategy serves the cube.**

**What changed.** The analytics door admits and row-scopes one object set
before either strategy runs. It held the cube's base object and the joins the
Expand Down Expand Up @@ -40,7 +40,9 @@ deployment with no security service applies no object-level check, as on the
data API.

**Refusals that change form.** On the ObjectQL strategy a related object the
caller may not read was already refused; it now answers the analytics door's
caller may not read was already refused on `POST /api/v1/analytics/query` and
`POST /api/v1/analytics/dataset/query`, though `POST /api/v1/analytics/sql`
printed the statement; on those two doors it now answers the analytics door's
refusal rather than the engine's, the same one a declared join gets. A filter,
a time window or a two-hop path through such an object moves from
`400 INVALID_FIELD` to that `403`. A relationship path whose relationship name
Expand Down
Loading