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(aws-apigateway): add ability to include authorizer context in apigw sfn integration #18892

Merged
merged 7 commits into from
Mar 4, 2022
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
5 changes: 3 additions & 2 deletions packages/@aws-cdk/aws-apigateway/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ Other metadata such as billing details, AWS account ID and resource ARNs are not

By default, a `prod` stage is provisioned.

In order to reduce the payload size sent to AWS Step Functions, `headers` are not forwarded to the Step Functions execution input. It is possible to choose whether `headers`, `requestContext`, `path` and `querystring` are included or not. By default, `headers` are excluded in all requests.
In order to reduce the payload size sent to AWS Step Functions, `headers` are not forwarded to the Step Functions execution input. It is possible to choose whether `headers`, `requestContext`, `path`, `querystring`, and `authorizer` are included or not. By default, `headers` are excluded in all requests.

More details about AWS Step Functions payload limit can be found at https://docs.aws.amazon.com/step-functions/latest/dg/limits-overview.html#service-limits-task-executions.

Expand Down Expand Up @@ -184,7 +184,7 @@ AWS Step Functions will receive the following execution input:
}
```

Additional information around the request such as the request context and headers can be included as part of the input
Additional information around the request such as the request context, authorizer context, and headers can be included as part of the input
forwarded to the state machine. The following example enables headers to be included in the input but not query string.

```ts fixture=stepfunctions
Expand All @@ -193,6 +193,7 @@ new apigateway.StepFunctionsRestApi(this, 'StepFunctionsRestApi', {
headers: true,
path: false,
querystring: false,
authorizer: false,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This part was actually not implemented. Please take a look at the build failure.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My bad -- I added it to the Integration but not the RestApi

requestContext: {
caller: true,
user: true,
Expand Down
16 changes: 16 additions & 0 deletions packages/@aws-cdk/aws-apigateway/lib/integrations/stepfunctions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,20 @@ export interface StepFunctionsExecutionIntegrationOptions extends IntegrationOpt
* @default false
*/
readonly headers?: boolean;

/**
* If the whole authorizer object, including custom context values should be in the execution input. The execution input will include a new key `authorizer`:
*
* {
* "body": {},
* "authorizer": {
* "key": "value"
* }
* }
*
* @default false
*/
readonly authorizer?: boolean;
}

/**
Expand Down Expand Up @@ -241,6 +255,7 @@ function templateString(
const includeHeader = options.headers?? false;
const includeQueryString = options.querystring?? true;
const includePath = options.path?? true;
const includeAuthorizer = options.authorizer ?? false;

if (options.requestContext && Object.keys(options.requestContext).length > 0) {
requestContextStr = requestContext(options.requestContext);
Expand All @@ -251,6 +266,7 @@ function templateString(
templateStr = templateStr.replace('%INCLUDE_HEADERS%', String(includeHeader));
templateStr = templateStr.replace('%INCLUDE_QUERYSTRING%', String(includeQueryString));
templateStr = templateStr.replace('%INCLUDE_PATH%', String(includePath));
templateStr = templateStr.replace('%INCLUDE_AUTHORIZER%', String(includeAuthorizer));
templateStr = templateStr.replace('%REQUESTCONTEXT%', requestContextStr);

return templateStr;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#set($includeHeaders = %INCLUDE_HEADERS%)
#set($includeQueryString = %INCLUDE_QUERYSTRING%)
#set($includePath = %INCLUDE_PATH%)
#set($includeAuthorizer = %INCLUDE_AUTHORIZER%)
#set($allParams = $input.params())
{
"stateMachineArn": "%STATEMACHINE%",
Expand Down Expand Up @@ -49,6 +50,17 @@
#set($inputString = "$inputString }")
#end

#if ($includeAuthorizer)
#set($inputString = "$inputString, @@authorizer@@:{")
#foreach($paramName in $context.authorizer.keySet())
#set($inputString = "$inputString @@$paramName@@: @@$util.escapeJavaScript($context.authorizer.get($paramName))@@")
#if($foreach.hasNext)
#set($inputString = "$inputString,")
#end
#end
#set($inputString = "$inputString }")
#end

#set($requestContext = "%REQUESTCONTEXT%")
## Check if the request context should be included as part of the execution input
#if($requestContext && !$requestContext.empty)
Expand Down
27 changes: 21 additions & 6 deletions packages/@aws-cdk/aws-apigateway/lib/stepfunctions-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,12 @@ import { Model } from './model';
*
*/
export interface StepFunctionsRestApiProps extends RestApiProps {
/**
* The default State Machine that handles all requests from this API.
*
* This stateMachine will be used as a the default integration for all methods in
* this API, unless specified otherwise in `addMethod`.
*/
/**
* The default State Machine that handles all requests from this API.
*
* This stateMachine will be used as a the default integration for all methods in
* this API, unless specified otherwise in `addMethod`.
*/
readonly stateMachine: sfn.IStateMachine;

/**
Expand Down Expand Up @@ -75,6 +75,20 @@ export interface StepFunctionsRestApiProps extends RestApiProps {
* @default false
*/
readonly headers?: boolean;

/**
* If the whole authorizer object, including custom context values should be in the execution input. The execution input will include a new key `authorizer`:
*
* {
* "body": {},
* "authorizer": {
* "key": "value"
* }
* }
*
* @default false
*/
readonly authorizer?: boolean;
}

/**
Expand All @@ -96,6 +110,7 @@ export class StepFunctionsRestApi extends RestApi {
path: props.path?? true,
querystring: props.querystring?? true,
headers: props.headers,
authorizer: props.authorizer,
});

super(scope, id, props);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -185,11 +185,11 @@
"Fn::Join": [
"",
[
"## Velocity Template used for API Gateway request mapping template\n##\n## This template forwards the request body, header, path, and querystring\n## to the execution input of the state machine.\n##\n## \"@@\" is used here as a placeholder for '\"' to avoid using escape characters.\n\n#set($inputString = '')\n#set($includeHeaders = true)\n#set($includeQueryString = false)\n#set($includePath = false)\n#set($allParams = $input.params())\n{\n \"stateMachineArn\": \"",
"## Velocity Template used for API Gateway request mapping template\n##\n## This template forwards the request body, header, path, and querystring\n## to the execution input of the state machine.\n##\n## \"@@\" is used here as a placeholder for '\"' to avoid using escape characters.\n\n#set($inputString = '')\n#set($includeHeaders = true)\n#set($includeQueryString = false)\n#set($includePath = false)\n#set($includeAuthorizer = false)\n#set($allParams = $input.params())\n{\n \"stateMachineArn\": \"",
{
"Ref": "StateMachine2E01A3A5"
},
"\",\n\n #set($inputString = \"$inputString,@@body@@: $input.body\")\n\n #if ($includeHeaders)\n #set($inputString = \"$inputString, @@header@@:{\")\n #foreach($paramName in $allParams.header.keySet())\n #set($inputString = \"$inputString @@$paramName@@: @@$util.escapeJavaScript($allParams.header.get($paramName))@@\")\n #if($foreach.hasNext)\n #set($inputString = \"$inputString,\")\n #end\n #end\n #set($inputString = \"$inputString }\")\n \n #end\n\n #if ($includeQueryString)\n #set($inputString = \"$inputString, @@querystring@@:{\")\n #foreach($paramName in $allParams.querystring.keySet())\n #set($inputString = \"$inputString @@$paramName@@: @@$util.escapeJavaScript($allParams.querystring.get($paramName))@@\")\n #if($foreach.hasNext)\n #set($inputString = \"$inputString,\")\n #end\n #end\n #set($inputString = \"$inputString }\")\n #end\n\n #if ($includePath)\n #set($inputString = \"$inputString, @@path@@:{\")\n #foreach($paramName in $allParams.path.keySet())\n #set($inputString = \"$inputString @@$paramName@@: @@$util.escapeJavaScript($allParams.path.get($paramName))@@\")\n #if($foreach.hasNext)\n #set($inputString = \"$inputString,\")\n #end\n #end\n #set($inputString = \"$inputString }\")\n #end\n \n #set($requestContext = \"{@@accountId@@:@@$context.identity.accountId@@,@@userArn@@:@@$context.identity.userArn@@}\")\n ## Check if the request context should be included as part of the execution input\n #if($requestContext && !$requestContext.empty)\n #set($inputString = \"$inputString,\")\n #set($inputString = \"$inputString @@requestContext@@: $requestContext\")\n #end\n\n #set($inputString = \"$inputString}\")\n #set($inputString = $inputString.replaceAll(\"@@\",'\"'))\n #set($len = $inputString.length() - 1)\n \"input\": \"{$util.escapeJavaScript($inputString.substring(1,$len))}\"\n}\n"
"\",\n\n #set($inputString = \"$inputString,@@body@@: $input.body\")\n\n #if ($includeHeaders)\n #set($inputString = \"$inputString, @@header@@:{\")\n #foreach($paramName in $allParams.header.keySet())\n #set($inputString = \"$inputString @@$paramName@@: @@$util.escapeJavaScript($allParams.header.get($paramName))@@\")\n #if($foreach.hasNext)\n #set($inputString = \"$inputString,\")\n #end\n #end\n #set($inputString = \"$inputString }\")\n \n #end\n\n #if ($includeQueryString)\n #set($inputString = \"$inputString, @@querystring@@:{\")\n #foreach($paramName in $allParams.querystring.keySet())\n #set($inputString = \"$inputString @@$paramName@@: @@$util.escapeJavaScript($allParams.querystring.get($paramName))@@\")\n #if($foreach.hasNext)\n #set($inputString = \"$inputString,\")\n #end\n #end\n #set($inputString = \"$inputString }\")\n #end\n\n #if ($includePath)\n #set($inputString = \"$inputString, @@path@@:{\")\n #foreach($paramName in $allParams.path.keySet())\n #set($inputString = \"$inputString @@$paramName@@: @@$util.escapeJavaScript($allParams.path.get($paramName))@@\")\n #if($foreach.hasNext)\n #set($inputString = \"$inputString,\")\n #end\n #end\n #set($inputString = \"$inputString }\")\n #end\n \n #if ($includeAuthorizer)\n #set($inputString = \"$inputString, @@authorizer@@:{\")\n #foreach($paramName in $context.authorizer.keySet())\n #set($inputString = \"$inputString @@$paramName@@: @@$util.escapeJavaScript($context.authorizer.get($paramName))@@\")\n #if($foreach.hasNext)\n #set($inputString = \"$inputString,\")\n #end\n #end\n #set($inputString = \"$inputString }\")\n #end\n\n #set($requestContext = \"{@@accountId@@:@@$context.identity.accountId@@,@@userArn@@:@@$context.identity.userArn@@}\")\n ## Check if the request context should be included as part of the execution input\n #if($requestContext && !$requestContext.empty)\n #set($inputString = \"$inputString,\")\n #set($inputString = \"$inputString @@requestContext@@: $requestContext\")\n #end\n\n #set($inputString = \"$inputString}\")\n #set($inputString = $inputString.replaceAll(\"@@\",'\"'))\n #set($len = $inputString.length() - 1)\n \"input\": \"{$util.escapeJavaScript($inputString.substring(1,$len))}\"\n}\n"
]
]
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,11 @@ describe('StepFunctionsIntegration', () => {
'Fn::Join': [
'',
[
"## Velocity Template used for API Gateway request mapping template\n##\n## This template forwards the request body, header, path, and querystring\n## to the execution input of the state machine.\n##\n## \"@@\" is used here as a placeholder for '\"' to avoid using escape characters.\n\n#set($inputString = '')\n#set($includeHeaders = false)\n#set($includeQueryString = true)\n#set($includePath = true)\n#set($allParams = $input.params())\n{\n \"stateMachineArn\": \"",
"## Velocity Template used for API Gateway request mapping template\n##\n## This template forwards the request body, header, path, and querystring\n## to the execution input of the state machine.\n##\n## \"@@\" is used here as a placeholder for '\"' to avoid using escape characters.\n\n#set($inputString = '')\n#set($includeHeaders = false)\n#set($includeQueryString = true)\n#set($includePath = true)\n#set($includeAuthorizer = false)\n#set($allParams = $input.params())\n{\n \"stateMachineArn\": \"",
{
Ref: 'StateMachine2E01A3A5',
},
"\",\n\n #set($inputString = \"$inputString,@@body@@: $input.body\")\n\n #if ($includeHeaders)\n #set($inputString = \"$inputString, @@header@@:{\")\n #foreach($paramName in $allParams.header.keySet())\n #set($inputString = \"$inputString @@$paramName@@: @@$util.escapeJavaScript($allParams.header.get($paramName))@@\")\n #if($foreach.hasNext)\n #set($inputString = \"$inputString,\")\n #end\n #end\n #set($inputString = \"$inputString }\")\n \n #end\n\n #if ($includeQueryString)\n #set($inputString = \"$inputString, @@querystring@@:{\")\n #foreach($paramName in $allParams.querystring.keySet())\n #set($inputString = \"$inputString @@$paramName@@: @@$util.escapeJavaScript($allParams.querystring.get($paramName))@@\")\n #if($foreach.hasNext)\n #set($inputString = \"$inputString,\")\n #end\n #end\n #set($inputString = \"$inputString }\")\n #end\n\n #if ($includePath)\n #set($inputString = \"$inputString, @@path@@:{\")\n #foreach($paramName in $allParams.path.keySet())\n #set($inputString = \"$inputString @@$paramName@@: @@$util.escapeJavaScript($allParams.path.get($paramName))@@\")\n #if($foreach.hasNext)\n #set($inputString = \"$inputString,\")\n #end\n #end\n #set($inputString = \"$inputString }\")\n #end\n \n #set($requestContext = \"\")\n ## Check if the request context should be included as part of the execution input\n #if($requestContext && !$requestContext.empty)\n #set($inputString = \"$inputString,\")\n #set($inputString = \"$inputString @@requestContext@@: $requestContext\")\n #end\n\n #set($inputString = \"$inputString}\")\n #set($inputString = $inputString.replaceAll(\"@@\",'\"'))\n #set($len = $inputString.length() - 1)\n \"input\": \"{$util.escapeJavaScript($inputString.substring(1,$len))}\"\n}\n",
"\",\n\n #set($inputString = \"$inputString,@@body@@: $input.body\")\n\n #if ($includeHeaders)\n #set($inputString = \"$inputString, @@header@@:{\")\n #foreach($paramName in $allParams.header.keySet())\n #set($inputString = \"$inputString @@$paramName@@: @@$util.escapeJavaScript($allParams.header.get($paramName))@@\")\n #if($foreach.hasNext)\n #set($inputString = \"$inputString,\")\n #end\n #end\n #set($inputString = \"$inputString }\")\n \n #end\n\n #if ($includeQueryString)\n #set($inputString = \"$inputString, @@querystring@@:{\")\n #foreach($paramName in $allParams.querystring.keySet())\n #set($inputString = \"$inputString @@$paramName@@: @@$util.escapeJavaScript($allParams.querystring.get($paramName))@@\")\n #if($foreach.hasNext)\n #set($inputString = \"$inputString,\")\n #end\n #end\n #set($inputString = \"$inputString }\")\n #end\n\n #if ($includePath)\n #set($inputString = \"$inputString, @@path@@:{\")\n #foreach($paramName in $allParams.path.keySet())\n #set($inputString = \"$inputString @@$paramName@@: @@$util.escapeJavaScript($allParams.path.get($paramName))@@\")\n #if($foreach.hasNext)\n #set($inputString = \"$inputString,\")\n #end\n #end\n #set($inputString = \"$inputString }\")\n #end\n \n #if ($includeAuthorizer)\n #set($inputString = \"$inputString, @@authorizer@@:{\")\n #foreach($paramName in $context.authorizer.keySet())\n #set($inputString = \"$inputString @@$paramName@@: @@$util.escapeJavaScript($context.authorizer.get($paramName))@@\")\n #if($foreach.hasNext)\n #set($inputString = \"$inputString,\")\n #end\n #end\n #set($inputString = \"$inputString }\")\n #end\n\n #set($requestContext = \"\")\n ## Check if the request context should be included as part of the execution input\n #if($requestContext && !$requestContext.empty)\n #set($inputString = \"$inputString,\")\n #set($inputString = \"$inputString @@requestContext@@: $requestContext\")\n #end\n\n #set($inputString = \"$inputString}\")\n #set($inputString = $inputString.replaceAll(\"@@\",'\"'))\n #set($len = $inputString.length() - 1)\n \"input\": \"{$util.escapeJavaScript($inputString.substring(1,$len))}\"\n}\n",
],
],
},
Expand Down Expand Up @@ -264,6 +264,34 @@ describe('StepFunctionsIntegration', () => {
});
});

test('authorizer context is included when specified by the integration', () => {
//GIVEN
const { stack, api, stateMachine } = givenSetup();

//WHEN
const integ = apigw.StepFunctionsIntegration.startExecution(stateMachine, {
authorizer: true,
});
api.root.addMethod('GET', integ);

Template.fromStack(stack).hasResourceProperties('AWS::ApiGateway::Method', {
Integration: {
RequestTemplates: {
'application/json': {
'Fn::Join': [
'',
[
Match.stringLikeRegexp('#set\\(\\$includeAuthorizer = true\\)'),
{ Ref: 'StateMachine2E01A3A5' },
Match.anyValue(),
],
],
},
},
},
});
});

test('works for imported RestApi', () => {
const stack = new cdk.Stack();
const api = apigw.RestApi.fromRestApiAttributes(stack, 'RestApi', {
Expand Down
Loading