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(appsync): add the grant method to the imported GraphqlApi #29086

Merged
merged 10 commits into from
Feb 28, 2024
151 changes: 150 additions & 1 deletion packages/aws-cdk-lib/aws-appsync/lib/graphqlapi-base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ import { IFunction } from '../../aws-lambda';
import { IDomain as IOpenSearchDomain } from '../../aws-opensearchservice';
import { IServerlessCluster } from '../../aws-rds';
import { ISecret } from '../../aws-secretsmanager';
import { CfnResource, IResource, Resource } from '../../core';
import { ArnFormat, CfnResource, IResource, Resource, Stack } from '../../core';
import { Grant, IGrantable } from '../../aws-iam';

/**
* Optional configuration for data sources
Expand Down Expand Up @@ -50,6 +51,67 @@ export interface HttpDataSourceOptions extends DataSourceOptions {
readonly authorizationConfig?: AwsIamConfig;
}

/**
* A class used to generate resource arns for AppSync
*/
export class IamResource {
/**
* Generate the resource names given custom arns
*
* @param arns The custom arns that need to be permissioned
*
* Example: custom('/types/Query/fields/getExample')
*/
public static custom(...arns: string[]): IamResource {
if (arns.length === 0) {
throw new Error('At least 1 custom ARN must be provided.');
}
return new IamResource(arns);
}

/**
* Generate the resource names given a type and fields
*
* @param type The type that needs to be allowed
* @param fields The fields that need to be allowed, if empty grant permissions to ALL fields
*
* Example: ofType('Query', 'GetExample')
*/
public static ofType(type: string, ...fields: string[]): IamResource {
const arns = fields.length ? fields.map((field) => `types/${type}/fields/${field}`) : [`types/${type}/*`];
return new IamResource(arns);
}

/**
* Generate the resource names that accepts all types: `*`
*/
public static all(): IamResource {
return new IamResource(['*']);
}

private arns: string[];

private constructor(arns: string[]) {
this.arns = arns;
}

/**
* Return the Resource ARN
*
* @param api The GraphQL API to give permissions
*/
public resourceArns(api: GraphqlApiBase): string[] {
return this.arns.map((arn) =>
Stack.of(api).formatArn({
service: 'appsync',
resource: `apis/${api.apiId}`,
arnFormat: ArnFormat.SLASH_RESOURCE_NAME,
resourceName: `${arn}`,
}),
);
}
}

/**
* Interface for GraphQL
*/
Expand Down Expand Up @@ -161,6 +223,43 @@ export interface IGraphqlApi extends IResource {
* @param construct the dependee
*/
addSchemaDependency(construct: CfnResource): boolean;

/**
* Adds an IAM policy statement associated with this GraphQLApi to an IAM
* principal's policy.
*
* @param grantee The principal
* @param resources The set of resources to allow (i.e. ...:[region]:[accountId]:apis/GraphQLId/...)
* @param actions The actions that should be granted to the principal (i.e. appsync:graphql )
*/
grant(grantee: IGrantable, resources: IamResource, ...actions: string[]): Grant;

/**
* Adds an IAM policy statement for Mutation access to this GraphQLApi to an IAM
* principal's policy.
*
* @param grantee The principal
* @param fields The fields to grant access to that are Mutations (leave blank for all)
*/
grantMutation(grantee: IGrantable, ...fields: string[]): Grant;

/**
* Adds an IAM policy statement for Query access to this GraphQLApi to an IAM
* principal's policy.
*
* @param grantee The principal
* @param fields The fields to grant access to that are Queries (leave blank for all)
*/
grantQuery(grantee: IGrantable, ...fields: string[]): Grant;

/**
* Adds an IAM policy statement for Subscription access to this GraphQLApi to an IAM
* principal's policy.
*
* @param grantee The principal
* @param fields The fields to grant access to that are Subscriptions (leave blank for all)
*/
grantSubscription(grantee: IGrantable, ...fields: string[]): Grant;
}

/**
Expand Down Expand Up @@ -335,4 +434,54 @@ export abstract class GraphqlApiBase extends Resource implements IGraphqlApi {
construct;
return false;
}

/**
* Adds an IAM policy statement associated with this GraphQLApi to an IAM
* principal's policy.
*
* @param grantee The principal
* @param resources The set of resources to allow (i.e. ...:[region]:[accountId]:apis/GraphQLId/...)
* @param actions The actions that should be granted to the principal (i.e. appsync:graphql )
*/
public grant(grantee: IGrantable, resources: IamResource, ...actions: string[]): Grant {
return Grant.addToPrincipal({
grantee,
actions,
resourceArns: resources.resourceArns(this),
scope: this,
});
}

/**
* Adds an IAM policy statement for Mutation access to this GraphQLApi to an IAM
* principal's policy.
*
* @param grantee The principal
* @param fields The fields to grant access to that are Mutations (leave blank for all)
*/
public grantMutation(grantee: IGrantable, ...fields: string[]): Grant {
return this.grant(grantee, IamResource.ofType('Mutation', ...fields), 'appsync:GraphQL');
}

/**
* Adds an IAM policy statement for Query access to this GraphQLApi to an IAM
* principal's policy.
*
* @param grantee The principal
* @param fields The fields to grant access to that are Queries (leave blank for all)
*/
public grantQuery(grantee: IGrantable, ...fields: string[]): Grant {
return this.grant(grantee, IamResource.ofType('Query', ...fields), 'appsync:GraphQL');
}

/**
* Adds an IAM policy statement for Subscription access to this GraphQLApi to an IAM
* principal's policy.
*
* @param grantee The principal
* @param fields The fields to grant access to that are Subscriptions (leave blank for all)
*/
public grantSubscription(grantee: IGrantable, ...fields: string[]): Grant {
return this.grant(grantee, IamResource.ofType('Subscription', ...fields), 'appsync:GraphQL');
}
}
109 changes: 0 additions & 109 deletions packages/aws-cdk-lib/aws-appsync/lib/graphqlapi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -444,65 +444,6 @@ export interface GraphqlApiProps {
readonly introspectionConfig?: IntrospectionConfig;
}

/**
* A class used to generate resource arns for AppSync
*/
export class IamResource {
/**
* Generate the resource names given custom arns
*
* @param arns The custom arns that need to be permissioned
*
* Example: custom('/types/Query/fields/getExample')
*/
public static custom(...arns: string[]): IamResource {
if (arns.length === 0) {
throw new Error('At least 1 custom ARN must be provided.');
}
return new IamResource(arns);
}

/**
* Generate the resource names given a type and fields
*
* @param type The type that needs to be allowed
* @param fields The fields that need to be allowed, if empty grant permissions to ALL fields
*
* Example: ofType('Query', 'GetExample')
*/
public static ofType(type: string, ...fields: string[]): IamResource {
const arns = fields.length ? fields.map((field) => `types/${type}/fields/${field}`) : [`types/${type}/*`];
return new IamResource(arns);
}

/**
* Generate the resource names that accepts all types: `*`
*/
public static all(): IamResource {
return new IamResource(['*']);
}

private arns: string[];

private constructor(arns: string[]) {
this.arns = arns;
}

/**
* Return the Resource ARN
*
* @param api The GraphQL API to give permissions
*/
public resourceArns(api: GraphqlApi): string[] {
return this.arns.map((arn) => Stack.of(api).formatArn({
service: 'appsync',
resource: `apis/${api.apiId}`,
arnFormat: ArnFormat.SLASH_RESOURCE_NAME,
resourceName: `${arn}`,
}));
}
}

/**
* Attributes for GraphQL imports
*/
Expand Down Expand Up @@ -762,56 +703,6 @@ export class GraphqlApi extends GraphqlApiBase {
}
}

/**
* Adds an IAM policy statement associated with this GraphQLApi to an IAM
* principal's policy.
*
* @param grantee The principal
* @param resources The set of resources to allow (i.e. ...:[region]:[accountId]:apis/GraphQLId/...)
* @param actions The actions that should be granted to the principal (i.e. appsync:graphql )
*/
public grant(grantee: IGrantable, resources: IamResource, ...actions: string[]): Grant {
return Grant.addToPrincipal({
grantee,
actions,
resourceArns: resources.resourceArns(this),
scope: this,
});
}

/**
* Adds an IAM policy statement for Mutation access to this GraphQLApi to an IAM
* principal's policy.
*
* @param grantee The principal
* @param fields The fields to grant access to that are Mutations (leave blank for all)
*/
public grantMutation(grantee: IGrantable, ...fields: string[]): Grant {
return this.grant(grantee, IamResource.ofType('Mutation', ...fields), 'appsync:GraphQL');
}

/**
* Adds an IAM policy statement for Query access to this GraphQLApi to an IAM
* principal's policy.
*
* @param grantee The principal
* @param fields The fields to grant access to that are Queries (leave blank for all)
*/
public grantQuery(grantee: IGrantable, ...fields: string[]): Grant {
return this.grant(grantee, IamResource.ofType('Query', ...fields), 'appsync:GraphQL');
}

/**
* Adds an IAM policy statement for Subscription access to this GraphQLApi to an IAM
* principal's policy.
*
* @param grantee The principal
* @param fields The fields to grant access to that are Subscriptions (leave blank for all)
*/
public grantSubscription(grantee: IGrantable, ...fields: string[]): Grant {
return this.grant(grantee, IamResource.ofType('Subscription', ...fields), 'appsync:GraphQL');
}

private validateAuthorizationProps(modes: AuthorizationMode[]) {
if (modes.filter((mode) => mode.authorizationType === AuthorizationType.LAMBDA).length > 1) {
throw new Error('You can only have a single AWS Lambda function configured to authorize your API.');
Expand Down