diff --git a/src/components/FeatureFlags/feature-flags.json b/src/components/FeatureFlags/feature-flags.json index a2e95d99bf9..73f774d3b88 100644 --- a/src/components/FeatureFlags/feature-flags.json +++ b/src/components/FeatureFlags/feature-flags.json @@ -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 + } + ] } } }, diff --git a/src/fragments/lib-v1/graphqlapi/ios/subscribe-data.mdx b/src/fragments/lib-v1/graphqlapi/ios/subscribe-data.mdx index c4f51356633..ce1e4e01a2f 100644 --- a/src/fragments/lib-v1/graphqlapi/ios/subscribe-data.mdx +++ b/src/fragments/lib-v1/graphqlapi/ios/subscribe-data.mdx @@ -82,7 +82,7 @@ func createSubscription() { -### Unsubscribing from updates +## Unsubscribing from updates #### Listener (iOS 11+) diff --git a/src/fragments/lib/graphqlapi/flutter/subscribe-data.mdx b/src/fragments/lib/graphqlapi/flutter/subscribe-data.mdx index 94f62434e52..a27c521b8ff 100644 --- a/src/fragments/lib/graphqlapi/flutter/subscribe-data.mdx +++ b/src/fragments/lib/graphqlapi/flutter/subscribe-data.mdx @@ -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) diff --git a/src/fragments/lib/graphqlapi/ios/subscribe-data.mdx b/src/fragments/lib/graphqlapi/ios/subscribe-data.mdx index 8aed3a7d48a..72008794ea5 100644 --- a/src/fragments/lib/graphqlapi/ios/subscribe-data.mdx +++ b/src/fragments/lib/graphqlapi/ios/subscribe-data.mdx @@ -87,9 +87,9 @@ func createSubscription() { -### Unsubscribing from updates +## Unsubscribing from updates -#### Async/Await +### Async/Await To unsubscribe from updates, you can call `cancel()` on the subscription. @@ -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. diff --git a/src/pages/[platform]/build-a-backend/data/data-modeling/relationships/index.mdx b/src/pages/[platform]/build-a-backend/data/data-modeling/relationships/index.mdx index 5a36e4a1ba1..e7d030c32fd 100644 --- a/src/pages/[platform]/build-a-backend/data/data-modeling/relationships/index.mdx +++ b/src/pages/[platform]/build-a-backend/data/data-modeling/relationships/index.mdx @@ -24,7 +24,7 @@ export const getStaticPaths = async () => { export function getStaticProps(context) { return { props: { - + meta } }; @@ -32,6 +32,12 @@ export function getStaticProps(context) { 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" + + + ## Types of relationships |Relationship|Code|Description|Example| diff --git a/src/pages/[platform]/build-a-backend/data/subscribe-data/index.mdx b/src/pages/[platform]/build-a-backend/data/subscribe-data/index.mdx index 93591efd0da..58894b78cb0 100644 --- a/src/pages/[platform]/build-a-backend/data/subscribe-data/index.mdx +++ b/src/pages/[platform]/build-a-backend/data/subscribe-data/index.mdx @@ -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" + + + ## 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: diff --git a/src/pages/gen1/[platform]/build-a-backend/graphqlapi/data-modeling/index.mdx b/src/pages/gen1/[platform]/build-a-backend/graphqlapi/data-modeling/index.mdx index eea49006a3d..59c389676cb 100644 --- a/src/pages/gen1/[platform]/build-a-backend/graphqlapi/data-modeling/index.mdx +++ b/src/pages/gen1/[platform]/build-a-backend/graphqlapi/data-modeling/index.mdx @@ -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" + + + ### Has One relationship 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 ```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") # } ``` diff --git a/src/pages/gen1/[platform]/build-a-backend/graphqlapi/relational-models/index.mdx b/src/pages/gen1/[platform]/build-a-backend/graphqlapi/relational-models/index.mdx index 59813b96137..ed7851b5522 100644 --- a/src/pages/gen1/[platform]/build-a-backend/graphqlapi/relational-models/index.mdx +++ b/src/pages/gen1/[platform]/build-a-backend/graphqlapi/relational-models/index.mdx @@ -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. + + +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. + + + ## Prerequisites The following examples have a minimum version requirement of the following: diff --git a/src/pages/gen1/[platform]/build-a-backend/graphqlapi/subscribe-data/index.mdx b/src/pages/gen1/[platform]/build-a-backend/graphqlapi/subscribe-data/index.mdx index 50adb8b5abf..51af5300967 100644 --- a/src/pages/gen1/[platform]/build-a-backend/graphqlapi/subscribe-data/index.mdx +++ b/src/pages/gen1/[platform]/build-a-backend/graphqlapi/subscribe-data/index.mdx @@ -49,6 +49,12 @@ Before you begin, you will need: +{/* This component contains approved messaging and cannot be removed or modified without prior approval */} + +import { ProtectedRedactionGen1Message } from "@/protected/ProtectedRedactionMessage" + + + ## 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. diff --git a/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/subscribe-data/index.mdx b/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/subscribe-data/index.mdx index 9fd2e89d703..0f758f8ec05 100644 --- a/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/subscribe-data/index.mdx +++ b/src/pages/gen1/[platform]/prev/build-a-backend/graphqlapi/subscribe-data/index.mdx @@ -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" + + + ## 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. diff --git a/src/protected/ProtectedRedactionMessage/__tests__/ProtectedRedactionMessage.test.tsx b/src/protected/ProtectedRedactionMessage/__tests__/ProtectedRedactionMessage.test.tsx new file mode 100644 index 00000000000..53ec88223b1 --- /dev/null +++ b/src/protected/ProtectedRedactionMessage/__tests__/ProtectedRedactionMessage.test.tsx @@ -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(//); + }); + + 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(//); + }); + + 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(//); + }); + + 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(//); + }); + + 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(//); + }); + + /* + 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(); + + expect(container.firstChild).toMatchSnapshot(); + }); + + it('should render the protected redaction message for Gen 2', async () => { + const { container } = render(); + + expect(container.firstChild).toMatchSnapshot(); + }); +}); diff --git a/src/protected/ProtectedRedactionMessage/__tests__/__snapshots__/ProtectedRedactionMessage.test.tsx.snap b/src/protected/ProtectedRedactionMessage/__tests__/__snapshots__/ProtectedRedactionMessage.test.tsx.snap new file mode 100644 index 00000000000..42d72237658 --- /dev/null +++ b/src/protected/ProtectedRedactionMessage/__tests__/__snapshots__/ProtectedRedactionMessage.test.tsx.snap @@ -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`] = ` +
+ +
+
+

+ 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 field 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. +

+

+ 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. +
  • +
+
+
+
+`; + +exports[`Protected Redaction Messages should render the protected redaction message for Gen 2 1`] = ` +
+ +
+
+

+ With Amplify Data Construct + + @aws-amplify/data-construct@1.8.4 + + , 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. +

+
+
+
+`; diff --git a/src/protected/ProtectedRedactionMessage/index.tsx b/src/protected/ProtectedRedactionMessage/index.tsx new file mode 100644 index 00000000000..c4180a29e42 --- /dev/null +++ b/src/protected/ProtectedRedactionMessage/index.tsx @@ -0,0 +1,84 @@ +import { Callout } from '@/components/Callout'; + +// WARNING: The messaging in this component should NOT be changed without the appropriate approvals +export const ProtectedRedactionGen1Message = () => ( + +

+ 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 field + 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. +

+

+ 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. +
  • +
+
+); + +// WARNING: The messaging in this component should NOT be changed without the appropriate approvals +export const ProtectedRedactionGen2Message = () => ( + +

+ With Amplify Data Construct @aws-amplify/data-construct@1.8.4 + , 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. +

+
+);