Skip to content

Commit c71f404

Browse files
Jacco Kulmanpraneetap
authored andcommitted
feat: Cognito event sources #229 (#1066)
1 parent 1f71077 commit c71f404

File tree

15 files changed

+555
-19
lines changed

15 files changed

+555
-19
lines changed

docs/cloudformation_compatibility.rst

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,15 @@ ReservedConcurrentExecutions All
6868
Events Properties
6969
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
7070

71+
Cognito
72+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
73+
======================== ================================== ========================
74+
Property Name Intrinsic(s) Supported Reasons
75+
======================== ================================== ========================
76+
UserPool Ref of a AWS::Cognito::UserPool Properties in the AWS::Cognito::UserPool are used to construct different attributes.
77+
Trigger All
78+
======================== ================================== ========================
79+
7180
S3
7281
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
7382
======================== ================================== ========================

docs/internals/generated_resources.rst

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,46 @@ AWS::Lambda::Permissions MyFunction\ **ThumbnailApi**\ Permission\ **P
128128

129129
NOTE: ``ServerlessRestApi*`` resources are generated one per stack.
130130

131+
Cognito
132+
^^^
133+
134+
Example:
135+
136+
.. code:: yaml
137+
138+
MyFunction:
139+
Type: AWS::Serverless::Function
140+
Properties:
141+
...
142+
Events:
143+
CognitoTrigger:
144+
Type: Cognito
145+
Properties:
146+
UserPool: !Ref MyUserPool
147+
Trigger: PreSignUp
148+
...
149+
150+
MyUserPool:
151+
Type: AWS::Cognito::UserPool
152+
153+
Additional generated resources:
154+
155+
================================== ================================
156+
CloudFormation Resource Type Logical ID
157+
================================== ================================
158+
AWS::Lambda::Permissions *MyFunction*\ CognitoPermission
159+
AWS::Cognito::UserPool Existing MyUserPool resource is modified to append ``LambdaConfig``
160+
property where the Lambda function trigger is defined
161+
================================== ================================
162+
163+
NOTE: You **must** refer to a Cognito UserPool defined in the same template. This is for two reasons:
164+
165+
1. SAM needs to add a ``LambdaConfig`` property to the UserPool resource by reading and modifying the
166+
resource definition
167+
168+
2. Lambda triggers are specified as a property on the UserPool resource. Since CloudFormation cannot modify a resource
169+
created outside of the stack, this bucket needs to be defined within the template.
170+
131171
S3
132172
^^^
133173

examples/2016-10-31/api_cognito_auth/template.yaml

Lines changed: 6 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -93,8 +93,6 @@ Resources:
9393
Type: AWS::Cognito::UserPool
9494
Properties:
9595
UserPoolName: !Ref CognitoUserPoolName
96-
LambdaConfig:
97-
PreSignUp: !GetAtt PreSignupLambdaFunction.Arn
9896
Policies:
9997
PasswordPolicy:
10098
MinimumLength: 8
@@ -124,20 +122,12 @@ Resources:
124122
MemorySize: 128
125123
Runtime: nodejs8.10
126124
Timeout: 3
127-
128-
LambdaCognitoUserPoolExecutionPermission:
129-
Type: AWS::Lambda::Permission
130-
Properties:
131-
Action: lambda:InvokeFunction
132-
FunctionName: !GetAtt PreSignupLambdaFunction.Arn
133-
Principal: cognito-idp.amazonaws.com
134-
SourceArn: !Sub 'arn:${AWS::Partition}:cognito-idp:${AWS::Region}:${AWS::AccountId}:userpool/${MyCognitoUserPool}'
135-
# TODO: Add a CognitoUserPool Event Source to SAM to create this permission for you.
136-
# Events:
137-
# CognitoUserPoolPreSignup:
138-
# Type: CognitoUserPool
139-
# Properties:
140-
# UserPool: !Ref MyCognitoUserPool
125+
Events:
126+
CognitoUserPoolPreSignup:
127+
Type: Cognito
128+
Properties:
129+
UserPool: !Ref MyCognitoUserPool
130+
Trigger: PreSignUp
141131

142132
Outputs:
143133
Region:

samtranslator/model/cognito.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
from samtranslator.model import PropertyType, Resource
2+
from samtranslator.model.types import is_type, list_of, is_str
3+
from samtranslator.model.intrinsics import fnGetAtt, ref
4+
5+
6+
class CognitoUserPool(Resource):
7+
resource_type = 'AWS::Cognito::UserPool'
8+
property_types = {
9+
'AdminCreateUserConfig': PropertyType(False, is_type(dict)),
10+
'AliasAttributes': PropertyType(False, list_of(is_str())),
11+
'AutoVerifiedAttributes': PropertyType(False, list_of(is_str())),
12+
'DeviceConfiguration': PropertyType(False, is_type(dict)),
13+
'EmailConfiguration': PropertyType(False, is_type(dict)),
14+
'EmailVerificationMessage': PropertyType(False, is_str()),
15+
'EmailVerificationSubject': PropertyType(False, is_str()),
16+
'LambdaConfig': PropertyType(False, is_type(dict)),
17+
'MfaConfiguration': PropertyType(False, is_str()),
18+
'Policies': PropertyType(False, is_type(dict)),
19+
'Schema': PropertyType(False, list_of(dict)),
20+
'SmsAuthenticationMessage': PropertyType(False, is_str()),
21+
'SmsConfiguration': PropertyType(False, list_of(dict)),
22+
'SmsVerificationMessage': PropertyType(False, is_str()),
23+
'UsernameAttributes': PropertyType(False, list_of(is_str())),
24+
'UserPoolAddOns': PropertyType(False, list_of(dict)),
25+
'UserPoolName': PropertyType(False, is_str()),
26+
'UserPoolTags': PropertyType(False, is_str()),
27+
'VerificationMessageTemplate': PropertyType(False, is_type(dict))
28+
}
29+
30+
runtime_attrs = {
31+
"name": lambda self: ref(self.logical_id),
32+
"arn": lambda self: fnGetAtt(self.logical_id, "Arn"),
33+
"provider_name": lambda self: fnGetAtt(self.logical_id, "ProviderName"),
34+
"provider_url": lambda self: fnGetAtt(self.logical_id, "ProviderURL")
35+
}

samtranslator/model/eventsources/push.py

Lines changed: 79 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from samtranslator.model.lambda_ import LambdaPermission
1212
from samtranslator.model.events import EventsRule
1313
from samtranslator.model.iot import IotTopicRule
14+
from samtranslator.model.cognito import CognitoUserPool
1415
from samtranslator.translator.arn_generator import ArnGenerator
1516
from samtranslator.model.exceptions import InvalidEventException, InvalidResourceException
1617
from samtranslator.swagger.swagger import SwaggerEditor
@@ -42,14 +43,17 @@ class PushEventSource(ResourceMacro):
4243
"""
4344
principal = None
4445

45-
def _construct_permission(self, function, source_arn=None, source_account=None, suffix="", event_source_token=None):
46+
def _construct_permission(
47+
self, function, source_arn=None, source_account=None, suffix="", event_source_token=None, prefix=None):
4648
"""Constructs the Lambda Permission resource allowing the source service to invoke the function this event
4749
source triggers.
4850
4951
:returns: the permission resource
5052
:rtype: model.lambda_.LambdaPermission
5153
"""
52-
lambda_permission = LambdaPermission(self.logical_id + 'Permission' + suffix,
54+
if prefix is None:
55+
prefix = self.logical_id
56+
lambda_permission = LambdaPermission(prefix + 'Permission' + suffix,
5357
attributes=function.get_passthrough_resource_attributes())
5458

5559
try:
@@ -741,3 +745,76 @@ def _construct_iot_rule(self, function):
741745
rule.set_resource_attribute(CONDITION, function.resource_attributes[CONDITION])
742746

743747
return rule
748+
749+
750+
class Cognito(PushEventSource):
751+
resource_type = 'Cognito'
752+
principal = 'cognito-idp.amazonaws.com'
753+
754+
property_types = {
755+
'UserPool': PropertyType(True, is_str()),
756+
'Trigger': PropertyType(True, one_of(is_str(), list_of(is_str())))
757+
}
758+
759+
def resources_to_link(self, resources):
760+
if isinstance(self.UserPool, dict) and 'Ref' in self.UserPool:
761+
userpool_id = self.UserPool['Ref']
762+
if userpool_id in resources:
763+
return {
764+
'userpool': resources[userpool_id],
765+
'userpool_id': userpool_id
766+
}
767+
raise InvalidEventException(
768+
self.relative_id,
769+
"Cognito events must reference a Cognito UserPool in the same template.")
770+
771+
def to_cloudformation(self, **kwargs):
772+
function = kwargs.get('function')
773+
774+
if not function:
775+
raise TypeError("Missing required keyword argument: function")
776+
777+
if 'userpool' not in kwargs or kwargs['userpool'] is None:
778+
raise TypeError("Missing required keyword argument: userpool")
779+
780+
if 'userpool_id' not in kwargs or kwargs['userpool_id'] is None:
781+
raise TypeError("Missing required keyword argument: userpool_id")
782+
783+
userpool = kwargs['userpool']
784+
userpool_id = kwargs['userpool_id']
785+
786+
resources = []
787+
resources.append(
788+
self._construct_permission(
789+
function, event_source_token=self.UserPool, prefix=function.logical_id + "Cognito"))
790+
791+
self._inject_lambda_config(function, userpool)
792+
resources.append(CognitoUserPool.from_dict(userpool_id, userpool))
793+
return resources
794+
795+
def _inject_lambda_config(self, function, userpool):
796+
event_triggers = self.Trigger
797+
if isinstance(self.Trigger, string_types):
798+
event_triggers = [self.Trigger]
799+
800+
# TODO can these be conditional?
801+
802+
properties = userpool.get('Properties', None)
803+
if properties is None:
804+
properties = {}
805+
userpool['Properties'] = properties
806+
807+
lambda_config = properties.get('LambdaConfig', None)
808+
if lambda_config is None:
809+
lambda_config = {}
810+
properties['LambdaConfig'] = lambda_config
811+
812+
for event_trigger in event_triggers:
813+
if event_trigger not in lambda_config:
814+
lambda_config[event_trigger] = function.get_runtime_attr("arn")
815+
else:
816+
raise InvalidEventException(
817+
self.relative_id,
818+
'Cognito trigger "{trigger}" defined multiple times.'.format(
819+
trigger=self.Trigger))
820+
return userpool

samtranslator/translator/verify_logical_id.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
'AWS::S3::Bucket': 'AWS::S3::Bucket',
77
'AWS::SNS::Topic': 'AWS::SNS::Topic',
88
'AWS::DynamoDB::Table': 'AWS::Serverless::SimpleTable',
9-
'AWS::CloudFormation::Stack': 'AWS::Serverless::Application'
9+
'AWS::CloudFormation::Stack': 'AWS::Serverless::Application',
10+
'AWS::Cognito::UserPool': 'AWS::Cognito::UserPool'
1011
}
1112

1213

samtranslator/validator/sam_schema/schema.json

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -383,6 +383,39 @@
383383
],
384384
"type": "object"
385385
},
386+
"AWS::Serverless::Function.CognitoEvent": {
387+
"additionalProperties": false,
388+
"properties": {
389+
"UserPool": {
390+
"anyOf": [
391+
{
392+
"type": "string"
393+
},
394+
{
395+
"type": "object"
396+
}
397+
]
398+
},
399+
"Trigger": {
400+
"anyOf": [
401+
{
402+
"type": "string"
403+
},
404+
{
405+
"items": {
406+
"type": "string"
407+
},
408+
"type": "array"
409+
}
410+
]
411+
}
412+
},
413+
"required": [
414+
"UserPool",
415+
"Trigger"
416+
],
417+
"type": "object"
418+
},
386419
"AWS::Serverless::Function.DynamoDBEvent": {
387420
"additionalProperties": false,
388421
"properties": {
@@ -443,6 +476,9 @@
443476
},
444477
{
445478
"$ref": "#/definitions/AWS::Serverless::Function.AlexaSkillEvent"
479+
},
480+
{
481+
"$ref": "#/definitions/AWS::Serverless::Function.CognitoEvent"
446482
}
447483
]
448484
},
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
Resources:
2+
UserPool:
3+
Type: AWS::Cognito::UserPool
4+
Properties:
5+
LambdaConfig:
6+
PreAuthentication: "Test"
7+
ImplicitApiFunction:
8+
Type: AWS::Serverless::Function
9+
Properties:
10+
CodeUri: s3://sam-demo-bucket/member_portal.zip
11+
Handler: index.gethtml
12+
Runtime: nodejs4.3
13+
Events:
14+
OneTrigger:
15+
Type: Cognito
16+
Properties:
17+
UserPool:
18+
Ref: UserPool
19+
Trigger: PreSignUp
20+
TwoTrigger:
21+
Type: Cognito
22+
Properties:
23+
UserPool:
24+
Ref: UserPool
25+
Trigger: [Test1, Test2]
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
Resources:
2+
UserPool:
3+
Type: AWS::Cognito::UserPool
4+
5+
ImplicitApiFunction:
6+
Type: AWS::Serverless::Function
7+
Properties:
8+
CodeUri: s3://sam-demo-bucket/member_portal.zip
9+
Handler: index.gethtml
10+
Runtime: nodejs4.3
11+
Events:
12+
OneTrigger:
13+
Type: Cognito
14+
Properties:
15+
UserPool:
16+
Ref: UserPool
17+
Trigger: PreSignUp
18+
TwoTrigger:
19+
Type: Cognito
20+
Properties:
21+
UserPool:
22+
Ref: UserPool
23+
Trigger: PreSignUp

0 commit comments

Comments
 (0)