Skip to content

Commit

Permalink
feat: add admin roles which have admin control over a graphql api
Browse files Browse the repository at this point in the history
  • Loading branch information
SwaySway committed Oct 30, 2021
1 parent f72e59b commit ecc4008
Show file tree
Hide file tree
Showing 36 changed files with 371 additions and 389 deletions.
2 changes: 1 addition & 1 deletion packages/amplify-e2e-core/src/categories/api.ts
Expand Up @@ -8,7 +8,7 @@ import { EOL } from 'os';
import { modifiedApi } from './resources/modified-api-index';

export function getSchemaPath(schemaName: string): string {
return `${__dirname}/../../../amplify-e2e-tests/schemas/${schemaName}`;
return path.join(__dirname, '..', '..', '..', 'amplify-e2e-tests', 'schemas', schemaName);
}

export function apiGqlCompile(cwd: string, testingWithLatestCodebase: boolean = false) {
Expand Down
5 changes: 5 additions & 0 deletions packages/amplify-e2e-tests/schemas/iam_simple_model.graphql
@@ -0,0 +1,5 @@
type Todo @model @auth(rules: [{ allow: private, provider: iam }]) {
id: ID!
name: String!
description: String
}
71 changes: 62 additions & 9 deletions packages/amplify-e2e-tests/src/__tests__/function_9.test.ts
@@ -1,40 +1,33 @@
import {
addApiWithoutSchema,
addApi,
addAuthWithDefault,
addDDBWithTrigger,
addFunction,
addS3StorageWithSettings,
addSimpleDDB,
AddStorageSettings,
amplifyPush,
amplifyPushAuth,
amplifyPushForce,
createNewProjectDir,
deleteProject,
deleteProjectDir,
getBackendAmplifyMeta,
getFunctionSrcNode,
getProjectMeta,
initJSProjectWithProfile,
invokeFunction,
overrideFunctionSrcNode,
addNodeDependencies,
readJsonFile,
updateFunction,
overrideFunctionCodeNode,
getBackendConfig,
addFeatureFlag,
addAuthWithGroupsAndAdminAPI,
getFunction,
loadFunctionTestFile,
updateApiSchema,
createRandomName,
} from 'amplify-e2e-core';
import fs from 'fs-extra';
import path from 'path';
import _ from 'lodash';

const GraphQLTransformerLatestVersion = 2;

describe('nodejs', () => {
describe('amplify add function with additional permissions', () => {
let projRoot: string;
Expand Down Expand Up @@ -262,6 +255,66 @@ describe('nodejs', () => {
expect(gqlResponse.data.createTodo.description).toEqual('sampleDesc');
});

it('should be able to query AppSync with minimal permissions with featureFlag for function and vNext GraphQL API', async () => {
const appName = 'functiongqlvnext';
const random = Math.floor(Math.random() * 10000);
const fnName = `apienvvar${random}`;
const createTodo = `
mutation CreateTodo($input: CreateTodoInput!) {
createTodo(input: $input) {
id
name
description
createdAt
updatedAt
}
}`;
await initJSProjectWithProfile(projRoot, { name: appName });
addFeatureFlag(projRoot, 'graphqltransformer', 'transformerversion', GraphQLTransformerLatestVersion);
await addApi(projRoot, {
IAM: {},
});
updateApiSchema(projRoot, appName, 'iam_simple_model.graphql');
const beforeMeta = getBackendConfig(projRoot);
const apiName = Object.keys(beforeMeta.api)[0];
await addFunction(
projRoot,
{
name: fnName,
functionTemplate: 'Hello World',
additionalPermissions: {
permissions: ['api'],
choices: ['api'],
resources: [apiName],
operations: ['Mutation'],
},
},
'nodejs',
);
// Pin aws-appsync to 4.0.3 until https://github.com/awslabs/aws-mobile-appsync-sdk-js/issues/647 is fixed.
addNodeDependencies(projRoot, fnName, ['aws-appsync@4.0.3', 'isomorphic-fetch', 'graphql-tag']);
overrideFunctionCodeNode(projRoot, fnName, 'mutation-appsync.js');
await amplifyPush(projRoot);
const meta = getProjectMeta(projRoot);
const { Region: region, Name: functionName } = Object.keys(meta.function).map(key => meta.function[key])[0].output;
const lambdaCFN = readJsonFile(
path.join(projRoot, 'amplify', 'backend', 'function', fnName, `${fnName}-cloudformation-template.json`),
);
const urlKey = Object.keys(lambdaCFN.Resources.LambdaFunction.Properties.Environment.Variables).filter(value =>
value.endsWith('GRAPHQLAPIENDPOINTOUTPUT'),
)[0];
const payloadObj = { urlKey, mutation: createTodo, variables: { input: { name: 'todo', description: 'sampleDesc' } } };
const fnResponse = await invokeFunction(functionName, JSON.stringify(payloadObj), region);

expect(fnResponse.StatusCode).toBe(200);
expect(fnResponse.Payload).toBeDefined();
const gqlResponse = JSON.parse(fnResponse.Payload as string);

expect(gqlResponse.data).toBeDefined();
expect(gqlResponse.data.createTodo.name).toEqual('todo');
expect(gqlResponse.data.createTodo.description).toEqual('sampleDesc');
});

it('should be able to make console calls with feature flag turned off', async () => {
const random = Math.floor(Math.random() * 10000);
const fnName = `apienvvar${random}`;
Expand Down
@@ -0,0 +1,98 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`Test simple model with AdminUI enabled should add IAM policy only for fields that have explicit IAM auth 1`] = `
"## [Start] Authorization Steps. **
$util.qr($ctx.stash.put(\\"hasAuth\\", true))
#set( $inputFields = $util.parseJson($util.toJson($ctx.args.input.keySet())) )
#set( $isAuthorized = false )
#set( $allowedFields = [] )
#if( $util.authType() == \\"IAM Authorization\\" )
#set( $adminRoles = [\\"us-fake-1_uuid_Full-access/CognitoIdentityCredentials\\",\\"us-fake-1_uuid_Manage-only/CognitoIdentityCredentials\\"] )
#foreach( $adminRole in $adminRoles )
#if( $ctx.identity.userArn.contains($adminRole) )
#return($util.toJson({}))
#end
#end
$util.unauthorized()
#end
#if( $util.authType() == \\"User Pool Authorization\\" )
#end
#if( !$isAuthorized && $allowedFields.isEmpty() )
$util.unauthorized()
#end
#if( !$isAuthorized )
#set( $deniedFields = $util.list.copyAndRemoveAll($inputFields, $allowedFields) )
#if( $deniedFields.size() > 0 )
$util.error(\\"Unauthorized on \${deniedFields}\\", \\"Unauthorized\\")
#end
#end
$util.toJson({\\"version\\":\\"2018-05-29\\",\\"payload\\":{}})
## [End] Authorization Steps. **"
`;

exports[`Test simple model with AdminUI enabled should add IAM policy only for fields that have explicit IAM auth 2`] = `
"## [Start] Authorization Steps. **
$util.qr($ctx.stash.put(\\"hasAuth\\", true))
#if( $ctx.error )
$util.error($ctx.error.message, $ctx.error.type)
#end
#set( $inputFields = $util.parseJson($util.toJson($ctx.args.input.keySet())) )
#set( $isAuthorized = false )
#set( $allowedFields = [] )
#set( $nullAllowedFields = [] )
#set( $deniedFields = {} )
#if( $util.authType() == \\"IAM Authorization\\" )
#set( $adminRoles = [\\"us-fake-1_uuid_Full-access/CognitoIdentityCredentials\\",\\"us-fake-1_uuid_Manage-only/CognitoIdentityCredentials\\"] )
#foreach( $adminRole in $adminRoles )
#if( $ctx.identity.userArn.contains($adminRole) )
#return($util.toJson({}))
#end
#end
$util.unauthorized()
#end
#if( $util.authType() == \\"User Pool Authorization\\" )
#end
#if( !$isAuthorized && $allowedFields.isEmpty() && $nullAllowedFields.isEmpty() )
$util.unauthorized()
#end
#if( !$isAuthorized )
#foreach( $entry in $util.map.copyAndRetainAllKeys($ctx.args.input, $inputFields).entrySet() )
#if( $util.isNull($entry.value) && !$nullAllowedFields.contains($entry.key) )
$util.qr($deniedFields.put($entry.key, \\"\\"))
#end
#end
#foreach( $deniedField in $util.list.copyAndRemoveAll($inputFields, $allowedFields) )
$util.qr($deniedFields.put($deniedField, \\"\\"))
#end
#end
#if( $deniedFields.keySet().size() > 0 )
$util.error(\\"Unauthorized on \${deniedFields.keySet()}\\", \\"Unauthorized\\")
#end
$util.toJson({})
## [End] Authorization Steps. **"
`;

exports[`Test simple model with AdminUI enabled should add IAM policy only for fields that have explicit IAM auth 3`] = `
"## [Start] Authorization Steps. **
$util.qr($ctx.stash.put(\\"hasAuth\\", true))
#set( $isAuthorized = false )
#if( $util.authType() == \\"IAM Authorization\\" )
#set( $adminRoles = [\\"us-fake-1_uuid_Full-access/CognitoIdentityCredentials\\",\\"us-fake-1_uuid_Manage-only/CognitoIdentityCredentials\\"] )
#foreach( $adminRole in $adminRoles )
#if( $ctx.identity.userArn.contains($adminRole) )
#return($util.toJson({}))
#end
#end
$util.unauthorized()
#end
#if( $util.authType() == \\"User Pool Authorization\\" )
#end
#if( !$isAuthorized )
$util.unauthorized()
#end
$util.toJson({\\"version\\":\\"2018-05-29\\",\\"payload\\":{}})
## [End] Authorization Steps. **"
`;
Expand Up @@ -3,6 +3,8 @@ import { ModelTransformer } from '@aws-amplify/graphql-model-transformer';
import { GraphQLTransform } from '@aws-amplify/graphql-transformer-core';
import _ from 'lodash';

const ADMIN_UI_ROLES = ['us-fake-1_uuid_Full-access/CognitoIdentityCredentials', 'us-fake-1_uuid_Manage-only/CognitoIdentityCredentials'];

test('test simple model with public auth rule and amplify admin app is present', () => {
const validSchema = `
type Post @model @auth(rules: [{allow: public}]) {
Expand All @@ -25,8 +27,7 @@ test('test simple model with public auth rule and amplify admin app is present',
transformers: [
new ModelTransformer(),
new AuthTransformer({
addAwsIamAuthInOutputSchema: true,
adminUserPoolID: 'us-fake-1_uuid',
adminRoles: ADMIN_UI_ROLES,
}),
],
});
Expand All @@ -51,12 +52,7 @@ test('Test simple model with public auth rule and amplify admin app is not enabl
},
additionalAuthenticationProviders: [],
},
transformers: [
new ModelTransformer(),
new AuthTransformer({
addAwsIamAuthInOutputSchema: false,
}),
],
transformers: [new ModelTransformer(), new AuthTransformer()],
});
const out = transformer.transform(validSchema);
expect(out).toBeDefined();
Expand Down Expand Up @@ -86,8 +82,7 @@ test('Test model with public auth rule without all operations and amplify admin
transformers: [
new ModelTransformer(),
new AuthTransformer({
addAwsIamAuthInOutputSchema: true,
adminUserPoolID: 'us-fake-1_uuid',
adminRoles: ADMIN_UI_ROLES,
}),
],
});
Expand Down Expand Up @@ -127,8 +122,7 @@ test('Test simple model with private auth rule and amplify admin app is present'
transformers: [
new ModelTransformer(),
new AuthTransformer({
addAwsIamAuthInOutputSchema: true,
adminUserPoolID: 'us-fake-1_uuid',
adminRoles: ADMIN_UI_ROLES,
}),
],
});
Expand Down Expand Up @@ -157,12 +151,7 @@ test('Test simple model with private auth rule and amplify admin app not enabled
},
],
},
transformers: [
new ModelTransformer(),
new AuthTransformer({
addAwsIamAuthInOutputSchema: false,
}),
],
transformers: [new ModelTransformer(), new AuthTransformer()],
});
const out = transformer.transform(validSchema);
expect(out).toBeDefined();
Expand Down Expand Up @@ -192,18 +181,7 @@ test('Test simple model with private auth rule, few operations, and amplify admi
transformers: [
new ModelTransformer(),
new AuthTransformer({
authConfig: {
defaultAuthentication: {
authenticationType: 'AMAZON_COGNITO_USER_POOLS',
},
additionalAuthenticationProviders: [
{
authenticationType: 'AWS_IAM',
},
],
},
addAwsIamAuthInOutputSchema: true,
adminUserPoolID: 'us-fake-1_uuid',
adminRoles: ADMIN_UI_ROLES,
}),
],
});
Expand Down Expand Up @@ -245,12 +223,7 @@ test('Test simple model with private IAM auth rule, few operations, and amplify
},
],
},
transformers: [
new ModelTransformer(),
new AuthTransformer({
addAwsIamAuthInOutputSchema: false,
}),
],
transformers: [new ModelTransformer(), new AuthTransformer()],
});
const out = transformer.transform(validSchema);
expect(out).toBeDefined();
Expand Down Expand Up @@ -288,8 +261,7 @@ test('Test simple model with AdminUI enabled should add IAM policy only for fiel
transformers: [
new ModelTransformer(),
new AuthTransformer({
addAwsIamAuthInOutputSchema: true,
adminUserPoolID: 'us-fake-1_uuid',
adminRoles: ADMIN_UI_ROLES,
}),
],
});
Expand Down Expand Up @@ -325,13 +297,6 @@ test('Test simple model with AdminUI enabled should add IAM policy only for fiel
]);
// should throw unauthorized if it's not signed by the admin ui iam role
['Mutation.createPost.auth.1.req.vtl', 'Mutation.updatePost.auth.1.res.vtl', 'Mutation.deletePost.auth.1.res.vtl'].forEach(r => {
expect(out.pipelineFunctions[r]).toContain(
'#if( $util.authType() == "IAM Authorization" )\n' +
' #if( $ctx.identity.userArn.contains("us-fake-1_uuid_Full-access/CognitoIdentityCredentials") || $ctx.identity.userArn.contains("us-fake-1_uuid_Manage-only/CognitoIdentityCredentials") )\n' +
' #return($util.toJson({})\n' +
' #end\n' +
'$util.unauthorized()\n' +
'#end',
);
expect(out.pipelineFunctions[r]).toMatchSnapshot();
});
});

0 comments on commit ecc4008

Please sign in to comment.