Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add warning regarding subscription and mutation redaction of relational fields #7768

Merged
merged 22 commits into from
Jul 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
5ced513
add warning regarding subscription and mutation redaction of relation…
chrisbonifacio Jun 20, 2024
a089726
replace authn with authz in warning
chrisbonifacio Jun 21, 2024
7e7b870
chore(api): Callout for field redaction on Swift Android relational m…
lawmicha Jun 24, 2024
19a0c68
Update src/pages/gen1/[platform]/build-a-backend/graphqlapi/relationa…
lawmicha Jun 24, 2024
3e58b37
add feature flag to warning for gen 1
chrisbonifacio Jun 24, 2024
1f60542
Merge branch 'main' of github.com:aws-amplify/docs into cbonif/add-re…
chrisbonifacio Jun 28, 2024
ff0e75c
Merge branch 'cbonif/add-redaction-warning' of github.com:aws-amplify…
chrisbonifacio Jun 28, 2024
3176079
Fix heading order in fragments affecting this page
hbuchel Jun 28, 2024
0d02cbf
add protected redaction message components
chrisbonifacio Jun 28, 2024
ac07c2c
Add tests for redaction message Gen 1 and Gen 2 components
chrisbonifacio Jun 28, 2024
8bf4037
add snapshots for redaction Gen 1 and Gen 2 component tests
chrisbonifacio Jun 28, 2024
ec05139
Adds ProtectedRedactionMessage components for Gen 1 and Gen2
chrisbonifacio Jun 28, 2024
1045efb
Render ProtectedRedactionGen1Message component on Gen 1 realtime page
chrisbonifacio Jun 28, 2024
2e540e1
Render ProtectedRedactionGen1Message component on Gen 2 data modeling…
chrisbonifacio Jun 28, 2024
797f116
Render ProtectedRedactionGen2Message component on Gen 2 realtime page
chrisbonifacio Jun 28, 2024
c35da8f
Render ProtectedRedactionGen1Message component on Gen 1 data modeling…
chrisbonifacio Jun 28, 2024
c9dbc63
Render ProtectedRedactionGen1Message component on Gen 1 V5 realtime page
chrisbonifacio Jun 28, 2024
fa3d30d
Merge branch 'cbonif/add-redaction-warning' of github.com:aws-amplify…
chrisbonifacio Jun 28, 2024
b907514
add subscriptionsInheritPrimaryAuth as a feature flag
chrisbonifacio Jun 28, 2024
013d5dd
correct version for subscriptionsInheritPrimaryAuth
chrisbonifacio Jun 28, 2024
22b6a18
remove commented code
chrisbonifacio Jun 28, 2024
fbba8a1
fix heading order
Jul 1, 2024
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
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
2 changes: 1 addition & 1 deletion src/fragments/lib-v1/graphqlapi/ios/subscribe-data.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ func createSubscription() {

</BlockSwitcher>

### Unsubscribing from updates
## Unsubscribing from updates

#### Listener (iOS 11+)

Expand Down
2 changes: 1 addition & 1 deletion src/fragments/lib/graphqlapi/flutter/subscribe-data.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ Amplify.Hub.listen(
);
```

#### SubscriptionStatus
### SubscriptionStatus

- **`connected`** - Connected and working with no issues
- **`connecting`** - Attempting to connect (both initial connection and reconnection)
Expand Down
6 changes: 3 additions & 3 deletions src/fragments/lib/graphqlapi/ios/subscribe-data.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -87,9 +87,9 @@ func createSubscription() {

</BlockSwitcher>

### Unsubscribing from updates
## Unsubscribing from updates

#### Async/Await
### Async/Await

To unsubscribe from updates, you can call `cancel()` on the subscription.

Expand All @@ -100,7 +100,7 @@ func cancelSubscription() {
}
```

#### Combine
### Combine

Calling `cancel()` on the sequence will disconnect the subscription from the backend. Any downstream subscribers will also be cancelled.

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();
});
});
Loading
Loading