Skip to content

Commit

Permalink
fix(api): protect sql relational fields when using owner rule
Browse files Browse the repository at this point in the history
  • Loading branch information
sundersc committed Apr 15, 2024
1 parent cd20897 commit 05a9bd4
Show file tree
Hide file tree
Showing 6 changed files with 195 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Original file line number Diff line number Diff line change
Expand Up @@ -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'],
}));
});
});
};
Original file line number Diff line number Diff line change
Expand Up @@ -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, \\"\\")))
{
Expand Down Expand Up @@ -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\\",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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, \\"\\")
}))
Expand Down Expand Up @@ -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, \\"\\")
}))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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, \\"\\")
}))
Expand Down Expand Up @@ -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, \\"\\")
}))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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([]))),
Expand Down Expand Up @@ -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'),
Expand Down Expand Up @@ -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')));
}

0 comments on commit 05a9bd4

Please sign in to comment.