Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(amplify-category-api): add global sandbox mode directive on schema generation #8074

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
198 changes: 114 additions & 84 deletions .circleci/config.yml

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { defineGlobalSandboxMode } from '../../../../provider-utils/awscloudformation/utils/global-sandbox-mode';
import { $TSContext } from 'amplify-cli-core';

describe('global sandbox mode GraphQL directive', () => {
it('returns AMPLIFY_DIRECTIVE type with code comment, directive, and env name', () => {
const envName = 'envone';
const ctx = <$TSContext>{
amplify: {
getEnvInfo() {
return { envName };
},
},
};

expect(defineGlobalSandboxMode(ctx))
.toBe(`# This allows public create, read, update, and delete access for a limited time to all models via API Key.
# To configure PRODUCTION-READY authorization rules, review: https://docs.amplify.aws/cli/graphql-transformer/auth
type AMPLIFY_GLOBAL @allow_public_data_access_with_api_key(in: \"${envName}\") # FOR TESTING ONLY!\n
`);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import _ from 'lodash';
import { getAppSyncResourceName, getAppSyncAuthConfig, checkIfAuthExists, authConfigHasApiKey } from './utils/amplify-meta-utils';
import { printApiKeyWarnings } from './utils/print-api-key-warnings';
import { isNameUnique } from './utils/check-case-sensitivity';
import { FeatureFlags } from 'amplify-cli-core';

// keep in sync with ServiceName in amplify-category-function, but probably it will not change
const FunctionServiceNameLambdaFunction = 'Lambda';
Expand Down Expand Up @@ -90,7 +91,20 @@ class CfnApiArtifactHandler implements ApiArtifactHandler {
authConfig,
});

this.context.amplify.updateamplifyMetaAfterResourceAdd(category, serviceConfig.apiName, this.createAmplifyMeta(authConfig));
const useExperimentalPipelineTransformer = FeatureFlags.getBoolean('graphQLTransformer.useExperimentalPipelinedTransformer');
let globalSandboxModeConfig;

if (useExperimentalPipelineTransformer) {
const envName = this.context.amplify.getEnvInfo().envName;
globalSandboxModeConfig = {};
globalSandboxModeConfig[envName] = { enabled: true };
}

this.context.amplify.updateamplifyMetaAfterResourceAdd(
category,
serviceConfig.apiName,
this.createAmplifyMeta(authConfig, globalSandboxModeConfig),
);
return serviceConfig.apiName;
};

Expand Down Expand Up @@ -135,11 +149,12 @@ class CfnApiArtifactHandler implements ApiArtifactHandler {

private getResourceDir = (apiName: string) => path.join(this.context.amplify.pathManager.getBackendDirPath(), category, apiName);

private createAmplifyMeta = authConfig => ({
private createAmplifyMeta = (authConfig, globalSandboxModeConfig) => ({
service: 'AppSync',
providerPlugin: provider,
output: {
authConfig,
globalSandboxModeConfig,
},
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
$TSContext,
open,
} from 'amplify-cli-core';
import { defineGlobalSandboxMode } from '../utils/global-sandbox-mode';

const serviceName = 'AppSync';
const elasticContainerServiceName = 'ElasticContainer';
Expand Down Expand Up @@ -208,6 +209,9 @@ export const serviceWalkthrough = async (context: $TSContext, defaultValuesFilen
schemaContent = fs.readFileSync(schemaFilePath, 'utf8');
askToEdit = false;
} else {
const useExperimentalPipelineTransformer = FeatureFlags.getBoolean('graphQLTransformer.useExperimentalPipelinedTransformer');
schemaContent += useExperimentalPipelineTransformer ? defineGlobalSandboxMode(context) : '';

// Schema template selection
const templateSelectionQuestion = {
type: inputs[4].type,
Expand All @@ -219,7 +223,7 @@ export const serviceWalkthrough = async (context: $TSContext, defaultValuesFilen

const { templateSelection } = await inquirer.prompt(templateSelectionQuestion);
const schemaFilePath = path.join(graphqlSchemaDir, templateSelection);
schemaContent = fs.readFileSync(schemaFilePath, 'utf8');
schemaContent += fs.readFileSync(schemaFilePath, 'utf8');
}

return {
Expand Down Expand Up @@ -508,9 +512,11 @@ export async function askAdditionalAuthQuestions(context, authConfig, defaultAut
if (await context.prompt.confirm('Configure additional auth types?')) {
// Get additional auth configured
const remainingAuthProviderChoices = authProviderChoices.filter(p => p.value !== defaultAuthType);
const currentAdditionalAuth = ((currentAuthConfig && currentAuthConfig.additionalAuthenticationProviders
? currentAuthConfig.additionalAuthenticationProviders
: []) as any[]).map(authProvider => authProvider.authenticationType);
const currentAdditionalAuth = (
(currentAuthConfig && currentAuthConfig.additionalAuthenticationProviders
? currentAuthConfig.additionalAuthenticationProviders
: []) as any[]
).map(authProvider => authProvider.authenticationType);

const additionalProvidersQuestion: CheckboxQuestion = {
type: 'checkbox',
Expand Down Expand Up @@ -677,9 +683,10 @@ function validateDays(input) {
}

function validateIssuerUrl(input) {
const isValid = /^(((?!http:\/\/(?!localhost))([a-zA-Z0-9.]{1,}):\/\/([a-zA-Z0-9-._~:?#@!$&'()*+,;=/]{1,})\/)|(?!http)(?!https)([a-zA-Z0-9.]{1,}):\/\/)$/.test(
input,
);
const isValid =
/^(((?!http:\/\/(?!localhost))([a-zA-Z0-9.]{1,}):\/\/([a-zA-Z0-9-._~:?#@!$&'()*+,;=/]{1,})\/)|(?!http)(?!https)([a-zA-Z0-9.]{1,}):\/\/)$/.test(
input,
);

if (!isValid) {
return 'The value must be a valid URI with a trailing forward slash. HTTPS must be used instead of HTTP unless you are using localhost.';
Expand Down Expand Up @@ -779,8 +786,8 @@ const buildPolicyResource = (resourceName: string, path: string | null) => {
{
Ref: `${category}${resourceName}GraphQLAPIIdOutput`,
},
...(path ? [path] : [])
]
...(path ? [path] : []),
],
],
};
};
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export function defineGlobalSandboxMode(context: any): string {
const envName = context.amplify.getEnvInfo().envName;

return `# This allows public create, read, update, and delete access for a limited time to all models via API Key.
# To configure PRODUCTION-READY authorization rules, review: https://docs.amplify.aws/cli/graphql-transformer/auth
type AMPLIFY_GLOBAL @allow_public_data_access_with_api_key(in: \"${envName}\") # FOR TESTING ONLY!\n
`;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
type AMPLIFY_GLOBAL @allow_public_data_access_with_api_key(in: "dev")

type Todo @model {
id: ID!
content: String
}
45 changes: 45 additions & 0 deletions packages/amplify-e2e-tests/src/__tests__/sandbox-mode.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import {
initJSProjectWithProfile,
deleteProject,
createNewProjectDir,
deleteProjectDir,
addApiWithSchema,
amplifyPush,
getProjectMeta,
} from 'amplify-e2e-core';
import { testSchema } from '../schema-api-directives';

describe('api directives @allow_public_data_access_with_api_key', () => {
let projectDir: string;
const envName = 'dev';

beforeEach(async () => {
projectDir = await createNewProjectDir('model');
await initJSProjectWithProfile(projectDir, { envName });
});

afterEach(async () => {
await deleteProject(projectDir);
deleteProjectDir(projectDir);
});

it('schema and files generate with sandbox mode', async () => {
await addApiWithSchema(projectDir, 'model_with_sandbox_mode.graphql');
await amplifyPush(projectDir);

const meta = getProjectMeta(projectDir);
const { output } = meta.api.simplemodel;
const { authConfig, globalSandboxModeConfig, GraphQLAPIIdOutput, GraphQLAPIEndpointOutput, GraphQLAPIKeyOutput } = output;

expect(globalSandboxModeConfig[envName].enabled).toBe(true);
expect(authConfig.defaultAuthentication.authenticationType).toBe('API_KEY');
expect(authConfig.defaultAuthentication.apiKeyConfig.apiKeyExpirationDate).toBeDefined();

expect(GraphQLAPIIdOutput).toBeDefined();
expect(GraphQLAPIEndpointOutput).toBeDefined();
expect(GraphQLAPIKeyOutput).toBeDefined();

const testresult = await testSchema(projectDir, 'model', 'generates');
expect(testresult).toBeTruthy();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -128,10 +128,15 @@ export class GraphQLTransform {
aws_iam: true,
aws_oidc: true,
aws_cognito_user_pools: true,
allow_public_data_access_with_api_key: true,
deprecated: true,
},
);
let allModelDefinitions = [...context.inputDocument.definitions];

const ampGlobalIdx = allModelDefinitions.findIndex(el => el.kind === 'ObjectTypeDefinition' && el.name.value === 'AMPLIFY_GLOBAL');
if (ampGlobalIdx > -1) allModelDefinitions.splice(ampGlobalIdx, 1);

for (const transformer of this.transformers) {
allModelDefinitions = allModelDefinitions.concat(...transformer.typeDefinitions, transformer.directive);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ directive @aws_api_key on FIELD_DEFINITION | OBJECT
directive @aws_iam on FIELD_DEFINITION | OBJECT
directive @aws_oidc on FIELD_DEFINITION | OBJECT
directive @aws_cognito_user_pools(cognito_groups: [String!]) on FIELD_DEFINITION | OBJECT
directive @allow_public_data_access_with_api_key(in: [String!]) on OBJECT
# Allows transformer libraries to deprecate directive arguments.
directive @deprecated(reason: String) on FIELD_DEFINITION | INPUT_FIELD_DEFINITION | ENUM | ENUM_VALUE
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ export class TransformerResolver implements TransformerResolverProvider {
}
break;
default:
throw new Error('Unknow DataSource type');
throw new Error('Unknown DataSource type');
}
}
api.host.addResolver(
Expand Down