Skip to content

Commit 9e79e97

Browse files
chrisbonifaciolawmichahbuchelkatiegoines
authored
add warning regarding subscription and mutation redaction of relational fields (#7768)
* add warning regarding subscription and mutation redaction of relational fields * replace authn with authz in warning * chore(api): Callout for field redaction on Swift Android relational models * Update src/pages/gen1/[platform]/build-a-backend/graphqlapi/relational-models/index.mdx * add feature flag to warning for gen 1 * Fix heading order in fragments affecting this page * add protected redaction message components * Add tests for redaction message Gen 1 and Gen 2 components * add snapshots for redaction Gen 1 and Gen 2 component tests * Adds ProtectedRedactionMessage components for Gen 1 and Gen2 * Render ProtectedRedactionGen1Message component on Gen 1 realtime page * Render ProtectedRedactionGen1Message component on Gen 2 data modeling page * Render ProtectedRedactionGen2Message component on Gen 2 realtime page * Render ProtectedRedactionGen1Message component on Gen 1 data modeling page * Render ProtectedRedactionGen1Message component on Gen 1 V5 realtime page * add subscriptionsInheritPrimaryAuth as a feature flag * correct version for subscriptionsInheritPrimaryAuth * remove commented code * fix heading order --------- Co-authored-by: Michael Law <1365977+lawmicha@users.noreply.github.com> Co-authored-by: Heather <hbuchel@gmail.com> Co-authored-by: katiegoines <katiegoines@gmail.com>
1 parent a640904 commit 9e79e97

File tree

10 files changed

+380
-4
lines changed

10 files changed

+380
-4
lines changed

src/components/FeatureFlags/feature-flags.json

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -371,6 +371,26 @@
371371
"defaultExistingProject": false
372372
}
373373
]
374+
},
375+
"subscriptionsInheritPrimaryAuth": {
376+
"description": "Toggles whether subscriptions will inherit related authorization when relational fields are set as required",
377+
"type": "Feature",
378+
"valueType": "Boolean",
379+
"versionAdded": "12.12.4",
380+
"values": [
381+
{
382+
"value": "true",
383+
"description": "Subscriptions will inherit the primary model authorization rules for the relational fields",
384+
"defaultNewProject": false,
385+
"defaultExistingProject": true
386+
},
387+
{
388+
"value": "false",
389+
"description": "Relational fields will be redacted in mutation response when there is a difference between auth rules between primary and related models.",
390+
"defaultNewProject": true,
391+
"defaultExistingProject": false
392+
}
393+
]
374394
}
375395
}
376396
},

src/pages/[platform]/build-a-backend/data/data-modeling/relationships/index.mdx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,20 @@ export const getStaticPaths = async () => {
2424
export function getStaticProps(context) {
2525
return {
2626
props: {
27-
27+
2828
meta
2929
}
3030
};
3131
}
3232

3333
When modeling application data, you often need to establish relationships between different data models. In Amplify Data, you can create one-to-many, one-to-one, and many-to-many relationships in your Data schema. On the client-side, Amplify Data allows you to lazy or eager load of related data.
3434

35+
{/* This component contains approved messaging and cannot be removed or modified without prior approval */}
36+
37+
import { ProtectedRedactionGen2Message } from "@/protected/ProtectedRedactionMessage"
38+
39+
<ProtectedRedactionGen2Message />
40+
3541
## Types of relationships
3642

3743
|Relationship|Code|Description|Example|

src/pages/[platform]/build-a-backend/data/subscribe-data/index.mdx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,12 @@ Before you begin, you will need:
3838
- An [application connected to the API](/[platform]/build-a-backend/data/connect-to-API/)
3939
- Data already created to modify
4040

41+
{/* This component contains approved messaging and cannot be removed or modified without prior approval */}
42+
43+
import { ProtectedRedactionGen2Message } from "@/protected/ProtectedRedactionMessage"
44+
45+
<ProtectedRedactionGen2Message />
46+
4147
## Set up a real-time list query
4248

4349
The recommended way to fetch a list of data is to use `observeQuery` to get a real-time list of your app data at all times. You can integrate `observeQuery` with React's `useState` and `useEffect` hooks in the following way:

src/pages/gen1/[platform]/build-a-backend/graphqlapi/data-modeling/index.mdx

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,12 @@ Create "has one", "has many", "belongs to", and "many to many" relationships bet
292292
| `@belongsTo` | Use a "belongs to" relationship to make a "has one" or "has many" relationship bi-directional. For example, a Project has one Team and a Team belongs to a Project. This allows you to query the team from the project record and vice versa. |
293293
| `@manyToMany` | Configures a "join table" between two models to facilitate a many-to-many relationship. For example, a Blog has many Tags and a Tag has many Blogs. |
294294

295+
{/* This component contains approved messaging and cannot be removed or modified without prior approval */}
296+
297+
import { ProtectedRedactionGen1Message } from "@/protected/ProtectedRedactionMessage"
298+
299+
<ProtectedRedactionGen1Message />
300+
295301
### Has One relationship
296302

297303
import gqlv2callout from '/src/fragments/cli/gqlv2callout.mdx';
@@ -794,11 +800,11 @@ You can use the `@default` directive to specify a default value for optional [sc
794800
```graphql
795801
type Todo @model {
796802
content: String @default(value: "My new Todo")
797-
# Note: all "value" parameters must be passed as a string value.
803+
# Note: all "value" parameters must be passed as a string value.
798804
# Under the hood, Amplify will parse the string values into respective types.
799-
# For example, to set a default value for an integer field,
805+
# For example, to set a default value for an integer field,
800806
# you must pass in `"0"` instead of `0` without the double-quotes.
801-
likes: Int @default(value: "0") #
807+
likes: Int @default(value: "0") #
802808
}
803809
```
804810

src/pages/gen1/[platform]/build-a-backend/graphqlapi/relational-models/index.mdx

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,30 @@ API (GraphQL) has the capability to handle relationships between Models, such as
2929

3030
By default, GraphQL APIs requests generate a selection set with a depth of 0. Connected relationship models are not returned in the initial request, but can be lazily loaded as needed with an additional API request. We provide mechanisms to customize the selection set, which allows connected relationships to be eagerly loaded on the initial request.
3131

32+
<Callout warning>
33+
34+
With versions of Amplify CLI `@aws-amplify/cli@12.12.2` and API Category `@aws-amplify/amplify-category-api@5.11.5`, an improvement was made to how relational field data is handled in subscriptions when different authorization rules apply to related models in a schema. The improvement redacts the values for the relational fields, displaying them as null or empty, to prevent unauthorized access to relational data.
35+
36+
This redaction occurs whenever it cannot be determined that the child model will be protected by the same permissions as the parent model.
37+
38+
Because subscriptions are tied to mutations and the selection set provided in the result of a mutation is then passed through to the subscription, relational fields in the result of mutations must be redacted.
39+
40+
If an authorized end-user needs access to the redacted relational fields, they should perform a query to read the relational data.
41+
42+
Additionally, subscriptions will inherit related authorization when relational fields are set as required. To better protect relational data, consider modifying the schema to use optional relational fields.
43+
44+
- **Lazy and Eager Loading**: Lazy and eager loading relationships is no longer supported for Mutations and Subscriptions. However, you can continue to perform eager or lazy loading for Queries.
45+
46+
- **Subscriptions and Related Models**: When performing a subscription and you need to retrieve the related model, perform a lazy or eager loaded query using the model identifier from the subscription event to continue to retrieve the related data.
47+
48+
Based on the security posture of your application, you can choose to revert to the subscription behavior before this improvement was made.
49+
To do so, use the `subscriptionsInheritPrimaryAuth` feature flag under `graphqltransformer` in the `amplify/backend/cli.json` file.
50+
51+
- If enabled, subscriptions will inherit the primary model authorization rules for the relational fields.
52+
- If disabled, relational fields will be redacted in mutation response when there is a difference between auth rules between primary and related models.
53+
54+
</Callout>
55+
3256
## Prerequisites
3357

3458
The following examples have a minimum version requirement of the following:

src/pages/gen1/[platform]/build-a-backend/graphqlapi/subscribe-data/index.mdx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,12 @@ Before you begin, you will need:
4949

5050
</InlineFilter>
5151

52+
{/* This component contains approved messaging and cannot be removed or modified without prior approval */}
53+
54+
import { ProtectedRedactionGen1Message } from "@/protected/ProtectedRedactionMessage"
55+
56+
<ProtectedRedactionGen1Message />
57+
5258
## Set up a real-time subscription
5359

5460
Subscriptions is a GraphQL feature that allows the server to send data to its clients when a specific event happens. For example, you can subscribe to an event when a new record is created, updated, or deleted through the API. You can enable real-time data integration in your app with a subscription.

src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/subscribe-data/index.mdx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,12 @@ Before you begin, you will need:
3838
- An [application connected to the API](/gen1/[platform]/prev/build-a-backend/graphqlapi/connect-to-api/)
3939
- Data already created to modify
4040

41+
{/* This component contains approved messaging and cannot be removed or modified without prior approval */}
42+
43+
import { ProtectedRedactionGen1Message } from "@/protected/ProtectedRedactionMessage"
44+
45+
<ProtectedRedactionGen1Message />
46+
4147
## Set up a real-time subscription
4248

4349
Subscriptions is a GraphQL feature that allows the server to send data to its clients when a specific event happens. For example, you can subscribe to an event when a new record is created, updated, or deleted through the API. You can enable real-time data integration in your app with a subscription.
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import * as React from 'react';
2+
import { render } from '@testing-library/react';
3+
import {
4+
ProtectedRedactionGen1Message,
5+
ProtectedRedactionGen2Message
6+
} from '../index';
7+
import fs from 'fs';
8+
9+
// REALTIME DATA
10+
const GEN1_V5_REALTIME_DATA_PAGE_PATH =
11+
'src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/subscribe-data/index.mdx';
12+
13+
const GEN1_V6_REALTIME_DATA_PAGE_PATH =
14+
'src/pages/gen1/[platform]/build-a-backend/graphqlapi/subscribe-data/index.mdx';
15+
16+
const GEN2_REALTIME_DATA_PAGE_PATH =
17+
'src/pages/[platform]/build-a-backend/data/subscribe-data/index.mdx';
18+
19+
// DATA MODELING
20+
21+
const GEN1_V6_DATA_MODELING_PAGE_PATH =
22+
'src/pages/gen1/[platform]/build-a-backend/graphqlapi/data-modeling/index.mdx';
23+
24+
const GEN2_DATA_MODELING_PAGE_PATH =
25+
'src/pages/[platform]/build-a-backend/data/data-modeling/relationships/index.mdx';
26+
27+
describe('Protected Redaction Messages', () => {
28+
/*
29+
This test is to ensure that the ProtectedRedactionGen1Message component appears on the Gen 1 realtime data pages and cannot be removed or modified without approval.
30+
*/
31+
it('should render ProtectedRedactionGen1Message component on the Gen 1 V5 realtime data page', async () => {
32+
const pageData = fs.readFileSync(GEN1_V5_REALTIME_DATA_PAGE_PATH, {
33+
encoding: 'utf8'
34+
});
35+
expect(pageData).toMatch(/<ProtectedRedactionGen1Message \/>/);
36+
});
37+
38+
it('should render ProtectedRedactionGen1Message component on the Gen 1 V6 realtime data page', async () => {
39+
const pageData = fs.readFileSync(GEN1_V6_REALTIME_DATA_PAGE_PATH, {
40+
encoding: 'utf8'
41+
});
42+
expect(pageData).toMatch(/<ProtectedRedactionGen1Message \/>/);
43+
});
44+
45+
it('should render ProtectedRedactionGen1Message component on the Gen 2 realtime data page', async () => {
46+
const pageData = fs.readFileSync(GEN2_REALTIME_DATA_PAGE_PATH, {
47+
encoding: 'utf8'
48+
});
49+
expect(pageData).toMatch(/<ProtectedRedactionGen2Message \/>/);
50+
});
51+
52+
it('should render ProtectedRedactionGen1Message component on the Gen 1 V6 data modeling page', async () => {
53+
const pageData = fs.readFileSync(GEN1_V6_DATA_MODELING_PAGE_PATH, {
54+
encoding: 'utf8'
55+
});
56+
expect(pageData).toMatch(/<ProtectedRedactionGen1Message \/>/);
57+
});
58+
59+
it('should render ProtectedRedactionGen1Message component on the Gen 2 data modeling page', async () => {
60+
const pageData = fs.readFileSync(GEN2_DATA_MODELING_PAGE_PATH, {
61+
encoding: 'utf8'
62+
});
63+
expect(pageData).toMatch(/<ProtectedRedactionGen2Message \/>/);
64+
});
65+
66+
/*
67+
This test is to ensure that the messaging on the ProtectedRedactionGen1Message component does not change
68+
and cannot be removed or modified without approval.
69+
*/
70+
it('should render the protected redaction message for Gen 1', async () => {
71+
const { container } = render(<ProtectedRedactionGen1Message />);
72+
73+
expect(container.firstChild).toMatchSnapshot();
74+
});
75+
76+
it('should render the protected redaction message for Gen 2', async () => {
77+
const { container } = render(<ProtectedRedactionGen2Message />);
78+
79+
expect(container.firstChild).toMatchSnapshot();
80+
});
81+
});
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`Protected Redaction Messages should render the protected redaction message for Gen 1 1`] = `
4+
<div
5+
class="amplify-flex amplify-message amplify-message--filled amplify-message--warning"
6+
>
7+
<div
8+
aria-hidden="true"
9+
class="amplify-message__icon"
10+
>
11+
<span
12+
class="amplify-icon"
13+
style="width: 1em; height: 1em;"
14+
>
15+
<svg
16+
fill="none"
17+
height="24"
18+
viewBox="0 0 24 24"
19+
width="24"
20+
xmlns="http://www.w3.org/2000/svg"
21+
>
22+
<path
23+
d="M1 21H23L12 2L1 21ZM13 18H11V16H13V18ZM13 14H11V10H13V14Z"
24+
fill="currentColor"
25+
/>
26+
</svg>
27+
</span>
28+
</div>
29+
<div
30+
class="amplify-flex amplify-message__content"
31+
>
32+
<div>
33+
<p>
34+
With versions of Amplify CLI
35+
<code>
36+
@aws-amplify/cli@12.12.2
37+
</code>
38+
and API Category
39+
<code>
40+
@aws-amplify/amplify-category-api@5.11.5
41+
</code>
42+
, an improvement was made to how relational field data is handled in subscriptions when different authorization rules apply to related models in a schema. The improvement redacts the values for the relational fields, displaying them as null or empty, to prevent unauthorized access to relational data. This redaction occurs whenever it cannot be determined that the child model will be protected by the same permissions as the parent model.
43+
</p>
44+
<p>
45+
Because subscriptions are tied to mutations and the selection set provided in the result of a mutation is then passed through to the subscription, relational fields in the result of mutations must be redacted.
46+
</p>
47+
<p>
48+
If an authorized end-user needs access to the redacted relational field they should perform a query to read the relational data.
49+
</p>
50+
<p>
51+
Additionally, subscriptions will inherit related authorization when relational fields are set as required. To better protect relational data, consider modifying the schema to use optional relational fields.
52+
</p>
53+
<p>
54+
Based on the security posture of your application, you can choose to revert to the subscription behavior before this improvement was made.
55+
</p>
56+
<p>
57+
To do so, use the
58+
<code>
59+
subscriptionsInheritPrimaryAuth
60+
</code>
61+
feature flag under
62+
<code>
63+
graphqltransformer
64+
</code>
65+
in the
66+
67+
<code>
68+
amplify/backend/cli.json
69+
</code>
70+
file.
71+
</p>
72+
<ul>
73+
<li>
74+
If enabled, subscriptions will inherit the primary model authorization rules for the relational fields.
75+
</li>
76+
<li>
77+
If disabled, relational fields will be redacted in mutation response when there is a difference between auth rules between primary and related models.
78+
</li>
79+
</ul>
80+
</div>
81+
</div>
82+
</div>
83+
`;
84+
85+
exports[`Protected Redaction Messages should render the protected redaction message for Gen 2 1`] = `
86+
<div
87+
class="amplify-flex amplify-message amplify-message--filled amplify-message--warning"
88+
>
89+
<div
90+
aria-hidden="true"
91+
class="amplify-message__icon"
92+
>
93+
<span
94+
class="amplify-icon"
95+
style="width: 1em; height: 1em;"
96+
>
97+
<svg
98+
fill="none"
99+
height="24"
100+
viewBox="0 0 24 24"
101+
width="24"
102+
xmlns="http://www.w3.org/2000/svg"
103+
>
104+
<path
105+
d="M1 21H23L12 2L1 21ZM13 18H11V16H13V18ZM13 14H11V10H13V14Z"
106+
fill="currentColor"
107+
/>
108+
</svg>
109+
</span>
110+
</div>
111+
<div
112+
class="amplify-flex amplify-message__content"
113+
>
114+
<div>
115+
<p>
116+
With Amplify Data Construct
117+
<code>
118+
@aws-amplify/data-construct@1.8.4
119+
</code>
120+
, an improvement was made to how relational field data is handled in subscriptions when different authorization rules apply to related models in a schema. The improvement redacts the values for the relational fields, displaying them as null or empty, to prevent unauthorized access to relational data.
121+
</p>
122+
<p>
123+
This redaction occurs whenever it cannot be determined that the child model will be protected by the same permissions as the parent model.
124+
</p>
125+
<p>
126+
Because subscriptions are tied to mutations and the selection set provided in the result of a mutation is then passed through to the subscription, relational fields in the result of mutations must be redacted.
127+
</p>
128+
<p>
129+
If an authorized end-user needs access to the redacted relational fields, they should perform a query to read the relational data.
130+
</p>
131+
<p>
132+
Additionally, subscriptions will inherit related authorization when relational fields are set as required. To better protect relational data, consider modifying the schema to use optional relational fields.
133+
</p>
134+
</div>
135+
</div>
136+
</div>
137+
`;

0 commit comments

Comments
 (0)