From 611edb05ad4f859643547147f2fc0d42c400077d Mon Sep 17 00:00:00 2001 From: Tim Schmelter Date: Mon, 6 Jan 2025 09:56:37 -0800 Subject: [PATCH 1/2] Revert "Revert "fix: fix policies with overridden table names (#3075)" (#3088)" This reverts commit e592dbaeb1ccb5d46f3b723f89ec073c430d08cf. --- packages/amplify-category-api/package.json | 2 +- .../model-transformer-override.test.ts.snap | 176 ++++++++++++++++++ .../amplify-e2e-core/src/utils/sdk-calls.ts | 30 ++- .../api-override-ddb-table-name.test.ts | 119 ++++++++++++ .../package.json | 2 +- .../dynamo-model-resource-generator.ts | 21 ++- 6 files changed, 342 insertions(+), 8 deletions(-) create mode 100644 packages/amplify-e2e-tests/src/__tests__/api-override-ddb-table-name.test.ts diff --git a/packages/amplify-category-api/package.json b/packages/amplify-category-api/package.json index cb12f6afa0..f1aab9b1d0 100644 --- a/packages/amplify-category-api/package.json +++ b/packages/amplify-category-api/package.json @@ -103,7 +103,7 @@ "coverageProvider": "v8", "coverageThreshold": { "global": { - "branches": 68, + "branches": 67, "functions": 42, "lines": 40 } diff --git a/packages/amplify-category-api/src/__tests__/graphql-transformer/override/__snapshots__/model-transformer-override.test.ts.snap b/packages/amplify-category-api/src/__tests__/graphql-transformer/override/__snapshots__/model-transformer-override.test.ts.snap index 853fb0e4a9..5bf5f98768 100644 --- a/packages/amplify-category-api/src/__tests__/graphql-transformer/override/__snapshots__/model-transformer-override.test.ts.snap +++ b/packages/amplify-category-api/src/__tests__/graphql-transformer/override/__snapshots__/model-transformer-override.test.ts.snap @@ -755,6 +755,50 @@ $util.toJson({})", }, "Type": "AWS::IAM::Role", }, + "PostIAMRoleDefaultPolicy04190CA0": Object { + "Properties": Object { + "PolicyDocument": Object { + "Statement": Array [ + Object { + "Action": Array [ + "dynamodb:BatchGetItem", + "dynamodb:GetRecords", + "dynamodb:GetShardIterator", + "dynamodb:Query", + "dynamodb:GetItem", + "dynamodb:Scan", + "dynamodb:ConditionCheckItem", + "dynamodb:BatchWriteItem", + "dynamodb:PutItem", + "dynamodb:UpdateItem", + "dynamodb:DeleteItem", + "dynamodb:DescribeTable", + ], + "Effect": "Allow", + "Resource": Array [ + Object { + "Fn::GetAtt": Array [ + "PostTable", + "Arn", + ], + }, + Object { + "Ref": "AWS::NoValue", + }, + ], + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "PostIAMRoleDefaultPolicy04190CA0", + "Roles": Array [ + Object { + "Ref": "PostIAMRole83BF708F", + }, + ], + }, + "Type": "AWS::IAM::Policy", + }, "PostTable": Object { "DeletionPolicy": "Delete", "Properties": Object { @@ -1525,6 +1569,50 @@ Object { }, "Type": "AWS::IAM::Role", }, + "CommentIAMRoleDefaultPolicyA8D6F6B5": Object { + "Properties": Object { + "PolicyDocument": Object { + "Statement": Array [ + Object { + "Action": Array [ + "dynamodb:BatchGetItem", + "dynamodb:GetRecords", + "dynamodb:GetShardIterator", + "dynamodb:Query", + "dynamodb:GetItem", + "dynamodb:Scan", + "dynamodb:ConditionCheckItem", + "dynamodb:BatchWriteItem", + "dynamodb:PutItem", + "dynamodb:UpdateItem", + "dynamodb:DeleteItem", + "dynamodb:DescribeTable", + ], + "Effect": "Allow", + "Resource": Array [ + Object { + "Fn::GetAtt": Array [ + "CommentTable", + "Arn", + ], + }, + Object { + "Ref": "AWS::NoValue", + }, + ], + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "CommentIAMRoleDefaultPolicyA8D6F6B5", + "Roles": Array [ + Object { + "Ref": "CommentIAMRoleD5EC5F51", + }, + ], + }, + "Type": "AWS::IAM::Policy", + }, "CommentTable": Object { "DeletionPolicy": "Delete", "Properties": Object { @@ -3006,6 +3094,50 @@ $util.toJson({})", }, "Type": "AWS::IAM::Role", }, + "PostIAMRoleDefaultPolicy04190CA0": Object { + "Properties": Object { + "PolicyDocument": Object { + "Statement": Array [ + Object { + "Action": Array [ + "dynamodb:BatchGetItem", + "dynamodb:GetRecords", + "dynamodb:GetShardIterator", + "dynamodb:Query", + "dynamodb:GetItem", + "dynamodb:Scan", + "dynamodb:ConditionCheckItem", + "dynamodb:BatchWriteItem", + "dynamodb:PutItem", + "dynamodb:UpdateItem", + "dynamodb:DeleteItem", + "dynamodb:DescribeTable", + ], + "Effect": "Allow", + "Resource": Array [ + Object { + "Fn::GetAtt": Array [ + "PostTable", + "Arn", + ], + }, + Object { + "Ref": "AWS::NoValue", + }, + ], + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "PostIAMRoleDefaultPolicy04190CA0", + "Roles": Array [ + Object { + "Ref": "PostIAMRole83BF708F", + }, + ], + }, + "Type": "AWS::IAM::Policy", + }, "PostTable": Object { "DeletionPolicy": "Delete", "Properties": Object { @@ -3748,6 +3880,50 @@ Object { }, "Type": "AWS::IAM::Role", }, + "CommentIAMRoleDefaultPolicyA8D6F6B5": Object { + "Properties": Object { + "PolicyDocument": Object { + "Statement": Array [ + Object { + "Action": Array [ + "dynamodb:BatchGetItem", + "dynamodb:GetRecords", + "dynamodb:GetShardIterator", + "dynamodb:Query", + "dynamodb:GetItem", + "dynamodb:Scan", + "dynamodb:ConditionCheckItem", + "dynamodb:BatchWriteItem", + "dynamodb:PutItem", + "dynamodb:UpdateItem", + "dynamodb:DeleteItem", + "dynamodb:DescribeTable", + ], + "Effect": "Allow", + "Resource": Array [ + Object { + "Fn::GetAtt": Array [ + "CommentTable", + "Arn", + ], + }, + Object { + "Ref": "AWS::NoValue", + }, + ], + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "CommentIAMRoleDefaultPolicyA8D6F6B5", + "Roles": Array [ + Object { + "Ref": "CommentIAMRoleD5EC5F51", + }, + ], + }, + "Type": "AWS::IAM::Policy", + }, "CommentTable": Object { "DeletionPolicy": "Delete", "Properties": Object { diff --git a/packages/amplify-e2e-core/src/utils/sdk-calls.ts b/packages/amplify-e2e-core/src/utils/sdk-calls.ts index 7d4c2e8e1f..3444ca430d 100644 --- a/packages/amplify-e2e-core/src/utils/sdk-calls.ts +++ b/packages/amplify-e2e-core/src/utils/sdk-calls.ts @@ -201,7 +201,35 @@ export const getAmplifyBackendJobStatus = async (jobId: string, appId: string, e .promise(); }; -export const listRolePolicies = async (roleName: string, region: string) => { +export const listRoleNamesContaining = async (searchString: string, region: string): Promise => { + const service = new IAM({ region }); + + const roles: string[] = []; + let isTruncated = true; + let marker: string | undefined; + + while (isTruncated) { + const params = marker ? { Marker: marker } : {}; + const response = await service.listRoles(params).promise(); + + const matchingRoles = response.Roles.filter((role) => role.RoleName.includes(searchString)); + roles.push(...matchingRoles.map((r) => r.RoleName)); + + isTruncated = response.IsTruncated; + marker = response.Marker; + } + + return roles; +}; + +export const getRolePolicy = async (roleName: string, policyName: string, region: string): Promise => { + const service = new IAM({ region }); + const rawDocument = (await service.getRolePolicy({ PolicyName: policyName, RoleName: roleName }).promise()).PolicyDocument; + const decodedDocument = decodeURIComponent(rawDocument); + return JSON.parse(decodedDocument); +}; + +export const listRolePolicies = async (roleName: string, region: string): Promise => { const service = new IAM({ region }); return (await service.listRolePolicies({ RoleName: roleName }).promise()).PolicyNames; }; diff --git a/packages/amplify-e2e-tests/src/__tests__/api-override-ddb-table-name.test.ts b/packages/amplify-e2e-tests/src/__tests__/api-override-ddb-table-name.test.ts new file mode 100644 index 0000000000..8884f9cfa5 --- /dev/null +++ b/packages/amplify-e2e-tests/src/__tests__/api-override-ddb-table-name.test.ts @@ -0,0 +1,119 @@ +import path from 'path'; +import * as fs from 'fs-extra'; +import { + addApiWithoutSchema, + amplifyOverrideApi, + amplifyPush, + amplifyPushOverride, + createNewProjectDir, + deleteProject, + deleteProjectDir, + getAppSyncApi, + getDDBTable, + getProjectMeta, + getRolePolicy, + initJSProjectWithProfile, + listRolePolicies, + listRoleNamesContaining, + replaceOverrideFileWithProjectInfo, + updateApiSchema, + updateSchema, +} from 'amplify-category-api-e2e-core'; + +describe('Override table name', () => { + let projRoot: string; + let projFolderName: string; + beforeEach(async () => { + projFolderName = 'overridename'; + projRoot = await createNewProjectDir(projFolderName); + }); + + afterEach(async () => { + const metaFilePath = path.join(projRoot, 'amplify', '#current-cloud-backend', 'amplify-meta.json'); + if (fs.existsSync(metaFilePath)) { + await deleteProject(projRoot); + } + deleteProjectDir(projRoot); + }); + + it('Generates correct permissions policies for DynamoDB tables with overridden names', async () => { + const now = Math.floor(Date.now() / 1000); + const modelName = `Override${now}`; + + const schema = /* GraphQL */ ` + type ${modelName} @model { + id: ID! + content: String + } + `; + + const envName = 'integtest'; + const projName = 'overridetest'; + const cliInputsFilePath = path.join(projRoot, 'amplify', 'backend', 'api', `${projName}`, 'cli-inputs.json'); + await initJSProjectWithProfile(projRoot, { name: projName, envName }); + await addApiWithoutSchema(projRoot); + + updateSchema(projRoot, projName, schema); + expect(fs.existsSync(cliInputsFilePath)).toBe(true); + + await amplifyPush(projRoot); + + await amplifyOverrideApi(projRoot, {}); + + const overriddenTableName = `OverrideTest${now}Custom`; + const overrideCode = /* TypeScript */ ` + export function override(props: any) { + props.models['${modelName}'].modelDDBTable.tableName = '${overriddenTableName}'; + } + `; + const destOverrideFilePath = path.join(projRoot, 'amplify', 'backend', 'api', `${projName}`, 'override.ts'); + fs.writeFileSync(destOverrideFilePath, overrideCode); + + await amplifyPushOverride(projRoot); + + const meta = getProjectMeta(projRoot); + const region = meta.providers.awscloudformation.Region; + const { output } = meta.api.overridetest; + const { GraphQLAPIIdOutput, GraphQLAPIEndpointOutput, GraphQLAPIKeyOutput } = output; + const { graphqlApi } = await getAppSyncApi(GraphQLAPIIdOutput, region); + + expect(graphqlApi).toBeDefined(); + expect(graphqlApi.apiId).toEqual(GraphQLAPIIdOutput); + expect(GraphQLAPIIdOutput).toBeDefined(); + expect(GraphQLAPIEndpointOutput).toBeDefined(); + expect(GraphQLAPIKeyOutput).toBeDefined(); + + const defaultTableName = `${modelName}-${graphqlApi.apiId}-${envName}`; + const error = { message: null }; + try { + const defaultTable = await getDDBTable(defaultTableName, region); + expect(defaultTable).toBeUndefined(); + } catch (ex) { + Object.assign(error, ex); + } + expect(error).toBeDefined(); + expect(error.message).toContain(`${defaultTableName} not found`); + + const actualTable = await getDDBTable(overriddenTableName, region); + expect(actualTable).toBeDefined(); + + // Validate policy. The role will be created with the prefix {modelName}IAMRole. It should have 2 policies: one created by Amplify, one + // created by AppSync's CDK call during the `addDynamoDbDataSource` flow. We expect the policy statements for the latter to refer to the + // overridden table name. + const matchingRoleNames = await listRoleNamesContaining(modelName, region); + expect(matchingRoleNames).toBeDefined(); + expect(matchingRoleNames.length).toEqual(1); + const roleName = matchingRoleNames[0]; + + const policies = await listRolePolicies(roleName, region); + expect(policies).toBeDefined(); + expect(policies.length).toBe(2); + + const defaultPolicy = policies.find((p) => p.startsWith(`${modelName}IAMRoleDefault`)); + expect(defaultPolicy).toBeDefined(); + + const policyObject = await getRolePolicy(roleName, defaultPolicy, region); + expect(policyObject).toBeDefined(); + expect(policyObject.Statement[0].Resource[0]).toContain(overriddenTableName); + }); +}); diff --git a/packages/amplify-graphql-auth-transformer/package.json b/packages/amplify-graphql-auth-transformer/package.json index ceb3d25492..1164184cf7 100644 --- a/packages/amplify-graphql-auth-transformer/package.json +++ b/packages/amplify-graphql-auth-transformer/package.json @@ -70,7 +70,7 @@ "coverageProvider": "v8", "coverageThreshold": { "global": { - "branches": 88, + "branches": 87, "functions": 90, "lines": 90 } diff --git a/packages/amplify-graphql-model-transformer/src/resources/dynamo-model-resource-generator.ts b/packages/amplify-graphql-model-transformer/src/resources/dynamo-model-resource-generator.ts index a348ba6dee..363d5b5716 100644 --- a/packages/amplify-graphql-model-transformer/src/resources/dynamo-model-resource-generator.ts +++ b/packages/amplify-graphql-model-transformer/src/resources/dynamo-model-resource-generator.ts @@ -231,7 +231,19 @@ export class DynamoModelResourceGenerator extends ModelResourceGenerator { } /** - * createIAMRole + * Create a role, assumable by AppSync, with a policy statement named `DynamoDBAccess`. Policy actions are scoped to the table named + * according to Amplify's default convention (`{modelName}-{apiId}-{envName}`). + * + * In most cases, this will duplicate the `...IAMRoleDefaultPolicy` that is automatically generated by the AppSync CDK's + * `addDynamoDbDataSource` API in {@link createModelTableDataSource}. We create it anyway for 3 reasons: + * + * 1. The `...IAMRoleDefaultPolicy` respects [table name + * overrides](https://docs.amplify.aws/gen1/javascript/build-a-backend/graphqlapi/modify-amplify-generated-resources/#customize-amplify-generated-resources-for-model-directive). + * Overrides are not available to us during creation of this role. + * 2. The `DynamoDBAccess` policy is exposed as an Amplify-generated resource for overrides. Existing customers may be using this and + * depending on the policy statements + * 3. The `DynamoDBAccess` policy includes statements enabling Lambda sync flows, if enabled. Note that the sync policy does not rely on + * table name, so it does not need to be aware of table name overrides. */ createIAMRole = (context: TransformerContextProvider, def: ObjectTypeDefinitionNode, scope: Construct, tableName: string): iam.IRole => { const roleName = context.resourceHelper.generateIAMRoleName(ModelResourceIDs.ModelTableIAMRoleID(def!.name.value)); @@ -239,8 +251,8 @@ export class DynamoModelResourceGenerator extends ModelResourceGenerator { const role = new iam.Role(scope, ModelResourceIDs.ModelTableIAMRoleID(def!.name.value), { roleName, assumedBy: new iam.ServicePrincipal('appsync.amazonaws.com'), - // Use an inline policy here to prevent unnecessary policy resources from being generated - // and slowing down deployments. + // Use an inline policy here to prevent unnecessary policy CloudFormation resources from being generated. Note that CDK will still + // create a CFN resource for `IAMRoleDefaultPolicy` inlinePolicies: { DynamoDBAccess: new iam.PolicyDocument({ statements: [ @@ -296,7 +308,6 @@ export class DynamoModelResourceGenerator extends ModelResourceGenerator { ); } - // return an `IRole` to prevent modification and default policy generation. - return role.withoutPolicyUpdates(); + return role; }; } From f58e095b7d5fd5ea4d3776b6c2bd93ab85cc7a82 Mon Sep 17 00:00:00 2001 From: Tim Schmelter Date: Mon, 13 Jan 2025 08:25:49 -0800 Subject: [PATCH 2/2] test: migrate OIDC auth tests --- .../amplify-e2e-core/src/categories/api.ts | 6 +- .../rds-mysql-oidc-auth-fields.test.ts | 9 - .../src/__tests__/rds-mysql-oidc-auth.test.ts | 1348 ------- .../__tests__/rds-pg-oidc-auth-fields.test.ts | 9 - .../src/__tests__/rds-pg-oidc-auth.test.ts | 9 - .../rds-auth-oidc-fields.ts | 3592 ----------------- .../src/rds-v2-tests-common/rds-auth-oidc.ts | 1353 ------- 7 files changed, 4 insertions(+), 6322 deletions(-) delete mode 100644 packages/amplify-e2e-tests/src/__tests__/rds-mysql-oidc-auth-fields.test.ts delete mode 100644 packages/amplify-e2e-tests/src/__tests__/rds-mysql-oidc-auth.test.ts delete mode 100644 packages/amplify-e2e-tests/src/__tests__/rds-pg-oidc-auth-fields.test.ts delete mode 100644 packages/amplify-e2e-tests/src/__tests__/rds-pg-oidc-auth.test.ts delete mode 100644 packages/amplify-e2e-tests/src/rds-v2-tests-common/rds-auth-oidc-fields.ts delete mode 100644 packages/amplify-e2e-tests/src/rds-v2-tests-common/rds-auth-oidc.ts diff --git a/packages/amplify-e2e-core/src/categories/api.ts b/packages/amplify-e2e-core/src/categories/api.ts index 3e796b77a2..f8b3a66f15 100644 --- a/packages/amplify-e2e-core/src/categories/api.ts +++ b/packages/amplify-e2e-core/src/categories/api.ts @@ -1,3 +1,5 @@ +/* eslint-disable prefer-arrow/prefer-arrow-functions */ +/* eslint-disable func-style */ import * as path from 'path'; import { ConflictHandlerType } from '@aws-amplify/graphql-transformer-core'; import * as fs from 'fs-extra'; @@ -729,7 +731,7 @@ export function updateRestApi(cwd: string, settings: Partial { const transformerVersion = settings?.transformerVersion ?? 2; delete settings?.transformerVersion; const authTypesToSkipSetup = settings?.authTypesToSkipSetup ?? []; @@ -737,7 +739,7 @@ export function addApi(projectDir: string, settings?: any) { let authTypesToSelectFrom = allAuthTypes.slice(); return new Promise((resolve, reject) => { - let chain = spawn(getCLIPath(defaultOptions.testingWithLatestCodebase), ['add', 'api'], { cwd: projectDir, stripColors: true }) + const chain = spawn(getCLIPath(defaultOptions.testingWithLatestCodebase), ['add', 'api'], { cwd: projectDir, stripColors: true }) .wait('Select from one of the below mentioned services:') .sendCarriageReturn(); diff --git a/packages/amplify-e2e-tests/src/__tests__/rds-mysql-oidc-auth-fields.test.ts b/packages/amplify-e2e-tests/src/__tests__/rds-mysql-oidc-auth-fields.test.ts deleted file mode 100644 index 2670a37260..0000000000 --- a/packages/amplify-e2e-tests/src/__tests__/rds-mysql-oidc-auth-fields.test.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { ImportedRDSType } from '@aws-amplify/graphql-transformer-core'; -import { testOIDCFieldAuth } from '../rds-v2-tests-common/rds-auth-oidc-fields'; - -// to deal with bug in cognito-identity-js -(global as any).fetch = require('node-fetch'); - -describe('SQL MySQL OIDC field Auth', () => { - testOIDCFieldAuth(ImportedRDSType.MYSQL); -}); diff --git a/packages/amplify-e2e-tests/src/__tests__/rds-mysql-oidc-auth.test.ts b/packages/amplify-e2e-tests/src/__tests__/rds-mysql-oidc-auth.test.ts deleted file mode 100644 index 24119a1ee5..0000000000 --- a/packages/amplify-e2e-tests/src/__tests__/rds-mysql-oidc-auth.test.ts +++ /dev/null @@ -1,1348 +0,0 @@ -import { - addApi, - amplifyPush, - createNewProjectDir, - deleteDBInstance, - deleteProject, - deleteProjectDir, - addAuthWithPreTokenGenerationTrigger, - importRDSDatabase, - initJSProjectWithProfile, - setupRDSInstanceAndData, - sleep, - updateAuthAddUserGroups, - getProjectMeta, -} from 'amplify-category-api-e2e-core'; -import { existsSync, writeFileSync, removeSync } from 'fs-extra'; -import generator from 'generate-password'; -import path from 'path'; -import { schema as generateSchema, sqlCreateStatements } from './auth-test-schemas/oidc-provider'; -import { - createModelOperationHelpers, - configureAppSyncClients, - checkOperationResult, - checkListItemExistence, - appendAmplifyInput, - getAppSyncEndpoint, - updatePreAuthTrigger, -} from '../rds-v2-test-utils'; -import { - setupUser, - getUserPoolId, - signInUser, - getUserPoolIssUrl, - getAppClientIDWeb, - configureAmplify, - getConfiguredAppsyncClientOIDCAuth, -} from '../schema-api-directives'; -import { gql } from 'graphql-tag'; -import { ImportedRDSType } from '@aws-amplify/graphql-transformer-core'; -import { SQL_TESTS_USE_BETA } from '../rds-v2-tests-common/sql-e2e-config'; - -// to deal with bug in cognito-identity-js -(global as any).fetch = require('node-fetch'); - -describe('RDS OIDC provider Auth tests', () => { - const [db_user, db_password, db_identifier] = generator.generateMultiple(3); - const schema = generateSchema(ImportedRDSType.MYSQL); - // Generate settings for RDS instance - const username = db_user; - const password = db_password; - let region = 'us-east-1'; - let port = 3306; - const database = 'default_db'; - let host = 'localhost'; - const identifier = `integtest${db_identifier}`; - const projName = 'rdsoidcauth'; - const userName1 = 'user1'; - const userName2 = 'user2'; - const adminGroupName = 'Admin'; - const devGroupName = 'Dev'; - const userPassword = 'user@Password'; - const oidcProvider = 'oidc'; - let graphQlEndpoint = 'localhost'; - - let projRoot; - let appSyncClients = {}; - const userMap = {}; - const engine = ImportedRDSType.MYSQL; - - beforeAll(async () => { - console.log(sqlCreateStatements(engine)); - projRoot = await createNewProjectDir(projName); - await setupAmplifyProject(); - }); - - afterAll(async () => { - const metaFilePath = path.join(projRoot, 'amplify', '#current-cloud-backend', 'amplify-meta.json'); - if (existsSync(metaFilePath)) { - await deleteProject(projRoot); - } - deleteProjectDir(projRoot); - await cleanupDatabase(); - }); - - const setupDatabase = async (): Promise => { - const dbConfig = { - identifier, - engine: 'mysql' as const, - dbname: database, - username, - password, - region, - }; - - const db = await setupRDSInstanceAndData(dbConfig, sqlCreateStatements(engine)); - port = db.port; - host = db.endpoint; - }; - - const cleanupDatabase = async (): Promise => { - await deleteDBInstance(identifier, region); - }; - - const subscriptionWithOwner = (name: string, ownerField: string = 'owner'): string => { - return /* GraphQL */ ` - subscription On${name}($${ownerField}: String) { - on${name}(${ownerField}: $${ownerField}) { - id - content - ${ownerField} - } - } - `; - }; - - const setupAmplifyProject = async (): Promise => { - const apiName = projName; - await initJSProjectWithProfile(projRoot, { - disableAmplifyAppCreation: false, - name: projName, - }); - - const metaAfterInit = getProjectMeta(projRoot); - region = metaAfterInit.providers.awscloudformation.Region; - - await addAuthWithPreTokenGenerationTrigger(projRoot); - updatePreAuthTrigger(projRoot, 'user_id'); - await amplifyPush(projRoot, false, { - skipCodegen: true, - useBetaSqlLayer: SQL_TESTS_USE_BETA, - }); - - await addApi(projRoot, { - 'OpenID Connect': { - oidcProviderName: 'awscognitouserpool', - oidcProviderDomain: getUserPoolIssUrl(projRoot), - oidcClientId: getAppClientIDWeb(projRoot), - ttlaIssueInMillisecond: '3600000', - ttlaAuthInMillisecond: '3600000', - }, - 'API key': {}, - transformerVersion: 2, - }); - - const rdsSchemaFilePath = path.join(projRoot, 'amplify', 'backend', 'api', apiName, 'schema.sql.graphql'); - const ddbSchemaFilePath = path.join(projRoot, 'amplify', 'backend', 'api', apiName, 'schema.graphql'); - removeSync(ddbSchemaFilePath); - - await setupDatabase(); - await importRDSDatabase(projRoot, { - database, - host, - port, - username, - password, - useVpc: true, - apiExists: true, - }); - writeFileSync(rdsSchemaFilePath, appendAmplifyInput(schema, ImportedRDSType.MYSQL), 'utf8'); - - await updateAuthAddUserGroups(projRoot, [adminGroupName, devGroupName]); - await amplifyPush(projRoot, false, { - useBetaSqlLayer: SQL_TESTS_USE_BETA, - }); - await sleep(2 * 60 * 1000); // Wait for 2 minutes for the VPC endpoints to be live. - - const userPoolId = getUserPoolId(projRoot); - configureAmplify(projRoot); - await setupUser(userPoolId, userName1, userPassword, adminGroupName); - await setupUser(userPoolId, userName2, userPassword, devGroupName); - graphQlEndpoint = getAppSyncEndpoint(projRoot, apiName); - const user1 = await signInUser(userName1, userPassword); - userMap[userName1] = user1; - const user2 = await signInUser(userName2, userPassword); - userMap[userName2] = user2; - appSyncClients = await configureAppSyncClients(projRoot, apiName, [oidcProvider], userMap); - }; - - test('logged in user can perform CRUD and subscription operations', async () => { - const modelName = 'TodoPrivate'; - const modelOperationHelpers = createModelOperationHelpers(appSyncClients[oidcProvider][userName1], schema); - const todoHelper = modelOperationHelpers[modelName]; - - const todo = { - content: 'Todo', - }; - const resultSetName = `create${modelName}`; - const createResult = await todoHelper.create(resultSetName, todo); - expect(createResult.data[resultSetName].id).toBeDefined(); - todo['id'] = createResult.data[resultSetName].id; - checkOperationResult(createResult, todo, resultSetName); - - const todoUpdated = { - id: todo['id'], - content: 'Todo updated', - }; - const updateResult = await todoHelper.update(`update${modelName}`, todoUpdated); - checkOperationResult(updateResult, todoUpdated, `update${modelName}`); - - const getResult = await todoHelper.get({ - id: todo['id'], - }); - checkOperationResult(getResult, todoUpdated, `get${modelName}`); - - const listTodosResult = await todoHelper.list(); - checkListItemExistence(listTodosResult, `list${modelName}s`, todo['id'], true); - - const deleteResult = await todoHelper.delete(`delete${modelName}`, { - id: todo['id'], - }); - checkOperationResult(deleteResult, todoUpdated, `delete${modelName}`); - - const todoRandom = { - id: Date.now().toString(), - content: 'Todo', - }; - const todoRandomUpdated = { - ...todoRandom, - content: 'Todo updated', - }; - const actorClient = getConfiguredAppsyncClientOIDCAuth(graphQlEndpoint, region, userMap[userName1]); - const subTodoHelper = createModelOperationHelpers(actorClient, schema)[modelName]; - - const onCreateSubscriptionResult = await subTodoHelper.subscribe('onCreate', [ - async () => { - await subTodoHelper.create(`create${modelName}`, todoRandom); - }, - ]); - expect(onCreateSubscriptionResult).toHaveLength(1); - checkOperationResult(onCreateSubscriptionResult[0], todoRandom, `onCreate${modelName}`); - const onUpdateSubscriptionResult = await subTodoHelper.subscribe('onUpdate', [ - async () => { - await subTodoHelper.update(`update${modelName}`, todoRandomUpdated); - }, - ]); - expect(onUpdateSubscriptionResult).toHaveLength(1); - checkOperationResult(onUpdateSubscriptionResult[0], todoRandomUpdated, `onUpdate${modelName}`); - const onDeleteSubscriptionResult = await subTodoHelper.subscribe('onDelete', [ - async () => { - await subTodoHelper.delete(`delete${modelName}`, { id: todoRandom.id }); - }, - ]); - expect(onDeleteSubscriptionResult).toHaveLength(1); - checkOperationResult(onDeleteSubscriptionResult[0], todoRandomUpdated, `onDelete${modelName}`); - }); - - test('owner of a record can perform CRUD and subscription operations using default owner field', async () => { - const modelName = 'TodoOwner'; - const modelOperationHelpers = createModelOperationHelpers(appSyncClients[oidcProvider][userName1], schema); - const todoHelper = modelOperationHelpers[modelName]; - - const todo = { - content: 'Todo', - }; - const resultSetName = `create${modelName}`; - const createResult = await todoHelper.create(resultSetName, todo); - expect(createResult.data[resultSetName].id).toBeDefined(); - todo['id'] = createResult.data[resultSetName].id; - expect(createResult.data[resultSetName].content).toEqual(todo.content); - expect(createResult.data[resultSetName].owner).toBeDefined(); - - const todoWithOwner = { - ...todo, - owner: createResult.data[resultSetName].owner, - }; - - const todoUpdated = { - id: todo['id'], - content: 'Todo updated', - owner: todoWithOwner.owner, - }; - const updateResult = await todoHelper.update(`update${modelName}`, todoUpdated); - checkOperationResult(updateResult, todoUpdated, `update${modelName}`); - - const getResult = await todoHelper.get({ - id: todo['id'], - }); - checkOperationResult(getResult, todoUpdated, `get${modelName}`); - - const listTodosResult = await todoHelper.list(); - checkListItemExistence(listTodosResult, `list${modelName}s`, todo['id'], true); - - const deleteResult = await todoHelper.delete(`delete${modelName}`, { - id: todo['id'], - }); - checkOperationResult(deleteResult, todoUpdated, `delete${modelName}`); - - const todoRandom = { - id: Date.now().toString(), - content: 'Todo', - owner: userName1, - }; - const todoRandomUpdated = { - ...todoRandom, - content: 'Todo updated', - }; - const actorClient = getConfiguredAppsyncClientOIDCAuth(graphQlEndpoint, region, userMap[userName1]); - const subTodoHelper = createModelOperationHelpers(actorClient, schema)[modelName]; - - const onCreateSubscriptionResult = await subTodoHelper.subscribe('onCreate', [ - async () => { - await subTodoHelper.create(`create${modelName}`, todoRandom); - }, - ]); - expect(onCreateSubscriptionResult).toHaveLength(1); - checkOperationResult(onCreateSubscriptionResult[0], todoRandom, `onCreate${modelName}`); - const onUpdateSubscriptionResult = await subTodoHelper.subscribe('onUpdate', [ - async () => { - await subTodoHelper.update(`update${modelName}`, todoRandomUpdated); - }, - ]); - expect(onUpdateSubscriptionResult).toHaveLength(1); - checkOperationResult(onUpdateSubscriptionResult[0], todoRandomUpdated, `onUpdate${modelName}`); - const onDeleteSubscriptionResult = await subTodoHelper.subscribe('onDelete', [ - async () => { - await subTodoHelper.delete(`delete${modelName}`, { id: todoRandom.id }); - }, - ]); - expect(onDeleteSubscriptionResult).toHaveLength(1); - checkOperationResult(onDeleteSubscriptionResult[0], todoRandomUpdated, `onDelete${modelName}`); - }); - - test('non-owner of a record cannot access or subscribe to it using default owner field', async () => { - const modelName = 'TodoOwner'; - const modelOperationHelpersOwner = createModelOperationHelpers(appSyncClients[oidcProvider][userName1], schema); - const modelOperationHelpersNonOwner = createModelOperationHelpers(appSyncClients[oidcProvider][userName2], schema); - const todoHelperOwner = modelOperationHelpersOwner[modelName]; - const todoHelperNonOwner = modelOperationHelpersNonOwner[modelName]; - - const todo = { - content: 'Todo', - }; - const resultSetName = `create${modelName}`; - const createResult = await todoHelperOwner.create(resultSetName, todo); - expect(createResult.data[resultSetName].id).toBeDefined(); - todo['id'] = createResult.data[resultSetName].id; - - const todoUpdated = { - id: todo['id'], - content: 'Todo updated', - owner: userName2, - }; - await expect(async () => { - await todoHelperNonOwner.update(`update${modelName}`, todoUpdated); - }).rejects.toThrowErrorMatchingInlineSnapshot(`"GraphQL error: Not Authorized to access updateTodoOwner on type Mutation"`); - - await expect(async () => { - const getResult = await todoHelperNonOwner.get({ - id: todo['id'], - }); - }).rejects.toThrowErrorMatchingInlineSnapshot(`"GraphQL error: Not Authorized to access getTodoOwner on type Query"`); - - const listTodosResult = await todoHelperNonOwner.list(); - checkListItemExistence(listTodosResult, `list${modelName}s`, todo['id']); - - await expect(async () => { - await todoHelperNonOwner.delete(`delete${modelName}`, { - id: todo['id'], - }); - }).rejects.toThrowErrorMatchingInlineSnapshot(`"GraphQL error: Not Authorized to access deleteTodoOwner on type Mutation"`); - - const todoRandom = { - id: Date.now().toString(), - content: 'Todo', - owner: userName1, - }; - const todoRandomUpdated = { - ...todoRandom, - content: 'Todo updated', - }; - const actorClient = getConfiguredAppsyncClientOIDCAuth(graphQlEndpoint, region, userMap[userName1]); - const observerClient = getConfiguredAppsyncClientOIDCAuth(graphQlEndpoint, region, userMap[userName2]); - const actorTodoHelper = createModelOperationHelpers(actorClient, schema)[modelName]; - const observerTodoHelper = createModelOperationHelpers(observerClient, schema)[modelName]; - - const onCreateSubscriptionResult = await observerTodoHelper.subscribe('onCreate', [ - async () => { - await actorTodoHelper.create(`create${modelName}`, todoRandom); - }, - ]); - expect(onCreateSubscriptionResult).toHaveLength(0); - const onUpdateSubscriptionResult = await observerTodoHelper.subscribe('onUpdate', [ - async () => { - await actorTodoHelper.update(`update${modelName}`, todoRandomUpdated); - }, - ]); - expect(onUpdateSubscriptionResult).toHaveLength(0); - const onDeleteSubscriptionResult = await observerTodoHelper.subscribe('onDelete', [ - async () => { - await actorTodoHelper.delete(`delete${modelName}`, { id: todoRandom.id }); - }, - ]); - expect(onDeleteSubscriptionResult).toHaveLength(0); - }); - - test('custom owner field used to store owner information', async () => { - const modelName = 'TodoOwnerFieldString'; - const modelOperationHelpers = createModelOperationHelpers(appSyncClients[oidcProvider][userName1], schema); - const todoHelper = modelOperationHelpers[modelName]; - - const todo = { - content: 'Todo', - }; - const resultSetName = `create${modelName}`; - const createResult = await todoHelper.create(resultSetName, todo); - expect(createResult.data[resultSetName].id).toBeDefined(); - todo['id'] = createResult.data[resultSetName].id; - expect(createResult.data[resultSetName].content).toEqual(todo.content); - expect(createResult.data[resultSetName].author).toEqual(userName1); - - const todoWithOwner = { - ...todo, - author: userName1, - }; - - const todo1Updated = { - id: todo['id'], - content: 'Todo updated', - author: todoWithOwner.author, - }; - const updateResult = await todoHelper.update(`update${modelName}`, todo1Updated); - checkOperationResult(updateResult, todo1Updated, `update${modelName}`); - - const getResult = await todoHelper.get({ - id: todo['id'], - }); - checkOperationResult(getResult, todo1Updated, `get${modelName}`); - - const listTodosResult = await todoHelper.list(); - checkListItemExistence(listTodosResult, `list${modelName}s`, todo['id'], true); - - const deleteResult = await todoHelper.delete(`delete${modelName}`, { - id: todo['id'], - }); - checkOperationResult(deleteResult, todo1Updated, `delete${modelName}`); - - const todoRandom = { - id: Date.now().toString(), - content: 'Todo', - author: userName1, - }; - const todoRandomUpdated = { - ...todoRandom, - content: 'Todo updated', - }; - const actorClient = getConfiguredAppsyncClientOIDCAuth(graphQlEndpoint, region, userMap[userName1]); - const subTodoHelper = createModelOperationHelpers(actorClient, schema)[modelName]; - - const onCreateSubscriptionResult = await subTodoHelper.subscribe( - 'onCreate', - [ - async () => { - await subTodoHelper.create(`create${modelName}`, todoRandom); - }, - ], - { author: userName1 }, - subscriptionWithOwner(`Create${modelName}`, 'author'), - ); - expect(onCreateSubscriptionResult).toHaveLength(1); - checkOperationResult(onCreateSubscriptionResult[0], todoRandom, `onCreate${modelName}`); - const onUpdateSubscriptionResult = await subTodoHelper.subscribe('onUpdate', [ - async () => { - await subTodoHelper.update(`update${modelName}`, todoRandomUpdated); - }, - ]); - expect(onUpdateSubscriptionResult).toHaveLength(1); - checkOperationResult(onUpdateSubscriptionResult[0], todoRandomUpdated, `onUpdate${modelName}`); - const onDeleteSubscriptionResult = await subTodoHelper.subscribe('onDelete', [ - async () => { - await subTodoHelper.delete(`delete${modelName}`, { id: todoRandom.id }); - }, - ]); - expect(onDeleteSubscriptionResult).toHaveLength(1); - checkOperationResult(onDeleteSubscriptionResult[0], todoRandomUpdated, `onDelete${modelName}`); - }); - - test('non-owner of a record cannot pretend to be an owner and gain access', async () => { - const modelName = 'TodoOwnerFieldString'; - const modelOperationHelpersOwner = createModelOperationHelpers(appSyncClients[oidcProvider][userName1], schema); - const modelOperationHelpersNonOwner = createModelOperationHelpers(appSyncClients[oidcProvider][userName2], schema); - const todoHelperOwner = modelOperationHelpersOwner[modelName]; - const todoHelperNonOwner = modelOperationHelpersNonOwner[modelName]; - - const todo = { - content: 'Todo', - }; - const resultSetName = `create${modelName}`; - const createResult = await todoHelperOwner.create(resultSetName, todo); - expect(createResult.data[resultSetName].id).toBeDefined(); - todo['id'] = createResult.data[resultSetName].id; - - const todoUpdated = { - id: todo['id'], - content: 'Todo updated', - author: userName1, - }; - await expect(async () => { - await todoHelperNonOwner.update(`update${modelName}`, todoUpdated); - }).rejects.toThrowErrorMatchingInlineSnapshot(`"GraphQL error: Not Authorized to access updateTodoOwnerFieldString on type Mutation"`); - - await expect(async () => { - const getResult = await todoHelperNonOwner.get({ - id: todo['id'], - }); - }).rejects.toThrowErrorMatchingInlineSnapshot(`"GraphQL error: Not Authorized to access getTodoOwnerFieldString on type Query"`); - - const listTodosResult = await todoHelperNonOwner.list(); - checkListItemExistence(listTodosResult, `list${modelName}s`, todo['id']); - - await expect(async () => { - await todoHelperNonOwner.delete(`delete${modelName}`, { - id: todo['id'], - }); - }).rejects.toThrowErrorMatchingInlineSnapshot(`"GraphQL error: Not Authorized to access deleteTodoOwnerFieldString on type Mutation"`); - - const todoRandom = { - id: Date.now().toString(), - content: 'Todo', - author: userName1, - }; - const todoRandomUpdated = { - ...todoRandom, - content: 'Todo updated', - }; - - const actorClient = getConfiguredAppsyncClientOIDCAuth(graphQlEndpoint, region, userMap[userName1]); - const observerClient = getConfiguredAppsyncClientOIDCAuth(graphQlEndpoint, region, userMap[userName2]); - const actorTodoHelper = createModelOperationHelpers(actorClient, schema)[modelName]; - const observerTodoHelper = createModelOperationHelpers(observerClient, schema)[modelName]; - - const onCreateSubscriptionResult = await observerTodoHelper.subscribe( - 'onCreate', - [ - async () => { - await actorTodoHelper.create(`create${modelName}`, todoRandom); - }, - ], - { author: userName1 }, - subscriptionWithOwner(`Create${modelName}`, 'author'), - ); - expect(onCreateSubscriptionResult).toHaveLength(0); - const onUpdateSubscriptionResult = await observerTodoHelper.subscribe('onUpdate', [ - async () => { - await actorTodoHelper.update(`update${modelName}`, todoRandomUpdated); - }, - ]); - expect(onUpdateSubscriptionResult).toHaveLength(0); - const onDeleteSubscriptionResult = await observerTodoHelper.subscribe('onDelete', [ - async () => { - await actorTodoHelper.delete(`delete${modelName}`, { id: todoRandom.id }); - }, - ]); - expect(onDeleteSubscriptionResult).toHaveLength(0); - }); - - test('member in list of owners can perform CRUD and subscription operations', async () => { - const modelName = 'TodoOwnerFieldList'; - const modelOperationHelpers = createModelOperationHelpers(appSyncClients[oidcProvider][userName1], schema); - const todoHelper = modelOperationHelpers[modelName]; - - const todo = { - content: 'Todo', - authors: [userName1], - }; - const resultSetName = `create${modelName}`; - const createResult = await todoHelper.create(resultSetName, todo); - expect(createResult.data[resultSetName].id).toBeDefined(); - todo['id'] = createResult.data[resultSetName].id; - expect(createResult.data[resultSetName].content).toEqual(todo.content); - expect(createResult.data[resultSetName].authors).toEqual([userName1]); - - const todoUpdated = { - id: todo['id'], - content: 'Todo updated', - authors: [userName1], - }; - const updateResult = await todoHelper.update(`update${modelName}`, todoUpdated); - checkOperationResult(updateResult, todoUpdated, `update${modelName}`); - - const getResult = await todoHelper.get({ - id: todo['id'], - }); - checkOperationResult(getResult, todoUpdated, `get${modelName}`); - - const listTodosResult = await todoHelper.list(); - checkListItemExistence(listTodosResult, `list${modelName}s`, todo['id'], true); - - const deleteResult = await todoHelper.delete(`delete${modelName}`, { - id: todo['id'], - }); - checkOperationResult(deleteResult, todoUpdated, `delete${modelName}`); - - const todoRandom = { - id: Date.now().toString(), - content: 'Todo', - authors: [userName1], - }; - const todoRandomUpdated = { - ...todoRandom, - content: 'Todo updated', - }; - const actorClient = getConfiguredAppsyncClientOIDCAuth(graphQlEndpoint, region, userMap[userName1]); - const subTodoHelper = createModelOperationHelpers(actorClient, schema)[modelName]; - - const onCreateSubscriptionResult = await subTodoHelper.subscribe('onCreate', [ - async () => { - await subTodoHelper.create(`create${modelName}`, todoRandom); - }, - ]); - expect(onCreateSubscriptionResult).toHaveLength(1); - checkOperationResult(onCreateSubscriptionResult[0], todoRandom, `onCreate${modelName}`); - const onUpdateSubscriptionResult = await subTodoHelper.subscribe('onUpdate', [ - async () => { - await subTodoHelper.update(`update${modelName}`, todoRandomUpdated); - }, - ]); - expect(onUpdateSubscriptionResult).toHaveLength(1); - checkOperationResult(onUpdateSubscriptionResult[0], todoRandomUpdated, `onUpdate${modelName}`); - const onDeleteSubscriptionResult = await subTodoHelper.subscribe('onDelete', [ - async () => { - await subTodoHelper.delete(`delete${modelName}`, { id: todoRandom.id }); - }, - ]); - expect(onDeleteSubscriptionResult).toHaveLength(1); - checkOperationResult(onDeleteSubscriptionResult[0], todoRandomUpdated, `onDelete${modelName}`); - }); - - test('non-owner of a record cannot add themself to owner list', async () => { - const modelName = 'TodoOwnerFieldList'; - const modelOperationHelpersOwner = createModelOperationHelpers(appSyncClients[oidcProvider][userName1], schema); - const modelOperationHelpersNonOwner = createModelOperationHelpers(appSyncClients[oidcProvider][userName2], schema); - const todoHelperOwner = modelOperationHelpersOwner[modelName]; - const todoHelperNonOwner = modelOperationHelpersNonOwner[modelName]; - - const todo = { - content: 'Todo', - authors: [userName1], - }; - const resultSetName = `create${modelName}`; - const createResult = await todoHelperOwner.create(resultSetName, todo); - expect(createResult.data[resultSetName].id).toBeDefined(); - todo['id'] = createResult.data[resultSetName].id; - - const todoUpdated = { - id: todo['id'], - content: 'Todo updated', - authors: [userName1, userName2], - }; - await expect(async () => { - await todoHelperNonOwner.update(`update${modelName}`, todoUpdated); - }).rejects.toThrowErrorMatchingInlineSnapshot(`"GraphQL error: Not Authorized to access updateTodoOwnerFieldList on type Mutation"`); - - await expect(async () => { - const getResult = await todoHelperNonOwner.get({ - id: todo['id'], - }); - }).rejects.toThrowErrorMatchingInlineSnapshot(`"GraphQL error: Not Authorized to access getTodoOwnerFieldList on type Query"`); - - const listTodosResult = await todoHelperNonOwner.list(); - checkListItemExistence(listTodosResult, `list${modelName}s`, todo['id']); - - await expect(async () => { - await todoHelperNonOwner.delete(`delete${modelName}`, { - id: todo['id'], - }); - }).rejects.toThrowErrorMatchingInlineSnapshot(`"GraphQL error: Not Authorized to access deleteTodoOwnerFieldList on type Mutation"`); - - const todoRandom = { - id: Date.now().toString(), - content: 'Todo', - authors: [userName1], - }; - const todoRandomUpdated = { - ...todoRandom, - content: 'Todo updated', - }; - const actorClient = getConfiguredAppsyncClientOIDCAuth(graphQlEndpoint, region, userMap[userName1]); - const observerClient = getConfiguredAppsyncClientOIDCAuth(graphQlEndpoint, region, userMap[userName2]); - const actorTodoHelper = createModelOperationHelpers(actorClient, schema)[modelName]; - const observerTodoHelper = createModelOperationHelpers(observerClient, schema)[modelName]; - - const onCreateSubscriptionResult = await observerTodoHelper.subscribe('onCreate', [ - async () => { - await actorTodoHelper.create(`create${modelName}`, todoRandom); - }, - ]); - expect(onCreateSubscriptionResult).toHaveLength(0); - const onUpdateSubscriptionResult = await observerTodoHelper.subscribe('onUpdate', [ - async () => { - await actorTodoHelper.update(`update${modelName}`, todoRandomUpdated); - }, - ]); - expect(onUpdateSubscriptionResult).toHaveLength(0); - const onDeleteSubscriptionResult = await observerTodoHelper.subscribe('onDelete', [ - async () => { - await actorTodoHelper.delete(`delete${modelName}`, { id: todoRandom.id }); - }, - ]); - expect(onDeleteSubscriptionResult).toHaveLength(0); - }); - - test('owner can add another user to the owner list', async () => { - const modelName = 'TodoOwnerFieldList'; - const modelOperationHelpersOwner = createModelOperationHelpers(appSyncClients[oidcProvider][userName1], schema); - const modelOperationHelpersNonOwner = createModelOperationHelpers(appSyncClients[oidcProvider][userName2], schema); - const todoHelperOwner = modelOperationHelpersOwner[modelName]; - const todoHelperAnotherOwner = modelOperationHelpersNonOwner[modelName]; - - const todo = { - content: 'Todo', - authors: [userName1, userName2], - }; - const resultSetName = `create${modelName}`; - const createResult = await todoHelperOwner.create(resultSetName, todo); - expect(createResult.data[resultSetName].id).toBeDefined(); - todo['id'] = createResult.data[resultSetName].id; - - const todoUpdated = { - id: todo['id'], - content: 'Todo updated', - authors: [userName1, userName2], - }; - const updateResult = await todoHelperAnotherOwner.update(`update${modelName}`, todoUpdated); - checkOperationResult(updateResult, todoUpdated, `update${modelName}`); - - const getResult = await todoHelperAnotherOwner.get({ - id: todo['id'], - }); - checkOperationResult(getResult, todoUpdated, `get${modelName}`); - - const listTodosResult = await todoHelperAnotherOwner.list(); - checkListItemExistence(listTodosResult, `list${modelName}s`, todo['id'], true); - - const deleteResult = await todoHelperAnotherOwner.delete(`delete${modelName}`, { - id: todo['id'], - }); - checkOperationResult(deleteResult, todoUpdated, `delete${modelName}`); - - const todoRandom = { - id: Date.now().toString(), - content: 'Todo', - authors: [userName1, userName2], - }; - const todoRandomUpdated = { - ...todoRandom, - content: 'Todo updated', - }; - const actorClient = getConfiguredAppsyncClientOIDCAuth(graphQlEndpoint, region, userMap[userName1]); - const observerClient = getConfiguredAppsyncClientOIDCAuth(graphQlEndpoint, region, userMap[userName2]); - const actorTodoHelper = createModelOperationHelpers(actorClient, schema)[modelName]; - const observerTodoHelper = createModelOperationHelpers(observerClient, schema)[modelName]; - - const onCreateSubscriptionResult = await observerTodoHelper.subscribe('onCreate', [ - async () => { - await actorTodoHelper.create(`create${modelName}`, todoRandom); - }, - ]); - expect(onCreateSubscriptionResult).toHaveLength(1); - checkOperationResult(onCreateSubscriptionResult[0], todoRandom, `onCreate${modelName}`); - const onUpdateSubscriptionResult = await observerTodoHelper.subscribe('onUpdate', [ - async () => { - await actorTodoHelper.update(`update${modelName}`, todoRandomUpdated); - }, - ]); - expect(onUpdateSubscriptionResult).toHaveLength(1); - checkOperationResult(onUpdateSubscriptionResult[0], todoRandomUpdated, `onUpdate${modelName}`); - const onDeleteSubscriptionResult = await observerTodoHelper.subscribe('onDelete', [ - async () => { - await actorTodoHelper.delete(`delete${modelName}`, { id: todoRandom.id }); - }, - ]); - expect(onDeleteSubscriptionResult).toHaveLength(1); - checkOperationResult(onDeleteSubscriptionResult[0], todoRandomUpdated, `onDelete${modelName}`); - }); - - test('users in static group can perform CRUD and subscription operations', async () => { - const modelName = 'TodoStaticGroup'; - const modelOperationHelpers = createModelOperationHelpers(appSyncClients[oidcProvider][userName1], schema); - const todoHelper = modelOperationHelpers[modelName]; - - const todo = { - content: 'Todo', - }; - const resultSetName = `create${modelName}`; - const createResult = await todoHelper.create(resultSetName, todo); - expect(createResult.data[resultSetName].id).toBeDefined(); - todo['id'] = createResult.data[resultSetName].id; - expect(createResult.data[resultSetName].content).toEqual(todo.content); - - const todoUpdated = { - id: todo['id'], - content: 'Todo updated', - }; - const updateResult = await todoHelper.update(`update${modelName}`, todoUpdated); - checkOperationResult(updateResult, todoUpdated, `update${modelName}`); - - const getResult = await todoHelper.get({ - id: todo['id'], - }); - checkOperationResult(getResult, todoUpdated, `get${modelName}`); - - const listTodosResult = await todoHelper.list(); - checkListItemExistence(listTodosResult, `list${modelName}s`, todo['id'], true); - - const deleteResult = await todoHelper.delete(`delete${modelName}`, { - id: todo['id'], - }); - checkOperationResult(deleteResult, todoUpdated, `delete${modelName}`); - - const todoRandom = { - id: Date.now().toString(), - content: 'Todo', - }; - const todoRandomUpdated = { - ...todoRandom, - content: 'Todo updated', - }; - const actorClient = getConfiguredAppsyncClientOIDCAuth(graphQlEndpoint, region, userMap[userName1]); - const subTodoHelper = createModelOperationHelpers(actorClient, schema)[modelName]; - - const onCreateSubscriptionResult = await subTodoHelper.subscribe('onCreate', [ - async () => { - await subTodoHelper.create(`create${modelName}`, todoRandom); - }, - ]); - expect(onCreateSubscriptionResult).toHaveLength(1); - checkOperationResult(onCreateSubscriptionResult[0], todoRandom, `onCreate${modelName}`); - const onUpdateSubscriptionResult = await subTodoHelper.subscribe('onUpdate', [ - async () => { - await subTodoHelper.update(`update${modelName}`, todoRandomUpdated); - }, - ]); - expect(onUpdateSubscriptionResult).toHaveLength(1); - checkOperationResult(onUpdateSubscriptionResult[0], todoRandomUpdated, `onUpdate${modelName}`); - const onDeleteSubscriptionResult = await subTodoHelper.subscribe('onDelete', [ - async () => { - await subTodoHelper.delete(`delete${modelName}`, { id: todoRandom.id }); - }, - ]); - expect(onDeleteSubscriptionResult).toHaveLength(1); - checkOperationResult(onDeleteSubscriptionResult[0], todoRandomUpdated, `onDelete${modelName}`); - }); - - test('users not in static group cannot perform CRUD operations', async () => { - const modelName = 'TodoStaticGroup'; - const modelOperationHelpersAdmin = createModelOperationHelpers(appSyncClients[oidcProvider][userName1], schema); - const modelOperationHelpersNonAdmin = createModelOperationHelpers(appSyncClients[oidcProvider][userName2], schema); - const todoHelperAdmin = modelOperationHelpersAdmin[modelName]; - const todoHelperNonAdmin = modelOperationHelpersNonAdmin[modelName]; - - const todo = { - content: 'Todo', - }; - const resultSetName = `create${modelName}`; - const createResult = await todoHelperAdmin.create(resultSetName, todo); - expect(createResult.data[resultSetName].id).toBeDefined(); - todo['id'] = createResult.data[resultSetName].id; - - await expect(async () => { - await todoHelperNonAdmin.create(`create${modelName}`, todo); - }).rejects.toThrowErrorMatchingInlineSnapshot(`"GraphQL error: Not Authorized to access createTodoStaticGroup on type Mutation"`); - - const todoUpdated = { - id: todo['id'], - content: 'Todo updated', - }; - await expect(async () => { - await todoHelperNonAdmin.update(`update${modelName}`, todoUpdated); - }).rejects.toThrowErrorMatchingInlineSnapshot(`"GraphQL error: Not Authorized to access updateTodoStaticGroup on type Mutation"`); - - expect( - async () => - await todoHelperNonAdmin.get({ - id: todo['id'], - }), - ).rejects.toThrowErrorMatchingInlineSnapshot(`"GraphQL error: Not Authorized to access getTodoStaticGroup on type Query"`); - - await expect(async () => { - await todoHelperNonAdmin.list(); - }).rejects.toThrowErrorMatchingInlineSnapshot(`"GraphQL error: Not Authorized to access listTodoStaticGroups on type Query"`); - - await expect(async () => { - await todoHelperNonAdmin.delete(`delete${modelName}`, { - id: todo['id'], - }); - }).rejects.toThrowErrorMatchingInlineSnapshot(`"GraphQL error: Not Authorized to access deleteTodoStaticGroup on type Mutation"`); - }); - - test('users in group stored as string can perform CRUD and subscription operations', async () => { - const modelName = 'TodoGroupFieldString'; - const modelOperationHelpers = createModelOperationHelpers(appSyncClients[oidcProvider][userName1], schema); - const todoHelper = modelOperationHelpers[modelName]; - - const todo = { - content: 'Todo', - groupField: adminGroupName, - }; - const resultSetName = `create${modelName}`; - const createResult = await todoHelper.create(resultSetName, todo); - expect(createResult.data[resultSetName].id).toBeDefined(); - todo['id'] = createResult.data[resultSetName].id; - expect(createResult.data[resultSetName].content).toEqual(todo.content); - expect(createResult.data[resultSetName].groupField).toEqual(adminGroupName); - - const todoUpdated = { - id: todo['id'], - content: 'Todo updated', - groupField: adminGroupName, - }; - const updateResult = await todoHelper.update(`update${modelName}`, todoUpdated); - checkOperationResult(updateResult, todoUpdated, `update${modelName}`); - - const getResult = await todoHelper.get({ - id: todo['id'], - }); - checkOperationResult(getResult, todoUpdated, `get${modelName}`); - - const listTodosResult = await todoHelper.list(); - checkListItemExistence(listTodosResult, `list${modelName}s`, todo['id'], true); - - const deleteResult = await todoHelper.delete(`delete${modelName}`, { - id: todo['id'], - }); - checkOperationResult(deleteResult, todoUpdated, `delete${modelName}`); - - const todoRandom = { - id: Date.now().toString(), - content: 'Todo', - groupField: adminGroupName, - }; - const todoRandomUpdated = { - ...todoRandom, - content: 'Todo updated', - }; - const actorClient = getConfiguredAppsyncClientOIDCAuth(graphQlEndpoint, region, userMap[userName1]); - const subTodoHelper = createModelOperationHelpers(actorClient, schema)[modelName]; - - const onCreateSubscriptionResult = await subTodoHelper.subscribe('onCreate', [ - async () => { - await subTodoHelper.create(`create${modelName}`, todoRandom); - }, - ]); - expect(onCreateSubscriptionResult).toHaveLength(1); - checkOperationResult(onCreateSubscriptionResult[0], todoRandom, `onCreate${modelName}`); - const onUpdateSubscriptionResult = await subTodoHelper.subscribe('onUpdate', [ - async () => { - await subTodoHelper.update(`update${modelName}`, todoRandomUpdated); - }, - ]); - expect(onUpdateSubscriptionResult).toHaveLength(1); - checkOperationResult(onUpdateSubscriptionResult[0], todoRandomUpdated, `onUpdate${modelName}`); - const onDeleteSubscriptionResult = await subTodoHelper.subscribe('onDelete', [ - async () => { - await subTodoHelper.delete(`delete${modelName}`, { id: todoRandom.id }); - }, - ]); - expect(onDeleteSubscriptionResult).toHaveLength(1); - checkOperationResult(onDeleteSubscriptionResult[0], todoRandomUpdated, `onDelete${modelName}`); - }); - - test('users cannot spoof their group membership and gain access', async () => { - const modelName = 'TodoGroupFieldString'; - const modelOperationHelpersAdmin = createModelOperationHelpers(appSyncClients[oidcProvider][userName1], schema); - const modelOperationHelpersNonAdmin = createModelOperationHelpers(appSyncClients[oidcProvider][userName2], schema); - const todoHelperAdmin = modelOperationHelpersAdmin[modelName]; - const todoHelperNonAdmin = modelOperationHelpersNonAdmin[modelName]; - - const todo = { - content: 'Todo', - groupField: adminGroupName, - }; - const resultSetName = `create${modelName}`; - const createResult = await todoHelperAdmin.create(resultSetName, todo); - expect(createResult.data[resultSetName].id).toBeDefined(); - todo['id'] = createResult.data[resultSetName].id; - - await expect(async () => { - await todoHelperNonAdmin.create(resultSetName, todo); - }).rejects.toThrowErrorMatchingInlineSnapshot(`"GraphQL error: Not Authorized to access createTodoGroupFieldString on type Mutation"`); - - const todoUpdated = { - id: todo['id'], - content: 'Todo updated', - groupField: devGroupName, - }; - await expect(async () => { - await todoHelperNonAdmin.update(`update${modelName}`, todoUpdated); - }).rejects.toThrowErrorMatchingInlineSnapshot(`"GraphQL error: Not Authorized to access updateTodoGroupFieldString on type Mutation"`); - - await expect(async () => { - const getResult = await todoHelperNonAdmin.get({ - id: todo['id'], - }); - }).rejects.toThrowErrorMatchingInlineSnapshot(`"GraphQL error: Not Authorized to access getTodoGroupFieldString on type Query"`); - - const listTodosResult = await todoHelperNonAdmin.list(); - checkListItemExistence(listTodosResult, `list${modelName}s`, todo['id']); - - await expect(async () => { - await todoHelperNonAdmin.delete(`delete${modelName}`, { - id: todo['id'], - }); - }).rejects.toThrowErrorMatchingInlineSnapshot(`"GraphQL error: Not Authorized to access deleteTodoGroupFieldString on type Mutation"`); - }); - - test('users in groups stored as list can perform CRUD and subscription operations', async () => { - const modelName = 'TodoGroupFieldList'; - const modelOperationHelpers = createModelOperationHelpers(appSyncClients[oidcProvider][userName1], schema); - const todoHelper = modelOperationHelpers[modelName]; - - const todo = { - content: 'Todo', - groupsField: [adminGroupName], - }; - const resultSetName = `create${modelName}`; - const createResult = await todoHelper.create(resultSetName, todo); - expect(createResult.data[resultSetName].id).toBeDefined(); - todo['id'] = createResult.data[resultSetName].id; - expect(createResult.data[resultSetName].content).toEqual(todo.content); - expect(createResult.data[resultSetName].groupsField).toEqual([adminGroupName]); - - const todo1Updated = { - id: todo['id'], - content: 'Todo updated', - groupsField: [adminGroupName], - }; - const updateResult = await todoHelper.update(`update${modelName}`, todo1Updated); - checkOperationResult(updateResult, todo1Updated, `update${modelName}`); - - const getResult = await todoHelper.get({ - id: todo['id'], - }); - checkOperationResult(getResult, todo1Updated, `get${modelName}`); - - const listTodosResult = await todoHelper.list(); - checkListItemExistence(listTodosResult, `list${modelName}s`, todo['id'], true); - - const deleteResult = await todoHelper.delete(`delete${modelName}`, { - id: todo['id'], - }); - checkOperationResult(deleteResult, todo1Updated, `delete${modelName}`); - - const todoRandom = { - id: Date.now().toString(), - content: 'Todo', - groupsField: [adminGroupName], - }; - const todoRandomUpdated = { - ...todoRandom, - content: 'Todo updated', - }; - const actorClient = getConfiguredAppsyncClientOIDCAuth(graphQlEndpoint, region, userMap[userName1]); - const subTodoHelper = createModelOperationHelpers(actorClient, schema)[modelName]; - - const onCreateSubscriptionResult = await subTodoHelper.subscribe('onCreate', [ - async () => { - await subTodoHelper.create(`create${modelName}`, todoRandom); - }, - ]); - expect(onCreateSubscriptionResult).toHaveLength(1); - checkOperationResult(onCreateSubscriptionResult[0], todoRandom, `onCreate${modelName}`); - const onUpdateSubscriptionResult = await subTodoHelper.subscribe('onUpdate', [ - async () => { - await subTodoHelper.update(`update${modelName}`, todoRandomUpdated); - }, - ]); - expect(onUpdateSubscriptionResult).toHaveLength(1); - checkOperationResult(onUpdateSubscriptionResult[0], todoRandomUpdated, `onUpdate${modelName}`); - const onDeleteSubscriptionResult = await subTodoHelper.subscribe('onDelete', [ - async () => { - await subTodoHelper.delete(`delete${modelName}`, { id: todoRandom.id }); - }, - ]); - expect(onDeleteSubscriptionResult).toHaveLength(1); - checkOperationResult(onDeleteSubscriptionResult[0], todoRandomUpdated, `onDelete${modelName}`); - }); - - test('users not part of allowed groups cannot access the records or modify allowed groups', async () => { - const modelName = 'TodoGroupFieldList'; - const modelOperationHelpersAdmin = createModelOperationHelpers(appSyncClients[oidcProvider][userName1], schema); - const modelOperationHelpersNonAdmin = createModelOperationHelpers(appSyncClients[oidcProvider][userName2], schema); - const todoHelperAdmin = modelOperationHelpersAdmin[modelName]; - const todoHelperNonAdmin = modelOperationHelpersNonAdmin[modelName]; - - const todo = { - content: 'Todo', - groupsField: [adminGroupName], - }; - const resultSetName = `create${modelName}`; - const createResult = await todoHelperAdmin.create(resultSetName, todo); - expect(createResult.data[resultSetName].id).toBeDefined(); - todo['id'] = createResult.data[resultSetName].id; - - const todoUpdated = { - id: todo['id'], - content: 'Todo updated', - groupsField: [adminGroupName, devGroupName], - }; - await expect(async () => { - await todoHelperNonAdmin.update(`update${modelName}`, todoUpdated); - }).rejects.toThrowErrorMatchingInlineSnapshot(`"GraphQL error: Not Authorized to access updateTodoGroupFieldList on type Mutation"`); - - await expect(async () => { - const getResult = await todoHelperNonAdmin.get({ - id: todo['id'], - }); - }).rejects.toThrowErrorMatchingInlineSnapshot(`"GraphQL error: Not Authorized to access getTodoGroupFieldList on type Query"`); - - const listTodosResult = await todoHelperNonAdmin.list(); - checkListItemExistence(listTodosResult, `list${modelName}s`, todo['id']); - - await expect(async () => { - await todoHelperNonAdmin.delete(`delete${modelName}`, { - id: todo['id'], - }); - }).rejects.toThrowErrorMatchingInlineSnapshot(`"GraphQL error: Not Authorized to access deleteTodoGroupFieldList on type Mutation"`); - - const todoRandom = { - id: Date.now().toString(), - content: 'Todo', - groupsField: [adminGroupName], - }; - const todoRandomUpdated = { - ...todoRandom, - content: 'Todo updated', - }; - const actorClient = getConfiguredAppsyncClientOIDCAuth(graphQlEndpoint, region, userMap[userName1]); - const observerClient = getConfiguredAppsyncClientOIDCAuth(graphQlEndpoint, region, userMap[userName2]); - const actorTodoHelper = createModelOperationHelpers(actorClient, schema)[modelName]; - const observerTodoHelper = createModelOperationHelpers(observerClient, schema)[modelName]; - - const onCreateSubscriptionResult = await observerTodoHelper.subscribe('onCreate', [ - async () => { - await actorTodoHelper.create(`create${modelName}`, todoRandom); - }, - ]); - expect(onCreateSubscriptionResult).toHaveLength(0); - const onUpdateSubscriptionResult = await observerTodoHelper.subscribe('onUpdate', [ - async () => { - await actorTodoHelper.update(`update${modelName}`, todoRandomUpdated); - }, - ]); - expect(onUpdateSubscriptionResult).toHaveLength(0); - const onDeleteSubscriptionResult = await observerTodoHelper.subscribe('onDelete', [ - async () => { - await actorTodoHelper.delete(`delete${modelName}`, { id: todoRandom.id }); - }, - ]); - expect(onDeleteSubscriptionResult).toHaveLength(0); - }); - - test('Admin user can give access to another group of users', async () => { - const modelName = 'TodoGroupFieldList'; - const modelOperationHelpersAdmin = createModelOperationHelpers(appSyncClients[oidcProvider][userName1], schema); - const modelOperationHelpersNonAdmin = createModelOperationHelpers(appSyncClients[oidcProvider][userName2], schema); - const todoHelperAdmin = modelOperationHelpersAdmin[modelName]; - const todoHelperNonAdmin = modelOperationHelpersNonAdmin[modelName]; - - const todo = { - content: 'Todo', - groupsField: [adminGroupName, devGroupName], - }; - const resultSetName = `create${modelName}`; - const createResult = await todoHelperAdmin.create(resultSetName, todo); - expect(createResult.data[resultSetName].id).toBeDefined(); - todo['id'] = createResult.data[resultSetName].id; - - const todoUpdated = { - id: todo['id'], - content: 'Todo updated', - groupsField: [adminGroupName, devGroupName], - }; - const updateResult = await todoHelperNonAdmin.update(`update${modelName}`, todoUpdated); - checkOperationResult(updateResult, todoUpdated, `update${modelName}`); - - const getResult = await todoHelperNonAdmin.get({ - id: todo['id'], - }); - checkOperationResult(getResult, todoUpdated, `get${modelName}`); - - const listTodosResult = await todoHelperNonAdmin.list(); - checkListItemExistence(listTodosResult, `list${modelName}s`, todo['id'], true); - - const deleteResult = await todoHelperNonAdmin.delete(`delete${modelName}`, { - id: todo['id'], - }); - checkOperationResult(deleteResult, todoUpdated, `delete${modelName}`); - - const todoRandom = { - id: Date.now().toString(), - content: 'Todo', - groupsField: [adminGroupName, devGroupName], - }; - const todoRandomUpdated = { - ...todoRandom, - content: 'Todo updated', - }; - const actorClient = getConfiguredAppsyncClientOIDCAuth(graphQlEndpoint, region, userMap[userName1]); - const observerClient = getConfiguredAppsyncClientOIDCAuth(graphQlEndpoint, region, userMap[userName2]); - const actorTodoHelper = createModelOperationHelpers(actorClient, schema)[modelName]; - const observerTodoHelper = createModelOperationHelpers(observerClient, schema)[modelName]; - - const onCreateSubscriptionResult = await observerTodoHelper.subscribe('onCreate', [ - async () => { - await actorTodoHelper.create(`create${modelName}`, todoRandom); - }, - ]); - expect(onCreateSubscriptionResult).toHaveLength(1); - checkOperationResult(onCreateSubscriptionResult[0], todoRandom, `onCreate${modelName}`); - const onUpdateSubscriptionResult = await observerTodoHelper.subscribe('onUpdate', [ - async () => { - await actorTodoHelper.update(`update${modelName}`, todoRandomUpdated); - }, - ]); - expect(onUpdateSubscriptionResult).toHaveLength(1); - checkOperationResult(onUpdateSubscriptionResult[0], todoRandomUpdated, `onUpdate${modelName}`); - const onDeleteSubscriptionResult = await observerTodoHelper.subscribe('onDelete', [ - async () => { - await actorTodoHelper.delete(`delete${modelName}`, { id: todoRandom.id }); - }, - ]); - expect(onDeleteSubscriptionResult).toHaveLength(1); - checkOperationResult(onDeleteSubscriptionResult[0], todoRandomUpdated, `onDelete${modelName}`); - }); - - test('logged in user can perform custom operations', async () => { - const appSyncClient = appSyncClients[oidcProvider][userName2]; - const todo = { - id: Date.now().toString(), - content: 'Todo', - }; - const createTodoCustom = /* GraphQL */ ` - mutation CreateTodoCustom($id: ID!, $content: String) { - addTodoPrivate(id: $id, content: $content) { - id - content - } - } - `; - const createResult = await appSyncClient.mutate({ - mutation: gql(createTodoCustom), - fetchPolicy: 'no-cache', - variables: todo, - }); - expect(createResult.data.addTodoPrivate).toBeDefined(); - - const getTodoCustom = /* GraphQL */ ` - query GetTodoCustom($id: ID!) { - customGetTodoPrivate(id: $id) { - id - content - } - } - `; - const getResult = await appSyncClient.query({ - query: gql(getTodoCustom), - fetchPolicy: 'no-cache', - variables: { - id: todo.id, - }, - }); - expect(getResult.data.customGetTodoPrivate).toHaveLength(1); - expect(getResult.data.customGetTodoPrivate[0].id).toEqual(todo.id); - expect(getResult.data.customGetTodoPrivate[0].content).toEqual(todo.content); - }); - - test('users in static group can perform custom operations', async () => { - const appSyncClient = appSyncClients[oidcProvider][userName1]; - const todo = { - id: Date.now().toString(), - content: 'Todo', - }; - const createTodoCustom = /* GraphQL */ ` - mutation CreateTodoCustom($id: ID!, $content: String) { - addTodoStaticGroup(id: $id, content: $content) { - id - content - } - } - `; - const createResult = await appSyncClient.mutate({ - mutation: gql(createTodoCustom), - fetchPolicy: 'no-cache', - variables: todo, - }); - expect(createResult.data.addTodoStaticGroup).toBeDefined(); - - const getTodoCustom = /* GraphQL */ ` - query GetTodoCustom($id: ID!) { - customGetTodoStaticGroup(id: $id) { - id - content - } - } - `; - const getResult = await appSyncClient.query({ - query: gql(getTodoCustom), - fetchPolicy: 'no-cache', - variables: { - id: todo.id, - }, - }); - expect(getResult.data.customGetTodoStaticGroup).toHaveLength(1); - expect(getResult.data.customGetTodoStaticGroup[0].id).toEqual(todo.id); - expect(getResult.data.customGetTodoStaticGroup[0].content).toEqual(todo.content); - }); - - test('users not in static group cannot perform custom operations', async () => { - const appSyncClient = appSyncClients[oidcProvider][userName2]; - const todo = { - id: Date.now().toString(), - content: 'Todo', - }; - const createTodoCustom = /* GraphQL */ ` - mutation CreateTodoCustom($id: ID!, $content: String) { - addTodoStaticGroup(id: $id, content: $content) { - id - content - } - } - `; - await expect(async () => { - await appSyncClient.mutate({ - mutation: gql(createTodoCustom), - fetchPolicy: 'no-cache', - variables: todo, - }); - }).rejects.toThrowErrorMatchingInlineSnapshot(`"GraphQL error: Not Authorized to access addTodoStaticGroup on type Mutation"`); - - const getTodoCustom = /* GraphQL */ ` - query GetTodoCustom($id: ID!) { - customGetTodoStaticGroup(id: $id) { - id - content - } - } - `; - await expect(async () => { - await appSyncClient.query({ - query: gql(getTodoCustom), - fetchPolicy: 'no-cache', - variables: { - id: todo.id, - }, - }); - }).rejects.toThrowErrorMatchingInlineSnapshot(`"GraphQL error: Not Authorized to access customGetTodoStaticGroup on type Query"`); - }); -}); diff --git a/packages/amplify-e2e-tests/src/__tests__/rds-pg-oidc-auth-fields.test.ts b/packages/amplify-e2e-tests/src/__tests__/rds-pg-oidc-auth-fields.test.ts deleted file mode 100644 index 23a4e4067e..0000000000 --- a/packages/amplify-e2e-tests/src/__tests__/rds-pg-oidc-auth-fields.test.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { ImportedRDSType } from '@aws-amplify/graphql-transformer-core'; -import { testOIDCFieldAuth } from '../rds-v2-tests-common/rds-auth-oidc-fields'; - -// to deal with bug in cognito-identity-js -(global as any).fetch = require('node-fetch'); - -describe('SQL Postgres OIDC Field Auth', () => { - testOIDCFieldAuth(ImportedRDSType.POSTGRESQL); -}); diff --git a/packages/amplify-e2e-tests/src/__tests__/rds-pg-oidc-auth.test.ts b/packages/amplify-e2e-tests/src/__tests__/rds-pg-oidc-auth.test.ts deleted file mode 100644 index af41f8a370..0000000000 --- a/packages/amplify-e2e-tests/src/__tests__/rds-pg-oidc-auth.test.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { ImportedRDSType } from '@aws-amplify/graphql-transformer-core'; -import { testOIDCAuth } from '../rds-v2-tests-common/rds-auth-oidc'; - -// to deal with bug in cognito-identity-js -(global as any).fetch = require('node-fetch'); - -describe('RDS Postgres OIDC Auth', () => { - testOIDCAuth(ImportedRDSType.POSTGRESQL); -}); diff --git a/packages/amplify-e2e-tests/src/rds-v2-tests-common/rds-auth-oidc-fields.ts b/packages/amplify-e2e-tests/src/rds-v2-tests-common/rds-auth-oidc-fields.ts deleted file mode 100644 index 8a820e46b8..0000000000 --- a/packages/amplify-e2e-tests/src/rds-v2-tests-common/rds-auth-oidc-fields.ts +++ /dev/null @@ -1,3592 +0,0 @@ -import { - addApi, - amplifyPush, - createNewProjectDir, - deleteDBInstance, - deleteProject, - deleteProjectDir, - importRDSDatabase, - initJSProjectWithProfile, - setupRDSInstanceAndData, - sleep, - updateAuthAddUserGroups, - getProjectMeta, - addAuthWithPreTokenGenerationTrigger, -} from 'amplify-category-api-e2e-core'; -import { existsSync, writeFileSync, removeSync } from 'fs-extra'; -import generator from 'generate-password'; -import path from 'path'; -import { schema, sqlCreateStatements } from '../__tests__/auth-test-schemas/oidc-provider-fields'; -import { - createModelOperationHelpers, - configureAppSyncClients, - checkOperationResult, - checkListItemExistence, - appendAmplifyInput, - getAppSyncEndpoint, - getDefaultDatabasePort, - checkListResponseErrors, - expectNullFields, - expectedFieldErrors, - expectedOperationError, - updatePreAuthTrigger, -} from '../rds-v2-test-utils'; -import { - setupUser, - getUserPoolId, - signInUser, - configureAmplify, - getUserPoolIssUrl, - getAppClientIDWeb, - getConfiguredAppsyncClientOIDCAuth, -} from '../schema-api-directives'; -import { ImportedRDSType } from '@aws-amplify/graphql-transformer-core'; -import { SQL_TESTS_USE_BETA } from './sql-e2e-config'; -import { GQLQueryHelper } from '../query-utils/gql-helper'; - -// to deal with bug in cognito-identity-js -(global as any).fetch = require('node-fetch'); - -export const testOIDCFieldAuth = (engine: ImportedRDSType): void => { - describe('SQL OIDC provider Field Auth tests', () => { - const [db_user, db_password, db_identifier] = generator.generateMultiple(3); - - // Generate settings for RDS instance - const username = db_user; - const password = db_password; - let region = 'us-east-1'; - let port = getDefaultDatabasePort(engine); - const database = 'default_db'; - let host = 'localhost'; - const identifier = `integtest${db_identifier}`; - const projName = 'fieldauthoidc'; - const userName1 = 'user1'; - const userName2 = 'user2'; - const adminGroupName = 'Admin'; - const devGroupName = 'Dev'; - const userPassword = 'user@Password'; - const oidcProvider = 'oidc'; - const apiKeyProvider = 'apiKey'; - let graphQlEndpoint = 'localhost'; - - let projRoot; - let user1ModelOperationHelpers: { [key: string]: GQLQueryHelper }; - let user2ModelOperationHelpers: { [key: string]: GQLQueryHelper }; - const userMap = {}; - const apiName = projName; - - beforeAll(async () => { - console.log(sqlCreateStatements(engine)); - projRoot = await createNewProjectDir(projName); - await setupAmplifyProject(); - }); - - afterAll(async () => { - const metaFilePath = path.join(projRoot, 'amplify', '#current-cloud-backend', 'amplify-meta.json'); - if (existsSync(metaFilePath)) { - await deleteProject(projRoot); - } - deleteProjectDir(projRoot); - await cleanupDatabase(); - }); - - const setupDatabase = async (): Promise => { - const dbConfig = { - identifier, - engine, - dbname: database, - username, - password, - region, - port, - }; - - const db = await setupRDSInstanceAndData(dbConfig, sqlCreateStatements(engine)); - port = db.port; - host = db.endpoint; - }; - - const cleanupDatabase = async (): Promise => { - await deleteDBInstance(identifier, region); - }; - - const setupAmplifyProject = async (): Promise => { - await initJSProjectWithProfile(projRoot, { - disableAmplifyAppCreation: false, - name: projName, - }); - - const metaAfterInit = getProjectMeta(projRoot); - region = metaAfterInit.providers.awscloudformation.Region; - - await addAuthWithPreTokenGenerationTrigger(projRoot); - updatePreAuthTrigger(projRoot, 'user_id'); - await amplifyPush(projRoot, false, { - useBetaSqlLayer: SQL_TESTS_USE_BETA, - skipCodegen: true, - }); - - await addApi(projRoot, { - 'OpenID Connect': { - oidcProviderName: 'awscognitouserpool', - oidcProviderDomain: getUserPoolIssUrl(projRoot), - oidcClientId: getAppClientIDWeb(projRoot), - ttlaIssueInMillisecond: '3600000', - ttlaAuthInMillisecond: '3600000', - }, - 'API key': {}, - transformerVersion: 2, - }); - - const rdsSchemaFilePath = path.join(projRoot, 'amplify', 'backend', 'api', apiName, 'schema.sql.graphql'); - const ddbSchemaFilePath = path.join(projRoot, 'amplify', 'backend', 'api', apiName, 'schema.graphql'); - removeSync(ddbSchemaFilePath); - - await setupDatabase(); - await importRDSDatabase(projRoot, { - database, - engine, - host, - port, - username, - password, - useVpc: true, - apiExists: true, - }); - - writeFileSync(rdsSchemaFilePath, appendAmplifyInput(schema, engine), 'utf8'); - - await updateAuthAddUserGroups(projRoot, [adminGroupName, devGroupName]); - await amplifyPush(projRoot, false, { - useBetaSqlLayer: SQL_TESTS_USE_BETA, - }); - await sleep(30 * 1000); // Wait for 30 seconds for the VPC endpoints to be live. - - const userPoolId = getUserPoolId(projRoot); - configureAmplify(projRoot); - await setupUser(userPoolId, userName1, userPassword, adminGroupName); - await setupUser(userPoolId, userName2, userPassword, devGroupName); - graphQlEndpoint = getAppSyncEndpoint(projRoot, apiName); - const user1 = await signInUser(userName1, userPassword); - userMap[userName1] = user1; - const user2 = await signInUser(userName2, userPassword); - userMap[userName2] = user2; - const appSyncClients = await configureAppSyncClients(projRoot, apiName, [oidcProvider, apiKeyProvider], userMap); - user1ModelOperationHelpers = createModelOperationHelpers(appSyncClients[oidcProvider][userName1], schema); - user2ModelOperationHelpers = createModelOperationHelpers(appSyncClients[oidcProvider][userName2], schema); - }; - - test('Private model auth and allowed field operations', async () => { - const modelName = 'TodoPrivateContentVarious'; - const user1TodoHelper = user1ModelOperationHelpers[modelName]; - const user2TodoHelper = user2ModelOperationHelpers[modelName]; - - const todo = { - privateContent: 'Private Content', - ownerContent: 'Owner Content', - adminContent: 'Admin Content', - groupContent: 'Group Content', - authors: [userName1], - customGroup: adminGroupName, - customGroups: [adminGroupName], - }; - const createResultSetName = `create${modelName}`; - const updateResultSetName = `update${modelName}`; - const user1CreateAllowedSet = ` - id - owner - authors - customGroup - customGroups - privateContent - ownerContent - adminContent - groupContent - `; - - // owner(user1) creates a record with only allowed fields - const createResult1 = await user1TodoHelper.create(createResultSetName, todo, user1CreateAllowedSet); - expect(createResult1.data[createResultSetName].id).toBeDefined(); - todo['id'] = createResult1.data[createResultSetName].id; - todo['owner'] = userName1; - // protected fields are nullified in mutation responses - expectNullFields(createResult1.data[createResultSetName], [ - 'owner', - 'authors', - 'customGroup', - 'customGroups', - 'privateContent', - 'ownerContent', - 'adminContent', - 'groupContent', - ]); - - // user1 can update the allowed fields and add user2 to dyamic owners list field - const todoUpdated1 = { - id: todo['id'], - authors: [userName1, userName2], - customGroups: [adminGroupName, devGroupName], - privateContent: 'Private Content updated', - ownersContent: 'Owners Content updated', - adminContent: 'Admin Content updated', - groupsContent: 'Groups Content updated', - }; - const user1UpdateAllowedSet = ` - id - owner - authors - customGroup - customGroups - privateContent - ownersContent - adminContent - groupsContent - `; - const updateResult1 = await user1TodoHelper.update(updateResultSetName, todoUpdated1, user1UpdateAllowedSet); - expect(updateResult1.data[updateResultSetName].id).toEqual(todo['id']); - expectNullFields(updateResult1.data[updateResultSetName], [ - 'owner', - 'authors', - 'customGroup', - 'customGroups', - 'privateContent', - 'ownersContent', - 'adminContent', - 'groupsContent', - ]); - - // user1 can read the allowed fields - const completeResultSet = ` - id - owner - authors - customGroup - customGroups - privateContent - ownerContent - ownersContent - adminContent - groupContent - groupsContent - `; - const user1ReadAllowedSet = completeResultSet; - const getResult1 = await user1TodoHelper.get( - { - id: todo['id'], - }, - user1ReadAllowedSet, - false, - ); - checkOperationResult(getResult1, { ...todo, ...todoUpdated1 }, `get${modelName}`); - - const listTodosResult1 = await user1TodoHelper.list({}, user1ReadAllowedSet, `list${modelName}`, false, 'all'); - checkListItemExistence(listTodosResult1, `list${modelName}`, todo['id'], true); - - // user2 can update the private and dynamic owners list protected fields. - const todoUpdated2 = { - id: todo['id'], - owner: todo['owner'], - authors: [userName2], - customGroup: devGroupName, - customGroups: [devGroupName], - privateContent: 'Private Content updated 1', - ownersContent: 'Owners Content updated 1', - groupsContent: 'Groups Content updated 1', - }; - const user2UpdateAllowedSet = ` - id - owner - authors - customGroup - customGroups - privateContent - ownersContent - groupsContent - `; - const updateResult2 = await user2TodoHelper.update(updateResultSetName, todoUpdated2, user2UpdateAllowedSet); - expect(updateResult2.data[updateResultSetName].id).toEqual(todo['id']); - expectNullFields(updateResult2.data[updateResultSetName], [ - 'owner', - 'authors', - 'customGroup', - 'customGroups', - 'privateContent', - 'ownersContent', - 'groupsContent', - ]); - - // user2 can read the allowed fields - const user2ReadAllowedSet = user2UpdateAllowedSet; - const getResult2 = await user2TodoHelper.get( - { - id: todo['id'], - }, - user2ReadAllowedSet, - false, - ); - checkOperationResult(getResult2, todoUpdated2, `get${modelName}`); - - const listTodosResult2 = await user2TodoHelper.list({}, user2ReadAllowedSet, `list${modelName}`, false, 'all'); - checkListItemExistence(listTodosResult2, `list${modelName}`, todo['id'], true); - - // unless one has delete access to all fields in the model, delete is expected to fail - await expect( - async () => await user1TodoHelper.delete(`delete${modelName}`, { id: todo['id'] }, user1CreateAllowedSet), - ).rejects.toThrowErrorMatchingInlineSnapshot(expectedOperationError(`delete${modelName}`, 'Mutation')); - - // user2 can listen to updates on the non-protected fields - const todoRandom = { - ...todo, - id: Date.now().toString(), - }; - const todoRandomUpdated = { - ...todoUpdated1, - id: todoRandom.id, - owner: userName1, - privateContent: 'Private Content updated', - adminContent: 'Admin Content updated', - }; - const subscriberClient = getConfiguredAppsyncClientOIDCAuth(graphQlEndpoint, region, userMap[userName2]); - const subTodoHelper = createModelOperationHelpers(subscriberClient, schema)[modelName]; - - const onCreateSubscriptionResult = await subTodoHelper.subscribe( - 'onCreate', - [ - async () => { - await user1TodoHelper.create(createResultSetName, todoRandom, user1CreateAllowedSet); - }, - ], - {}, - user1CreateAllowedSet, - false, - ); - expect(onCreateSubscriptionResult).toHaveLength(1); - expectNullFields(onCreateSubscriptionResult[0].data[`onCreate${modelName}`], [ - 'owner', - 'authors', - 'customGroup', - 'customGroups', - 'privateContent', - 'ownerContent', - 'adminContent', - 'groupContent', - ]); - - const onUpdateSubscriptionResult = await subTodoHelper.subscribe( - 'onUpdate', - [ - async () => { - await user1TodoHelper.update(`update${modelName}`, todoRandomUpdated, user1UpdateAllowedSet); - }, - ], - {}, - user1UpdateAllowedSet, - false, - ); - expect(onUpdateSubscriptionResult).toHaveLength(1); - expectNullFields(onUpdateSubscriptionResult[0].data[`onUpdate${modelName}`], [ - 'owner', - 'authors', - 'customGroup', - 'customGroups', - 'privateContent', - 'ownersContent', - 'adminContent', - 'groupsContent', - ]); - - const onDeleteSubscriptionResult = await subTodoHelper.subscribe('onDelete', [], {}, user1UpdateAllowedSet, false); - expect(onDeleteSubscriptionResult).toHaveLength(0); - }); - - test('Private model auth and restricted field operations', async () => { - const modelName = 'TodoPrivateContentVarious'; - const user1TodoHelper = user1ModelOperationHelpers[modelName]; - const user2TodoHelper = user2ModelOperationHelpers[modelName]; - - const todoPrivateFields = { - owner: userName1, - authors: [userName1], - customGroup: adminGroupName, - customGroups: [adminGroupName], - privateContent: 'Private Content', - }; - const createResultSetName = `create${modelName}`; - const updateResultSetName = `update${modelName}`; - const privateResultSet = ` - id - owner - authors - customGroup - customGroups - privateContent - `; - - // cannot create a record with public protected field - await expect( - async () => await user1TodoHelper.create(createResultSetName, { ...todoPrivateFields, publicContent: 'Public Content' }), - ).rejects.toThrowErrorMatchingInlineSnapshot(expectedOperationError(createResultSetName, 'Mutation')); - - // cannot create a record with dynamic owner list protected field that does not allow create operation - await expect( - async () => await user1TodoHelper.create(createResultSetName, { ...todoPrivateFields, ownersContent: 'Owners Content' }), - ).rejects.toThrowErrorMatchingInlineSnapshot(expectedOperationError(createResultSetName, 'Mutation')); - - // cannot create a record with dynamic groups list protected field that does not allow create operation - await expect( - async () => await user1TodoHelper.create(createResultSetName, { ...todoPrivateFields, groupsContent: 'Groups Content' }), - ).rejects.toThrowErrorMatchingInlineSnapshot(expectedOperationError(createResultSetName, 'Mutation')); - - // Create a record with allowed fields, so we can test the update and delete operations. - const user1CreateAllowedSet = ` - ${privateResultSet} - ownerContent - adminContent - groupContent - `; - const createResult1 = await user1TodoHelper.create( - createResultSetName, - { ...todoPrivateFields, ownerContent: 'Owner Content', adminContent: 'Admin Content', groupContent: 'Group Content' }, - user1CreateAllowedSet, - ); - expect(createResult1.data[createResultSetName].id).toBeDefined(); - todoPrivateFields['id'] = createResult1.data[createResultSetName].id; - // protected fields are nullified in mutation responses - const nulledPrivateFields = ['owner', 'authors', 'customGroup', 'customGroups', 'privateContent']; - expectNullFields(createResult1.data[createResultSetName], [...nulledPrivateFields, 'ownerContent', 'adminContent', 'groupContent']); - - const privateAndPublicSet = ` - ${privateResultSet} - publicContent - `; - // cannot update a record with public protected field - await expect( - async () => - await user1TodoHelper.update(updateResultSetName, { ...todoPrivateFields, publicContent: 'Public Content' }, privateAndPublicSet), - ).rejects.toThrowErrorMatchingInlineSnapshot(expectedOperationError(updateResultSetName, 'Mutation')); - - const privateAndOwnerSet = ` - ${privateResultSet} - ownerContent - `; - // cannot update a record with owner protected field that does not allow update operation - await expect( - async () => - await user1TodoHelper.update(updateResultSetName, { ...todoPrivateFields, ownerContent: 'Owner Content' }, privateAndOwnerSet), - ).rejects.toThrowErrorMatchingInlineSnapshot(expectedOperationError(updateResultSetName, 'Mutation')); - - const privateAndOwnersSet = ` - ${privateResultSet} - ownersContent - `; - // non-owner cannot update a record with dynamic owner list protected field - await expect( - async () => - await user2TodoHelper.update(updateResultSetName, { ...todoPrivateFields, ownersContent: 'Owners Content' }, privateAndOwnersSet), - ).rejects.toThrowErrorMatchingInlineSnapshot(expectedOperationError(updateResultSetName, 'Mutation')); - - const privateAndGroupSet = ` - ${privateResultSet} - groupContent - `; - // cannot update a record with group protected field that does not allow update operation - await expect( - async () => - await user1TodoHelper.update(updateResultSetName, { ...todoPrivateFields, groupContent: 'Group Content' }, privateAndGroupSet), - ).rejects.toThrowErrorMatchingInlineSnapshot(expectedOperationError(updateResultSetName, 'Mutation')); - - const privateAndGroupsSet = ` - ${privateResultSet} - groupsContent - `; - // non-owner cannot update a record with dynamic group list protected field - await expect( - async () => - await user2TodoHelper.update(updateResultSetName, { ...todoPrivateFields, groupsContent: 'Groups Content' }, privateAndGroupsSet), - ).rejects.toThrowErrorMatchingInlineSnapshot(expectedOperationError(updateResultSetName, 'Mutation')); - - // cannot read a record with public protected field - const getResult1 = await user1TodoHelper.get({ id: todoPrivateFields['id'] }, privateAndPublicSet, false, 'all'); - checkOperationResult( - getResult1, - { ...todoPrivateFields, publicContent: null }, - `get${modelName}`, - false, - expectedFieldErrors(['publicContent'], modelName), - ); - - const listTodosResult1 = await user1TodoHelper.list({}, privateAndPublicSet, `list${modelName}`, false, 'all'); - checkListItemExistence(listTodosResult1, `list${modelName}`, todoPrivateFields['id'], true); - checkListResponseErrors(listTodosResult1, expectedFieldErrors(['publicContent'], modelName, false)); - - // non-owner or user not part of allowed groups cannot read owner, group protected fields - const ownerAndGroupFields = ['ownerContent', 'ownersContent', 'adminContent', 'groupContent', 'groupsContent']; - const nonPublicSet = ` - ${privateResultSet} - ${ownerAndGroupFields.join('\n')} - `; - const getResult2 = await user2TodoHelper.get({ id: todoPrivateFields['id'] }, nonPublicSet, false, 'all'); - checkOperationResult( - getResult2, - { ...todoPrivateFields, ownerContent: null, ownersContent: null, adminContent: null, groupContent: null, groupsContent: null }, - `get${modelName}`, - false, - expectedFieldErrors(ownerAndGroupFields, modelName), - ); - - const listTodosResult2 = await user2TodoHelper.list({}, nonPublicSet, `list${modelName}`, false, 'all'); - checkListItemExistence(listTodosResult2, `list${modelName}`, todoPrivateFields['id'], true); - checkListResponseErrors(listTodosResult2, expectedFieldErrors(ownerAndGroupFields, modelName, false)); - }); - - test('Owner model auth and allowed field operations', async () => { - const modelName = 'TodoOwnerContentVarious'; - const user1TodoHelper = user1ModelOperationHelpers[modelName]; - const user2TodoHelper = user2ModelOperationHelpers[modelName]; - - const todo = { - authors: [userName1], - customGroup: adminGroupName, - customGroups: [adminGroupName], - privateContent: 'Private Content', - ownersContent: 'Owners Content', - adminContent: 'Admin Content', - groupsContent: 'Groups Content', - }; - const createResultSetName = `create${modelName}`; - const updateResultSetName = `update${modelName}`; - const privateResultSet = ` - id - privateContent - `; - const ownerResultSet = ` - ${privateResultSet} - owner - authors - customGroup - customGroups - `; - const adminOwnerResultSet = ` - ${ownerResultSet} - adminContent - `; - const setWithOwnerAndGroupContent = ` - ${adminOwnerResultSet} - ownerContent - groupContent - `; - const setWithOwnersAndGroupsContent = ` - ${adminOwnerResultSet} - ownersContent - groupsContent - `; - const completeResultSet = ` - ${adminOwnerResultSet} - ownerContent - ownersContent - groupContent - groupsContent - `; - - // owner(user1) creates a record with only allowed fields - const createResult1 = await user1TodoHelper.create(createResultSetName, todo, setWithOwnersAndGroupsContent); - expect(createResult1.data[createResultSetName].id).toBeDefined(); - todo['id'] = createResult1.data[createResultSetName].id; - todo['owner'] = userName1; - // protected fields are nullified in mutation responses - const nulledOwnerFields = ['owner', 'authors', 'customGroup', 'customGroups', 'privateContent']; - expectNullFields(createResult1.data[createResultSetName], [...nulledOwnerFields, 'ownersContent', 'groupsContent']); - - // user1 can update the allowed fields and add user2 to dyamic owners list field - const todoUpdated1 = { - id: todo['id'], - authors: [userName1, userName2], - customGroups: [adminGroupName, devGroupName], - privateContent: 'Private Content updated', - ownerContent: 'Owner Content', - adminContent: 'Admin Content updated', - groupContent: 'Group Content', - }; - const updateResult1 = await user1TodoHelper.update(updateResultSetName, todoUpdated1, setWithOwnerAndGroupContent); - expect(updateResult1.data[updateResultSetName].id).toEqual(todo['id']); - expectNullFields(updateResult1.data[updateResultSetName], [...nulledOwnerFields, 'ownerContent', 'groupContent']); - - // user1 can read the allowed fields - const getResult1 = await user1TodoHelper.get( - { - id: todo['id'], - }, - completeResultSet, - false, - ); - checkOperationResult(getResult1, { ...todo, ...todoUpdated1 }, `get${modelName}`); - - const listTodosResult1 = await user1TodoHelper.list({}, completeResultSet, `list${modelName}`, false, 'all'); - checkListItemExistence(listTodosResult1, `list${modelName}`, todo['id'], true); - - // unless one has delete access to all fields in the model, delete is expected to fail - await expect( - async () => await user1TodoHelper.delete(`delete${modelName}`, { id: todo['id'] }, privateResultSet), - ).rejects.toThrowErrorMatchingInlineSnapshot(expectedOperationError(`delete${modelName}`, 'Mutation')); - - // user2 can update the private field - const todoUpdated2 = { - id: todo['id'], - privateContent: 'Private Content updated 1', - }; - const updateResult2 = await user2TodoHelper.update(updateResultSetName, todoUpdated2, privateResultSet); - expect(updateResult2.data[updateResultSetName].id).toEqual(todo['id']); - expectNullFields(updateResult2.data[updateResultSetName], ['privateContent']); - - // user2 can read the allowed fields - const user2ReadAllowedSet = ` - id - privateContent - ownersContent - groupsContent - `; - const getResult2 = await user2TodoHelper.get( - { - id: todo['id'], - }, - user2ReadAllowedSet, - false, - ); - checkOperationResult( - getResult2, - { - id: todo['id'], - privateContent: todoUpdated2['privateContent'], - ownersContent: todo['ownersContent'], - groupsContent: todo['groupsContent'], - }, - `get${modelName}`, - ); - - const listTodosResult2 = await user2TodoHelper.list({}, user2ReadAllowedSet, `list${modelName}`, false, 'all'); - checkListItemExistence(listTodosResult2, `list${modelName}`, todo['id'], true); - - // owner can listen to updates on allowed fields - const todoRandom = { - ...todo, - id: Date.now().toString(), - }; - const todoRandomUpdated = { - ...todoUpdated1, - id: todoRandom.id, - owner: userName1, - privateContent: 'Private Content updated', - }; - const subscriberClient = getConfiguredAppsyncClientOIDCAuth(graphQlEndpoint, region, userMap[userName1]); - const subTodoHelper = createModelOperationHelpers(subscriberClient, schema)[modelName]; - - const onCreateSubscriptionResult = await subTodoHelper.subscribe( - 'onCreate', - [ - async () => { - await user1TodoHelper.create(createResultSetName, todoRandom, setWithOwnersAndGroupsContent); - }, - ], - {}, - setWithOwnersAndGroupsContent, - false, - ); - expect(onCreateSubscriptionResult).toHaveLength(1); - checkOperationResult( - onCreateSubscriptionResult[0], - { - ...todoRandom, - owner: null, - authors: null, - customGroup: null, - customGroups: null, - privateContent: null, - adminContent: null, - ownersContent: null, - groupsContent: null, - }, - `onCreate${modelName}`, - ); - - const onUpdateSubscriptionResult = await subTodoHelper.subscribe( - 'onUpdate', - [ - async () => { - await user1TodoHelper.update(`update${modelName}`, todoRandomUpdated, setWithOwnerAndGroupContent); - }, - ], - {}, - setWithOwnerAndGroupContent, - false, - ); - expect(onUpdateSubscriptionResult).toHaveLength(1); - checkOperationResult( - onUpdateSubscriptionResult[0], - { - ...todoRandomUpdated, - owner: null, - authors: null, - customGroup: null, - customGroups: null, - privateContent: null, - adminContent: null, - ownerContent: null, - groupContent: null, - }, - `onUpdate${modelName}`, - ); - - const onDeleteSubscriptionResult = await subTodoHelper.subscribe('onDelete', [], {}, setWithOwnersAndGroupsContent, false); - expect(onDeleteSubscriptionResult).toHaveLength(0); - }); - - test('Owner model auth and restricted field operations', async () => { - const modelName = 'TodoOwnerContentVarious'; - const user1TodoHelper = user1ModelOperationHelpers[modelName]; - const user2TodoHelper = user2ModelOperationHelpers[modelName]; - - const todo = { - authors: [userName1], - customGroup: adminGroupName, - customGroups: [adminGroupName], - privateContent: 'Private Content', - ownersContent: 'Owners Content', - adminContent: 'Admin Content', - groupsContent: 'Groups Content', - }; - const createResultSetName = `create${modelName}`; - const updateResultSetName = `update${modelName}`; - const privateResultSet = ` - id - privateContent - `; - const ownerResultSet = ` - ${privateResultSet} - owner - authors - customGroup - customGroups - `; - const adminOwnerResultSet = ` - ${ownerResultSet} - adminContent - `; - const setWithOwnerAndGroupContent = ` - ${adminOwnerResultSet} - ownerContent - groupContent - `; - const setWithOwnersAndGroupsContent = ` - ${adminOwnerResultSet} - ownersContent - groupsContent - `; - const completeResultSet = ` - ${adminOwnerResultSet} - ownerContent - ownersContent - groupContent - groupsContent - `; - - // user1 cannot create a record by specifying user2 as the owner - await expect( - async () => await user1TodoHelper.create(createResultSetName, { ...todo, owner: 'user2' }), - ).rejects.toThrowErrorMatchingInlineSnapshot(expectedOperationError(createResultSetName, 'Mutation')); - - // user cannot create a record with public protected field - await expect( - async () => await user1TodoHelper.create(createResultSetName, { ...todo, publicContent: 'Public Content' }), - ).rejects.toThrowErrorMatchingInlineSnapshot(expectedOperationError(createResultSetName, 'Mutation')); - - // user cannot create a record with a owner protected field that does not allow create operation - await expect( - async () => await user1TodoHelper.create(createResultSetName, { ...todo, ownerContent: 'Owner Content' }), - ).rejects.toThrowErrorMatchingInlineSnapshot(expectedOperationError(createResultSetName, 'Mutation')); - - // user cannot create a record with a group protected field that does not allow create operation - await expect( - async () => await user1TodoHelper.create(createResultSetName, { ...todo, groupContent: 'Group Content' }), - ).rejects.toThrowErrorMatchingInlineSnapshot(expectedOperationError(createResultSetName, 'Mutation')); - - // Create a record with allowed fields, so we can test the update and delete operations. - const createResult1 = await user1TodoHelper.create(createResultSetName, todo, setWithOwnersAndGroupsContent); - expect(createResult1.data[createResultSetName].id).toBeDefined(); - todo['id'] = createResult1.data[createResultSetName].id; - todo['owner'] = userName1; - // protected fields are nullified in mutation responses - const nulledOwnerFields = ['owner', 'authors', 'customGroup', 'customGroups', 'privateContent']; - expectNullFields(createResult1.data[createResultSetName], [...nulledOwnerFields, 'ownersContent']); - - const publicFieldSet = ` - id - publicContent - `; - // owner cannot update a record with public protected field - await expect( - async () => await user1TodoHelper.update(updateResultSetName, { id: todo['id'], publicContent: 'Public Content' }, publicFieldSet), - ).rejects.toThrowErrorMatchingInlineSnapshot(expectedOperationError(updateResultSetName, 'Mutation')); - - // owner cannot update a record with dynamic list of owners protected field that does not allow update operation - await expect( - async () => await user1TodoHelper.update(updateResultSetName, { ...todo, ownersContent: 'Owners Content' }), - ).rejects.toThrowErrorMatchingInlineSnapshot(expectedOperationError(updateResultSetName, 'Mutation')); - - // owner cannot update a record with dynamic list of groups protected field that does not allow update operation - await expect( - async () => await user1TodoHelper.update(updateResultSetName, { ...todo, groupsContent: 'Groups Content' }), - ).rejects.toThrowErrorMatchingInlineSnapshot(expectedOperationError(updateResultSetName, 'Mutation')); - - // non-owner cannot update a record to re-assign ownership - const ownerFieldSet = ` - id - owner - `; - await expect( - async () => await user2TodoHelper.update(updateResultSetName, { id: todo['id'], owner: 'user2' }, ownerFieldSet), - ).rejects.toThrowErrorMatchingInlineSnapshot(expectedOperationError(updateResultSetName, 'Mutation')); - - // non-owner cannot update a record to re-assign owners in dynamic owners list - const ownersFieldSet = ` - id - authors - `; - await expect( - async () => await user2TodoHelper.update(updateResultSetName, { id: todo['id'], authors: ['user2'] }, ownersFieldSet), - ).rejects.toThrowErrorMatchingInlineSnapshot(expectedOperationError(updateResultSetName, 'Mutation')); - - // non-owner cannot update a record to re-assign group membership - const groupFieldSet = ` - id - customGroup - `; - await expect( - async () => await user2TodoHelper.update(updateResultSetName, { id: todo['id'], customGroup: devGroupName }, groupFieldSet), - ).rejects.toThrowErrorMatchingInlineSnapshot(expectedOperationError(updateResultSetName, 'Mutation')); - - // non-owner cannot update a record to re-assign group memberships stored as dynamic list - const groupsFieldSet = ` - id - customGroups - `; - await expect( - async () => await user2TodoHelper.update(updateResultSetName, { id: todo['id'], customGroups: [devGroupName] }, groupsFieldSet), - ).rejects.toThrowErrorMatchingInlineSnapshot(expectedOperationError(updateResultSetName, 'Mutation')); - - // non-owner cannot update a record with an owner protected field - const ownerContentFieldSet = ` - id - ownerContent - `; - await expect( - async () => await user2TodoHelper.update(updateResultSetName, { ...todo, ownerContent: 'Owner Content' }, ownerContentFieldSet), - ).rejects.toThrowErrorMatchingInlineSnapshot(expectedOperationError(updateResultSetName, 'Mutation')); - - // non-member of group cannot update a record with a group protected field - const groupContentFieldSet = ` - id - groupContent - `; - await expect( - async () => await user2TodoHelper.update(updateResultSetName, { ...todo, groupContent: 'Group Content' }, groupContentFieldSet), - ).rejects.toThrowErrorMatchingInlineSnapshot(expectedOperationError(updateResultSetName, 'Mutation')); - - // owner cannot read a record with public protected field - const getResult1 = await user1TodoHelper.get({ id: todo['id'] }, publicFieldSet, false, 'all'); - checkOperationResult( - getResult1, - { id: todo['id'], publicContent: null }, - `get${modelName}`, - false, - expectedFieldErrors(['publicContent'], modelName), - ); - - const listTodosResult1 = await user1TodoHelper.list({}, publicFieldSet, `list${modelName}`, false, 'all'); - checkListItemExistence(listTodosResult1, `list${modelName}`, todo['id'], true); - checkListResponseErrors(listTodosResult1, expectedFieldErrors(['publicContent'], modelName, false)); - - // non-owner cannot read a record with owner and group protected fields in the selection set - const ownerReadFieldSet = ` - id - owner - authors - customGroup - customGroups - ownerContent - ownersContent - adminContent - groupContent - groupsContent - `; - const getResult2 = await user2TodoHelper.get({ id: todo['id'] }, ownerReadFieldSet, false, 'all'); - const expectedReadErrorFields = [ - 'owner', - 'authors', - 'ownerContent', - 'ownersContent', - 'adminContent', - 'groupContent', - 'groupsContent', - ]; - checkOperationResult( - getResult2, - { - id: todo['id'], - owner: null, - authors: null, - customGroup: null, - customGroups: null, - ownerContent: null, - ownersContent: null, - adminContent: null, - groupContent: null, - groupsContent: null, - }, - `get${modelName}`, - false, - expectedFieldErrors(expectedReadErrorFields, modelName), - ); - - const listTodosResult2 = await user2TodoHelper.list({}, ownerReadFieldSet, `list${modelName}`, false, 'all'); - checkListItemExistence(listTodosResult2, `list${modelName}`, todo['id'], true); - checkListResponseErrors(listTodosResult2, expectedFieldErrors(expectedReadErrorFields, modelName, false)); - - // non-owner cannot listen to updates on owner protected fields - const todoRandom = { - ...todo, - id: Date.now().toString(), - }; - const todoRandomUpdated = { - id: todoRandom.id, - owner: userName1, - privateContent: 'Private Content updated', - ownerContent: 'Owner Content updated', - groupContent: 'Group Content updated', - }; - const subscriberClient = getConfiguredAppsyncClientOIDCAuth(graphQlEndpoint, region, userMap[userName2]); - const subTodoHelper = createModelOperationHelpers(subscriberClient, schema)[modelName]; - - const onCreateSubscriptionResult = await subTodoHelper.subscribe('onCreate', [ - async () => { - await user1TodoHelper.create(createResultSetName, todoRandom, setWithOwnersAndGroupsContent); - }, - ]); - const expectedSubscriptionNullFields = [...expectedReadErrorFields, 'privateContent', 'publicContent']; - expect(onCreateSubscriptionResult).toHaveLength(1); - expect(onCreateSubscriptionResult[0].data[`onCreate${modelName}`].id).toEqual(todoRandom.id); - expectNullFields(onCreateSubscriptionResult[0].data[`onCreate${modelName}`], expectedSubscriptionNullFields); - - const onUpdateSubscriptionResult = await subTodoHelper.subscribe('onUpdate', [ - async () => { - await user1TodoHelper.update(`update${modelName}`, todoRandomUpdated, setWithOwnerAndGroupContent); - }, - ]); - expect(onUpdateSubscriptionResult).toHaveLength(1); - expect(onUpdateSubscriptionResult[0].data[`onUpdate${modelName}`].id).toEqual(todoRandom.id); - expectNullFields(onUpdateSubscriptionResult[0].data[`onUpdate${modelName}`], expectedSubscriptionNullFields); - - const onDeleteSubscriptionResult = await subTodoHelper.subscribe('onDelete', []); - expect(onDeleteSubscriptionResult).toHaveLength(0); - }); - - test('Custom owner model auth and allowed field operations', async () => { - const modelName = 'TodoCustomOwnerContentVarious'; - const user1TodoHelper = user1ModelOperationHelpers[modelName]; - const user2TodoHelper = user2ModelOperationHelpers[modelName]; - - const todo = { - customId: Date.now().toString(), - author: userName1, - privateContent: 'Private Content', - ownerContent: 'Owner Content', - }; - const createResultSetName = `create${modelName}`; - const updateResultSetName = `update${modelName}`; - const deleteResultSetName = `delete${modelName}`; - const privateResultSet = ` - customId - privateContent - `; - const ownerResultSet = ` - ${privateResultSet} - author - `; - const completeOwnerResultSet = ` - ${ownerResultSet} - ownerContent - `; - - // owner(user1) creates a record with only allowed fields - const createResult1 = await user1TodoHelper.create(createResultSetName, todo, completeOwnerResultSet); - checkOperationResult(createResult1, { ...todo, author: null, privateContent: null, ownerContent: null }, createResultSetName); - - // user1 can update the allowed fields - const todoUpdated1 = { - customId: todo['customId'], - author: userName1, - privateContent: 'Private Content updated', - }; - const updateResult1 = await user1TodoHelper.update(updateResultSetName, todoUpdated1, completeOwnerResultSet); - checkOperationResult( - updateResult1, - { ...todo, ...todoUpdated1, author: null, privateContent: null, ownerContent: null }, - updateResultSetName, - ); - - // user1 can read the allowed fields - const getResult1 = await user1TodoHelper.get( - { - customId: todo['customId'], - }, - completeOwnerResultSet, - false, - 'all', - 'customId', - ); - checkOperationResult(getResult1, { ...todo, ...todoUpdated1 }, `get${modelName}`); - - const listTodosResult1 = await user1TodoHelper.list({}, completeOwnerResultSet, `list${modelName}`, false, 'all'); - checkListItemExistence(listTodosResult1, `list${modelName}`, todo['customId'], true, 'customId'); - - // user2 can update the private field - const todoUpdated2 = { - customId: todo['customId'], - privateContent: 'Private Content updated 1', - }; - const updateResult2 = await user2TodoHelper.update(updateResultSetName, todoUpdated2, privateResultSet); - expect(updateResult2.data[updateResultSetName].customId).toEqual(todo['customId']); - expectNullFields(updateResult2.data[updateResultSetName], ['privateContent']); - - // user2 can read the allowed fields - const getResult2 = await user2TodoHelper.get( - { - customId: todo['customId'], - }, - privateResultSet, - false, - 'all', - 'customId', - ); - checkOperationResult( - getResult2, - { - customId: todo['customId'], - privateContent: todoUpdated2['privateContent'], - }, - `get${modelName}`, - ); - - const listTodosResult2 = await user2TodoHelper.list({}, privateResultSet, `list${modelName}`, false, 'all'); - checkListItemExistence(listTodosResult2, `list${modelName}`, todo['customId'], true, 'customId'); - - // user1 can delete the record - const deleteResult1 = await user1TodoHelper.delete(deleteResultSetName, { customId: todo['customId'] }, completeOwnerResultSet); - checkOperationResult( - deleteResult1, - { ...todo, ...todoUpdated1, privateContent: null, ownerContent: null, author: null }, - deleteResultSetName, - ); - - // owner(user1) can listen to updates on allowed fields - const todoRandom = { - ...todo, - customId: Date.now().toString(), - }; - const todoRandomUpdated = { - customId: todoRandom.customId, - author: userName1, - privateContent: 'Private Content updated', - }; - const subscriberClient = getConfiguredAppsyncClientOIDCAuth(graphQlEndpoint, region, userMap[userName1]); - const subTodoHelper = createModelOperationHelpers(subscriberClient, schema)[modelName]; - - const onCreateSubscriptionResult = await subTodoHelper.subscribe( - 'onCreate', - [ - async () => { - await user1TodoHelper.create(createResultSetName, todoRandom, completeOwnerResultSet); - }, - ], - {}, - completeOwnerResultSet, - false, - ); - expect(onCreateSubscriptionResult).toHaveLength(1); - checkOperationResult( - onCreateSubscriptionResult[0], - { ...todoRandom, author: null, privateContent: null, ownerContent: null }, - `onCreate${modelName}`, - ); - - const onUpdateSubscriptionResult = await subTodoHelper.subscribe( - 'onUpdate', - [ - async () => { - await user1TodoHelper.update(updateResultSetName, todoRandomUpdated, completeOwnerResultSet); - }, - ], - {}, - completeOwnerResultSet, - false, - ); - expect(onUpdateSubscriptionResult).toHaveLength(1); - checkOperationResult( - onUpdateSubscriptionResult[0], - { ...todoRandomUpdated, author: null, privateContent: null, ownerContent: null }, - `onUpdate${modelName}`, - ); - - const onDeleteSubscriptionResult = await subTodoHelper.subscribe( - 'onDelete', - [ - async () => { - await user1TodoHelper.delete(deleteResultSetName, { customId: todoRandom.customId }, completeOwnerResultSet); - }, - ], - {}, - completeOwnerResultSet, - false, - ); - expect(onDeleteSubscriptionResult).toHaveLength(1); - checkOperationResult( - onDeleteSubscriptionResult[0], - { ...todoRandomUpdated, author: null, privateContent: null, ownerContent: null }, - `onDelete${modelName}`, - ); - }); - - test('Custom owner model auth and restricted field operations', async () => { - const modelName = 'TodoCustomOwnerContentVarious'; - const user1TodoHelper = user1ModelOperationHelpers[modelName]; - const user2TodoHelper = user2ModelOperationHelpers[modelName]; - - const todo = { - customId: Date.now().toString(), - author: userName1, - privateContent: 'Private Content', - ownerContent: 'Owner Content', - }; - const createResultSetName = `create${modelName}`; - const updateResultSetName = `update${modelName}`; - const deleteResultSetName = `delete${modelName}`; - const privateResultSet = ` - customId - privateContent - `; - const ownerResultSet = ` - ${privateResultSet} - author - `; - const completeOwnerResultSet = ` - ${ownerResultSet} - ownerContent - `; - - // user1 cannot create a record by specifying user2 as the owner - await expect( - async () => await user1TodoHelper.create(createResultSetName, { ...todo, author: 'user2' }, completeOwnerResultSet), - ).rejects.toThrowErrorMatchingInlineSnapshot(expectedOperationError(createResultSetName, 'Mutation')); - - // user cannot create a record with public protected field - await expect( - async () => await user1TodoHelper.create(createResultSetName, { ...todo, publicContent: 'Public Content' }), - ).rejects.toThrowErrorMatchingInlineSnapshot(expectedOperationError(createResultSetName, 'Mutation')); - - // Create a record with allowed fields, so we can test the update and delete operations. - const createResult1 = await user1TodoHelper.create(createResultSetName, todo, completeOwnerResultSet); - checkOperationResult(createResult1, { ...todo, author: null, privateContent: null, ownerContent: null }, createResultSetName); - - const publicFieldSet = ` - customId - publicContent - `; - // owner cannot update a record with public protected field - await expect( - async () => - await user1TodoHelper.update( - updateResultSetName, - { customId: todo['customId'], publicContent: 'Public Content' }, - publicFieldSet, - ), - ).rejects.toThrowErrorMatchingInlineSnapshot(expectedOperationError(updateResultSetName, 'Mutation')); - - // owner cannot update a record with a protected field that does not allow update operation - await expect( - async () => await user1TodoHelper.update(updateResultSetName, { ...todo, ownerContent: 'Owner Content' }), - ).rejects.toThrowErrorMatchingInlineSnapshot(expectedOperationError(updateResultSetName, 'Mutation')); - - // non-owner cannot update a record to re-assign ownership - const ownerFieldSet = ` - customId - author - `; - await expect( - async () => await user2TodoHelper.update(updateResultSetName, { customId: todo['customId'], author: 'user2' }, ownerFieldSet), - ).rejects.toThrowErrorMatchingInlineSnapshot(expectedOperationError(updateResultSetName, 'Mutation')); - - // non-owner cannot update a record with an owner protected field - const ownerContentFieldSet = ` - customId - ownerContent - `; - await expect( - async () => await user2TodoHelper.update(updateResultSetName, { ...todo, ownerContent: 'Owner Content' }, ownerContentFieldSet), - ).rejects.toThrowErrorMatchingInlineSnapshot(expectedOperationError(updateResultSetName, 'Mutation')); - - // owner cannot read a record with public protected field - const getResult1 = await user1TodoHelper.get({ customId: todo['customId'] }, publicFieldSet, false, 'all', 'customId'); - checkOperationResult( - getResult1, - { customId: todo['customId'], publicContent: null }, - `get${modelName}`, - false, - expectedFieldErrors(['publicContent'], modelName), - ); - - const listTodosResult1 = await user1TodoHelper.list({}, publicFieldSet, `list${modelName}`, false, 'all'); - checkListItemExistence(listTodosResult1, `list${modelName}`, todo['customId'], true, 'customId'); - checkListResponseErrors(listTodosResult1, expectedFieldErrors(['publicContent'], modelName, false)); - - // non-owner cannot read a record with owner protected fields in the selection set - const ownerReadFieldSet = ` - customId - author - ownerContent - `; - const getResult2 = await user2TodoHelper.get({ customId: todo['customId'] }, ownerReadFieldSet, false, 'all', 'customId'); - checkOperationResult( - getResult2, - { customId: todo['customId'], author: null, ownerContent: null }, - `get${modelName}`, - false, - expectedFieldErrors(['author', 'ownerContent'], modelName), - ); - - const listTodosResult2 = await user2TodoHelper.list({}, ownerReadFieldSet, `list${modelName}`, false, 'all'); - checkListItemExistence(listTodosResult2, `list${modelName}`, todo['customId'], true, 'customId'); - checkListResponseErrors(listTodosResult2, expectedFieldErrors(['author', 'ownerContent'], modelName, false)); - - // non-owner cannot listen to updates on owner protected fields - const todoRandom = { - ...todo, - customId: Date.now().toString(), - }; - const todoRandomUpdated = { - customId: todoRandom.customId, - author: userName1, - privateContent: 'Private Content updated', - }; - const subscriberClient = getConfiguredAppsyncClientOIDCAuth(graphQlEndpoint, region, userMap[userName2]); - const subTodoHelper = createModelOperationHelpers(subscriberClient, schema)[modelName]; - - const onCreateSubscriptionResult = await subTodoHelper.subscribe( - 'onCreate', - [ - async () => { - await user1TodoHelper.create(createResultSetName, todoRandom, completeOwnerResultSet); - }, - ], - {}, - completeOwnerResultSet, - false, - ); - expect(onCreateSubscriptionResult).toHaveLength(1); - expect(onCreateSubscriptionResult[0].data[`onCreate${modelName}`].customId).toEqual(todoRandom.customId); - expectNullFields(onCreateSubscriptionResult[0].data[`onCreate${modelName}`], ['author', 'privateContent', 'ownerContent']); - - const onUpdateSubscriptionResult = await subTodoHelper.subscribe( - 'onUpdate', - [ - async () => { - await user1TodoHelper.update(updateResultSetName, todoRandomUpdated, completeOwnerResultSet); - }, - ], - {}, - completeOwnerResultSet, - false, - ); - expect(onUpdateSubscriptionResult).toHaveLength(1); - expect(onUpdateSubscriptionResult[0].data[`onUpdate${modelName}`].customId).toEqual(todoRandom.customId); - expectNullFields(onUpdateSubscriptionResult[0].data[`onUpdate${modelName}`], ['author', 'privateContent', 'ownerContent']); - - const onDeleteSubscriptionResult = await subTodoHelper.subscribe('onDelete', [ - async () => { - await user1TodoHelper.delete(deleteResultSetName, { customId: todoRandom.customId }, completeOwnerResultSet); - }, - ]); - expect(onDeleteSubscriptionResult).toHaveLength(1); - expect(onDeleteSubscriptionResult[0].data[`onDelete${modelName}`].customId).toEqual(todoRandom.customId); - expectNullFields(onDeleteSubscriptionResult[0].data[`onDelete${modelName}`], [ - 'author', - 'privateContent', - 'publicContent', - 'ownerContent', - ]); - }); - - test('Custom list of owners model auth and allowed field operations', async () => { - const modelName = 'TodoCustomOwnersContentVarious'; - const user1TodoHelper = user1ModelOperationHelpers[modelName]; - const user2TodoHelper = user2ModelOperationHelpers[modelName]; - - const todo = { - customId: Date.now().toString(), - authors: [userName1], - privateContent: 'Private Content', - }; - const createResultSetName = `create${modelName}`; - const updateResultSetName = `update${modelName}`; - const deleteResultSetName = `delete${modelName}`; - const privateResultSet = ` - customId - privateContent - `; - const ownerResultSet = ` - ${privateResultSet} - authors - `; - const completeOwnerResultSet = ` - ${ownerResultSet} - ownersContent - `; - - // owner(user1) creates a record with only allowed fields - const createResult1 = await user1TodoHelper.create(createResultSetName, todo, completeOwnerResultSet); - checkOperationResult(createResult1, { ...todo, authors: null, privateContent: null, ownersContent: null }, createResultSetName); - - // user1 can update the allowed fields - const todoUpdated1 = { - customId: todo['customId'], - authors: [userName1, userName2], - privateContent: 'Private Content updated', - ownersContent: 'Owners Content', - }; - const updateResult1 = await user1TodoHelper.update(updateResultSetName, todoUpdated1, completeOwnerResultSet); - checkOperationResult( - updateResult1, - { ...todo, ...todoUpdated1, authors: null, privateContent: null, ownersContent: null }, - updateResultSetName, - ); - - // user1 can read the allowed fields - const getResult1 = await user1TodoHelper.get( - { - customId: todo['customId'], - }, - completeOwnerResultSet, - false, - 'all', - 'customId', - ); - checkOperationResult(getResult1, { ...todo, ...todoUpdated1 }, `get${modelName}`); - - const listTodosResult1 = await user1TodoHelper.list({}, completeOwnerResultSet, `list${modelName}`, false, 'all'); - checkListItemExistence(listTodosResult1, `list${modelName}`, todo['customId'], true, 'customId'); - - // user2 can update the dynamic owners protected field - const todoUpdated2 = { - customId: todo['customId'], - ownersContent: 'Owners Content Updated', - }; - const user2UpdateSet = ` - customId - ownersContent - `; - const updateResult2 = await user2TodoHelper.update(updateResultSetName, todoUpdated2, user2UpdateSet); - expect(updateResult2.data[updateResultSetName].customId).toEqual(todo['customId']); - expectNullFields(updateResult2.data[updateResultSetName], ['ownersContent']); - - // user2 can update the private field - const todoUpdated3 = { - customId: todo['customId'], - privateContent: 'Private Content updated 1', - }; - const updateResult3 = await user2TodoHelper.update(updateResultSetName, todoUpdated3, privateResultSet); - expect(updateResult3.data[updateResultSetName].customId).toEqual(todo['customId']); - expectNullFields(updateResult3.data[updateResultSetName], ['privateContent']); - - // user2 can read the allowed fields - const getResult2 = await user2TodoHelper.get( - { - customId: todo['customId'], - }, - privateResultSet, - false, - 'all', - 'customId', - ); - checkOperationResult( - getResult2, - { - customId: todo['customId'], - privateContent: todoUpdated3['privateContent'], - }, - `get${modelName}`, - ); - - const listTodosResult2 = await user2TodoHelper.list({}, privateResultSet, `list${modelName}`, false, 'all'); - checkListItemExistence(listTodosResult2, `list${modelName}`, todo['customId'], true, 'customId'); - - // owner(user1) can listen to updates on allowed fields - const todoRandom = { - ...todo, - customId: Date.now().toString(), - }; - const todoRandomUpdated = { - customId: todoRandom.customId, - authors: [userName1], - privateContent: 'Private Content updated', - }; - const subscriberClient = getConfiguredAppsyncClientOIDCAuth(graphQlEndpoint, region, userMap[userName1]); - const subTodoHelper = createModelOperationHelpers(subscriberClient, schema)[modelName]; - - const onCreateSubscriptionResult = await subTodoHelper.subscribe( - 'onCreate', - [ - async () => { - await user1TodoHelper.create(createResultSetName, todoRandom, completeOwnerResultSet); - }, - ], - {}, - completeOwnerResultSet, - false, - ); - expect(onCreateSubscriptionResult).toHaveLength(1); - checkOperationResult( - onCreateSubscriptionResult[0], - { ...todoRandom, authors: null, privateContent: null, ownersContent: null }, - `onCreate${modelName}`, - ); - - const onUpdateSubscriptionResult = await subTodoHelper.subscribe( - 'onUpdate', - [ - async () => { - await user1TodoHelper.update(updateResultSetName, todoRandomUpdated, completeOwnerResultSet); - }, - ], - {}, - completeOwnerResultSet, - false, - ); - expect(onUpdateSubscriptionResult).toHaveLength(1); - checkOperationResult( - onUpdateSubscriptionResult[0], - { ...todoRandomUpdated, authors: null, privateContent: null, ownersContent: null }, - `onUpdate${modelName}`, - ); - }); - - test('Custom list of owners model auth and restricted field operations', async () => { - const modelName = 'TodoCustomOwnersContentVarious'; - const user1TodoHelper = user1ModelOperationHelpers[modelName]; - const user2TodoHelper = user2ModelOperationHelpers[modelName]; - - const todo = { - customId: Date.now().toString(), - privateContent: 'Private Content', - }; - const createResultSetName = `create${modelName}`; - const updateResultSetName = `update${modelName}`; - const deleteResultSetName = `delete${modelName}`; - const privateResultSet = ` - customId - privateContent - `; - const ownerResultSet = ` - ${privateResultSet} - authors - `; - const completeOwnerResultSet = ` - ${ownerResultSet} - ownersContent - `; - - // user1 cannot create a record by specifying user2 as the only owner - await expect( - async () => await user1TodoHelper.create(createResultSetName, { ...todo, authors: [userName2] }, completeOwnerResultSet), - ).rejects.toThrowErrorMatchingInlineSnapshot(expectedOperationError(createResultSetName, 'Mutation')); - - // user cannot create a record with dynamic owner list protected field that does not allow create operation - await expect( - async () => - await user1TodoHelper.create( - createResultSetName, - { ...todo, authors: [userName1], ownersContent: 'Owners Content' }, - completeOwnerResultSet, - ), - ).rejects.toThrowErrorMatchingInlineSnapshot(expectedOperationError(createResultSetName, 'Mutation')); - - // user cannot create a record with public protected field - await expect( - async () => await user1TodoHelper.create(createResultSetName, { ...todo, publicContent: 'Public Content' }), - ).rejects.toThrowErrorMatchingInlineSnapshot(expectedOperationError(createResultSetName, 'Mutation')); - - // Create a record with non-public fields which is allowed, so we can test the update and delete operations. - const createResult = await user1TodoHelper.create(createResultSetName, { ...todo, authors: [userName1] }, ownerResultSet); - checkOperationResult(createResult, { ...todo, authors: null, privateContent: null }, createResultSetName); - todo['authors'] = [userName1]; - - const publicFieldSet = ` - customId - publicContent - `; - // owner cannot update a record with public protected field - await expect( - async () => - await user1TodoHelper.update( - updateResultSetName, - { customId: todo['customId'], publicContent: 'Public Content' }, - publicFieldSet, - ), - ).rejects.toThrowErrorMatchingInlineSnapshot(expectedOperationError(updateResultSetName, 'Mutation')); - - // non-owner cannot update a record to re-assign ownership - const ownerFieldSet = ` - customId - authors - `; - await expect( - async () => await user2TodoHelper.update(updateResultSetName, { customId: todo['customId'], authors: ['user2'] }, ownerFieldSet), - ).rejects.toThrowErrorMatchingInlineSnapshot(expectedOperationError(updateResultSetName, 'Mutation')); - - // non-owner cannot update a record with a dynamic owner list protected field - const ownersContentFieldSet = ` - customId - ownersContent - `; - await expect( - async () => await user2TodoHelper.update(updateResultSetName, { ...todo, ownersContent: 'Owners Content' }, ownersContentFieldSet), - ).rejects.toThrowErrorMatchingInlineSnapshot(expectedOperationError(updateResultSetName, 'Mutation')); - - // owner cannot read a record with public protected field - const getResult1 = await user1TodoHelper.get({ customId: todo['customId'] }, publicFieldSet, false, 'all', 'customId'); - checkOperationResult( - getResult1, - { customId: todo['customId'], publicContent: null }, - `get${modelName}`, - false, - expectedFieldErrors(['publicContent'], modelName), - ); - - const listTodosResult1 = await user1TodoHelper.list({}, publicFieldSet, `list${modelName}`, false, 'all'); - checkListItemExistence(listTodosResult1, `list${modelName}`, todo['customId'], true, 'customId'); - checkListResponseErrors(listTodosResult1, expectedFieldErrors(['publicContent'], modelName, false)); - - // non-owner cannot read a record with dynamic owner list protected field in the selection set - const ownerReadFieldSet = ` - customId - authors - ownersContent - `; - const getResult2 = await user2TodoHelper.get({ customId: todo['customId'] }, ownerReadFieldSet, false, 'all', 'customId'); - checkOperationResult( - getResult2, - { customId: todo['customId'], authors: null, ownersContent: null }, - `get${modelName}`, - false, - expectedFieldErrors(['authors', 'ownersContent'], modelName), - ); - - const listTodosResult2 = await user2TodoHelper.list({}, ownerReadFieldSet, `list${modelName}`, false, 'all'); - checkListItemExistence(listTodosResult2, `list${modelName}`, todo['customId'], true, 'customId'); - checkListResponseErrors(listTodosResult2, expectedFieldErrors(['authors', 'ownersContent'], modelName, false)); - - // non-owner cannot listen to updates on owner protected fields - const todoRandom = { - ...todo, - customId: Date.now().toString(), - }; - const todoRandomUpdated = { - customId: todoRandom.customId, - authors: [userName1], - privateContent: 'Private Content updated', - ownersContent: 'Owners Content updated', - }; - const subscriberClient = getConfiguredAppsyncClientOIDCAuth(graphQlEndpoint, region, userMap[userName2]); - const subTodoHelper = createModelOperationHelpers(subscriberClient, schema)[modelName]; - - const onCreateSubscriptionResult = await subTodoHelper.subscribe( - 'onCreate', - [ - async () => { - await user1TodoHelper.create(createResultSetName, todoRandom, completeOwnerResultSet); - }, - ], - {}, - completeOwnerResultSet, - false, - ); - expect(onCreateSubscriptionResult).toHaveLength(1); - expect(onCreateSubscriptionResult[0].data[`onCreate${modelName}`].customId).toEqual(todoRandom.customId); - expectNullFields(onCreateSubscriptionResult[0].data[`onCreate${modelName}`], ['authors', 'privateContent', 'ownersContent']); - - const onUpdateSubscriptionResult = await subTodoHelper.subscribe( - 'onUpdate', - [ - async () => { - await user1TodoHelper.update(updateResultSetName, todoRandomUpdated, completeOwnerResultSet); - }, - ], - {}, - completeOwnerResultSet, - false, - ); - expect(onUpdateSubscriptionResult).toHaveLength(1); - expect(onUpdateSubscriptionResult[0].data[`onUpdate${modelName}`].customId).toEqual(todoRandom.customId); - expectNullFields(onUpdateSubscriptionResult[0].data[`onUpdate${modelName}`], ['authors', 'privateContent', 'ownersContent']); - }); - - test('admin group protected model and allowed field operations', async () => { - const modelName = 'TodoAdminContentVarious'; - const user1TodoHelper = user1ModelOperationHelpers[modelName]; - const user2TodoHelper = user2ModelOperationHelpers[modelName]; - - const todo = { - owner: userName1, - authors: [userName1], - customGroup: adminGroupName, - customGroups: [adminGroupName], - privateContent: 'Private Content', - ownersContent: 'Owners Content', - groupsContent: 'Groups Content', - }; - const createResultSetName = `create${modelName}`; - const updateResultSetName = `update${modelName}`; - const privateResultSet = ` - id - privateContent - `; - const adminResultSet = ` - ${privateResultSet} - owner - authors - customGroup - customGroups - `; - const setWithOwnerAndGroupContent = ` - ${adminResultSet} - ownerContent - groupContent - `; - const setWithOwnersAndGroupsContent = ` - ${adminResultSet} - ownersContent - groupsContent - `; - const completeResultSet = ` - ${adminResultSet} - ownerContent - ownersContent - groupContent - groupsContent - `; - - // admin(user1) creates a record with only allowed fields - const createResult1 = await user1TodoHelper.create(createResultSetName, todo, setWithOwnersAndGroupsContent); - expect(createResult1.data[createResultSetName].id).toBeDefined(); - todo['id'] = createResult1.data[createResultSetName].id; - todo['owner'] = userName1; - // protected fields are nullified in mutation responses - const nulledOwnerFields = ['owner', 'authors', 'customGroup', 'customGroups', 'privateContent']; - expectNullFields(createResult1.data[createResultSetName], [...nulledOwnerFields, 'ownersContent', 'groupsContent']); - - // user1 can update the allowed fields and add user2 to dyamic owners list field - const todoUpdated1 = { - id: todo['id'], - authors: [userName1, userName2], - customGroups: [adminGroupName, devGroupName], - privateContent: 'Private Content updated', - ownerContent: 'Owner Content', - groupContent: 'Group Content', - }; - const updateResult1 = await user1TodoHelper.update(updateResultSetName, todoUpdated1, setWithOwnerAndGroupContent); - expect(updateResult1.data[updateResultSetName].id).toEqual(todo['id']); - expectNullFields(updateResult1.data[updateResultSetName], [...nulledOwnerFields, 'ownerContent', 'groupContent']); - - // user1 can read the allowed fields - const getResult1 = await user1TodoHelper.get( - { - id: todo['id'], - }, - completeResultSet, - false, - ); - checkOperationResult(getResult1, { ...todo, ...todoUpdated1 }, `get${modelName}`); - - const listTodosResult1 = await user1TodoHelper.list({}, completeResultSet, `list${modelName}`, false, 'all'); - checkListItemExistence(listTodosResult1, `list${modelName}`, todo['id'], true); - - // unless one has delete access to all fields in the model, delete is expected to fail - await expect( - async () => await user1TodoHelper.delete(`delete${modelName}`, { id: todo['id'] }, privateResultSet), - ).rejects.toThrowErrorMatchingInlineSnapshot(expectedOperationError(`delete${modelName}`, 'Mutation')); - - // user2 can update the private field - const todoUpdated2 = { - id: todo['id'], - privateContent: 'Private Content updated 1', - }; - const updateResult2 = await user2TodoHelper.update(updateResultSetName, todoUpdated2, privateResultSet); - expect(updateResult2.data[updateResultSetName].id).toEqual(todo['id']); - expectNullFields(updateResult2.data[updateResultSetName], ['privateContent']); - - // user2 can read the allowed fields - const user2ReadAllowedSet = ` - id - privateContent - ownersContent - groupsContent - `; - const getResult2 = await user2TodoHelper.get( - { - id: todo['id'], - }, - user2ReadAllowedSet, - false, - ); - checkOperationResult( - getResult2, - { - id: todo['id'], - privateContent: todoUpdated2['privateContent'], - ownersContent: todo['ownersContent'], - groupsContent: todo['groupsContent'], - }, - `get${modelName}`, - ); - - const listTodosResult2 = await user2TodoHelper.list({}, user2ReadAllowedSet, `list${modelName}`, false, 'all'); - checkListItemExistence(listTodosResult2, `list${modelName}`, todo['id'], true); - - // owner can listen to updates on allowed fields - const todoRandom = { - ...todo, - id: Date.now().toString(), - }; - const todoRandomUpdated = { - ...todoUpdated1, - id: todoRandom.id, - owner: userName1, - privateContent: 'Private Content updated', - }; - const subscriberClient = getConfiguredAppsyncClientOIDCAuth(graphQlEndpoint, region, userMap[userName1]); - const subTodoHelper = createModelOperationHelpers(subscriberClient, schema)[modelName]; - - const onCreateSubscriptionResult = await subTodoHelper.subscribe( - 'onCreate', - [ - async () => { - await user1TodoHelper.create(createResultSetName, todoRandom, setWithOwnersAndGroupsContent); - }, - ], - {}, - setWithOwnersAndGroupsContent, - false, - ); - expect(onCreateSubscriptionResult).toHaveLength(1); - checkOperationResult( - onCreateSubscriptionResult[0], - { - ...todoRandom, - owner: null, - authors: null, - customGroup: null, - customGroups: null, - privateContent: null, - ownersContent: null, - groupsContent: null, - }, - `onCreate${modelName}`, - ); - - const onUpdateSubscriptionResult = await subTodoHelper.subscribe( - 'onUpdate', - [ - async () => { - await user1TodoHelper.update(`update${modelName}`, todoRandomUpdated, setWithOwnerAndGroupContent); - }, - ], - {}, - setWithOwnerAndGroupContent, - false, - ); - expect(onUpdateSubscriptionResult).toHaveLength(1); - checkOperationResult( - onUpdateSubscriptionResult[0], - { - ...todoRandomUpdated, - owner: null, - authors: null, - customGroup: null, - customGroups: null, - privateContent: null, - ownerContent: null, - groupContent: null, - }, - `onUpdate${modelName}`, - ); - - const onDeleteSubscriptionResult = await subTodoHelper.subscribe('onDelete', [], {}, setWithOwnersAndGroupsContent, false); - expect(onDeleteSubscriptionResult).toHaveLength(0); - }); - - test('admin group protected model and restricted field operations', async () => { - const modelName = 'TodoAdminContentVarious'; - const user1TodoHelper = user1ModelOperationHelpers[modelName]; - const user2TodoHelper = user2ModelOperationHelpers[modelName]; - - const todo = { - owner: userName1, - authors: [userName1], - customGroup: adminGroupName, - customGroups: [adminGroupName], - privateContent: 'Private Content', - ownersContent: 'Owners Content', - groupsContent: 'Groups Content', - }; - const createResultSetName = `create${modelName}`; - const updateResultSetName = `update${modelName}`; - const privateResultSet = ` - id - privateContent - `; - const adminResultSet = ` - ${privateResultSet} - owner - authors - customGroup - customGroups - `; - const setWithOwnerAndGroupContent = ` - ${adminResultSet} - ownerContent - groupContent - `; - const setWithOwnersAndGroupsContent = ` - ${adminResultSet} - ownersContent - groupsContent - `; - - // admin cannot create a record with public protected field - await expect( - async () => await user1TodoHelper.create(createResultSetName, { ...todo, publicContent: 'Public Content' }), - ).rejects.toThrowErrorMatchingInlineSnapshot(expectedOperationError(createResultSetName, 'Mutation')); - - // admin owner cannot create a record with a owner protected field that does not allow create operation - await expect( - async () => await user1TodoHelper.create(createResultSetName, { ...todo, ownerContent: 'Owner Content' }), - ).rejects.toThrowErrorMatchingInlineSnapshot(expectedOperationError(createResultSetName, 'Mutation')); - - // admin cannot create a record with a group protected field that does not allow create operation - await expect( - async () => await user1TodoHelper.create(createResultSetName, { ...todo, groupContent: 'Group Content' }), - ).rejects.toThrowErrorMatchingInlineSnapshot(expectedOperationError(createResultSetName, 'Mutation')); - - // Create a record with allowed fields, so we can test the update and delete operations. - const createResult1 = await user1TodoHelper.create(createResultSetName, todo, setWithOwnersAndGroupsContent); - expect(createResult1.data[createResultSetName].id).toBeDefined(); - todo['id'] = createResult1.data[createResultSetName].id; - todo['owner'] = userName1; - // protected fields are nullified in mutation responses - const nulledOwnerFields = ['owner', 'authors', 'customGroup', 'customGroups', 'privateContent']; - expectNullFields(createResult1.data[createResultSetName], [...nulledOwnerFields, 'ownersContent']); - - const publicFieldSet = ` - id - publicContent - `; - // admin cannot update a record with public protected field - await expect( - async () => await user1TodoHelper.update(updateResultSetName, { id: todo['id'], publicContent: 'Public Content' }, publicFieldSet), - ).rejects.toThrowErrorMatchingInlineSnapshot(expectedOperationError(updateResultSetName, 'Mutation')); - - // admin cannot update a record with dynamic list of owners protected field that does not allow update operation - await expect( - async () => await user1TodoHelper.update(updateResultSetName, { ...todo, ownersContent: 'Owners Content' }), - ).rejects.toThrowErrorMatchingInlineSnapshot(expectedOperationError(updateResultSetName, 'Mutation')); - - // admin cannot update a record with dynamic list of groups protected field that does not allow update operation - await expect( - async () => await user1TodoHelper.update(updateResultSetName, { ...todo, groupsContent: 'Groups Content' }), - ).rejects.toThrowErrorMatchingInlineSnapshot(expectedOperationError(updateResultSetName, 'Mutation')); - - // non-admin cannot update a record to re-assign ownership - const ownerFieldSet = ` - id - owner - `; - await expect( - async () => await user2TodoHelper.update(updateResultSetName, { id: todo['id'], owner: 'user2' }, ownerFieldSet), - ).rejects.toThrowErrorMatchingInlineSnapshot(expectedOperationError(updateResultSetName, 'Mutation')); - - // non-admin cannot update a record to re-assign owners in dynamic owners list - const ownersFieldSet = ` - id - authors - `; - await expect( - async () => await user2TodoHelper.update(updateResultSetName, { id: todo['id'], authors: ['user2'] }, ownersFieldSet), - ).rejects.toThrowErrorMatchingInlineSnapshot(expectedOperationError(updateResultSetName, 'Mutation')); - - // non-admin cannot update a record to re-assign group membership - const groupFieldSet = ` - id - customGroup - `; - await expect( - async () => await user2TodoHelper.update(updateResultSetName, { id: todo['id'], customGroup: devGroupName }, groupFieldSet), - ).rejects.toThrowErrorMatchingInlineSnapshot(expectedOperationError(updateResultSetName, 'Mutation')); - - // non-admin cannot update a record to re-assign group memberships stored as dynamic list - const groupsFieldSet = ` - id - customGroups - `; - await expect( - async () => await user2TodoHelper.update(updateResultSetName, { id: todo['id'], customGroups: [devGroupName] }, groupsFieldSet), - ).rejects.toThrowErrorMatchingInlineSnapshot(expectedOperationError(updateResultSetName, 'Mutation')); - - // non-owner cannot update a record with an owner protected field - const ownerContentFieldSet = ` - id - ownerContent - `; - await expect( - async () => await user2TodoHelper.update(updateResultSetName, { ...todo, ownerContent: 'Owner Content' }, ownerContentFieldSet), - ).rejects.toThrowErrorMatchingInlineSnapshot(expectedOperationError(updateResultSetName, 'Mutation')); - - // non-member of group cannot update a record with a group protected field - const groupContentFieldSet = ` - id - groupContent - `; - await expect( - async () => await user2TodoHelper.update(updateResultSetName, { ...todo, groupContent: 'Group Content' }, groupContentFieldSet), - ).rejects.toThrowErrorMatchingInlineSnapshot(expectedOperationError(updateResultSetName, 'Mutation')); - - // admin cannot read a record with public protected field - const getResult1 = await user1TodoHelper.get({ id: todo['id'] }, publicFieldSet, false, 'all'); - checkOperationResult( - getResult1, - { id: todo['id'], publicContent: null }, - `get${modelName}`, - false, - expectedFieldErrors(['publicContent'], modelName), - ); - - const listTodosResult1 = await user1TodoHelper.list({}, publicFieldSet, `list${modelName}`, false, 'all'); - checkListItemExistence(listTodosResult1, `list${modelName}`, todo['id'], true); - checkListResponseErrors(listTodosResult1, expectedFieldErrors(['publicContent'], modelName, false)); - - // non-admin and non-owner cannot read a record with owner and group protected fields in the selection set - const ownerReadFieldSet = ` - id - owner - authors - customGroup - customGroups - ownerContent - ownersContent - groupContent - groupsContent - `; - const getResult2 = await user2TodoHelper.get({ id: todo['id'] }, ownerReadFieldSet, false, 'all'); - const expectedReadErrorFields = ['owner', 'authors', 'ownerContent', 'ownersContent', 'groupContent', 'groupsContent']; - checkOperationResult( - getResult2, - { - id: todo['id'], - owner: null, - authors: null, - customGroup: null, - customGroups: null, - ownerContent: null, - ownersContent: null, - groupContent: null, - groupsContent: null, - }, - `get${modelName}`, - false, - expectedFieldErrors(expectedReadErrorFields, modelName), - ); - - const listTodosResult2 = await user2TodoHelper.list({}, ownerReadFieldSet, `list${modelName}`, false, 'all'); - checkListItemExistence(listTodosResult2, `list${modelName}`, todo['id'], true); - checkListResponseErrors(listTodosResult2, expectedFieldErrors(expectedReadErrorFields, modelName, false)); - - // non-admin/owner cannot listen to updates on owner/group protected fields - const todoRandom = { - ...todo, - id: Date.now().toString(), - }; - const todoRandomUpdated = { - id: todoRandom.id, - owner: userName1, - privateContent: 'Private Content updated', - ownerContent: 'Owner Content updated', - groupContent: 'Group Content updated', - }; - const subscriberClient = getConfiguredAppsyncClientOIDCAuth(graphQlEndpoint, region, userMap[userName2]); - const subTodoHelper = createModelOperationHelpers(subscriberClient, schema)[modelName]; - - const onCreateSubscriptionResult = await subTodoHelper.subscribe('onCreate', [ - async () => { - await user1TodoHelper.create(createResultSetName, todoRandom, setWithOwnersAndGroupsContent); - }, - ]); - const expectedSubscriptionNullFields = [...expectedReadErrorFields, 'privateContent', 'publicContent']; - expect(onCreateSubscriptionResult).toHaveLength(1); - expect(onCreateSubscriptionResult[0].data[`onCreate${modelName}`].id).toEqual(todoRandom.id); - expectNullFields(onCreateSubscriptionResult[0].data[`onCreate${modelName}`], expectedSubscriptionNullFields); - - const onUpdateSubscriptionResult = await subTodoHelper.subscribe('onUpdate', [ - async () => { - await user1TodoHelper.update(`update${modelName}`, todoRandomUpdated, setWithOwnerAndGroupContent); - }, - ]); - expect(onUpdateSubscriptionResult).toHaveLength(1); - expect(onUpdateSubscriptionResult[0].data[`onUpdate${modelName}`].id).toEqual(todoRandom.id); - expectNullFields(onUpdateSubscriptionResult[0].data[`onUpdate${modelName}`], expectedSubscriptionNullFields); - - const onDeleteSubscriptionResult = await subTodoHelper.subscribe('onDelete', []); - expect(onDeleteSubscriptionResult).toHaveLength(0); - }); - - test('custom group field protected model and allowed field operations', async () => { - const modelName = 'TodoCustomGroupContentVarious'; - const user1TodoHelper = user1ModelOperationHelpers[modelName]; - const user2TodoHelper = user2ModelOperationHelpers[modelName]; - - const todo = { - customId: Date.now().toString(), - owner: userName1, - authors: [userName1], - customGroup: adminGroupName, - customGroups: [adminGroupName], - privateContent: 'Private Content', - ownersContent: 'Owners Content', - adminContent: 'Admin Content', - groupsContent: 'Groups Content', - }; - const createResultSetName = `create${modelName}`; - const updateResultSetName = `update${modelName}`; - const deleteResultSetName = `delete${modelName}`; - const privateResultSet = ` - customId - privateContent - `; - const adminResultSet = ` - ${privateResultSet} - owner - authors - customGroup - customGroups - adminContent - `; - const setWithOwnerContent = ` - ${adminResultSet} - ownerContent - `; - const setWithOwnersAndGroupsContent = ` - ${adminResultSet} - ownersContent - groupsContent - `; - const completeResultSet = ` - ${adminResultSet} - ownerContent - ownersContent - groupsContent - `; - - // admin(user1) creates a record with only allowed fields - const createResult1 = await user1TodoHelper.create(createResultSetName, todo, setWithOwnersAndGroupsContent); - expect(createResult1.data[createResultSetName].customId).toBeDefined(); - todo['customId'] = createResult1.data[createResultSetName].customId; - todo['owner'] = userName1; - // protected fields are nullified in mutation responses - const nulledMutationFields = ['owner', 'authors', 'customGroup', 'customGroups', 'privateContent', 'adminContent']; - expectNullFields(createResult1.data[createResultSetName], [...nulledMutationFields, 'ownersContent', 'groupsContent']); - - // user in allowed group can update the allowed fields and add user2 to dyamic owners list field - const todoUpdated1 = { - customId: todo['customId'], - authors: [userName1, userName2], - customGroups: [adminGroupName, devGroupName], - privateContent: 'Private Content updated', - ownerContent: 'Owner Content', - }; - const updateResult1 = await user1TodoHelper.update(updateResultSetName, todoUpdated1, setWithOwnerContent); - expect(updateResult1.data[updateResultSetName].customId).toEqual(todo['customId']); - expectNullFields(updateResult1.data[updateResultSetName], [...nulledMutationFields, 'ownerContent']); - - // user in allowed group can read the allowed fields - const getResult1 = await user1TodoHelper.get( - { - customId: todo['customId'], - }, - completeResultSet, - false, - 'all', - 'customId', - ); - checkOperationResult(getResult1, { ...todo, ...todoUpdated1 }, `get${modelName}`); - - const listTodosResult1 = await user1TodoHelper.list({}, completeResultSet, `list${modelName}`, false, 'all'); - checkListItemExistence(listTodosResult1, `list${modelName}`, todo['customId'], true, 'customId'); - - // user not in allowed group can update the private field - const todoUpdated2 = { - customId: todo['customId'], - privateContent: 'Private Content updated 1', - }; - const updateResult2 = await user2TodoHelper.update(updateResultSetName, todoUpdated2, privateResultSet); - expect(updateResult2.data[updateResultSetName].customId).toEqual(todo['customId']); - expectNullFields(updateResult2.data[updateResultSetName], ['privateContent']); - - // user2 who is now in allowed group can read the allowed fields - const user2ReadAllowedSet = ` - customId - privateContent - ownersContent - groupsContent - `; - const getResult2 = await user2TodoHelper.get( - { - customId: todo['customId'], - }, - user2ReadAllowedSet, - false, - 'all', - 'customId', - ); - checkOperationResult( - getResult2, - { - customId: todo['customId'], - privateContent: todoUpdated2['privateContent'], - ownersContent: todo['ownersContent'], - groupsContent: todo['groupsContent'], - }, - `get${modelName}`, - ); - - const listTodosResult2 = await user2TodoHelper.list({}, user2ReadAllowedSet, `list${modelName}`, false, 'all'); - checkListItemExistence(listTodosResult2, `list${modelName}`, todo['customId'], true, 'customId'); - - // user in allowed group can delete the record - const deleteResult1 = await user1TodoHelper.delete(deleteResultSetName, { customId: todo.customId }, completeResultSet); - expect(deleteResult1.data[deleteResultSetName].customId).toEqual(todo['customId']); - expectNullFields(deleteResult1.data[deleteResultSetName], [ - ...nulledMutationFields, - 'ownerContent', - 'ownersContent', - 'groupsContent', - ]); - - // user in allowed group can listen to updates on allowed fields - const todoRandom = { - ...todo, - customId: Date.now().toString(), - }; - const todoRandomUpdated = { - ...todoUpdated1, - customId: todoRandom.customId, - owner: userName1, - privateContent: 'Private Content updated', - }; - const subscriberClient = getConfiguredAppsyncClientOIDCAuth(graphQlEndpoint, region, userMap[userName1]); - const subTodoHelper = createModelOperationHelpers(subscriberClient, schema)[modelName]; - - const onCreateSubscriptionResult = await subTodoHelper.subscribe( - 'onCreate', - [ - async () => { - await user1TodoHelper.create(createResultSetName, todoRandom, setWithOwnersAndGroupsContent); - }, - ], - {}, - setWithOwnersAndGroupsContent, - false, - ); - expect(onCreateSubscriptionResult).toHaveLength(1); - checkOperationResult( - onCreateSubscriptionResult[0], - { - ...todoRandom, - owner: null, - authors: null, - customGroup: null, - customGroups: null, - privateContent: null, - ownersContent: null, - adminContent: null, - groupsContent: null, - }, - `onCreate${modelName}`, - ); - - const onUpdateSubscriptionResult = await subTodoHelper.subscribe( - 'onUpdate', - [ - async () => { - await user1TodoHelper.update(`update${modelName}`, todoRandomUpdated, setWithOwnerContent); - }, - ], - {}, - setWithOwnerContent, - false, - ); - expect(onUpdateSubscriptionResult).toHaveLength(1); - checkOperationResult( - onUpdateSubscriptionResult[0], - { - ...todoRandomUpdated, - owner: null, - authors: null, - customGroup: null, - customGroups: null, - privateContent: null, - ownerContent: null, - adminContent: null, - }, - `onUpdate${modelName}`, - ); - - const onDeleteSubscriptionResult = await subTodoHelper.subscribe( - 'onDelete', - [ - async () => { - await user1TodoHelper.delete(deleteResultSetName, { customId: todoRandom.customId }, completeResultSet); - }, - ], - {}, - completeResultSet, - false, - ); - expect(onDeleteSubscriptionResult).toHaveLength(1); - checkOperationResult( - onDeleteSubscriptionResult[0], - { - ...todoRandomUpdated, - owner: null, - authors: null, - customGroup: null, - customGroups: null, - privateContent: null, - ownerContent: null, - ownersContent: null, - adminContent: null, - groupsContent: null, - }, - `onDelete${modelName}`, - ); - }); - - test('custom group field protected model and restricted field operations', async () => { - const modelName = 'TodoCustomGroupContentVarious'; - const user1TodoHelper = user1ModelOperationHelpers[modelName]; - const user2TodoHelper = user2ModelOperationHelpers[modelName]; - - const todo = { - customId: Date.now().toString(), - owner: userName1, - authors: [userName1], - customGroup: adminGroupName, - customGroups: [adminGroupName], - privateContent: 'Private Content', - ownersContent: 'Owners Content', - adminContent: 'Admin Content', - groupsContent: 'Groups Content', - }; - const createResultSetName = `create${modelName}`; - const updateResultSetName = `update${modelName}`; - const deleteResultSetName = `delete${modelName}`; - const privateResultSet = ` - customId - privateContent - `; - const adminResultSet = ` - ${privateResultSet} - owner - authors - customGroup - customGroups - adminContent - `; - const setWithOwnerContent = ` - ${adminResultSet} - ownerContent - `; - const setWithOwnersAndGroupsContent = ` - ${adminResultSet} - ownersContent - groupsContent - `; - - const completeResultSet = ` - ${adminResultSet} - ownerContent - ownersContent - groupsContent - `; - - // user in allowed group cannot create a record with public protected field - await expect( - async () => await user1TodoHelper.create(createResultSetName, { ...todo, publicContent: 'Public Content' }), - ).rejects.toThrowErrorMatchingInlineSnapshot(expectedOperationError(createResultSetName, 'Mutation')); - - // user in allowed group cannot create a record with owner protected field that does not allow create operation - await expect( - async () => await user1TodoHelper.create(createResultSetName, { ...todo, ownerContent: 'Owner Content' }), - ).rejects.toThrowErrorMatchingInlineSnapshot(expectedOperationError(createResultSetName, 'Mutation')); - - // Create a record with allowed fields, so we can test the update and delete operations. - const createResult1 = await user1TodoHelper.create(createResultSetName, todo, setWithOwnersAndGroupsContent); - expect(createResult1.data[createResultSetName].customId).toBeDefined(); - todo['customId'] = createResult1.data[createResultSetName].customId; - todo['owner'] = userName1; - // protected fields are nullified in mutation responses - const nulledAdminFields = ['owner', 'authors', 'customGroup', 'customGroups', 'privateContent', 'adminContent']; - expectNullFields(createResult1.data[createResultSetName], [...nulledAdminFields, 'ownersContent', 'groupsContent']); - - const publicFieldSet = ` - customId - publicContent - `; - // user in allowed group cannot update a record with public protected field - await expect( - async () => - await user1TodoHelper.update( - updateResultSetName, - { customId: todo['customId'], publicContent: 'Public Content' }, - publicFieldSet, - ), - ).rejects.toThrowErrorMatchingInlineSnapshot(expectedOperationError(updateResultSetName, 'Mutation')); - - // user in allowed group cannot update a record with dynamic list of owners protected field that does not allow update operation - await expect( - async () => await user1TodoHelper.update(updateResultSetName, { ...todo, ownersContent: 'Owners Content' }), - ).rejects.toThrowErrorMatchingInlineSnapshot(expectedOperationError(updateResultSetName, 'Mutation')); - - // user in allowed group cannot update a record with dynamic list of groups protected field that does not allow update operation - await expect( - async () => await user1TodoHelper.update(updateResultSetName, { ...todo, groupsContent: 'Groups Content' }), - ).rejects.toThrowErrorMatchingInlineSnapshot(expectedOperationError(updateResultSetName, 'Mutation')); - - // user not in allowed group cannot update a record to re-assign ownership - const ownerFieldSet = ` - customId - owner - `; - await expect( - async () => await user2TodoHelper.update(updateResultSetName, { customId: todo['customId'], owner: 'user2' }, ownerFieldSet), - ).rejects.toThrowErrorMatchingInlineSnapshot(expectedOperationError(updateResultSetName, 'Mutation')); - - // user not in allowed group cannot update a record to re-assign owners in dynamic owners list - const ownersFieldSet = ` - customId - authors - `; - await expect( - async () => await user2TodoHelper.update(updateResultSetName, { customId: todo['customId'], authors: ['user2'] }, ownersFieldSet), - ).rejects.toThrowErrorMatchingInlineSnapshot(expectedOperationError(updateResultSetName, 'Mutation')); - - // user not in allowed group cannot update a record to re-assign group membership - const groupFieldSet = ` - customId - customGroup - `; - await expect( - async () => - await user2TodoHelper.update(updateResultSetName, { customId: todo['customId'], customGroup: devGroupName }, groupFieldSet), - ).rejects.toThrowErrorMatchingInlineSnapshot(expectedOperationError(updateResultSetName, 'Mutation')); - - // user not in allowed group cannot update a record to re-assign group memberships stored as dynamic list - const groupsFieldSet = ` - customId - customGroups - `; - await expect( - async () => - await user2TodoHelper.update(updateResultSetName, { customId: todo['customId'], customGroups: [devGroupName] }, groupsFieldSet), - ).rejects.toThrowErrorMatchingInlineSnapshot(expectedOperationError(updateResultSetName, 'Mutation')); - - // user not in allowed group cannot update a record with an owner protected field - const ownerContentFieldSet = ` - customId - ownerContent - `; - await expect( - async () => await user2TodoHelper.update(updateResultSetName, { ...todo, ownerContent: 'Owner Content' }, ownerContentFieldSet), - ).rejects.toThrowErrorMatchingInlineSnapshot(expectedOperationError(updateResultSetName, 'Mutation')); - - // user in allowed group cannot read a record with public protected field - const getResult1 = await user1TodoHelper.get({ customId: todo['customId'] }, publicFieldSet, false, 'all', 'customId'); - checkOperationResult( - getResult1, - { customId: todo['customId'], publicContent: null }, - `get${modelName}`, - false, - expectedFieldErrors(['publicContent'], modelName), - ); - - const listTodosResult1 = await user1TodoHelper.list({}, publicFieldSet, `list${modelName}`, false, 'all'); - checkListItemExistence(listTodosResult1, `list${modelName}`, todo['customId'], true, 'customId'); - checkListResponseErrors(listTodosResult1, expectedFieldErrors(['publicContent'], modelName, false)); - - // user not in allowed group and non-owner cannot read a record with owner and group protected fields in the selection set - const readFieldSet = ` - customId - owner - authors - customGroup - customGroups - ownerContent - ownersContent - adminContent - groupsContent - `; - const getResult2 = await user2TodoHelper.get({ customId: todo['customId'] }, readFieldSet, false, 'all', 'customId'); - const expectedReadErrorFields = [ - 'owner', - 'authors', - 'customGroup', - 'customGroups', - 'ownerContent', - 'ownersContent', - 'adminContent', - 'groupsContent', - ]; - checkOperationResult( - getResult2, - { - customId: todo['customId'], - owner: null, - authors: null, - customGroup: null, - customGroups: null, - ownerContent: null, - ownersContent: null, - adminContent: null, - groupsContent: null, - }, - `get${modelName}`, - false, - expectedFieldErrors(expectedReadErrorFields, modelName), - ); - - const listTodosResult2 = await user2TodoHelper.list({}, readFieldSet, `list${modelName}`, false, 'all'); - checkListItemExistence(listTodosResult2, `list${modelName}`, todo['customId'], true, 'customId'); - checkListResponseErrors(listTodosResult2, expectedFieldErrors(expectedReadErrorFields, modelName, false)); - - // user not in allowed group cannot listen to updates on owner/group protected fields - const todoRandom = { - ...todo, - customId: Date.now().toString(), - }; - const todoRandomUpdated = { - customId: todoRandom.customId, - owner: userName1, - privateContent: 'Private Content updated', - ownerContent: 'Owner Content updated', - }; - const subscriberClient = getConfiguredAppsyncClientOIDCAuth(graphQlEndpoint, region, userMap[userName2]); - const subTodoHelper = createModelOperationHelpers(subscriberClient, schema)[modelName]; - - const onCreateSubscriptionResult = await subTodoHelper.subscribe('onCreate', [ - async () => { - await user1TodoHelper.create(createResultSetName, todoRandom, setWithOwnersAndGroupsContent); - }, - ]); - const expectedSubscriptionNullFields = [...expectedReadErrorFields, 'privateContent', 'publicContent']; - expect(onCreateSubscriptionResult).toHaveLength(1); - expect(onCreateSubscriptionResult[0].data[`onCreate${modelName}`].customId).toEqual(todoRandom.customId); - expectNullFields(onCreateSubscriptionResult[0].data[`onCreate${modelName}`], [ - ...expectedSubscriptionNullFields, - 'ownersContent', - 'adminContent', - 'groupsContent', - ]); - - const onUpdateSubscriptionResult = await subTodoHelper.subscribe('onUpdate', [ - async () => { - await user1TodoHelper.update(`update${modelName}`, todoRandomUpdated, setWithOwnerContent); - }, - ]); - expect(onUpdateSubscriptionResult).toHaveLength(1); - expect(onUpdateSubscriptionResult[0].data[`onUpdate${modelName}`].customId).toEqual(todoRandom.customId); - expectNullFields(onUpdateSubscriptionResult[0].data[`onUpdate${modelName}`], [ - ...expectedSubscriptionNullFields, - 'ownerContent', - 'adminContent', - ]); - - const onDeleteSubscriptionResult = await subTodoHelper.subscribe('onDelete', [ - async () => { - await user1TodoHelper.delete(deleteResultSetName, { customId: todoRandom.customId }, completeResultSet); - }, - ]); - expect(onDeleteSubscriptionResult).toHaveLength(1); - expect(onDeleteSubscriptionResult[0].data[`onDelete${modelName}`].customId).toEqual(todoRandom.customId); - expectNullFields(onDeleteSubscriptionResult[0].data[`onDelete${modelName}`], [ - ...expectedSubscriptionNullFields, - 'ownerContent', - 'ownersContent', - 'adminContent', - 'groupsContent', - ]); - }); - - test('custom list of groups field protected model and allowed field operations', async () => { - const modelName = 'TodoCustomGroupsContentVarious'; - const user1TodoHelper = user1ModelOperationHelpers[modelName]; - const user2TodoHelper = user2ModelOperationHelpers[modelName]; - - const todo = { - customId: Date.now().toString(), - owner: userName1, - authors: [userName1], - customGroup: adminGroupName, - customGroups: [adminGroupName], - privateContent: 'Private Content', - adminContent: 'Admin Content', - ownersContent: 'Owners Content', - }; - const createResultSetName = `create${modelName}`; - const updateResultSetName = `update${modelName}`; - const privateResultSet = ` - customId - privateContent - `; - const adminResultSet = ` - ${privateResultSet} - owner - authors - customGroup - customGroups - adminContent - `; - const setWithOwnerAndGroupContent = ` - ${adminResultSet} - ownerContent - groupContent - `; - const setWithOwnersContent = ` - ${adminResultSet} - ownersContent - `; - const completeResultSet = ` - ${adminResultSet} - ownerContent - ownersContent - groupContent - `; - - // admin(user1) creates a record with only allowed fields - const createResult1 = await user1TodoHelper.create(createResultSetName, todo, setWithOwnersContent); - expect(createResult1.data[createResultSetName].customId).toBeDefined(); - todo['customId'] = createResult1.data[createResultSetName].customId; - todo['owner'] = userName1; - // protected fields are nullified in mutation responses - const nulledMutationFields = ['owner', 'authors', 'customGroup', 'customGroups', 'privateContent', 'adminContent']; - expectNullFields(createResult1.data[createResultSetName], [...nulledMutationFields, 'ownersContent']); - - // user part of allowed groups can update the allowed fields and add user2 to dyamic owners list field - const todoUpdated1 = { - customId: todo['customId'], - authors: [userName1, userName2], - customGroups: [adminGroupName, devGroupName], - privateContent: 'Private Content updated', - ownerContent: 'Owner Content', - groupContent: 'Group Content', - }; - const updateResult1 = await user1TodoHelper.update(updateResultSetName, todoUpdated1, setWithOwnerAndGroupContent); - expect(updateResult1.data[updateResultSetName].customId).toEqual(todo['customId']); - expectNullFields(updateResult1.data[updateResultSetName], [...nulledMutationFields, 'ownerContent', 'groupContent']); - - // user part of allowed groups can read the allowed fields - const getResult1 = await user1TodoHelper.get( - { - customId: todo['customId'], - }, - completeResultSet, - false, - 'all', - 'customId', - ); - checkOperationResult(getResult1, { ...todo, ...todoUpdated1 }, `get${modelName}`); - - const listTodosResult1 = await user1TodoHelper.list({}, completeResultSet, `list${modelName}`, false, 'all'); - checkListItemExistence(listTodosResult1, `list${modelName}`, todo['customId'], true, 'customId'); - - // user not part of allowed groups can update the private field - const todoUpdated2 = { - customId: todo['customId'], - privateContent: 'Private Content updated 1', - }; - const updateResult2 = await user2TodoHelper.update(updateResultSetName, todoUpdated2, privateResultSet); - expect(updateResult2.data[updateResultSetName].customId).toEqual(todo['customId']); - expectNullFields(updateResult2.data[updateResultSetName], ['privateContent']); - - // user2 who is now part of allowed groups can read the allowed fields - const user2ReadAllowedSet = ` - customId - privateContent - ownersContent - `; - const getResult2 = await user2TodoHelper.get( - { - customId: todo['customId'], - }, - user2ReadAllowedSet, - false, - 'all', - 'customId', - ); - checkOperationResult( - getResult2, - { - customId: todo['customId'], - privateContent: todoUpdated2['privateContent'], - ownersContent: todo['ownersContent'], - }, - `get${modelName}`, - ); - - const listTodosResult2 = await user2TodoHelper.list({}, user2ReadAllowedSet, `list${modelName}`, false, 'all'); - checkListItemExistence(listTodosResult2, `list${modelName}`, todo['customId'], true, 'customId'); - - // user part of allowed groups can listen to updates on allowed fields - const todoRandom = { - ...todo, - customId: Date.now().toString(), - }; - const todoRandomUpdated = { - ...todoUpdated1, - customId: todoRandom.customId, - owner: userName1, - }; - const subscriberClient = getConfiguredAppsyncClientOIDCAuth(graphQlEndpoint, region, userMap[userName1]); - const subTodoHelper = createModelOperationHelpers(subscriberClient, schema)[modelName]; - - const onCreateSubscriptionResult = await subTodoHelper.subscribe( - 'onCreate', - [ - async () => { - await user1TodoHelper.create(createResultSetName, todoRandom, setWithOwnersContent); - }, - ], - {}, - setWithOwnersContent, - false, - ); - expect(onCreateSubscriptionResult).toHaveLength(1); - checkOperationResult( - onCreateSubscriptionResult[0], - { - ...todoRandom, - owner: null, - authors: null, - customGroup: null, - customGroups: null, - privateContent: null, - ownersContent: null, - adminContent: null, - }, - `onCreate${modelName}`, - ); - - const onUpdateSubscriptionResult = await subTodoHelper.subscribe( - 'onUpdate', - [ - async () => { - await user1TodoHelper.update(`update${modelName}`, todoRandomUpdated, setWithOwnerAndGroupContent); - }, - ], - {}, - setWithOwnerAndGroupContent, - false, - ); - expect(onUpdateSubscriptionResult).toHaveLength(1); - checkOperationResult( - onUpdateSubscriptionResult[0], - { - ...todoRandomUpdated, - owner: null, - authors: null, - customGroup: null, - customGroups: null, - privateContent: null, - ownerContent: null, - adminContent: null, - groupContent: null, - }, - `onUpdate${modelName}`, - ); - }); - - test('custom list of groups field protected model and restricted field operations', async () => { - const modelName = 'TodoCustomGroupsContentVarious'; - const user1TodoHelper = user1ModelOperationHelpers[modelName]; - const user2TodoHelper = user2ModelOperationHelpers[modelName]; - - const todo = { - customId: Date.now().toString(), - owner: userName1, - authors: [userName1], - customGroup: adminGroupName, - customGroups: [adminGroupName], - privateContent: 'Private Content', - ownersContent: 'Owners Content', - adminContent: 'Admin Content', - }; - const createResultSetName = `create${modelName}`; - const updateResultSetName = `update${modelName}`; - const deleteResultSetName = `delete${modelName}`; - const privateResultSet = ` - customId - privateContent - `; - const adminResultSet = ` - ${privateResultSet} - owner - authors - customGroup - customGroups - adminContent - `; - const setWithOwnerAndGroupContent = ` - ${adminResultSet} - ownerContent - groupContent - `; - const setWithOwnersContent = ` - ${adminResultSet} - ownersContent - `; - - const completeResultSet = ` - ${adminResultSet} - ownerContent - ownersContent - groupContent - `; - - // user part of allowed groups cannot create a record with public protected field - await expect( - async () => await user1TodoHelper.create(createResultSetName, { ...todo, publicContent: 'Public Content' }), - ).rejects.toThrowErrorMatchingInlineSnapshot(expectedOperationError(createResultSetName, 'Mutation')); - - // user part of allowed groups cannot create a record with owner protected field that does not allow create operation - await expect( - async () => await user1TodoHelper.create(createResultSetName, { ...todo, ownerContent: 'Owner Content' }), - ).rejects.toThrowErrorMatchingInlineSnapshot(expectedOperationError(createResultSetName, 'Mutation')); - - // user part of allowed groups cannot create a record with list of groups protected field that does not allow create operation - await expect( - async () => await user1TodoHelper.create(createResultSetName, { ...todo, groupContent: 'Group Content' }), - ).rejects.toThrowErrorMatchingInlineSnapshot(expectedOperationError(createResultSetName, 'Mutation')); - - // Create a record with allowed fields, so we can test the update and delete operations. - const createResult1 = await user1TodoHelper.create(createResultSetName, todo, setWithOwnersContent); - expect(createResult1.data[createResultSetName].customId).toBeDefined(); - todo['customId'] = createResult1.data[createResultSetName].customId; - todo['owner'] = userName1; - // protected fields are nullified in mutation responses - const nulledAdminFields = ['owner', 'authors', 'customGroup', 'customGroups', 'privateContent', 'adminContent']; - expectNullFields(createResult1.data[createResultSetName], [...nulledAdminFields, 'ownersContent']); - - const publicFieldSet = ` - customId - publicContent - `; - // user part of allowed groups cannot update a record with public protected field - await expect( - async () => - await user1TodoHelper.update( - updateResultSetName, - { customId: todo['customId'], publicContent: 'Public Content' }, - publicFieldSet, - ), - ).rejects.toThrowErrorMatchingInlineSnapshot(expectedOperationError(updateResultSetName, 'Mutation')); - - // user part of allowed groups cannot update a record with dynamic list of owners protected field that does not allow update operation - await expect( - async () => await user1TodoHelper.update(updateResultSetName, { ...todo, ownersContent: 'Owners Content' }), - ).rejects.toThrowErrorMatchingInlineSnapshot(expectedOperationError(updateResultSetName, 'Mutation')); - - // user not part of allowed groups cannot update a record to re-assign ownership - const ownerFieldSet = ` - customId - owner - `; - await expect( - async () => await user2TodoHelper.update(updateResultSetName, { customId: todo['customId'], owner: 'user2' }, ownerFieldSet), - ).rejects.toThrowErrorMatchingInlineSnapshot(expectedOperationError(updateResultSetName, 'Mutation')); - - // user not part of allowed groups cannot update a record to re-assign owners in dynamic owners list - const ownersFieldSet = ` - customId - authors - `; - await expect( - async () => await user2TodoHelper.update(updateResultSetName, { customId: todo['customId'], authors: ['user2'] }, ownersFieldSet), - ).rejects.toThrowErrorMatchingInlineSnapshot(expectedOperationError(updateResultSetName, 'Mutation')); - - // user not part of allowed groups cannot update a record to re-assign group membership - const groupFieldSet = ` - customId - customGroup - `; - await expect( - async () => - await user2TodoHelper.update(updateResultSetName, { customId: todo['customId'], customGroup: devGroupName }, groupFieldSet), - ).rejects.toThrowErrorMatchingInlineSnapshot(expectedOperationError(updateResultSetName, 'Mutation')); - - // user not part of allowed groups cannot update a record to re-assign group memberships stored as dynamic list - const groupsFieldSet = ` - customId - customGroups - `; - await expect( - async () => - await user2TodoHelper.update(updateResultSetName, { customId: todo['customId'], customGroups: [devGroupName] }, groupsFieldSet), - ).rejects.toThrowErrorMatchingInlineSnapshot(expectedOperationError(updateResultSetName, 'Mutation')); - - // user not part of allowed groups cannot update a record with an owner protected field - const ownerContentFieldSet = ` - customId - ownerContent - `; - await expect( - async () => await user2TodoHelper.update(updateResultSetName, { ...todo, ownerContent: 'Owner Content' }, ownerContentFieldSet), - ).rejects.toThrowErrorMatchingInlineSnapshot(expectedOperationError(updateResultSetName, 'Mutation')); - - // user part of allowed groups cannot read a record with public protected field - const getResult1 = await user1TodoHelper.get({ customId: todo['customId'] }, publicFieldSet, false, 'all', 'customId'); - checkOperationResult( - getResult1, - { customId: todo['customId'], publicContent: null }, - `get${modelName}`, - false, - expectedFieldErrors(['publicContent'], modelName), - ); - - const listTodosResult1 = await user1TodoHelper.list({}, publicFieldSet, `list${modelName}`, false, 'all'); - checkListItemExistence(listTodosResult1, `list${modelName}`, todo['customId'], true, 'customId'); - checkListResponseErrors(listTodosResult1, expectedFieldErrors(['publicContent'], modelName, false)); - - // user not part of allowed groups and non-owner cannot read a record with owner and group protected fields in the selection set - const readFieldSet = ` - customId - owner - authors - customGroup - customGroups - ownerContent - ownersContent - adminContent - groupContent - `; - const getResult2 = await user2TodoHelper.get({ customId: todo['customId'] }, readFieldSet, false, 'all', 'customId'); - const expectedReadErrorFields = [ - 'owner', - 'authors', - 'customGroup', - 'customGroups', - 'ownerContent', - 'ownersContent', - 'adminContent', - 'groupContent', - ]; - checkOperationResult( - getResult2, - { - customId: todo['customId'], - owner: null, - authors: null, - customGroup: null, - customGroups: null, - ownerContent: null, - ownersContent: null, - adminContent: null, - groupContent: null, - }, - `get${modelName}`, - false, - expectedFieldErrors(expectedReadErrorFields, modelName), - ); - - const listTodosResult2 = await user2TodoHelper.list({}, readFieldSet, `list${modelName}`, false, 'all'); - checkListItemExistence(listTodosResult2, `list${modelName}`, todo['customId'], true, 'customId'); - checkListResponseErrors(listTodosResult2, expectedFieldErrors(expectedReadErrorFields, modelName, false)); - - // unless one has delete access to all fields in the model, delete is expected to fail - await expect( - async () => await user1TodoHelper.delete(`delete${modelName}`, { customId: todo['customId'] }, completeResultSet), - ).rejects.toThrowErrorMatchingInlineSnapshot(expectedOperationError(`delete${modelName}`, 'Mutation')); - - // user not part of allowed groups cannot listen to updates on owner/group protected fields - const todoRandom = { - ...todo, - customId: Date.now().toString(), - }; - const todoRandomUpdated = { - customId: todoRandom.customId, - owner: userName1, - privateContent: 'Private Content updated', - ownerContent: 'Owner Content updated', - }; - const subscriberClient = getConfiguredAppsyncClientOIDCAuth(graphQlEndpoint, region, userMap[userName2]); - const subTodoHelper = createModelOperationHelpers(subscriberClient, schema)[modelName]; - - const onCreateSubscriptionResult = await subTodoHelper.subscribe('onCreate', [ - async () => { - await user1TodoHelper.create(createResultSetName, todoRandom, setWithOwnersContent); - }, - ]); - const expectedSubscriptionNullFields = [...expectedReadErrorFields, 'privateContent', 'publicContent']; - expect(onCreateSubscriptionResult).toHaveLength(1); - expect(onCreateSubscriptionResult[0].data[`onCreate${modelName}`].customId).toEqual(todoRandom.customId); - expectNullFields(onCreateSubscriptionResult[0].data[`onCreate${modelName}`], [ - ...expectedSubscriptionNullFields, - 'ownersContent', - 'adminContent', - ]); - - const onUpdateSubscriptionResult = await subTodoHelper.subscribe('onUpdate', [ - async () => { - await user1TodoHelper.update(`update${modelName}`, todoRandomUpdated, setWithOwnerAndGroupContent); - }, - ]); - expect(onUpdateSubscriptionResult).toHaveLength(1); - expect(onUpdateSubscriptionResult[0].data[`onUpdate${modelName}`].customId).toEqual(todoRandom.customId); - expectNullFields(onUpdateSubscriptionResult[0].data[`onUpdate${modelName}`], [ - ...expectedSubscriptionNullFields, - 'ownerContent', - 'adminContent', - 'groupContent', - ]); - }); - - describe('Non Model protected fields and allowed operations', () => { - const modelName = 'TodoModel'; - const nonModelName = 'NoteNonModel'; - - const note = { - content: 'Note content', - adminContent: 'Admin content', - }; - const todoWithAdminNote = { - name: 'Reading books', - note, - }; - const todoWithoutAdminNote = { - name: 'Reading books', - note: { - content: note.content, - }, - }; - const createResultSetName = `create${modelName}`; - const updateResultSetName = `update${modelName}`; - const deleteResultSetName = `delete${modelName}`; - const privateResultSet = ` - id - name - note { - content - } - `; - const adminResultSet = ` - id - name - note { - content - adminContent - } - `; - const todoUpdated1 = { - name: 'Reading books updated', - note: { - content: 'Note content updated', - adminContent: 'Admin content updated', - }, - }; - - const todoUpdated2 = { - name: 'Reading books updated', - note: { - content: 'Note content updated', - }, - }; - - test('admin can create a record with all fields', async () => { - const user1TodoHelper = user1ModelOperationHelpers[modelName]; - const createResult1 = await user1TodoHelper.create(createResultSetName, todoWithAdminNote, adminResultSet); - expect(createResult1.data[createResultSetName].id).toBeDefined(); - todoWithAdminNote['id'] = createResult1.data[createResultSetName].id; - checkOperationResult( - createResult1, - { ...todoWithAdminNote, note: { ...todoWithAdminNote.note, __typename: nonModelName } }, - createResultSetName, - ); - }); - - test('non-admin can create a record with private non-model fields', async () => { - const user2TodoHelper = user2ModelOperationHelpers[modelName]; - const createResult2 = await user2TodoHelper.create(createResultSetName, todoWithoutAdminNote, privateResultSet); - expect(createResult2.data[createResultSetName].id).toBeDefined(); - todoWithoutAdminNote['id'] = createResult2.data[createResultSetName].id; - checkOperationResult( - createResult2, - { ...todoWithoutAdminNote, note: { ...todoWithoutAdminNote.note, __typename: nonModelName } }, - createResultSetName, - ); - }); - - test('admin can update all non-model fields', async () => { - const user1TodoHelper = user1ModelOperationHelpers[modelName]; - todoUpdated1['id'] = todoWithAdminNote['id']; - const updateResult1 = await user1TodoHelper.update(updateResultSetName, todoUpdated1, adminResultSet); - expect(updateResult1.data[updateResultSetName].id).toEqual(todoUpdated1['id']); - checkOperationResult( - updateResult1, - { ...todoUpdated1, note: { ...todoUpdated1.note, __typename: nonModelName } }, - updateResultSetName, - ); - }); - - test('non-admin can update private non-model fields', async () => { - const user2TodoHelper = user2ModelOperationHelpers[modelName]; - todoUpdated2['id'] = todoWithoutAdminNote['id']; - const updateResult2 = await user2TodoHelper.update(updateResultSetName, todoUpdated2, privateResultSet); - expect(updateResult2.data[updateResultSetName].id).toEqual(todoUpdated2['id']); - checkOperationResult( - updateResult2, - { ...todoUpdated2, note: { ...todoUpdated2.note, __typename: nonModelName } }, - updateResultSetName, - ); - }); - - test('admin can read all non-model fields', async () => { - const user1TodoHelper = user1ModelOperationHelpers[modelName]; - const getResult1 = await user1TodoHelper.get( - { - id: todoWithAdminNote['id'], - }, - adminResultSet, - false, - ); - checkOperationResult( - getResult1, - { ...todoWithAdminNote, ...todoUpdated1, note: { ...todoUpdated1.note, __typename: nonModelName } }, - `get${modelName}`, - ); - - const listTodosResult1 = await user1TodoHelper.list({}, adminResultSet, `list${modelName}s`, false, 'all'); - checkListItemExistence(listTodosResult1, `list${modelName}s`, todoWithAdminNote['id'], true); - }); - - test('non-admin can read private non-model fields', async () => { - const user2TodoHelper = user2ModelOperationHelpers[modelName]; - const getResult2 = await user2TodoHelper.get( - { - id: todoWithoutAdminNote['id'], - }, - privateResultSet, - false, - ); - checkOperationResult( - getResult2, - { ...todoWithoutAdminNote, ...todoUpdated2, note: { ...todoUpdated2.note, __typename: nonModelName } }, - `get${modelName}`, - ); - - const listTodosResult2 = await user2TodoHelper.list({}, privateResultSet, `list${modelName}s`, false, 'all'); - checkListItemExistence(listTodosResult2, `list${modelName}s`, todoWithoutAdminNote['id'], true); - }); - - test('admin can delete the record', async () => { - const user1TodoHelper = user1ModelOperationHelpers[modelName]; - const deleteResult1 = await user1TodoHelper.delete(deleteResultSetName, { id: todoWithAdminNote['id'] }, adminResultSet); - expect(deleteResult1.data[deleteResultSetName].id).toEqual(todoWithAdminNote['id']); - checkOperationResult( - deleteResult1, - { ...todoUpdated1, note: { ...todoUpdated1.note, __typename: nonModelName } }, - deleteResultSetName, - ); - }); - - test('non-admin can listen to mutations on all fields', async () => { - const user1TodoHelper = user1ModelOperationHelpers[modelName]; - // TODO: non-models auth field redaction. Re-visit this test after we decide the expected behavior. - const todoRandom = { - ...todoWithAdminNote, - id: Date.now().toString(), - }; - const todoRandomUpdated = { - ...todoUpdated1, - id: todoRandom.id, - }; - const subscriberClient = getConfiguredAppsyncClientOIDCAuth(graphQlEndpoint, region, userMap[userName2]); - const subTodoHelper = createModelOperationHelpers(subscriberClient, schema)[modelName]; - - const onCreateSubscriptionResult = await subTodoHelper.subscribe( - 'onCreate', - [ - async () => { - await user1TodoHelper.create(createResultSetName, todoRandom, adminResultSet); - }, - ], - {}, - adminResultSet, - false, - ); - expect(onCreateSubscriptionResult).toHaveLength(1); - const onCreateResultData = onCreateSubscriptionResult[0].data[`onCreate${modelName}`]; - expect(onCreateResultData?.id).toEqual(todoRandom.id); - checkOperationResult( - onCreateSubscriptionResult[0], - { ...todoRandom, note: { ...todoRandom.note, __typename: nonModelName } }, - `onCreate${modelName}`, - ); - - const onUpdateSubscriptionResult = await subTodoHelper.subscribe( - 'onUpdate', - [ - async () => { - await user1TodoHelper.update(`update${modelName}`, todoRandomUpdated, adminResultSet); - }, - ], - {}, - adminResultSet, - false, - ); - expect(onUpdateSubscriptionResult).toHaveLength(1); - const onUpdateResultData = onUpdateSubscriptionResult[0].data[`onUpdate${modelName}`]; - expect(onUpdateResultData?.id).toEqual(todoRandomUpdated.id); - checkOperationResult( - onUpdateSubscriptionResult[0], - { ...todoRandomUpdated, note: { ...todoRandomUpdated.note, __typename: nonModelName } }, - `onUpdate${modelName}`, - ); - - const onDeleteSubscriptionResult = await subTodoHelper.subscribe( - 'onDelete', - [ - async () => { - await user1TodoHelper.delete(deleteResultSetName, { id: todoRandomUpdated.id }, adminResultSet); - }, - ], - {}, - adminResultSet, - false, - ); - expect(onDeleteSubscriptionResult).toHaveLength(1); - const onDeleteResultData = onDeleteSubscriptionResult[0].data[`onDelete${modelName}`]; - expect(onDeleteResultData?.id).toEqual(todoRandomUpdated.id); - checkOperationResult( - onDeleteSubscriptionResult[0], - { ...todoRandomUpdated, note: { ...todoRandomUpdated.note, __typename: nonModelName } }, - `onDelete${modelName}`, - ); - }); - }); - - describe('Non Model protected fields and restricted operations', () => { - const modelName = 'TodoModel'; - const nonModelName = 'NoteNonModel'; - - const note = { - content: 'Note content', - adminContent: 'Admin content', - }; - const todoWithAdminNote = { - name: 'Reading books', - note, - }; - const createResultSetName = `create${modelName}`; - const updateResultSetName = `update${modelName}`; - const deleteResultSetName = `delete${modelName}`; - - const adminResultSet = ` - id - name - note { - content - adminContent - } - `; - - test('non-admin cannot create a record with admin protected non-model field', async () => { - const user2TodoHelper = user2ModelOperationHelpers[modelName]; - await expect( - async () => await user2TodoHelper.create(createResultSetName, todoWithAdminNote, adminResultSet), - ).rejects.toThrowErrorMatchingInlineSnapshot(expectedFieldErrors(['adminContent'], nonModelName)[0]); - }); - - test('admin can create a record with all fields', async () => { - const user1TodoHelper = user1ModelOperationHelpers[modelName]; - const createResult1 = await user1TodoHelper.create(createResultSetName, todoWithAdminNote, adminResultSet); - expect(createResult1.data[createResultSetName].id).toBeDefined(); - todoWithAdminNote['id'] = createResult1.data[createResultSetName].id; - }); - - test('non-admin cannot get the admin protected field during update', async () => { - const user2TodoHelper = user2ModelOperationHelpers[modelName]; - await expect( - async () => - await user2TodoHelper.update( - updateResultSetName, - { id: todoWithAdminNote['id'], note: { adminContent: 'Admin content updated', content: 'content updated' } }, - adminResultSet, - ), - ).rejects.toThrowErrorMatchingInlineSnapshot(expectedFieldErrors(['adminContent'], nonModelName)[0]); - }); - - test('non-admin cannot read the admin protected non-model field', async () => { - const user2TodoHelper = user2ModelOperationHelpers[modelName]; - const getResult1 = await user2TodoHelper.get({ id: todoWithAdminNote['id'] }, adminResultSet, false, 'all'); - checkOperationResult( - getResult1, - { - ...todoWithAdminNote, - note: { ...todoWithAdminNote.note, adminContent: null, __typename: nonModelName, content: 'content updated' }, - }, - `get${modelName}`, - false, - expectedFieldErrors(['adminContent'], modelName), - ); - - const listTodosResult1 = await user2TodoHelper.list({}, adminResultSet, `list${modelName}s`, false, 'all'); - checkListItemExistence(listTodosResult1, `list${modelName}s`, todoWithAdminNote['id'], true); - checkListResponseErrors(listTodosResult1, expectedFieldErrors(['adminContent'], nonModelName, false)); - }); - - test('non-admin cannot get the admin protected field during delete', async () => { - const user2TodoHelper = user2ModelOperationHelpers[modelName]; - await expect( - async () => await user2TodoHelper.delete(deleteResultSetName, { id: todoWithAdminNote['id'] }, adminResultSet), - ).rejects.toThrowErrorMatchingInlineSnapshot(expectedFieldErrors(['adminContent'], nonModelName)[0]); - }); - }); - - test('Model with renamed protected fields and allowed operations', async () => { - const modelName = 'TodoRenamedFields'; - const user1TodoHelper = user1ModelOperationHelpers[modelName]; - const user2TodoHelper = user2ModelOperationHelpers[modelName]; - - const todo = { - privateContent: 'Private Content', - author: userName1, - authors: [userName1], - customGroup: adminGroupName, - customGroups: [adminGroupName], - ownerContent: 'Owner Content', - adminContent: 'Admin Content', - groupContent: 'Group Content', - }; - const createResultSetName = `create${modelName}`; - const updateResultSetName = `update${modelName}`; - const user1CreateAllowedSet = ` - id - privateContent - author - authors - customGroup - customGroups - ownerContent - adminContent - groupContent - `; - - // owner(user1) creates a record with only allowed fields - const createResult1 = await user1TodoHelper.create(createResultSetName, todo, user1CreateAllowedSet); - expect(createResult1.data[createResultSetName].id).toBeDefined(); - expect(createResult1.data[createResultSetName].author).toEqual(userName1); - expect(createResult1.data[createResultSetName].authors).toEqual([userName1]); - expect(createResult1.data[createResultSetName].privateContent).toEqual(todo.privateContent); - expect(createResult1.data[createResultSetName].customGroup).toEqual(todo.customGroup); - expect(createResult1.data[createResultSetName].customGroups).toEqual(todo.customGroups); - todo['id'] = createResult1.data[createResultSetName].id; - // protected fields are nullified in mutation responses - expectNullFields(createResult1.data[createResultSetName], ['ownerContent', 'adminContent', 'groupContent']); - - // user1 can update the allowed fields and add user2 to dyamic owners and groups list fields - const todoUpdated1 = { - id: todo['id'], - authors: [userName1, userName2], - customGroups: [adminGroupName, devGroupName], - privateContent: 'Private Content updated', - ownersContent: 'Owners Content updated', - adminContent: 'Admin Content updated', - groupsContent: 'Groups Content updated', - }; - const user1UpdateAllowedSet = ` - id - author - authors - customGroup - customGroups - privateContent - ownersContent - adminContent - groupsContent - `; - const updateResult1 = await user1TodoHelper.update(updateResultSetName, todoUpdated1, user1UpdateAllowedSet); - expect(updateResult1.data[updateResultSetName].id).toEqual(todo['id']); - expect(updateResult1.data[updateResultSetName].author).toEqual(userName1); - expect(updateResult1.data[updateResultSetName].authors).toEqual([userName1, userName2]); - expect(updateResult1.data[updateResultSetName].privateContent).toEqual(todoUpdated1.privateContent); - expect(updateResult1.data[updateResultSetName].customGroup).toEqual(todo.customGroup); - expect(updateResult1.data[updateResultSetName].customGroups).toEqual(todoUpdated1.customGroups); - expectNullFields(updateResult1.data[updateResultSetName], ['ownersContent', 'adminContent', 'groupsContent']); - - // user1 can read the allowed fields - const completeResultSet = ` - id - author - authors - customGroup - customGroups - privateContent - ownerContent - ownersContent - adminContent - groupContent - groupsContent - `; - const user1ReadAllowedSet = completeResultSet; - const getResult1 = await user1TodoHelper.get( - { - id: todo['id'], - }, - user1ReadAllowedSet, - false, - ); - checkOperationResult(getResult1, { ...todo, ...todoUpdated1 }, `get${modelName}`); - - const listTodosResult1 = await user1TodoHelper.list({}, user1ReadAllowedSet, `list${modelName}`, false, 'all'); - checkListItemExistence(listTodosResult1, `list${modelName}`, todo['id'], true); - - // user2 can update the private and dynamic owners list protected fields. - const todoUpdated2 = { - id: todo['id'], - author: todo['author'], - authors: [userName2], - customGroup: devGroupName, - customGroups: [devGroupName], - privateContent: 'Private Content updated 1', - ownersContent: 'Owners Content updated 1', - groupsContent: 'Groups Content updated 1', - }; - const user2UpdateAllowedSet = ` - id - author - authors - customGroup - customGroups - privateContent - ownersContent - groupsContent - `; - const updateResult2 = await user2TodoHelper.update(updateResultSetName, todoUpdated2, user2UpdateAllowedSet); - expect(updateResult2.data[updateResultSetName].id).toEqual(todo['id']); - expect(updateResult2.data[updateResultSetName].author).toEqual(userName1); - expect(updateResult2.data[updateResultSetName].authors).toEqual([userName2]); - expect(updateResult2.data[updateResultSetName].privateContent).toEqual(todoUpdated2.privateContent); - expect(updateResult2.data[updateResultSetName].customGroup).toEqual(todoUpdated2.customGroup); - expect(updateResult2.data[updateResultSetName].customGroups).toEqual(todoUpdated2.customGroups); - expectNullFields(updateResult2.data[updateResultSetName], ['ownersContent', 'groupsContent']); - - // user2 can read the allowed fields - const user2ReadAllowedSet = user2UpdateAllowedSet; - const getResult2 = await user2TodoHelper.get( - { - id: todo['id'], - }, - user2ReadAllowedSet, - false, - ); - checkOperationResult(getResult2, todoUpdated2, `get${modelName}`); - - const listTodosResult2 = await user2TodoHelper.list({}, user2ReadAllowedSet, `list${modelName}`, false, 'all'); - checkListItemExistence(listTodosResult2, `list${modelName}`, todo['id'], true); - - // unless one has delete access to all fields in the model, delete is expected to fail - await expect( - async () => await user1TodoHelper.delete(`delete${modelName}`, { id: todo['id'] }, user1CreateAllowedSet), - ).rejects.toThrowErrorMatchingInlineSnapshot(expectedOperationError(`delete${modelName}`, 'Mutation')); - - // user2 can listen to updates on the non-protected fields - const todoRandom = { - ...todo, - id: Date.now().toString(), - }; - const todoRandomUpdated = { - ...todoUpdated1, - id: todoRandom.id, - author: userName1, - privateContent: 'Private Content updated', - adminContent: 'Admin Content updated', - }; - const subscriberClient = getConfiguredAppsyncClientOIDCAuth(graphQlEndpoint, region, userMap[userName2]); - const subTodoHelper = createModelOperationHelpers(subscriberClient, schema)[modelName]; - - const onCreateSubscriptionResult = await subTodoHelper.subscribe( - 'onCreate', - [ - async () => { - await user1TodoHelper.create(createResultSetName, todoRandom, user1CreateAllowedSet); - }, - ], - {}, - user1CreateAllowedSet, - false, - ); - expect(onCreateSubscriptionResult).toHaveLength(1); - expect(onCreateSubscriptionResult[0].data[`onCreate${modelName}`].privateContent).toEqual(todoRandom.privateContent); - expect(onCreateSubscriptionResult[0].data[`onCreate${modelName}`].customGroup).toEqual(todoRandom.customGroup); - expect(onCreateSubscriptionResult[0].data[`onCreate${modelName}`].customGroups).toEqual(todoRandom.customGroups); - expectNullFields(onCreateSubscriptionResult[0].data[`onCreate${modelName}`], ['ownerContent', 'adminContent', 'groupContent']); - - const onUpdateSubscriptionResult = await subTodoHelper.subscribe( - 'onUpdate', - [ - async () => { - await user1TodoHelper.update(`update${modelName}`, todoRandomUpdated, user1UpdateAllowedSet); - }, - ], - {}, - user1UpdateAllowedSet, - false, - ); - expect(onUpdateSubscriptionResult).toHaveLength(1); - expect(onUpdateSubscriptionResult[0].data[`onUpdate${modelName}`].privateContent).toEqual(todoRandomUpdated.privateContent); - expect(onUpdateSubscriptionResult[0].data[`onUpdate${modelName}`].customGroup).toEqual(todoRandom.customGroup); - expect(onUpdateSubscriptionResult[0].data[`onUpdate${modelName}`].customGroups).toEqual(todoRandomUpdated.customGroups); - expectNullFields(onUpdateSubscriptionResult[0].data[`onUpdate${modelName}`], ['ownersContent', 'adminContent', 'groupsContent']); - - const onDeleteSubscriptionResult = await subTodoHelper.subscribe('onDelete', [], {}, user1UpdateAllowedSet, false); - expect(onDeleteSubscriptionResult).toHaveLength(0); - }); - - test('Model with renamed protected fields and restricted operations', async () => { - const modelName = 'TodoRenamedFields'; - const user1TodoHelper = user1ModelOperationHelpers[modelName]; - const user2TodoHelper = user2ModelOperationHelpers[modelName]; - - const todoPrivateFields = { - author: userName1, - authors: [userName1], - customGroup: adminGroupName, - customGroups: [adminGroupName], - privateContent: 'Private Content', - }; - const createResultSetName = `create${modelName}`; - const updateResultSetName = `update${modelName}`; - const privateResultSet = ` - id - author - authors - customGroup - customGroups - privateContent - `; - - // cannot create a record with dynamic owner list protected field that does not allow create operation - await expect( - async () => await user1TodoHelper.create(createResultSetName, { ...todoPrivateFields, ownersContent: 'Owners Content' }), - ).rejects.toThrowErrorMatchingInlineSnapshot(expectedOperationError(createResultSetName, 'Mutation')); - - // cannot create a record with dynamic groups list protected field that does not allow create operation - await expect( - async () => await user1TodoHelper.create(createResultSetName, { ...todoPrivateFields, groupsContent: 'Groups Content' }), - ).rejects.toThrowErrorMatchingInlineSnapshot(expectedOperationError(createResultSetName, 'Mutation')); - - // Create a record with allowed fields, so we can test the update and delete operations. - const user1CreateAllowedSet = ` - ${privateResultSet} - ownerContent - adminContent - groupContent - `; - const createResult1 = await user1TodoHelper.create( - createResultSetName, - { ...todoPrivateFields, ownerContent: 'Owner Content', adminContent: 'Admin Content', groupContent: 'Group Content' }, - user1CreateAllowedSet, - ); - expect(createResult1.data[createResultSetName].id).toBeDefined(); - expect(createResult1.data[createResultSetName].author).toEqual(userName1); - expect(createResult1.data[createResultSetName].authors).toEqual([userName1]); - expect(createResult1.data[createResultSetName].privateContent).toEqual(todoPrivateFields.privateContent); - expect(createResult1.data[createResultSetName].customGroup).toEqual(todoPrivateFields.customGroup); - expect(createResult1.data[createResultSetName].customGroups).toEqual(todoPrivateFields.customGroups); - todoPrivateFields['id'] = createResult1.data[createResultSetName].id; - // protected fields are nullified in mutation responses - expectNullFields(createResult1.data[createResultSetName], ['ownerContent', 'adminContent', 'groupContent']); - - const privateAndOwnerSet = ` - ${privateResultSet} - ownerContent - `; - // cannot update a record with owner protected field that does not allow update operation - await expect( - async () => - await user1TodoHelper.update(updateResultSetName, { ...todoPrivateFields, ownerContent: 'Owner Content' }, privateAndOwnerSet), - ).rejects.toThrowErrorMatchingInlineSnapshot(expectedOperationError(updateResultSetName, 'Mutation')); - - const privateAndOwnersSet = ` - ${privateResultSet} - ownersContent - `; - // non-owner cannot update a record with dynamic owner list protected field - await expect( - async () => - await user2TodoHelper.update(updateResultSetName, { ...todoPrivateFields, ownersContent: 'Owners Content' }, privateAndOwnersSet), - ).rejects.toThrowErrorMatchingInlineSnapshot(expectedOperationError(updateResultSetName, 'Mutation')); - - const privateAndGroupSet = ` - ${privateResultSet} - groupContent - `; - // cannot update a record with group protected field that does not allow update operation - await expect( - async () => - await user1TodoHelper.update(updateResultSetName, { ...todoPrivateFields, groupContent: 'Group Content' }, privateAndGroupSet), - ).rejects.toThrowErrorMatchingInlineSnapshot(expectedOperationError(updateResultSetName, 'Mutation')); - - const privateAndGroupsSet = ` - ${privateResultSet} - groupsContent - `; - // non-owner cannot update a record with dynamic group list protected field - await expect( - async () => - await user2TodoHelper.update(updateResultSetName, { ...todoPrivateFields, groupsContent: 'Groups Content' }, privateAndGroupsSet), - ).rejects.toThrowErrorMatchingInlineSnapshot(expectedOperationError(updateResultSetName, 'Mutation')); - - // non-owner or user not part of allowed groups cannot read owner, group protected fields - const ownerAndGroupFields = ['ownerContent', 'ownersContent', 'adminContent', 'groupContent', 'groupsContent']; - const nonPublicSet = ` - ${privateResultSet} - ${ownerAndGroupFields.join('\n')} - `; - const getResult2 = await user2TodoHelper.get({ id: todoPrivateFields['id'] }, nonPublicSet, false, 'all'); - checkOperationResult( - getResult2, - { ...todoPrivateFields, ownerContent: null, ownersContent: null, adminContent: null, groupContent: null, groupsContent: null }, - `get${modelName}`, - false, - expectedFieldErrors(ownerAndGroupFields, modelName), - ); - - const listTodosResult2 = await user2TodoHelper.list({}, nonPublicSet, `list${modelName}`, false, 'all'); - checkListItemExistence(listTodosResult2, `list${modelName}`, todoPrivateFields['id'], true); - checkListResponseErrors(listTodosResult2, expectedFieldErrors(ownerAndGroupFields, modelName, false)); - }); - }); -}; diff --git a/packages/amplify-e2e-tests/src/rds-v2-tests-common/rds-auth-oidc.ts b/packages/amplify-e2e-tests/src/rds-v2-tests-common/rds-auth-oidc.ts deleted file mode 100644 index b5c86cd773..0000000000 --- a/packages/amplify-e2e-tests/src/rds-v2-tests-common/rds-auth-oidc.ts +++ /dev/null @@ -1,1353 +0,0 @@ -import { - addApi, - amplifyPush, - createNewProjectDir, - deleteDBInstance, - deleteProject, - deleteProjectDir, - addAuthWithPreTokenGenerationTrigger, - importRDSDatabase, - initJSProjectWithProfile, - setupRDSInstanceAndData, - sleep, - updateAuthAddUserGroups, - getProjectMeta, -} from 'amplify-category-api-e2e-core'; -import { existsSync, writeFileSync, removeSync } from 'fs-extra'; -import generator from 'generate-password'; -import path from 'path'; -import { schema as generateSchema, sqlCreateStatements } from '../__tests__/auth-test-schemas/oidc-provider'; -import { - createModelOperationHelpers, - configureAppSyncClients, - checkOperationResult, - checkListItemExistence, - appendAmplifyInput, - getAppSyncEndpoint, - updatePreAuthTrigger, - getDefaultDatabasePort, -} from '../rds-v2-test-utils'; -import { - setupUser, - getUserPoolId, - signInUser, - getUserPoolIssUrl, - getAppClientIDWeb, - configureAmplify, - getConfiguredAppsyncClientOIDCAuth, -} from '../schema-api-directives'; -import { gql } from 'graphql-tag'; -import { ImportedRDSType } from '@aws-amplify/graphql-transformer-core'; -import { SQL_TESTS_USE_BETA } from './sql-e2e-config'; - -// to deal with bug in cognito-identity-js -(global as any).fetch = require('node-fetch'); - -export const testOIDCAuth = (engine: ImportedRDSType): void => { - const schema = generateSchema(engine); - describe('RDS OIDC provider Auth tests', () => { - const [db_user, db_password, db_identifier] = generator.generateMultiple(3); - - // Generate settings for RDS instance - const username = db_user; - const password = db_password; - let region = 'us-east-1'; - let port = getDefaultDatabasePort(engine); - const database = 'default_db'; - let host = 'localhost'; - const identifier = `integtest${db_identifier}`; - const projName = 'rdsoidcauth'; - const userName1 = 'user1'; - const userName2 = 'user2'; - const adminGroupName = 'Admin'; - const devGroupName = 'Dev'; - const userPassword = 'user@Password'; - const oidcProvider = 'oidc'; - let graphQlEndpoint = 'localhost'; - - let projRoot; - let appSyncClients = {}; - const userMap = {}; - const apiName = projName; - - beforeAll(async () => { - console.log(sqlCreateStatements(engine)); - projRoot = await createNewProjectDir(projName); - await setupAmplifyProject(); - await createAppSyncClients(); - }); - - afterAll(async () => { - const metaFilePath = path.join(projRoot, 'amplify', '#current-cloud-backend', 'amplify-meta.json'); - if (existsSync(metaFilePath)) { - await deleteProject(projRoot); - } - deleteProjectDir(projRoot); - await cleanupDatabase(); - }); - - const setupDatabase = async (): Promise => { - const dbConfig = { - identifier, - engine, - dbname: database, - username, - password, - region, - port, - }; - - const db = await setupRDSInstanceAndData(dbConfig, sqlCreateStatements(engine)); - port = db.port; - host = db.endpoint; - }; - - const cleanupDatabase = async (): Promise => { - await deleteDBInstance(identifier, region); - }; - - const subscriptionWithOwner = (name: string, ownerField: string = 'owner'): string => { - return /* GraphQL */ ` - subscription On${name}($${ownerField}: String) { - on${name}(${ownerField}: $${ownerField}) { - id - content - ${ownerField} - } - } - `; - }; - - const setupAmplifyProject = async (): Promise => { - await initJSProjectWithProfile(projRoot, { - disableAmplifyAppCreation: false, - name: projName, - }); - - const metaAfterInit = getProjectMeta(projRoot); - region = metaAfterInit.providers.awscloudformation.Region; - - await addAuthWithPreTokenGenerationTrigger(projRoot); - updatePreAuthTrigger(projRoot, 'user_id'); - await amplifyPush(projRoot, false, { - useBetaSqlLayer: SQL_TESTS_USE_BETA, - skipCodegen: true, - }); - - await addApi(projRoot, { - 'OpenID Connect': { - oidcProviderName: 'awscognitouserpool', - oidcProviderDomain: getUserPoolIssUrl(projRoot), - oidcClientId: getAppClientIDWeb(projRoot), - ttlaIssueInMillisecond: '3600000', - ttlaAuthInMillisecond: '3600000', - }, - 'API key': {}, - transformerVersion: 2, - }); - - const rdsSchemaFilePath = path.join(projRoot, 'amplify', 'backend', 'api', apiName, 'schema.sql.graphql'); - const ddbSchemaFilePath = path.join(projRoot, 'amplify', 'backend', 'api', apiName, 'schema.graphql'); - removeSync(ddbSchemaFilePath); - - await setupDatabase(); - await importRDSDatabase(projRoot, { - database, - engine, - host, - port, - username, - password, - useVpc: true, - apiExists: true, - }); - writeFileSync(rdsSchemaFilePath, appendAmplifyInput(schema, engine), 'utf8'); - - await updateAuthAddUserGroups(projRoot, [adminGroupName, devGroupName]); - await amplifyPush(projRoot, false, { - useBetaSqlLayer: SQL_TESTS_USE_BETA, - }); - await sleep(1 * 60 * 1000); // Wait for a minute for the VPC endpoints to be live. - }; - - const createAppSyncClients = async (): Promise => { - const userPoolId = getUserPoolId(projRoot); - configureAmplify(projRoot); - await setupUser(userPoolId, userName1, userPassword, adminGroupName); - await setupUser(userPoolId, userName2, userPassword, devGroupName); - graphQlEndpoint = getAppSyncEndpoint(projRoot, apiName); - const user1 = await signInUser(userName1, userPassword); - userMap[userName1] = user1; - const user2 = await signInUser(userName2, userPassword); - userMap[userName2] = user2; - appSyncClients = await configureAppSyncClients(projRoot, apiName, [oidcProvider], userMap); - }; - - test('logged in user can perform CRUD and subscription operations', async () => { - const modelName = 'TodoPrivate'; - const modelOperationHelpers = createModelOperationHelpers(appSyncClients[oidcProvider][userName1], schema); - const todoHelper = modelOperationHelpers[modelName]; - - const todo = { - content: 'Todo', - }; - const resultSetName = `create${modelName}`; - const createResult = await todoHelper.create(resultSetName, todo); - expect(createResult.data[resultSetName].id).toBeDefined(); - todo['id'] = createResult.data[resultSetName].id; - checkOperationResult(createResult, todo, resultSetName); - - const todoUpdated = { - id: todo['id'], - content: 'Todo updated', - }; - const updateResult = await todoHelper.update(`update${modelName}`, todoUpdated); - checkOperationResult(updateResult, todoUpdated, `update${modelName}`); - - const getResult = await todoHelper.get({ - id: todo['id'], - }); - checkOperationResult(getResult, todoUpdated, `get${modelName}`); - - const listTodosResult = await todoHelper.list(); - checkListItemExistence(listTodosResult, `list${modelName}s`, todo['id'], true); - - const deleteResult = await todoHelper.delete(`delete${modelName}`, { - id: todo['id'], - }); - checkOperationResult(deleteResult, todoUpdated, `delete${modelName}`); - - const todoRandom = { - id: Date.now().toString(), - content: 'Todo', - }; - const todoRandomUpdated = { - ...todoRandom, - content: 'Todo updated', - }; - const actorClient = getConfiguredAppsyncClientOIDCAuth(graphQlEndpoint, region, userMap[userName1]); - const subTodoHelper = createModelOperationHelpers(actorClient, schema)[modelName]; - - const onCreateSubscriptionResult = await subTodoHelper.subscribe('onCreate', [ - async () => { - await subTodoHelper.create(`create${modelName}`, todoRandom); - }, - ]); - expect(onCreateSubscriptionResult).toHaveLength(1); - checkOperationResult(onCreateSubscriptionResult[0], todoRandom, `onCreate${modelName}`); - const onUpdateSubscriptionResult = await subTodoHelper.subscribe('onUpdate', [ - async () => { - await subTodoHelper.update(`update${modelName}`, todoRandomUpdated); - }, - ]); - expect(onUpdateSubscriptionResult).toHaveLength(1); - checkOperationResult(onUpdateSubscriptionResult[0], todoRandomUpdated, `onUpdate${modelName}`); - const onDeleteSubscriptionResult = await subTodoHelper.subscribe('onDelete', [ - async () => { - await subTodoHelper.delete(`delete${modelName}`, { id: todoRandom.id }); - }, - ]); - expect(onDeleteSubscriptionResult).toHaveLength(1); - checkOperationResult(onDeleteSubscriptionResult[0], todoRandomUpdated, `onDelete${modelName}`); - }); - - test('owner of a record can perform CRUD and subscription operations using default owner field', async () => { - const modelName = 'TodoOwner'; - const modelOperationHelpers = createModelOperationHelpers(appSyncClients[oidcProvider][userName1], schema); - const todoHelper = modelOperationHelpers[modelName]; - - const todo = { - content: 'Todo', - }; - const resultSetName = `create${modelName}`; - const createResult = await todoHelper.create(resultSetName, todo); - expect(createResult.data[resultSetName].id).toBeDefined(); - todo['id'] = createResult.data[resultSetName].id; - expect(createResult.data[resultSetName].content).toEqual(todo.content); - expect(createResult.data[resultSetName].owner).toBeDefined(); - - const todoWithOwner = { - ...todo, - owner: createResult.data[resultSetName].owner, - }; - - const todoUpdated = { - id: todo['id'], - content: 'Todo updated', - owner: todoWithOwner.owner, - }; - const updateResult = await todoHelper.update(`update${modelName}`, todoUpdated); - checkOperationResult(updateResult, todoUpdated, `update${modelName}`); - - const getResult = await todoHelper.get({ - id: todo['id'], - }); - checkOperationResult(getResult, todoUpdated, `get${modelName}`); - - const listTodosResult = await todoHelper.list(); - checkListItemExistence(listTodosResult, `list${modelName}s`, todo['id'], true); - - const deleteResult = await todoHelper.delete(`delete${modelName}`, { - id: todo['id'], - }); - checkOperationResult(deleteResult, todoUpdated, `delete${modelName}`); - - const todoRandom = { - id: Date.now().toString(), - content: 'Todo', - owner: userName1, - }; - const todoRandomUpdated = { - ...todoRandom, - content: 'Todo updated', - }; - const actorClient = getConfiguredAppsyncClientOIDCAuth(graphQlEndpoint, region, userMap[userName1]); - const subTodoHelper = createModelOperationHelpers(actorClient, schema)[modelName]; - - const onCreateSubscriptionResult = await subTodoHelper.subscribe('onCreate', [ - async () => { - await subTodoHelper.create(`create${modelName}`, todoRandom); - }, - ]); - expect(onCreateSubscriptionResult).toHaveLength(1); - checkOperationResult(onCreateSubscriptionResult[0], todoRandom, `onCreate${modelName}`); - const onUpdateSubscriptionResult = await subTodoHelper.subscribe('onUpdate', [ - async () => { - await subTodoHelper.update(`update${modelName}`, todoRandomUpdated); - }, - ]); - expect(onUpdateSubscriptionResult).toHaveLength(1); - checkOperationResult(onUpdateSubscriptionResult[0], todoRandomUpdated, `onUpdate${modelName}`); - const onDeleteSubscriptionResult = await subTodoHelper.subscribe('onDelete', [ - async () => { - await subTodoHelper.delete(`delete${modelName}`, { id: todoRandom.id }); - }, - ]); - expect(onDeleteSubscriptionResult).toHaveLength(1); - checkOperationResult(onDeleteSubscriptionResult[0], todoRandomUpdated, `onDelete${modelName}`); - }); - - test('non-owner of a record cannot access or subscribe to it using default owner field', async () => { - const modelName = 'TodoOwner'; - const modelOperationHelpersOwner = createModelOperationHelpers(appSyncClients[oidcProvider][userName1], schema); - const modelOperationHelpersNonOwner = createModelOperationHelpers(appSyncClients[oidcProvider][userName2], schema); - const todoHelperOwner = modelOperationHelpersOwner[modelName]; - const todoHelperNonOwner = modelOperationHelpersNonOwner[modelName]; - - const todo = { - content: 'Todo', - }; - const resultSetName = `create${modelName}`; - const createResult = await todoHelperOwner.create(resultSetName, todo); - expect(createResult.data[resultSetName].id).toBeDefined(); - todo['id'] = createResult.data[resultSetName].id; - - const todoUpdated = { - id: todo['id'], - content: 'Todo updated', - owner: userName2, - }; - await expect(todoHelperNonOwner.update(`update${modelName}`, todoUpdated)).rejects.toThrow( - 'GraphQL error: Not Authorized to access updateTodoOwner on type Mutation', - ); - - await expect(async () => { - const getResult = await todoHelperNonOwner.get({ - id: todo['id'], - }); - }).rejects.toThrowErrorMatchingInlineSnapshot(`"GraphQL error: Not Authorized to access getTodoOwner on type Query"`); - - const listTodosResult = await todoHelperNonOwner.list(); - checkListItemExistence(listTodosResult, `list${modelName}s`, todo['id']); - - await expect( - todoHelperNonOwner.delete(`delete${modelName}`, { - id: todo['id'], - }), - ).rejects.toThrow('GraphQL error: Not Authorized to access deleteTodoOwner on type Mutation'); - - const todoRandom = { - id: Date.now().toString(), - content: 'Todo', - owner: userName1, - }; - const todoRandomUpdated = { - ...todoRandom, - content: 'Todo updated', - }; - const actorClient = getConfiguredAppsyncClientOIDCAuth(graphQlEndpoint, region, userMap[userName1]); - const observerClient = getConfiguredAppsyncClientOIDCAuth(graphQlEndpoint, region, userMap[userName2]); - const actorTodoHelper = createModelOperationHelpers(actorClient, schema)[modelName]; - const observerTodoHelper = createModelOperationHelpers(observerClient, schema)[modelName]; - - const onCreateSubscriptionResult = await observerTodoHelper.subscribe('onCreate', [ - async () => { - await actorTodoHelper.create(`create${modelName}`, todoRandom); - }, - ]); - expect(onCreateSubscriptionResult).toHaveLength(0); - const onUpdateSubscriptionResult = await observerTodoHelper.subscribe('onUpdate', [ - async () => { - await actorTodoHelper.update(`update${modelName}`, todoRandomUpdated); - }, - ]); - expect(onUpdateSubscriptionResult).toHaveLength(0); - const onDeleteSubscriptionResult = await observerTodoHelper.subscribe('onDelete', [ - async () => { - await actorTodoHelper.delete(`delete${modelName}`, { id: todoRandom.id }); - }, - ]); - expect(onDeleteSubscriptionResult).toHaveLength(0); - }); - - test('custom owner field used to store owner information', async () => { - const modelName = 'TodoOwnerFieldString'; - const modelOperationHelpers = createModelOperationHelpers(appSyncClients[oidcProvider][userName1], schema); - const todoHelper = modelOperationHelpers[modelName]; - - const todo = { - content: 'Todo', - }; - const resultSetName = `create${modelName}`; - const createResult = await todoHelper.create(resultSetName, todo); - expect(createResult.data[resultSetName].id).toBeDefined(); - todo['id'] = createResult.data[resultSetName].id; - expect(createResult.data[resultSetName].content).toEqual(todo.content); - expect(createResult.data[resultSetName].author).toEqual(userName1); - - const todoWithOwner = { - ...todo, - author: userName1, - }; - - const todo1Updated = { - id: todo['id'], - content: 'Todo updated', - author: todoWithOwner.author, - }; - const updateResult = await todoHelper.update(`update${modelName}`, todo1Updated); - checkOperationResult(updateResult, todo1Updated, `update${modelName}`); - - const getResult = await todoHelper.get({ - id: todo['id'], - }); - checkOperationResult(getResult, todo1Updated, `get${modelName}`); - - const listTodosResult = await todoHelper.list(); - checkListItemExistence(listTodosResult, `list${modelName}s`, todo['id'], true); - - const deleteResult = await todoHelper.delete(`delete${modelName}`, { - id: todo['id'], - }); - checkOperationResult(deleteResult, todo1Updated, `delete${modelName}`); - - const todoRandom = { - id: Date.now().toString(), - content: 'Todo', - author: userName1, - }; - const todoRandomUpdated = { - ...todoRandom, - content: 'Todo updated', - }; - const actorClient = getConfiguredAppsyncClientOIDCAuth(graphQlEndpoint, region, userMap[userName1]); - const subTodoHelper = createModelOperationHelpers(actorClient, schema)[modelName]; - - const onCreateSubscriptionResult = await subTodoHelper.subscribe( - 'onCreate', - [ - async () => { - await subTodoHelper.create(`create${modelName}`, todoRandom); - }, - ], - { author: userName1 }, - subscriptionWithOwner(`Create${modelName}`, 'author'), - ); - expect(onCreateSubscriptionResult).toHaveLength(1); - checkOperationResult(onCreateSubscriptionResult[0], todoRandom, `onCreate${modelName}`); - const onUpdateSubscriptionResult = await subTodoHelper.subscribe('onUpdate', [ - async () => { - await subTodoHelper.update(`update${modelName}`, todoRandomUpdated); - }, - ]); - expect(onUpdateSubscriptionResult).toHaveLength(1); - checkOperationResult(onUpdateSubscriptionResult[0], todoRandomUpdated, `onUpdate${modelName}`); - const onDeleteSubscriptionResult = await subTodoHelper.subscribe('onDelete', [ - async () => { - await subTodoHelper.delete(`delete${modelName}`, { id: todoRandom.id }); - }, - ]); - expect(onDeleteSubscriptionResult).toHaveLength(1); - checkOperationResult(onDeleteSubscriptionResult[0], todoRandomUpdated, `onDelete${modelName}`); - }); - - test('non-owner of a record cannot pretend to be an owner and gain access', async () => { - const modelName = 'TodoOwnerFieldString'; - const modelOperationHelpersOwner = createModelOperationHelpers(appSyncClients[oidcProvider][userName1], schema); - const modelOperationHelpersNonOwner = createModelOperationHelpers(appSyncClients[oidcProvider][userName2], schema); - const todoHelperOwner = modelOperationHelpersOwner[modelName]; - const todoHelperNonOwner = modelOperationHelpersNonOwner[modelName]; - - const todo = { - content: 'Todo', - }; - const resultSetName = `create${modelName}`; - const createResult = await todoHelperOwner.create(resultSetName, todo); - expect(createResult.data[resultSetName].id).toBeDefined(); - todo['id'] = createResult.data[resultSetName].id; - - const todoUpdated = { - id: todo['id'], - content: 'Todo updated', - author: userName1, - }; - await expect(todoHelperNonOwner.update(`update${modelName}`, todoUpdated)).rejects.toThrow( - 'GraphQL error: Not Authorized to access updateTodoOwnerFieldString on type Mutation', - ); - - await expect(async () => { - const getResult = await todoHelperNonOwner.get({ - id: todo['id'], - }); - }).rejects.toThrowErrorMatchingInlineSnapshot(`"GraphQL error: Not Authorized to access getTodoOwnerFieldString on type Query"`); - - const listTodosResult = await todoHelperNonOwner.list(); - checkListItemExistence(listTodosResult, `list${modelName}s`, todo['id']); - - await expect( - todoHelperNonOwner.delete(`delete${modelName}`, { - id: todo['id'], - }), - ).rejects.toThrow('GraphQL error: Not Authorized to access deleteTodoOwnerFieldString on type Mutation'); - - const todoRandom = { - id: Date.now().toString(), - content: 'Todo', - author: userName1, - }; - const todoRandomUpdated = { - ...todoRandom, - content: 'Todo updated', - }; - - const actorClient = getConfiguredAppsyncClientOIDCAuth(graphQlEndpoint, region, userMap[userName1]); - const observerClient = getConfiguredAppsyncClientOIDCAuth(graphQlEndpoint, region, userMap[userName2]); - const actorTodoHelper = createModelOperationHelpers(actorClient, schema)[modelName]; - const observerTodoHelper = createModelOperationHelpers(observerClient, schema)[modelName]; - - const onCreateSubscriptionResult = await observerTodoHelper.subscribe( - 'onCreate', - [ - async () => { - await actorTodoHelper.create(`create${modelName}`, todoRandom); - }, - ], - { author: userName1 }, - subscriptionWithOwner(`Create${modelName}`, 'author'), - ); - expect(onCreateSubscriptionResult).toHaveLength(0); - const onUpdateSubscriptionResult = await observerTodoHelper.subscribe('onUpdate', [ - async () => { - await actorTodoHelper.update(`update${modelName}`, todoRandomUpdated); - }, - ]); - expect(onUpdateSubscriptionResult).toHaveLength(0); - const onDeleteSubscriptionResult = await observerTodoHelper.subscribe('onDelete', [ - async () => { - await actorTodoHelper.delete(`delete${modelName}`, { id: todoRandom.id }); - }, - ]); - expect(onDeleteSubscriptionResult).toHaveLength(0); - }); - - test('member in list of owners can perform CRUD and subscription operations', async () => { - const modelName = 'TodoOwnerFieldList'; - const modelOperationHelpers = createModelOperationHelpers(appSyncClients[oidcProvider][userName1], schema); - const todoHelper = modelOperationHelpers[modelName]; - - const todo = { - content: 'Todo', - authors: [userName1], - }; - const resultSetName = `create${modelName}`; - const createResult = await todoHelper.create(resultSetName, todo); - expect(createResult.data[resultSetName].id).toBeDefined(); - todo['id'] = createResult.data[resultSetName].id; - expect(createResult.data[resultSetName].content).toEqual(todo.content); - expect(createResult.data[resultSetName].authors).toEqual([userName1]); - - const todoUpdated = { - id: todo['id'], - content: 'Todo updated', - authors: [userName1], - }; - const updateResult = await todoHelper.update(`update${modelName}`, todoUpdated); - checkOperationResult(updateResult, todoUpdated, `update${modelName}`); - - const getResult = await todoHelper.get({ - id: todo['id'], - }); - checkOperationResult(getResult, todoUpdated, `get${modelName}`); - - const listTodosResult = await todoHelper.list(); - checkListItemExistence(listTodosResult, `list${modelName}s`, todo['id'], true); - - const deleteResult = await todoHelper.delete(`delete${modelName}`, { - id: todo['id'], - }); - checkOperationResult(deleteResult, todoUpdated, `delete${modelName}`); - - const todoRandom = { - id: Date.now().toString(), - content: 'Todo', - authors: [userName1], - }; - const todoRandomUpdated = { - ...todoRandom, - content: 'Todo updated', - }; - const actorClient = getConfiguredAppsyncClientOIDCAuth(graphQlEndpoint, region, userMap[userName1]); - const subTodoHelper = createModelOperationHelpers(actorClient, schema)[modelName]; - - const onCreateSubscriptionResult = await subTodoHelper.subscribe('onCreate', [ - async () => { - await subTodoHelper.create(`create${modelName}`, todoRandom); - }, - ]); - expect(onCreateSubscriptionResult).toHaveLength(1); - checkOperationResult(onCreateSubscriptionResult[0], todoRandom, `onCreate${modelName}`); - const onUpdateSubscriptionResult = await subTodoHelper.subscribe('onUpdate', [ - async () => { - await subTodoHelper.update(`update${modelName}`, todoRandomUpdated); - }, - ]); - expect(onUpdateSubscriptionResult).toHaveLength(1); - checkOperationResult(onUpdateSubscriptionResult[0], todoRandomUpdated, `onUpdate${modelName}`); - const onDeleteSubscriptionResult = await subTodoHelper.subscribe('onDelete', [ - async () => { - await subTodoHelper.delete(`delete${modelName}`, { id: todoRandom.id }); - }, - ]); - expect(onDeleteSubscriptionResult).toHaveLength(1); - checkOperationResult(onDeleteSubscriptionResult[0], todoRandomUpdated, `onDelete${modelName}`); - }); - - test('non-owner of a record cannot add themself to owner list', async () => { - const modelName = 'TodoOwnerFieldList'; - const modelOperationHelpersOwner = createModelOperationHelpers(appSyncClients[oidcProvider][userName1], schema); - const modelOperationHelpersNonOwner = createModelOperationHelpers(appSyncClients[oidcProvider][userName2], schema); - const todoHelperOwner = modelOperationHelpersOwner[modelName]; - const todoHelperNonOwner = modelOperationHelpersNonOwner[modelName]; - - const todo = { - content: 'Todo', - authors: [userName1], - }; - const resultSetName = `create${modelName}`; - const createResult = await todoHelperOwner.create(resultSetName, todo); - expect(createResult.data[resultSetName].id).toBeDefined(); - todo['id'] = createResult.data[resultSetName].id; - - const todoUpdated = { - id: todo['id'], - content: 'Todo updated', - authors: [userName1, userName2], - }; - await expect(todoHelperNonOwner.update(`update${modelName}`, todoUpdated)).rejects.toThrow( - 'GraphQL error: Not Authorized to access updateTodoOwnerFieldList on type Mutation', - ); - - await expect(async () => { - const getResult = await todoHelperNonOwner.get({ - id: todo['id'], - }); - }).rejects.toThrowErrorMatchingInlineSnapshot(`"GraphQL error: Not Authorized to access getTodoOwnerFieldList on type Query"`); - - const listTodosResult = await todoHelperNonOwner.list(); - checkListItemExistence(listTodosResult, `list${modelName}s`, todo['id']); - - await expect( - todoHelperNonOwner.delete(`delete${modelName}`, { - id: todo['id'], - }), - ).rejects.toThrow('GraphQL error: Not Authorized to access deleteTodoOwnerFieldList on type Mutation'); - - const todoRandom = { - id: Date.now().toString(), - content: 'Todo', - authors: [userName1], - }; - const todoRandomUpdated = { - ...todoRandom, - content: 'Todo updated', - }; - const actorClient = getConfiguredAppsyncClientOIDCAuth(graphQlEndpoint, region, userMap[userName1]); - const observerClient = getConfiguredAppsyncClientOIDCAuth(graphQlEndpoint, region, userMap[userName2]); - const actorTodoHelper = createModelOperationHelpers(actorClient, schema)[modelName]; - const observerTodoHelper = createModelOperationHelpers(observerClient, schema)[modelName]; - - const onCreateSubscriptionResult = await observerTodoHelper.subscribe('onCreate', [ - async () => { - await actorTodoHelper.create(`create${modelName}`, todoRandom); - }, - ]); - expect(onCreateSubscriptionResult).toHaveLength(0); - const onUpdateSubscriptionResult = await observerTodoHelper.subscribe('onUpdate', [ - async () => { - await actorTodoHelper.update(`update${modelName}`, todoRandomUpdated); - }, - ]); - expect(onUpdateSubscriptionResult).toHaveLength(0); - const onDeleteSubscriptionResult = await observerTodoHelper.subscribe('onDelete', [ - async () => { - await actorTodoHelper.delete(`delete${modelName}`, { id: todoRandom.id }); - }, - ]); - expect(onDeleteSubscriptionResult).toHaveLength(0); - }); - - test('owner can add another user to the owner list', async () => { - const modelName = 'TodoOwnerFieldList'; - const modelOperationHelpersOwner = createModelOperationHelpers(appSyncClients[oidcProvider][userName1], schema); - const modelOperationHelpersNonOwner = createModelOperationHelpers(appSyncClients[oidcProvider][userName2], schema); - const todoHelperOwner = modelOperationHelpersOwner[modelName]; - const todoHelperAnotherOwner = modelOperationHelpersNonOwner[modelName]; - - const todo = { - content: 'Todo', - authors: [userName1, userName2], - }; - const resultSetName = `create${modelName}`; - const createResult = await todoHelperOwner.create(resultSetName, todo); - expect(createResult.data[resultSetName].id).toBeDefined(); - todo['id'] = createResult.data[resultSetName].id; - - const todoUpdated = { - id: todo['id'], - content: 'Todo updated', - authors: [userName1, userName2], - }; - const updateResult = await todoHelperAnotherOwner.update(`update${modelName}`, todoUpdated); - checkOperationResult(updateResult, todoUpdated, `update${modelName}`); - - const getResult = await todoHelperAnotherOwner.get({ - id: todo['id'], - }); - checkOperationResult(getResult, todoUpdated, `get${modelName}`); - - const listTodosResult = await todoHelperAnotherOwner.list(); - checkListItemExistence(listTodosResult, `list${modelName}s`, todo['id'], true); - - const deleteResult = await todoHelperAnotherOwner.delete(`delete${modelName}`, { - id: todo['id'], - }); - checkOperationResult(deleteResult, todoUpdated, `delete${modelName}`); - - const todoRandom = { - id: Date.now().toString(), - content: 'Todo', - authors: [userName1, userName2], - }; - const todoRandomUpdated = { - ...todoRandom, - content: 'Todo updated', - }; - const actorClient = getConfiguredAppsyncClientOIDCAuth(graphQlEndpoint, region, userMap[userName1]); - const observerClient = getConfiguredAppsyncClientOIDCAuth(graphQlEndpoint, region, userMap[userName2]); - const actorTodoHelper = createModelOperationHelpers(actorClient, schema)[modelName]; - const observerTodoHelper = createModelOperationHelpers(observerClient, schema)[modelName]; - - const onCreateSubscriptionResult = await observerTodoHelper.subscribe('onCreate', [ - async () => { - await actorTodoHelper.create(`create${modelName}`, todoRandom); - }, - ]); - expect(onCreateSubscriptionResult).toHaveLength(1); - checkOperationResult(onCreateSubscriptionResult[0], todoRandom, `onCreate${modelName}`); - const onUpdateSubscriptionResult = await observerTodoHelper.subscribe('onUpdate', [ - async () => { - await actorTodoHelper.update(`update${modelName}`, todoRandomUpdated); - }, - ]); - expect(onUpdateSubscriptionResult).toHaveLength(1); - checkOperationResult(onUpdateSubscriptionResult[0], todoRandomUpdated, `onUpdate${modelName}`); - const onDeleteSubscriptionResult = await observerTodoHelper.subscribe('onDelete', [ - async () => { - await actorTodoHelper.delete(`delete${modelName}`, { id: todoRandom.id }); - }, - ]); - expect(onDeleteSubscriptionResult).toHaveLength(1); - checkOperationResult(onDeleteSubscriptionResult[0], todoRandomUpdated, `onDelete${modelName}`); - }); - - test('users in static group can perform CRUD and subscription operations', async () => { - const modelName = 'TodoStaticGroup'; - const modelOperationHelpers = createModelOperationHelpers(appSyncClients[oidcProvider][userName1], schema); - const todoHelper = modelOperationHelpers[modelName]; - - const todo = { - content: 'Todo', - }; - const resultSetName = `create${modelName}`; - const createResult = await todoHelper.create(resultSetName, todo); - expect(createResult.data[resultSetName].id).toBeDefined(); - todo['id'] = createResult.data[resultSetName].id; - expect(createResult.data[resultSetName].content).toEqual(todo.content); - - const todoUpdated = { - id: todo['id'], - content: 'Todo updated', - }; - const updateResult = await todoHelper.update(`update${modelName}`, todoUpdated); - checkOperationResult(updateResult, todoUpdated, `update${modelName}`); - - const getResult = await todoHelper.get({ - id: todo['id'], - }); - checkOperationResult(getResult, todoUpdated, `get${modelName}`); - - const listTodosResult = await todoHelper.list(); - checkListItemExistence(listTodosResult, `list${modelName}s`, todo['id'], true); - - const deleteResult = await todoHelper.delete(`delete${modelName}`, { - id: todo['id'], - }); - checkOperationResult(deleteResult, todoUpdated, `delete${modelName}`); - - const todoRandom = { - id: Date.now().toString(), - content: 'Todo', - }; - const todoRandomUpdated = { - ...todoRandom, - content: 'Todo updated', - }; - const actorClient = getConfiguredAppsyncClientOIDCAuth(graphQlEndpoint, region, userMap[userName1]); - const subTodoHelper = createModelOperationHelpers(actorClient, schema)[modelName]; - - const onCreateSubscriptionResult = await subTodoHelper.subscribe('onCreate', [ - async () => { - await subTodoHelper.create(`create${modelName}`, todoRandom); - }, - ]); - expect(onCreateSubscriptionResult).toHaveLength(1); - checkOperationResult(onCreateSubscriptionResult[0], todoRandom, `onCreate${modelName}`); - const onUpdateSubscriptionResult = await subTodoHelper.subscribe('onUpdate', [ - async () => { - await subTodoHelper.update(`update${modelName}`, todoRandomUpdated); - }, - ]); - expect(onUpdateSubscriptionResult).toHaveLength(1); - checkOperationResult(onUpdateSubscriptionResult[0], todoRandomUpdated, `onUpdate${modelName}`); - const onDeleteSubscriptionResult = await subTodoHelper.subscribe('onDelete', [ - async () => { - await subTodoHelper.delete(`delete${modelName}`, { id: todoRandom.id }); - }, - ]); - expect(onDeleteSubscriptionResult).toHaveLength(1); - checkOperationResult(onDeleteSubscriptionResult[0], todoRandomUpdated, `onDelete${modelName}`); - }); - - test('users not in static group cannot perform CRUD operations', async () => { - const modelName = 'TodoStaticGroup'; - const modelOperationHelpersAdmin = createModelOperationHelpers(appSyncClients[oidcProvider][userName1], schema); - const modelOperationHelpersNonAdmin = createModelOperationHelpers(appSyncClients[oidcProvider][userName2], schema); - const todoHelperAdmin = modelOperationHelpersAdmin[modelName]; - const todoHelperNonAdmin = modelOperationHelpersNonAdmin[modelName]; - - const todo = { - content: 'Todo', - }; - const resultSetName = `create${modelName}`; - const createResult = await todoHelperAdmin.create(resultSetName, todo); - expect(createResult.data[resultSetName].id).toBeDefined(); - todo['id'] = createResult.data[resultSetName].id; - - await expect(todoHelperNonAdmin.create(`create${modelName}`, todo)).rejects.toThrow( - 'GraphQL error: Not Authorized to access createTodoStaticGroup on type Mutation', - ); - - const todoUpdated = { - id: todo['id'], - content: 'Todo updated', - }; - await expect(todoHelperNonAdmin.update(`update${modelName}`, todoUpdated)).rejects.toThrow( - 'GraphQL error: Not Authorized to access updateTodoStaticGroup on type Mutation', - ); - - expect( - todoHelperNonAdmin.get({ - id: todo['id'], - }), - ).rejects.toThrow('GraphQL error: Not Authorized to access getTodoStaticGroup on type Query'); - - await expect(todoHelperNonAdmin.list()).rejects.toThrow('GraphQL error: Not Authorized to access listTodoStaticGroups on type Query'); - - await expect( - todoHelperNonAdmin.delete(`delete${modelName}`, { - id: todo['id'], - }), - ).rejects.toThrow('GraphQL error: Not Authorized to access deleteTodoStaticGroup on type Mutation'); - }); - - test('users in group stored as string can perform CRUD and subscription operations', async () => { - const modelName = 'TodoGroupFieldString'; - const modelOperationHelpers = createModelOperationHelpers(appSyncClients[oidcProvider][userName1], schema); - const todoHelper = modelOperationHelpers[modelName]; - - const todo = { - content: 'Todo', - groupField: adminGroupName, - }; - const resultSetName = `create${modelName}`; - const createResult = await todoHelper.create(resultSetName, todo); - expect(createResult.data[resultSetName].id).toBeDefined(); - todo['id'] = createResult.data[resultSetName].id; - expect(createResult.data[resultSetName].content).toEqual(todo.content); - expect(createResult.data[resultSetName].groupField).toEqual(adminGroupName); - - const todoUpdated = { - id: todo['id'], - content: 'Todo updated', - groupField: adminGroupName, - }; - const updateResult = await todoHelper.update(`update${modelName}`, todoUpdated); - checkOperationResult(updateResult, todoUpdated, `update${modelName}`); - - const getResult = await todoHelper.get({ - id: todo['id'], - }); - checkOperationResult(getResult, todoUpdated, `get${modelName}`); - - const listTodosResult = await todoHelper.list(); - checkListItemExistence(listTodosResult, `list${modelName}s`, todo['id'], true); - - const deleteResult = await todoHelper.delete(`delete${modelName}`, { - id: todo['id'], - }); - checkOperationResult(deleteResult, todoUpdated, `delete${modelName}`); - - const todoRandom = { - id: Date.now().toString(), - content: 'Todo', - groupField: adminGroupName, - }; - const todoRandomUpdated = { - ...todoRandom, - content: 'Todo updated', - }; - const actorClient = getConfiguredAppsyncClientOIDCAuth(graphQlEndpoint, region, userMap[userName1]); - const subTodoHelper = createModelOperationHelpers(actorClient, schema)[modelName]; - - const onCreateSubscriptionResult = await subTodoHelper.subscribe('onCreate', [ - async () => { - await subTodoHelper.create(`create${modelName}`, todoRandom); - }, - ]); - expect(onCreateSubscriptionResult).toHaveLength(1); - checkOperationResult(onCreateSubscriptionResult[0], todoRandom, `onCreate${modelName}`); - const onUpdateSubscriptionResult = await subTodoHelper.subscribe('onUpdate', [ - async () => { - await subTodoHelper.update(`update${modelName}`, todoRandomUpdated); - }, - ]); - expect(onUpdateSubscriptionResult).toHaveLength(1); - checkOperationResult(onUpdateSubscriptionResult[0], todoRandomUpdated, `onUpdate${modelName}`); - const onDeleteSubscriptionResult = await subTodoHelper.subscribe('onDelete', [ - async () => { - await subTodoHelper.delete(`delete${modelName}`, { id: todoRandom.id }); - }, - ]); - expect(onDeleteSubscriptionResult).toHaveLength(1); - checkOperationResult(onDeleteSubscriptionResult[0], todoRandomUpdated, `onDelete${modelName}`); - }); - - test('users cannot spoof their group membership and gain access', async () => { - const modelName = 'TodoGroupFieldString'; - const modelOperationHelpersAdmin = createModelOperationHelpers(appSyncClients[oidcProvider][userName1], schema); - const modelOperationHelpersNonAdmin = createModelOperationHelpers(appSyncClients[oidcProvider][userName2], schema); - const todoHelperAdmin = modelOperationHelpersAdmin[modelName]; - const todoHelperNonAdmin = modelOperationHelpersNonAdmin[modelName]; - - const todo = { - content: 'Todo', - groupField: adminGroupName, - }; - const resultSetName = `create${modelName}`; - const createResult = await todoHelperAdmin.create(resultSetName, todo); - expect(createResult.data[resultSetName].id).toBeDefined(); - todo['id'] = createResult.data[resultSetName].id; - - await expect(todoHelperNonAdmin.create(resultSetName, todo)).rejects.toThrow( - 'GraphQL error: Not Authorized to access createTodoGroupFieldString on type Mutation', - ); - - const todoUpdated = { - id: todo['id'], - content: 'Todo updated', - groupField: devGroupName, - }; - await expect(todoHelperNonAdmin.update(`update${modelName}`, todoUpdated)).rejects.toThrow( - 'GraphQL error: Not Authorized to access updateTodoGroupFieldString on type Mutation', - ); - - await expect(async () => { - const getResult = await todoHelperNonAdmin.get({ - id: todo['id'], - }); - }).rejects.toThrowErrorMatchingInlineSnapshot(`"GraphQL error: Not Authorized to access getTodoGroupFieldString on type Query"`); - - const listTodosResult = await todoHelperNonAdmin.list(); - checkListItemExistence(listTodosResult, `list${modelName}s`, todo['id']); - - await expect( - todoHelperNonAdmin.delete(`delete${modelName}`, { - id: todo['id'], - }), - ).rejects.toThrow('GraphQL error: Not Authorized to access deleteTodoGroupFieldString on type Mutation'); - }); - - test('users in groups stored as list can perform CRUD and subscription operations', async () => { - const modelName = 'TodoGroupFieldList'; - const modelOperationHelpers = createModelOperationHelpers(appSyncClients[oidcProvider][userName1], schema); - const todoHelper = modelOperationHelpers[modelName]; - - const todo = { - content: 'Todo', - groupsField: [adminGroupName], - }; - const resultSetName = `create${modelName}`; - const createResult = await todoHelper.create(resultSetName, todo); - expect(createResult.data[resultSetName].id).toBeDefined(); - todo['id'] = createResult.data[resultSetName].id; - expect(createResult.data[resultSetName].content).toEqual(todo.content); - expect(createResult.data[resultSetName].groupsField).toEqual([adminGroupName]); - - const todo1Updated = { - id: todo['id'], - content: 'Todo updated', - groupsField: [adminGroupName], - }; - const updateResult = await todoHelper.update(`update${modelName}`, todo1Updated); - checkOperationResult(updateResult, todo1Updated, `update${modelName}`); - - const getResult = await todoHelper.get({ - id: todo['id'], - }); - checkOperationResult(getResult, todo1Updated, `get${modelName}`); - - const listTodosResult = await todoHelper.list(); - checkListItemExistence(listTodosResult, `list${modelName}s`, todo['id'], true); - - const deleteResult = await todoHelper.delete(`delete${modelName}`, { - id: todo['id'], - }); - checkOperationResult(deleteResult, todo1Updated, `delete${modelName}`); - - const todoRandom = { - id: Date.now().toString(), - content: 'Todo', - groupsField: [adminGroupName], - }; - const todoRandomUpdated = { - ...todoRandom, - content: 'Todo updated', - }; - const actorClient = getConfiguredAppsyncClientOIDCAuth(graphQlEndpoint, region, userMap[userName1]); - const subTodoHelper = createModelOperationHelpers(actorClient, schema)[modelName]; - - const onCreateSubscriptionResult = await subTodoHelper.subscribe('onCreate', [ - async () => { - await subTodoHelper.create(`create${modelName}`, todoRandom); - }, - ]); - expect(onCreateSubscriptionResult).toHaveLength(1); - checkOperationResult(onCreateSubscriptionResult[0], todoRandom, `onCreate${modelName}`); - const onUpdateSubscriptionResult = await subTodoHelper.subscribe('onUpdate', [ - async () => { - await subTodoHelper.update(`update${modelName}`, todoRandomUpdated); - }, - ]); - expect(onUpdateSubscriptionResult).toHaveLength(1); - checkOperationResult(onUpdateSubscriptionResult[0], todoRandomUpdated, `onUpdate${modelName}`); - const onDeleteSubscriptionResult = await subTodoHelper.subscribe('onDelete', [ - async () => { - await subTodoHelper.delete(`delete${modelName}`, { id: todoRandom.id }); - }, - ]); - expect(onDeleteSubscriptionResult).toHaveLength(1); - checkOperationResult(onDeleteSubscriptionResult[0], todoRandomUpdated, `onDelete${modelName}`); - }); - - test('users not part of allowed groups cannot access the records or modify allowed groups', async () => { - const modelName = 'TodoGroupFieldList'; - const modelOperationHelpersAdmin = createModelOperationHelpers(appSyncClients[oidcProvider][userName1], schema); - const modelOperationHelpersNonAdmin = createModelOperationHelpers(appSyncClients[oidcProvider][userName2], schema); - const todoHelperAdmin = modelOperationHelpersAdmin[modelName]; - const todoHelperNonAdmin = modelOperationHelpersNonAdmin[modelName]; - - const todo = { - content: 'Todo', - groupsField: [adminGroupName], - }; - const resultSetName = `create${modelName}`; - const createResult = await todoHelperAdmin.create(resultSetName, todo); - expect(createResult.data[resultSetName].id).toBeDefined(); - todo['id'] = createResult.data[resultSetName].id; - - const todoUpdated = { - id: todo['id'], - content: 'Todo updated', - groupsField: [adminGroupName, devGroupName], - }; - await expect(todoHelperNonAdmin.update(`update${modelName}`, todoUpdated)).rejects.toThrow( - 'GraphQL error: Not Authorized to access updateTodoGroupFieldList on type Mutation', - ); - - await expect(async () => { - const getResult = await todoHelperNonAdmin.get({ - id: todo['id'], - }); - }).rejects.toThrowErrorMatchingInlineSnapshot(`"GraphQL error: Not Authorized to access getTodoGroupFieldList on type Query"`); - - const listTodosResult = await todoHelperNonAdmin.list(); - checkListItemExistence(listTodosResult, `list${modelName}s`, todo['id']); - - await expect( - todoHelperNonAdmin.delete(`delete${modelName}`, { - id: todo['id'], - }), - ).rejects.toThrow('GraphQL error: Not Authorized to access deleteTodoGroupFieldList on type Mutation'); - - const todoRandom = { - id: Date.now().toString(), - content: 'Todo', - groupsField: [adminGroupName], - }; - const todoRandomUpdated = { - ...todoRandom, - content: 'Todo updated', - }; - const actorClient = getConfiguredAppsyncClientOIDCAuth(graphQlEndpoint, region, userMap[userName1]); - const observerClient = getConfiguredAppsyncClientOIDCAuth(graphQlEndpoint, region, userMap[userName2]); - const actorTodoHelper = createModelOperationHelpers(actorClient, schema)[modelName]; - const observerTodoHelper = createModelOperationHelpers(observerClient, schema)[modelName]; - - const onCreateSubscriptionResult = await observerTodoHelper.subscribe('onCreate', [ - async () => { - await actorTodoHelper.create(`create${modelName}`, todoRandom); - }, - ]); - expect(onCreateSubscriptionResult).toHaveLength(0); - const onUpdateSubscriptionResult = await observerTodoHelper.subscribe('onUpdate', [ - async () => { - await actorTodoHelper.update(`update${modelName}`, todoRandomUpdated); - }, - ]); - expect(onUpdateSubscriptionResult).toHaveLength(0); - const onDeleteSubscriptionResult = await observerTodoHelper.subscribe('onDelete', [ - async () => { - await actorTodoHelper.delete(`delete${modelName}`, { id: todoRandom.id }); - }, - ]); - expect(onDeleteSubscriptionResult).toHaveLength(0); - }); - - test('Admin user can give access to another group of users', async () => { - const modelName = 'TodoGroupFieldList'; - const modelOperationHelpersAdmin = createModelOperationHelpers(appSyncClients[oidcProvider][userName1], schema); - const modelOperationHelpersNonAdmin = createModelOperationHelpers(appSyncClients[oidcProvider][userName2], schema); - const todoHelperAdmin = modelOperationHelpersAdmin[modelName]; - const todoHelperNonAdmin = modelOperationHelpersNonAdmin[modelName]; - - const todo = { - content: 'Todo', - groupsField: [adminGroupName, devGroupName], - }; - const resultSetName = `create${modelName}`; - const createResult = await todoHelperAdmin.create(resultSetName, todo); - expect(createResult.data[resultSetName].id).toBeDefined(); - todo['id'] = createResult.data[resultSetName].id; - - const todoUpdated = { - id: todo['id'], - content: 'Todo updated', - groupsField: [adminGroupName, devGroupName], - }; - const updateResult = await todoHelperNonAdmin.update(`update${modelName}`, todoUpdated); - checkOperationResult(updateResult, todoUpdated, `update${modelName}`); - - const getResult = await todoHelperNonAdmin.get({ - id: todo['id'], - }); - checkOperationResult(getResult, todoUpdated, `get${modelName}`); - - const listTodosResult = await todoHelperNonAdmin.list(); - checkListItemExistence(listTodosResult, `list${modelName}s`, todo['id'], true); - - const deleteResult = await todoHelperNonAdmin.delete(`delete${modelName}`, { - id: todo['id'], - }); - checkOperationResult(deleteResult, todoUpdated, `delete${modelName}`); - - const todoRandom = { - id: Date.now().toString(), - content: 'Todo', - groupsField: [adminGroupName, devGroupName], - }; - const todoRandomUpdated = { - ...todoRandom, - content: 'Todo updated', - }; - const actorClient = getConfiguredAppsyncClientOIDCAuth(graphQlEndpoint, region, userMap[userName1]); - const observerClient = getConfiguredAppsyncClientOIDCAuth(graphQlEndpoint, region, userMap[userName2]); - const actorTodoHelper = createModelOperationHelpers(actorClient, schema)[modelName]; - const observerTodoHelper = createModelOperationHelpers(observerClient, schema)[modelName]; - - const onCreateSubscriptionResult = await observerTodoHelper.subscribe('onCreate', [ - async () => { - await actorTodoHelper.create(`create${modelName}`, todoRandom); - }, - ]); - expect(onCreateSubscriptionResult).toHaveLength(1); - checkOperationResult(onCreateSubscriptionResult[0], todoRandom, `onCreate${modelName}`); - const onUpdateSubscriptionResult = await observerTodoHelper.subscribe('onUpdate', [ - async () => { - await actorTodoHelper.update(`update${modelName}`, todoRandomUpdated); - }, - ]); - expect(onUpdateSubscriptionResult).toHaveLength(1); - checkOperationResult(onUpdateSubscriptionResult[0], todoRandomUpdated, `onUpdate${modelName}`); - const onDeleteSubscriptionResult = await observerTodoHelper.subscribe('onDelete', [ - async () => { - await actorTodoHelper.delete(`delete${modelName}`, { id: todoRandom.id }); - }, - ]); - expect(onDeleteSubscriptionResult).toHaveLength(1); - checkOperationResult(onDeleteSubscriptionResult[0], todoRandomUpdated, `onDelete${modelName}`); - }); - - test('logged in user can perform custom operations', async () => { - const appSyncClient = appSyncClients[oidcProvider][userName2]; - const todo = { - id: Date.now().toString(), - content: 'Todo', - }; - const createTodoCustom = /* GraphQL */ ` - mutation CreateTodoCustom($id: ID!, $content: String) { - addTodoPrivate(id: $id, content: $content) { - id - content - } - } - `; - const createResult = await appSyncClient.mutate({ - mutation: gql(createTodoCustom), - fetchPolicy: 'no-cache', - variables: todo, - }); - expect(createResult.data.addTodoPrivate).toBeDefined(); - - const getTodoCustom = /* GraphQL */ ` - query GetTodoCustom($id: ID!) { - customGetTodoPrivate(id: $id) { - id - content - } - } - `; - const getResult = await appSyncClient.query({ - query: gql(getTodoCustom), - fetchPolicy: 'no-cache', - variables: { - id: todo.id, - }, - }); - expect(getResult.data.customGetTodoPrivate).toHaveLength(1); - expect(getResult.data.customGetTodoPrivate[0].id).toEqual(todo.id); - expect(getResult.data.customGetTodoPrivate[0].content).toEqual(todo.content); - }); - - test('users in static group can perform custom operations', async () => { - const appSyncClient = appSyncClients[oidcProvider][userName1]; - const todo = { - id: Date.now().toString(), - content: 'Todo', - }; - const createTodoCustom = /* GraphQL */ ` - mutation CreateTodoCustom($id: ID!, $content: String) { - addTodoStaticGroup(id: $id, content: $content) { - id - content - } - } - `; - const createResult = await appSyncClient.mutate({ - mutation: gql(createTodoCustom), - fetchPolicy: 'no-cache', - variables: todo, - }); - expect(createResult.data.addTodoStaticGroup).toBeDefined(); - - const getTodoCustom = /* GraphQL */ ` - query GetTodoCustom($id: ID!) { - customGetTodoStaticGroup(id: $id) { - id - content - } - } - `; - const getResult = await appSyncClient.query({ - query: gql(getTodoCustom), - fetchPolicy: 'no-cache', - variables: { - id: todo.id, - }, - }); - expect(getResult.data.customGetTodoStaticGroup).toHaveLength(1); - expect(getResult.data.customGetTodoStaticGroup[0].id).toEqual(todo.id); - expect(getResult.data.customGetTodoStaticGroup[0].content).toEqual(todo.content); - }); - - test('users not in static group cannot perform custom operations', async () => { - const appSyncClient = appSyncClients[oidcProvider][userName2]; - const todo = { - id: Date.now().toString(), - content: 'Todo', - }; - const createTodoCustom = /* GraphQL */ ` - mutation CreateTodoCustom($id: ID!, $content: String) { - addTodoStaticGroup(id: $id, content: $content) { - id - content - } - } - `; - await expect( - appSyncClient.mutate({ - mutation: gql(createTodoCustom), - fetchPolicy: 'no-cache', - variables: todo, - }), - ).rejects.toThrow('GraphQL error: Not Authorized to access addTodoStaticGroup on type Mutation'); - - const getTodoCustom = /* GraphQL */ ` - query GetTodoCustom($id: ID!) { - customGetTodoStaticGroup(id: $id) { - id - content - } - } - `; - await expect( - appSyncClient.query({ - query: gql(getTodoCustom), - fetchPolicy: 'no-cache', - variables: { - id: todo.id, - }, - }), - ).rejects.toThrow('GraphQL error: Not Authorized to access customGetTodoStaticGroup on type Query'); - }); - }); -};