diff --git a/packages/amplify-e2e-tests/src/__tests__/auth-test-schemas/userpool-provider-fields.ts b/packages/amplify-e2e-tests/src/__tests__/auth-test-schemas/userpool-provider-fields.ts index acb4e5da1c..b4f9634d7c 100644 --- a/packages/amplify-e2e-tests/src/__tests__/auth-test-schemas/userpool-provider-fields.ts +++ b/packages/amplify-e2e-tests/src/__tests__/auth-test-schemas/userpool-provider-fields.ts @@ -114,6 +114,32 @@ export const schema = ` groupContent: String @refersTo(name: "group_content") @auth(rules: [{ allow: groups, groupsField: "customGroup", operations: [create, read] }]) groupsContent: String @refersTo(name: "groups_content") @auth(rules: [{ allow: groups, groupsField: "customGroups", operations: [update, read] }]) } + + type PrimaryOne @model @auth(rules: [{ allow: owner }]) { + id: String! @primaryKey + owner: String + relatedOne: RelatedOne @hasOne(references: ["primaryId"]) + } + + type RelatedOne @model @auth(rules: [{ allow: owner, ownerField: "relatedOwner" }]) { + id: String! @primaryKey + relatedOwner: String + primaryId: String + primary: PrimaryOne @belongsTo(references: ["primaryId"]) + } + + type PrimaryTwo @model @auth(rules: [{ allow: owner }]) { + id: String! @primaryKey + owner: String + relatedTwos: [RelatedTwo] @hasMany(references: ["primaryId"]) + } + + type RelatedTwo @model @auth(rules: [{ allow: owner, ownerField: "relatedOwner" }]) { + id: String! @primaryKey + relatedOwner: String + primaryId: String + primary: PrimaryTwo @belongsTo(references: ["primaryId"]) + } `; export const sqlCreateStatements = (engine: ImportedRDSType): string[] => generateDDL(schema, engine); diff --git a/packages/amplify-e2e-tests/src/rds-v2-tests-common/rds-auth-userpool-fields.ts b/packages/amplify-e2e-tests/src/rds-v2-tests-common/rds-auth-userpool-fields.ts index acf9b49c9a..414ef22e9b 100644 --- a/packages/amplify-e2e-tests/src/rds-v2-tests-common/rds-auth-userpool-fields.ts +++ b/packages/amplify-e2e-tests/src/rds-v2-tests-common/rds-auth-userpool-fields.ts @@ -3542,5 +3542,151 @@ export const testUserPoolFieldAuth = (engine: ImportedRDSType): void => { checkListItemExistence(listTodosResult2, `list${modelName}`, todoPrivateFields['id'], true); checkListResponseErrors(listTodosResult2, expectedFieldErrors(ownerAndGroupFields, modelName, false)); }); + + test('owner rule on relational models should respect owner auth rule - has one and belongs to', async () => { + const modelNamePrimaryOne = 'PrimaryOne'; + const modelNameRelatedOne = 'RelatedOne'; + const user1PrimaryOneHelper = user1ModelOperationHelpers[modelNamePrimaryOne]; + const user2RelatedOneHelper = user2ModelOperationHelpers[modelNameRelatedOne]; + + const primaryRecord = { + id: '1', + owner: userName1, + }; + const relatedRecord = { + id: '1', + relatedOwner: userName2, + primaryId: primaryRecord['id'], + }; + const createResultSetNamePrimary = `create${modelNamePrimaryOne}`; + const createResultSetNameRelated = `create${modelNameRelatedOne}`; + + // Create a primary record with user1 + const createResultPrimary = await user1PrimaryOneHelper.create(createResultSetNamePrimary, primaryRecord, 'id owner'); + expect(createResultPrimary.data[createResultSetNamePrimary].id).toBeDefined(); + expect(createResultPrimary.data[createResultSetNamePrimary].id).toEqual(primaryRecord['id']); + expect(createResultPrimary.data[createResultSetNamePrimary].owner).toEqual(userName1); + + // Create a related record with user2 + const createResultRelated = await user2RelatedOneHelper.create(createResultSetNameRelated, relatedRecord, 'id relatedOwner primaryId'); + expect(createResultRelated.data[createResultSetNameRelated].id).toBeDefined(); + expect(createResultRelated.data[createResultSetNameRelated].id).toEqual(relatedRecord['id']); + expect(createResultRelated.data[createResultSetNameRelated].relatedOwner).toEqual(userName2); + expect(createResultRelated.data[createResultSetNameRelated].primaryId).toEqual(relatedRecord['primaryId']); + + // Get primary record with user1, related record should be null + const primaryWithRelatedQuery = ` + query GetPrimaryOne($id: String!) { + getPrimaryOne(id: $id) { + id + owner + relatedOne { + id + relatedOwner + } + } + } + `; + const getResultSetNamePrimary = `get${modelNamePrimaryOne}`; + const getResultPrimary = await user1PrimaryOneHelper.get({ id: primaryRecord['id'] }, primaryWithRelatedQuery); + expect(getResultPrimary.data[getResultSetNamePrimary].id).toEqual(primaryRecord['id']); + expect(getResultPrimary.data[getResultSetNamePrimary].owner).toEqual(userName1); + expect(getResultPrimary.data[getResultSetNamePrimary].relatedOne).toBeNull(); + + // Get related record with user2, related record should be null + const relatedWithPrimaryQuery = ` + query GetRelatedOne($id: String!) { + getRelatedOne(id: $id) { + id + relatedOwner + primaryId + primary { + id + owner + } + } + } + `; + const getResultSetNameRelated = `get${modelNameRelatedOne}`; + const getResultRelated = await user2RelatedOneHelper.get({ id: relatedRecord['id'] }, relatedWithPrimaryQuery); + expect(getResultRelated.data[getResultSetNameRelated].id).toEqual(relatedRecord['id']); + expect(getResultRelated.data[getResultSetNameRelated].relatedOwner).toEqual(userName2); + expect(getResultRelated.data[getResultSetNameRelated].primaryId).toEqual(relatedRecord['primaryId']); + expect(getResultRelated.data[getResultSetNameRelated].primary).toBeNull(); + }); + + test('owner rule on relational models should respect owner auth rule - has many and belongs to', async () => { + const modelNamePrimaryTwo = 'PrimaryTwo'; + const modelNameRelatedTwo = 'RelatedTwo'; + const user1PrimaryTwoHelper = user1ModelOperationHelpers[modelNamePrimaryTwo]; + const user1RelatedTwoHelper = user1ModelOperationHelpers[modelNameRelatedTwo]; + const user2RelatedTwoHelper = user2ModelOperationHelpers[modelNameRelatedTwo]; + + const primaryRecord = { + id: '1', + owner: userName1, + }; + const relatedRecord1 = { + id: '1', + relatedOwner: userName2, + primaryId: primaryRecord['id'], + }; + const relatedRecord2 = { + id: '2', + relatedOwner: userName1, + primaryId: primaryRecord['id'], + }; + const createResultSetNamePrimary = `create${modelNamePrimaryTwo}`; + const createResultSetNameRelated = `create${modelNameRelatedTwo}`; + + // Create a primary record with user1 + const createResultPrimary = await user1PrimaryTwoHelper.create(createResultSetNamePrimary, primaryRecord, 'id owner'); + expect(createResultPrimary.data[createResultSetNamePrimary].id).toBeDefined(); + expect(createResultPrimary.data[createResultSetNamePrimary].id).toEqual(primaryRecord['id']); + expect(createResultPrimary.data[createResultSetNamePrimary].owner).toEqual(userName1); + + // Create a related record with user2 + const createResultRelated1 = await user2RelatedTwoHelper.create(createResultSetNameRelated, relatedRecord1, 'id relatedOwner primaryId'); + expect(createResultRelated1.data[createResultSetNameRelated].id).toBeDefined(); + expect(createResultRelated1.data[createResultSetNameRelated].id).toEqual(relatedRecord1['id']); + expect(createResultRelated1.data[createResultSetNameRelated].relatedOwner).toEqual(userName2); + expect(createResultRelated1.data[createResultSetNameRelated].primaryId).toEqual(relatedRecord1['primaryId']); + + // Create a related record with user1 + const createResultRelated2 = await user1RelatedTwoHelper.create(createResultSetNameRelated, relatedRecord2, 'id relatedOwner primaryId'); + expect(createResultRelated2.data[createResultSetNameRelated].id).toBeDefined(); + expect(createResultRelated2.data[createResultSetNameRelated].id).toEqual(relatedRecord2['id']); + expect(createResultRelated2.data[createResultSetNameRelated].relatedOwner).toEqual(userName1); + expect(createResultRelated2.data[createResultSetNameRelated].primaryId).toEqual(relatedRecord2['primaryId']); + + // Get primary record with user1, related field should return only the records with relatedOwner as user1 + const primaryWithRelatedQuery = ` + query GetPrimaryTwo($id: String!) { + getPrimaryTwo(id: $id) { + id + owner + relatedTwos { + items { + id + relatedOwner + primaryId + } + } + } + } + `; + const getResultSetNamePrimary = `get${modelNamePrimaryTwo}`; + const getResultPrimary = await user1PrimaryTwoHelper.get({ id: primaryRecord['id'] }, primaryWithRelatedQuery); + expect(getResultPrimary.data[getResultSetNamePrimary].id).toEqual(primaryRecord['id']); + expect(getResultPrimary.data[getResultSetNamePrimary].owner).toEqual(userName1); + expect(getResultPrimary.data[getResultSetNamePrimary].relatedTwos).toBeDefined(); + expect(getResultPrimary.data[getResultSetNamePrimary].relatedTwos.items).toBeDefined(); + expect(getResultPrimary.data[getResultSetNamePrimary].relatedTwos.items.length).toEqual(1); + expect(getResultPrimary.data[getResultSetNamePrimary].relatedTwos.items[0]).toEqual(expect.objectContaining({ + id: relatedRecord2['id'], + relatedOwner: userName1, + primaryId: relatedRecord2['primaryId'], + })); + }); }); }; diff --git a/packages/amplify-graphql-relational-transformer/src/__tests__/__snapshots__/amplify-graphql-belongs-to-transformer.test.ts.snap b/packages/amplify-graphql-relational-transformer/src/__tests__/__snapshots__/amplify-graphql-belongs-to-transformer.test.ts.snap index 11c4105b3b..aa81f4327a 100644 --- a/packages/amplify-graphql-relational-transformer/src/__tests__/__snapshots__/amplify-graphql-belongs-to-transformer.test.ts.snap +++ b/packages/amplify-graphql-relational-transformer/src/__tests__/__snapshots__/amplify-graphql-belongs-to-transformer.test.ts.snap @@ -306,6 +306,9 @@ $util.qr($lambdaInput.args.putAll($util.defaultIfNull($context.arguments, {}))) #if( !$lambdaInput.args.input ) #set( $lambdaInput.args.input = {} ) #end +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + #set( $lambdaInput.args.metadata.authFilter = $ctx.stash.authFilter ) +#end $util.qr($lambdaInput.args.input.put(\\"firstName\\", $util.defaultIfNull($ctx.source.userFirstName, \\"\\"))) $util.qr($lambdaInput.args.input.put(\\"lastName\\", $util.defaultIfNull($ctx.source.userLastName, \\"\\"))) { @@ -627,6 +630,9 @@ $util.qr($lambdaInput.args.putAll($util.defaultIfNull($context.arguments, {}))) #if( !$lambdaInput.args.input ) #set( $lambdaInput.args.input = {} ) #end +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + #set( $lambdaInput.args.metadata.authFilter = $ctx.stash.authFilter ) +#end $util.qr($lambdaInput.args.input.put(\\"id\\", $util.defaultIfNull($ctx.source.userId, \\"\\"))) { \\"version\\": \\"2018-05-29\\", diff --git a/packages/amplify-graphql-relational-transformer/src/__tests__/__snapshots__/amplify-graphql-has-many-transformer.test.ts.snap b/packages/amplify-graphql-relational-transformer/src/__tests__/__snapshots__/amplify-graphql-has-many-transformer.test.ts.snap index 1a23fde436..7208b1a738 100644 --- a/packages/amplify-graphql-relational-transformer/src/__tests__/__snapshots__/amplify-graphql-has-many-transformer.test.ts.snap +++ b/packages/amplify-graphql-relational-transformer/src/__tests__/__snapshots__/amplify-graphql-has-many-transformer.test.ts.snap @@ -317,6 +317,9 @@ $util.qr($lambdaInput.args.putAll($util.defaultIfNull($context.arguments, {}))) #if( !$lambdaInput.args.filter ) #set( $lambdaInput.args.filter = {} ) #end +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + #set( $lambdaInput.args.metadata.authFilter = $ctx.stash.authFilter ) +#end $util.qr($lambdaInput.args.filter.put(\\"systemId\\", { \\"eq\\": $util.defaultIfNull($ctx.source.systemId, \\"\\") })) @@ -643,6 +646,9 @@ $util.qr($lambdaInput.args.putAll($util.defaultIfNull($context.arguments, {}))) #if( !$lambdaInput.args.filter ) #set( $lambdaInput.args.filter = {} ) #end +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + #set( $lambdaInput.args.metadata.authFilter = $ctx.stash.authFilter ) +#end $util.qr($lambdaInput.args.filter.put(\\"blogId\\", { \\"eq\\": $util.defaultIfNull($ctx.source.id, \\"\\") })) diff --git a/packages/amplify-graphql-relational-transformer/src/__tests__/__snapshots__/amplify-graphql-has-one-transformer.test.ts.snap b/packages/amplify-graphql-relational-transformer/src/__tests__/__snapshots__/amplify-graphql-has-one-transformer.test.ts.snap index e0760cea52..12f6d9f2a0 100644 --- a/packages/amplify-graphql-relational-transformer/src/__tests__/__snapshots__/amplify-graphql-has-one-transformer.test.ts.snap +++ b/packages/amplify-graphql-relational-transformer/src/__tests__/__snapshots__/amplify-graphql-has-one-transformer.test.ts.snap @@ -305,6 +305,9 @@ $util.qr($lambdaInput.args.putAll($util.defaultIfNull($context.arguments, {}))) #if( !$lambdaInput.args.input ) #set( $lambdaInput.args.input = {} ) #end +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + #set( $lambdaInput.args.metadata.authFilter = $ctx.stash.authFilter ) +#end $util.qr($lambdaInput.args.input.put(\\"userFirstName\\", { \\"eq\\": $util.defaultIfNull($ctx.source.firstName, \\"\\") })) @@ -629,6 +632,9 @@ $util.qr($lambdaInput.args.putAll($util.defaultIfNull($context.arguments, {}))) #if( !$lambdaInput.args.input ) #set( $lambdaInput.args.input = {} ) #end +#if( !$util.isNullOrEmpty($ctx.stash.authFilter) ) + #set( $lambdaInput.args.metadata.authFilter = $ctx.stash.authFilter ) +#end $util.qr($lambdaInput.args.input.put(\\"userId\\", { \\"eq\\": $util.defaultIfNull($ctx.source.id, \\"\\") })) diff --git a/packages/amplify-graphql-relational-transformer/src/resolver/rds-generator.ts b/packages/amplify-graphql-relational-transformer/src/resolver/rds-generator.ts index d38033ba5c..8e3970276b 100644 --- a/packages/amplify-graphql-relational-transformer/src/resolver/rds-generator.ts +++ b/packages/amplify-graphql-relational-transformer/src/resolver/rds-generator.ts @@ -104,6 +104,7 @@ export class RDSRelationalResolverGenerator extends RelationalResolverGenerator this.constructFieldMappingInput(), qref(methodCall(ref('lambdaInput.args.putAll'), methodCall(ref('util.defaultIfNull'), ref('context.arguments'), obj({})))), iff(not(ref('lambdaInput.args.filter')), set(ref('lambdaInput.args.filter'), obj({}))), + this.constructRelationalFieldAuthFilterStatement('lambdaInput.args.metadata.authFilter'), ...joinCondition, qref( methodCall(ref('lambdaInput.args.metadata.keys.addAll'), methodCall(ref('util.defaultIfNull'), ref('ctx.stash.keys'), list([]))), @@ -140,6 +141,7 @@ export class RDSRelationalResolverGenerator extends RelationalResolverGenerator this.constructFieldMappingInput(), qref(methodCall(ref('lambdaInput.args.putAll'), methodCall(ref('util.defaultIfNull'), ref('context.arguments'), obj({})))), iff(not(ref('lambdaInput.args.input')), set(ref('lambdaInput.args.input'), obj({}))), + this.constructRelationalFieldAuthFilterStatement('lambdaInput.args.metadata.authFilter'), ...joinCondition, obj({ version: str('2018-05-29'), @@ -298,4 +300,7 @@ export class RDSRelationalResolverGenerator extends RelationalResolverGenerator ), ]); }; + + constructRelationalFieldAuthFilterStatement = (keyName: string): Expression => + iff(not(methodCall(ref('util.isNullOrEmpty'), ref('ctx.stash.authFilter'))), set(ref(keyName), ref('ctx.stash.authFilter'))); }