diff --git a/.changeset/20887-analytics-nested-relation-engine-answer.md b/.changeset/20887-analytics-nested-relation-engine-answer.md index 5cb7f21ae00..ede9946ef36 100644 --- a/.changeset/20887-analytics-nested-relation-engine-answer.md +++ b/.changeset/20887-analytics-nested-relation-engine-answer.md @@ -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. diff --git a/.changeset/20917-analytics-field-permission-gate.md b/.changeset/20917-analytics-field-permission-gate.md index 8b099e60cde..8ec24209245 100644 --- a/.changeset/20917-analytics-field-permission-gate.md +++ b/.changeset/20917-analytics-field-permission-gate.md @@ -8,7 +8,7 @@ Clause-②: yes (narrowing) -**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 @@ -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 diff --git a/.changeset/20933-analytics-relationship-path-admission.md b/.changeset/20933-analytics-relationship-path-admission.md index 7b1ecf4d4c1..a737f05eea8 100644 --- a/.changeset/20933-analytics-relationship-path-admission.md +++ b/.changeset/20933-analytics-relationship-path-admission.md @@ -8,7 +8,7 @@ Clause-②: no (narrowing) -**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 @@ -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