Skip to content

Commit 2ab2872

Browse files
authored
Merge pull request #219 from horike37/feature/custom_authorizer_perm
feat: generate LambdaPermission for authorizers
2 parents 0124a24 + eeee5da commit 2ab2872

File tree

4 files changed

+226
-0
lines changed

4 files changed

+226
-0
lines changed
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
'use strict';
2+
const _ = require('lodash');
3+
const BbPromise = require('bluebird');
4+
5+
// a function URI looks like this
6+
// "Fn::Join": [
7+
// "",
8+
// [
9+
// "arn:",
10+
// { "Ref": "AWS::Partition" },
11+
// ":apigateway:",
12+
// { "Ref": "AWS::Region" },
13+
// ":lambda:path/2015-03-31/functions/",
14+
// {
15+
// "Fn::GetAtt": [
16+
// "HelloLambdaFunction",
17+
// "Arn"
18+
// ]
19+
// },
20+
// "/invocations"
21+
// ]
22+
// ]
23+
const getFunctionLogicalId = (uri) => {
24+
const parts = uri['Fn::Join'][1];
25+
const functionRef = parts.find(x => _.has(x, 'Fn::GetAtt'));
26+
return functionRef['Fn::GetAtt'][0];
27+
};
28+
29+
const getLambdaPermission = (logicalId) => ({
30+
Type: 'AWS::Lambda::Permission',
31+
Properties: {
32+
FunctionName: {
33+
'Fn::GetAtt': [logicalId, 'Arn'],
34+
},
35+
Action: 'lambda:InvokeFunction',
36+
Principal: {
37+
'Fn::Sub': 'apigateway.${AWS::URLSuffix}',
38+
},
39+
},
40+
});
41+
42+
module.exports = {
43+
compileHttpLambdaPermissions() {
44+
const resources = _.get(
45+
this.serverless, 'service.provider.compiledCloudFormationTemplate.Resources', {});
46+
const authorizers = _.values(resources).filter(r => r.Type === 'AWS::ApiGateway::Authorizer');
47+
const customAuthorizers = authorizers.filter(r =>
48+
r.Properties.Type === 'CUSTOM' || r.Properties.Type === 'TOKEN');
49+
const uris = customAuthorizers.map(r => r.Properties.AuthorizerUri);
50+
const funcLogicalIds = _.uniq(uris.map(getFunctionLogicalId));
51+
if (_.isEmpty(funcLogicalIds)) {
52+
return BbPromise.resolve();
53+
}
54+
55+
const lambdaPermissions = _.zipObject(
56+
funcLogicalIds.map(id => `${id}LambdaPermission`),
57+
funcLogicalIds.map(getLambdaPermission)
58+
);
59+
60+
_.merge(this.serverless.service.provider.compiledCloudFormationTemplate.Resources,
61+
lambdaPermissions);
62+
return BbPromise.resolve();
63+
},
64+
};
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
'use strict';
2+
3+
const _ = require('lodash');
4+
const expect = require('chai').expect;
5+
const Serverless = require('serverless/lib/Serverless');
6+
const AwsProvider = require('serverless/lib/plugins/aws/provider/awsProvider');
7+
const ServerlessStepFunctions = require('./../../../index');
8+
9+
describe('#compileHttpLambdaPermissions()', () => {
10+
let serverless;
11+
let serverlessStepFunctions;
12+
13+
beforeEach(() => {
14+
serverless = new Serverless();
15+
serverless.setProvider('aws', new AwsProvider(serverless));
16+
serverless.service.provider.compiledCloudFormationTemplate = { Resources: {} };
17+
serverless.service.service = 'new-service';
18+
serverless.service.stepfunctions = {
19+
stateMachines: {
20+
first: {
21+
},
22+
},
23+
};
24+
serverlessStepFunctions = new ServerlessStepFunctions(serverless);
25+
});
26+
27+
it('should not create a Lambda Permission resource when there are no Lambda authorizers', () => {
28+
serverlessStepFunctions.compileHttpLambdaPermissions().then(() => {
29+
const resources = serverlessStepFunctions.serverless.service.provider
30+
.compiledCloudFormationTemplate.Resources;
31+
const lambdaPermissions =
32+
_.values(resources).filter(x => x.Type === 'AWS::Lambda::Permission');
33+
expect(lambdaPermissions).to.have.lengthOf(0);
34+
});
35+
});
36+
37+
it('should create a Lambda Permission resource when there is a TOKEN authorizer', () => {
38+
serverlessStepFunctions.serverless.service.provider
39+
.compiledCloudFormationTemplate.Resources.HelloApiGatewayAuthorizer = {
40+
Type: 'AWS::ApiGateway::Authorizer',
41+
Properties: {
42+
AuthorizerResultTtlInSeconds: 300,
43+
IdentitySource: 'method.request.header.Authorization',
44+
Name: 'hello',
45+
RestApiId: {
46+
Ref: 'ApiGatewayRestApi',
47+
},
48+
AuthorizerUri: {
49+
'Fn::Join': [
50+
'',
51+
[
52+
'arn:',
53+
{
54+
Ref: 'AWS::Partition',
55+
},
56+
':apigateway:',
57+
{
58+
Ref: 'AWS::Region',
59+
},
60+
':lambda:path/2015-03-31/functions/',
61+
{
62+
'Fn::GetAtt': [
63+
'HelloLambdaFunction',
64+
'Arn',
65+
],
66+
},
67+
'/invocations',
68+
],
69+
],
70+
},
71+
Type: 'TOKEN',
72+
},
73+
};
74+
75+
serverlessStepFunctions.compileHttpLambdaPermissions().then(() => {
76+
const resources = serverlessStepFunctions.serverless.service.provider
77+
.compiledCloudFormationTemplate.Resources;
78+
const lambdaPermissions =
79+
_.values(resources).filter(x => x.Type === 'AWS::Lambda::Permission');
80+
expect(lambdaPermissions).to.have.lengthOf(1);
81+
expect(lambdaPermissions[0]).to.deep.eq({
82+
Type: 'AWS::Lambda::Permission',
83+
Properties: {
84+
FunctionName: {
85+
'Fn::GetAtt': ['HelloLambdaFunction', 'Arn'],
86+
},
87+
Action: 'lambda:InvokeFunction',
88+
Principal: {
89+
'Fn::Sub': 'apigateway.${AWS::URLSuffix}',
90+
},
91+
},
92+
});
93+
});
94+
});
95+
96+
it('should create a Lambda Permission resource when there is a CUSTOM authorizer', () => {
97+
serverlessStepFunctions.serverless.service.provider
98+
.compiledCloudFormationTemplate.Resources.HelloApiGatewayAuthorizer = {
99+
Type: 'AWS::ApiGateway::Authorizer',
100+
Properties: {
101+
AuthorizerResultTtlInSeconds: 300,
102+
IdentitySource: 'method.request.header.Authorization',
103+
Name: 'hello',
104+
RestApiId: {
105+
Ref: 'ApiGatewayRestApi',
106+
},
107+
AuthorizerUri: {
108+
'Fn::Join': [
109+
'',
110+
[
111+
'arn:',
112+
{
113+
Ref: 'AWS::Partition',
114+
},
115+
':apigateway:',
116+
{
117+
Ref: 'AWS::Region',
118+
},
119+
':lambda:path/2015-03-31/functions/',
120+
{
121+
'Fn::GetAtt': [
122+
'HelloLambdaFunction',
123+
'Arn',
124+
],
125+
},
126+
'/invocations',
127+
],
128+
],
129+
},
130+
Type: 'CUSTOM',
131+
},
132+
};
133+
134+
serverlessStepFunctions.compileHttpLambdaPermissions().then(() => {
135+
const resources = serverlessStepFunctions.serverless.service.provider
136+
.compiledCloudFormationTemplate.Resources;
137+
const lambdaPermissions =
138+
_.values(resources).filter(x => x.Type === 'AWS::Lambda::Permission');
139+
expect(lambdaPermissions).to.have.lengthOf(1);
140+
expect(lambdaPermissions[0]).to.deep.eq({
141+
Type: 'AWS::Lambda::Permission',
142+
Properties: {
143+
FunctionName: {
144+
'Fn::GetAtt': ['HelloLambdaFunction', 'Arn'],
145+
},
146+
Action: 'lambda:InvokeFunction',
147+
Principal: {
148+
'Fn::Sub': 'apigateway.${AWS::URLSuffix}',
149+
},
150+
},
151+
});
152+
});
153+
});
154+
});

lib/index.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ const httpApiKeys = require('./deploy/events/apiGateway/apiKeys');
1717
const httpUsagePlan = require('./deploy/events/apiGateway/usagePlan');
1818
const httpUsagePlanKeys = require('./deploy/events/apiGateway/usagePlanKeys');
1919
const httpIamRole = require('./deploy/events/apiGateway/iamRole');
20+
const httpLambdaPermissions = require('./deploy/events/apiGateway/lambdaPermissions');
2021
const httpDeployment = require('./deploy/events/apiGateway/deployment');
2122
const httpRestApi = require('./deploy/events/apiGateway/restApi');
2223
const httpInfo = require('./deploy/events/apiGateway/endpointInfo');
@@ -50,6 +51,7 @@ class ServerlessStepFunctions {
5051
httpResources,
5152
httpMethods,
5253
httpAuthorizers,
54+
httpLambdaPermissions,
5355
httpCors,
5456
httpApiKeys,
5557
httpUsagePlan,
@@ -127,6 +129,7 @@ class ServerlessStepFunctions {
127129
.then(this.compileResources)
128130
.then(this.compileMethods)
129131
.then(this.compileAuthorizers)
132+
.then(this.compileHttpLambdaPermissions)
130133
.then(this.compileCors)
131134
.then(this.compileHttpIamRole)
132135
.then(this.compileDeployment)

lib/index.test.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,9 @@ describe('#index', () => {
114114
.stub(serverlessStepFunctions, 'compileMethods').returns(BbPromise.resolve());
115115
const compileAuthorizersStub = sinon
116116
.stub(serverlessStepFunctions, 'compileAuthorizers').returns(BbPromise.resolve());
117+
const compileHttpLambdaPermissions = sinon
118+
.stub(serverlessStepFunctions, 'compileHttpLambdaPermissions')
119+
.returns(BbPromise.resolve());
117120
const compileCorsStub = sinon
118121
.stub(serverlessStepFunctions, 'compileCors').returns(BbPromise.resolve());
119122
const compileHttpIamRoleStub = sinon
@@ -135,6 +138,7 @@ describe('#index', () => {
135138
expect(compileResourcesStub.notCalled).to.be.equal(true);
136139
expect(compileMethodsStub.notCalled).to.be.equal(true);
137140
expect(compileAuthorizersStub.notCalled).to.be.equal(true);
141+
expect(compileHttpLambdaPermissions.notCalled).to.be.equal(true);
138142
expect(compileCorsStub.notCalled).to.be.equal(true);
139143
expect(compileHttpIamRoleStub.notCalled).to.be.equal(true);
140144
expect(compileDeploymentStub.notCalled).to.be.equal(true);
@@ -148,6 +152,7 @@ describe('#index', () => {
148152
serverlessStepFunctions.compileResources.restore();
149153
serverlessStepFunctions.compileMethods.restore();
150154
serverlessStepFunctions.compileAuthorizers.restore();
155+
serverlessStepFunctions.compileHttpLambdaPermissions.restore();
151156
serverlessStepFunctions.compileCors.restore();
152157
serverlessStepFunctions.compileHttpIamRole.restore();
153158
serverlessStepFunctions.compileDeployment.restore();

0 commit comments

Comments
 (0)