Skip to content

Commit

Permalink
add warning regarding subscription and mutation redaction of relation…
Browse files Browse the repository at this point in the history
…al 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>
  • Loading branch information
4 people authored Jul 1, 2024
1 parent a640904 commit 9e79e97
Show file tree
Hide file tree
Showing 10 changed files with 380 additions and 4 deletions.
20 changes: 20 additions & 0 deletions src/components/FeatureFlags/feature-flags.json
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,26 @@
"defaultExistingProject": false
}
]
},
"subscriptionsInheritPrimaryAuth": {
"description": "Toggles whether subscriptions will inherit related authorization when relational fields are set as required",
"type": "Feature",
"valueType": "Boolean",
"versionAdded": "12.12.4",
"values": [
{
"value": "true",
"description": "Subscriptions will inherit the primary model authorization rules for the relational fields",
"defaultNewProject": false,
"defaultExistingProject": true
},
{
"value": "false",
"description": "Relational fields will be redacted in mutation response when there is a difference between auth rules between primary and related models.",
"defaultNewProject": true,
"defaultExistingProject": false
}
]
}
}
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,20 @@ export const getStaticPaths = async () => {
export function getStaticProps(context) {
return {
props: {

meta
}
};
}

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.

{/* This component contains approved messaging and cannot be removed or modified without prior approval */}

import { ProtectedRedactionGen2Message } from "@/protected/ProtectedRedactionMessage"

<ProtectedRedactionGen2Message />

## Types of relationships

|Relationship|Code|Description|Example|
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,12 @@ Before you begin, you will need:
- An [application connected to the API](/[platform]/build-a-backend/data/connect-to-API/)
- Data already created to modify

{/* This component contains approved messaging and cannot be removed or modified without prior approval */}

import { ProtectedRedactionGen2Message } from "@/protected/ProtectedRedactionMessage"

<ProtectedRedactionGen2Message />

## Set up a real-time list query

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:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,12 @@ Create "has one", "has many", "belongs to", and "many to many" relationships bet
| `@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. |
| `@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. |

{/* This component contains approved messaging and cannot be removed or modified without prior approval */}

import { ProtectedRedactionGen1Message } from "@/protected/ProtectedRedactionMessage"

<ProtectedRedactionGen1Message />

### Has One relationship

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,30 @@ API (GraphQL) has the capability to handle relationships between Models, such as

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.

<Callout warning>

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.

This redaction occurs whenever it cannot be determined that the child model will be protected by the same permissions as the parent model.

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.

If an authorized end-user needs access to the redacted relational fields, they should perform a query to read the relational data.

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.

- **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.

- **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.

Based on the security posture of your application, you can choose to revert to the subscription behavior before this improvement was made.
To do so, use the `subscriptionsInheritPrimaryAuth` feature flag under `graphqltransformer` in the `amplify/backend/cli.json` file.

- If enabled, subscriptions will inherit the primary model authorization rules for the relational fields.
- If disabled, relational fields will be redacted in mutation response when there is a difference between auth rules between primary and related models.

</Callout>

## Prerequisites

The following examples have a minimum version requirement of the following:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,12 @@ Before you begin, you will need:

</InlineFilter>

{/* This component contains approved messaging and cannot be removed or modified without prior approval */}

import { ProtectedRedactionGen1Message } from "@/protected/ProtectedRedactionMessage"

<ProtectedRedactionGen1Message />

## Set up a real-time subscription

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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,12 @@ Before you begin, you will need:
- An [application connected to the API](/gen1/[platform]/prev/build-a-backend/graphqlapi/connect-to-api/)
- Data already created to modify

{/* This component contains approved messaging and cannot be removed or modified without prior approval */}

import { ProtectedRedactionGen1Message } from "@/protected/ProtectedRedactionMessage"

<ProtectedRedactionGen1Message />

## Set up a real-time subscription

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.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import * as React from 'react';
import { render } from '@testing-library/react';
import {
ProtectedRedactionGen1Message,
ProtectedRedactionGen2Message
} from '../index';
import fs from 'fs';

// REALTIME DATA
const GEN1_V5_REALTIME_DATA_PAGE_PATH =
'src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/subscribe-data/index.mdx';

const GEN1_V6_REALTIME_DATA_PAGE_PATH =
'src/pages/gen1/[platform]/build-a-backend/graphqlapi/subscribe-data/index.mdx';

const GEN2_REALTIME_DATA_PAGE_PATH =
'src/pages/[platform]/build-a-backend/data/subscribe-data/index.mdx';

// DATA MODELING

const GEN1_V6_DATA_MODELING_PAGE_PATH =
'src/pages/gen1/[platform]/build-a-backend/graphqlapi/data-modeling/index.mdx';

const GEN2_DATA_MODELING_PAGE_PATH =
'src/pages/[platform]/build-a-backend/data/data-modeling/relationships/index.mdx';

describe('Protected Redaction Messages', () => {
/*
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.
*/
it('should render ProtectedRedactionGen1Message component on the Gen 1 V5 realtime data page', async () => {
const pageData = fs.readFileSync(GEN1_V5_REALTIME_DATA_PAGE_PATH, {
encoding: 'utf8'
});
expect(pageData).toMatch(/<ProtectedRedactionGen1Message \/>/);
});

it('should render ProtectedRedactionGen1Message component on the Gen 1 V6 realtime data page', async () => {
const pageData = fs.readFileSync(GEN1_V6_REALTIME_DATA_PAGE_PATH, {
encoding: 'utf8'
});
expect(pageData).toMatch(/<ProtectedRedactionGen1Message \/>/);
});

it('should render ProtectedRedactionGen1Message component on the Gen 2 realtime data page', async () => {
const pageData = fs.readFileSync(GEN2_REALTIME_DATA_PAGE_PATH, {
encoding: 'utf8'
});
expect(pageData).toMatch(/<ProtectedRedactionGen2Message \/>/);
});

it('should render ProtectedRedactionGen1Message component on the Gen 1 V6 data modeling page', async () => {
const pageData = fs.readFileSync(GEN1_V6_DATA_MODELING_PAGE_PATH, {
encoding: 'utf8'
});
expect(pageData).toMatch(/<ProtectedRedactionGen1Message \/>/);
});

it('should render ProtectedRedactionGen1Message component on the Gen 2 data modeling page', async () => {
const pageData = fs.readFileSync(GEN2_DATA_MODELING_PAGE_PATH, {
encoding: 'utf8'
});
expect(pageData).toMatch(/<ProtectedRedactionGen2Message \/>/);
});

/*
This test is to ensure that the messaging on the ProtectedRedactionGen1Message component does not change
and cannot be removed or modified without approval.
*/
it('should render the protected redaction message for Gen 1', async () => {
const { container } = render(<ProtectedRedactionGen1Message />);

expect(container.firstChild).toMatchSnapshot();
});

it('should render the protected redaction message for Gen 2', async () => {
const { container } = render(<ProtectedRedactionGen2Message />);

expect(container.firstChild).toMatchSnapshot();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`Protected Redaction Messages should render the protected redaction message for Gen 1 1`] = `
<div
class="amplify-flex amplify-message amplify-message--filled amplify-message--warning"
>
<div
aria-hidden="true"
class="amplify-message__icon"
>
<span
class="amplify-icon"
style="width: 1em; height: 1em;"
>
<svg
fill="none"
height="24"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M1 21H23L12 2L1 21ZM13 18H11V16H13V18ZM13 14H11V10H13V14Z"
fill="currentColor"
/>
</svg>
</span>
</div>
<div
class="amplify-flex amplify-message__content"
>
<div>
<p>
With versions of Amplify CLI
<code>
@aws-amplify/cli@12.12.2
</code>
and API Category
<code>
@aws-amplify/amplify-category-api@5.11.5
</code>
, 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.
</p>
<p>
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.
</p>
<p>
If an authorized end-user needs access to the redacted relational field they should perform a query to read the relational data.
</p>
<p>
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.
</p>
<p>
Based on the security posture of your application, you can choose to revert to the subscription behavior before this improvement was made.
</p>
<p>
To do so, use the
<code>
subscriptionsInheritPrimaryAuth
</code>
feature flag under
<code>
graphqltransformer
</code>
in the
<code>
amplify/backend/cli.json
</code>
file.
</p>
<ul>
<li>
If enabled, subscriptions will inherit the primary model authorization rules for the relational fields.
</li>
<li>
If disabled, relational fields will be redacted in mutation response when there is a difference between auth rules between primary and related models.
</li>
</ul>
</div>
</div>
</div>
`;

exports[`Protected Redaction Messages should render the protected redaction message for Gen 2 1`] = `
<div
class="amplify-flex amplify-message amplify-message--filled amplify-message--warning"
>
<div
aria-hidden="true"
class="amplify-message__icon"
>
<span
class="amplify-icon"
style="width: 1em; height: 1em;"
>
<svg
fill="none"
height="24"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M1 21H23L12 2L1 21ZM13 18H11V16H13V18ZM13 14H11V10H13V14Z"
fill="currentColor"
/>
</svg>
</span>
</div>
<div
class="amplify-flex amplify-message__content"
>
<div>
<p>
With Amplify Data Construct
<code>
@aws-amplify/data-construct@1.8.4
</code>
, 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.
</p>
<p>
This redaction occurs whenever it cannot be determined that the child model will be protected by the same permissions as the parent model.
</p>
<p>
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.
</p>
<p>
If an authorized end-user needs access to the redacted relational fields, they should perform a query to read the relational data.
</p>
<p>
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.
</p>
</div>
</div>
</div>
`;
Loading

0 comments on commit 9e79e97

Please sign in to comment.