From 708da492a0b965dbeb2ebffa2af9f03094be03b3 Mon Sep 17 00:00:00 2001 From: Aleksander Dikanski Date: Fri, 16 Mar 2018 20:44:32 +0100 Subject: [PATCH] Fix issue wit custom authorizers when using pseudo parameters plugin --- lib/stackops/apiGateway.js | 15 +++--- test/data/auth-stack-2.json | 85 ++++++++++++++++++++++++++++++++ test/stackops/apiGateway.test.js | 33 +++++++++++++ 3 files changed, 125 insertions(+), 8 deletions(-) diff --git a/lib/stackops/apiGateway.js b/lib/stackops/apiGateway.js index fdbca2f..9ca6e1e 100644 --- a/lib/stackops/apiGateway.js +++ b/lib/stackops/apiGateway.js @@ -221,17 +221,16 @@ module.exports = function(currentTemplate, aliasStackTemplates, currentAliasStac }); // Audjust authorizer Uri and name (stage variables are not allowed in Uris here) + const isExternalRefAuthorizerPredicate = part => _.startsWith(part, 'arn:aws:lambda') || + (_.has(part, 'Fn::Sub') && _.startsWith(part['Fn::Sub'], 'arn:aws:lambda')); _.forOwn(authorizers, (authorizer, name) => { const authorizerType = _.get(authorizer, 'Properties.Type'); if (authorizerType === 'TOKEN' || authorizerType === 'REQUEST') { const uriParts = authorizer.Properties.AuthorizerUri['Fn::Join'][1]; - const isExternalRefAuthorizer = _.every(uriParts, part => !_.startsWith(part, 'arn:aws:lambda')); - if (isExternalRefAuthorizer) { - const funcIndex = _.findIndex(uriParts, part => - _.has(part, 'Fn::GetAtt') || _.startsWith(part, 'arn:aws:lambda')); - - // Use the SERVERLESS_ALIAS stage variable to determine the called function alias - uriParts.splice(funcIndex + 1, 0, ':${stageVariables.SERVERLESS_ALIAS}'); + const isExternalRefAuthorizer = _.some(uriParts, isExternalRefAuthorizerPredicate); + if (!isExternalRefAuthorizer) { + const funcIndex = _.findIndex(uriParts, part => _.startsWith(part, '/invocations')); + uriParts.splice(funcIndex , 0, ':${stageVariables.SERVERLESS_ALIAS}'); } } @@ -269,7 +268,7 @@ module.exports = function(currentTemplate, aliasStackTemplates, currentAliasStac const functionName = _.replace(name, /LambdaPermissionApiGateway$/, ''); const versionName = _.find(_.keys(versions), version => _.startsWith(version, functionName)); const aliasName = _.find(_.keys(aliases), alias => _.startsWith(alias, functionName)); - const isExternalRef = _.startsWith(permission.Properties.FunctionName, 'arn:aws:lambda'); + const isExternalRef = isExternalRefAuthorizerPredicate(permission.Properties.FunctionName); // Adjust references and alias permissions if (!isExternalRef) { diff --git a/test/data/auth-stack-2.json b/test/data/auth-stack-2.json index fc70a41..c140230 100644 --- a/test/data/auth-stack-2.json +++ b/test/data/auth-stack-2.json @@ -216,6 +216,21 @@ } } }, + "ApiGatewayResourceFunc2": { + "Type": "AWS::ApiGateway::Resource", + "Properties": { + "ParentId": { + "Fn::GetAtt": [ + "ApiGatewayRestApi", + "RootResourceId" + ] + }, + "PathPart": "func1", + "RestApiId": { + "Ref": "ApiGatewayRestApi" + } + } + }, "ApiGatewayMethodFunc1Get": { "Type": "AWS::ApiGateway::Method", "Properties": { @@ -258,6 +273,48 @@ }, "DependsOn": "TestauthApiGatewayAuthorizer" }, + "ApiGatewayMethodFunc2Get": { + "Type": "AWS::ApiGateway::Method", + "Properties": { + "HttpMethod": "GET", + "RequestParameters": {}, + "ResourceId": { + "Ref": "ApiGatewayResourceFunc2" + }, + "RestApiId": { + "Ref": "ApiGatewayRestApi" + }, + "AuthorizationType": "CUSTOM", + "AuthorizerId": { + "Ref": "PseudoParamCustomAuthApiGatewayAuthorizer" + }, + "Integration": { + "IntegrationHttpMethod": "POST", + "Type": "AWS_PROXY", + "Uri": { + "Fn::Join": [ + "", + [ + "arn:aws:apigateway:", + { + "Ref": "AWS::Region" + }, + ":lambda:path/2015-03-31/functions/", + { + "Fn::GetAtt": [ + "Testfct1LambdaFunction", + "Arn" + ] + }, + "/invocations" + ] + ] + } + }, + "MethodResponses": [] + }, + "DependsOn": "PseudoParamCustomAuthApiGatewayAuthorizer" + }, "TestauthApiGatewayAuthorizer": { "Type": "AWS::ApiGateway::Authorizer", "Properties": { @@ -288,6 +345,34 @@ "Type": "TOKEN" } }, + "PseudoParamCustomAuthApiGatewayAuthorizer": { + "Type": "AWS::ApiGateway::Authorizer", + "Properties": { + "AuthorizerResultTtlInSeconds": 0, + "IdentitySource": "method.request.header.Authorization", + "Name": "testauth", + "RestApiId": { + "Ref": "ApiGatewayRestApi" + }, + "AuthorizerUri": { + "Fn::Join": [ + "", + [ + "arn:aws:apigateway:", + { + "Ref": "AWS::Region" + }, + ":lambda:path/2015-03-31/functions/", + { + "Fn::Sub": "arn:aws:lambda:us-east-1:${AWS::AccountId}:function:custom-auth" + }, + "/invocations" + ] + ] + }, + "Type": "TOKEN" + } + }, "TestauthApiGatewayRequestAuthorizer": { "Type": "AWS::ApiGateway::Authorizer", "Properties": { diff --git a/test/stackops/apiGateway.test.js b/test/stackops/apiGateway.test.js index a4eb9f7..67bab5d 100644 --- a/test/stackops/apiGateway.test.js +++ b/test/stackops/apiGateway.test.js @@ -699,6 +699,39 @@ describe('API Gateway', () => { ])); }); + + it('should support externally referenced custom authorizers with Pseudo Parameters', () => { + stackTemplate = _.cloneDeep(require('../data/auth-stack-2.json')); + const template = serverless.service.provider.compiledCloudFormationTemplate = stackTemplate; + const compiledAliasTemplate = serverless.service.provider.compiledCloudFormationAliasTemplate = aliasTemplate; + return expect(awsAlias.aliasHandleApiGateway({}, [], {})).to.be.fulfilled + .then(() => BbPromise.all([ + expect(template) + .to.have.a.nested.property("Resources.ApiGatewayMethodFunc2Get.Properties.AuthorizerId") + .that.deep.equals({ Ref: "PseudoParamCustomAuthApiGatewayAuthorizermyAlias" }), + expect(template) + .to.have.a.nested.property("Resources.ApiGatewayMethodFunc2Get.DependsOn") + .that.equals("PseudoParamCustomAuthApiGatewayAuthorizermyAlias"), + expect(template) + .to.have.a.nested.property('Resources.PseudoParamCustomAuthApiGatewayAuthorizermyAlias.Properties.AuthorizerUri') + .that.deep.equals({ + "Fn::Join": [ + "", + [ + "arn:aws:apigateway:", + { + "Ref": "AWS::Region" + }, + ":lambda:path/2015-03-31/functions/", + { + "Fn::Sub": "arn:aws:lambda:us-east-1:${AWS::AccountId}:function:custom-auth" + }, + "/invocations" + ] + ]}), + ])); + + }); it('should transform string dependencies and references to authorizers', () => { const template = serverless.service.provider.compiledCloudFormationTemplate = stackTemplate;