Skip to content
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
9 changes: 9 additions & 0 deletions docs/cloudformation_compatibility.rst
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,15 @@ ReservedConcurrentExecutions All
Events Properties
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Cognito
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
======================== ================================== ========================
Property Name Intrinsic(s) Supported Reasons
======================== ================================== ========================
UserPool Ref of a AWS::Cognito::UserPool Properties in the AWS::Cognito::UserPool are used to construct different attributes.
Trigger All
======================== ================================== ========================

S3
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
======================== ================================== ========================
Expand Down
40 changes: 40 additions & 0 deletions docs/internals/generated_resources.rst
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,46 @@ AWS::Lambda::Permissions MyFunction\ **ThumbnailApi**\ Permission\ **P

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

Cognito
^^^

Example:

.. code:: yaml

MyFunction:
Type: AWS::Serverless::Function
Properties:
...
Events:
CognitoTrigger:
Type: Cognito
Properties:
UserPool: !Ref MyUserPool
Trigger: PreSignUp
...

MyUserPool:
Type: AWS::Cognito::UserPool

Additional generated resources:

================================== ================================
CloudFormation Resource Type Logical ID
================================== ================================
AWS::Lambda::Permissions *MyFunction*\ CognitoPermission
AWS::Cognito::UserPool Existing MyUserPool resource is modified to append ``LambdaConfig``
property where the Lambda function trigger is defined
================================== ================================

NOTE: You **must** refer to a Cognito UserPool defined in the same template. This is for two reasons:

1. SAM needs to add a ``LambdaConfig`` property to the UserPool resource by reading and modifying the
resource definition

2. Lambda triggers are specified as a property on the UserPool resource. Since CloudFormation cannot modify a resource
created outside of the stack, this bucket needs to be defined within the template.

S3
^^^

Expand Down
22 changes: 6 additions & 16 deletions examples/2016-10-31/api_cognito_auth/template.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,6 @@ Resources:
Type: AWS::Cognito::UserPool
Properties:
UserPoolName: !Ref CognitoUserPoolName
LambdaConfig:
PreSignUp: !GetAtt PreSignupLambdaFunction.Arn
Policies:
PasswordPolicy:
MinimumLength: 8
Expand Down Expand Up @@ -124,20 +122,12 @@ Resources:
MemorySize: 128
Runtime: nodejs8.10
Timeout: 3

LambdaCognitoUserPoolExecutionPermission:
Type: AWS::Lambda::Permission
Properties:
Action: lambda:InvokeFunction
FunctionName: !GetAtt PreSignupLambdaFunction.Arn
Principal: cognito-idp.amazonaws.com
SourceArn: !Sub 'arn:${AWS::Partition}:cognito-idp:${AWS::Region}:${AWS::AccountId}:userpool/${MyCognitoUserPool}'
# TODO: Add a CognitoUserPool Event Source to SAM to create this permission for you.
# Events:
# CognitoUserPoolPreSignup:
# Type: CognitoUserPool
# Properties:
# UserPool: !Ref MyCognitoUserPool
Events:
CognitoUserPoolPreSignup:
Type: Cognito
Properties:
UserPool: !Ref MyCognitoUserPool
Trigger: PreSignUp

Outputs:
Region:
Expand Down
35 changes: 35 additions & 0 deletions samtranslator/model/cognito.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
from samtranslator.model import PropertyType, Resource
from samtranslator.model.types import is_type, list_of, is_str
from samtranslator.model.intrinsics import fnGetAtt, ref


class CognitoUserPool(Resource):
resource_type = 'AWS::Cognito::UserPool'
property_types = {
'AdminCreateUserConfig': PropertyType(False, is_type(dict)),
'AliasAttributes': PropertyType(False, list_of(is_str())),
'AutoVerifiedAttributes': PropertyType(False, list_of(is_str())),
'DeviceConfiguration': PropertyType(False, is_type(dict)),
'EmailConfiguration': PropertyType(False, is_type(dict)),
'EmailVerificationMessage': PropertyType(False, is_str()),
'EmailVerificationSubject': PropertyType(False, is_str()),
'LambdaConfig': PropertyType(False, is_type(dict)),
'MfaConfiguration': PropertyType(False, is_str()),
'Policies': PropertyType(False, is_type(dict)),
'Schema': PropertyType(False, list_of(dict)),
'SmsAuthenticationMessage': PropertyType(False, is_str()),
'SmsConfiguration': PropertyType(False, list_of(dict)),
'SmsVerificationMessage': PropertyType(False, is_str()),
'UsernameAttributes': PropertyType(False, list_of(is_str())),
'UserPoolAddOns': PropertyType(False, list_of(dict)),
'UserPoolName': PropertyType(False, is_str()),
'UserPoolTags': PropertyType(False, is_str()),
'VerificationMessageTemplate': PropertyType(False, is_type(dict))
}

runtime_attrs = {
"name": lambda self: ref(self.logical_id),
"arn": lambda self: fnGetAtt(self.logical_id, "Arn"),
"provider_name": lambda self: fnGetAtt(self.logical_id, "ProviderName"),
"provider_url": lambda self: fnGetAtt(self.logical_id, "ProviderURL")
}
81 changes: 79 additions & 2 deletions samtranslator/model/eventsources/push.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from samtranslator.model.lambda_ import LambdaPermission
from samtranslator.model.events import EventsRule
from samtranslator.model.iot import IotTopicRule
from samtranslator.model.cognito import CognitoUserPool
from samtranslator.translator.arn_generator import ArnGenerator
from samtranslator.model.exceptions import InvalidEventException, InvalidResourceException
from samtranslator.swagger.swagger import SwaggerEditor
Expand Down Expand Up @@ -40,14 +41,17 @@ class PushEventSource(ResourceMacro):
"""
principal = None

def _construct_permission(self, function, source_arn=None, source_account=None, suffix="", event_source_token=None):
def _construct_permission(
self, function, source_arn=None, source_account=None, suffix="", event_source_token=None, prefix=None):
"""Constructs the Lambda Permission resource allowing the source service to invoke the function this event
source triggers.

:returns: the permission resource
:rtype: model.lambda_.LambdaPermission
"""
lambda_permission = LambdaPermission(self.logical_id + 'Permission' + suffix,
if prefix is None:
prefix = self.logical_id
lambda_permission = LambdaPermission(prefix + 'Permission' + suffix,
attributes=function.get_passthrough_resource_attributes())

try:
Expand Down Expand Up @@ -683,3 +687,76 @@ def _construct_iot_rule(self, function):
rule.set_resource_attribute(CONDITION, function.resource_attributes[CONDITION])

return rule


class Cognito(PushEventSource):
resource_type = 'Cognito'
principal = 'cognito-idp.amazonaws.com'

property_types = {
'UserPool': PropertyType(True, is_str()),
'Trigger': PropertyType(True, one_of(is_str(), list_of(is_str())))
}

def resources_to_link(self, resources):
if isinstance(self.UserPool, dict) and 'Ref' in self.UserPool:
userpool_id = self.UserPool['Ref']
if userpool_id in resources:
return {
'userpool': resources[userpool_id],
'userpool_id': userpool_id
}
raise InvalidEventException(
self.relative_id,
"Cognito events must reference a Cognito UserPool in the same template.")

def to_cloudformation(self, **kwargs):
function = kwargs.get('function')

if not function:
raise TypeError("Missing required keyword argument: function")

if 'userpool' not in kwargs or kwargs['userpool'] is None:
raise TypeError("Missing required keyword argument: userpool")

if 'userpool_id' not in kwargs or kwargs['userpool_id'] is None:
raise TypeError("Missing required keyword argument: userpool_id")

userpool = kwargs['userpool']
userpool_id = kwargs['userpool_id']

resources = []
resources.append(
self._construct_permission(
function, event_source_token=self.UserPool, prefix=function.logical_id + "Cognito"))

self._inject_lambda_config(function, userpool)
resources.append(CognitoUserPool.from_dict(userpool_id, userpool))
return resources

def _inject_lambda_config(self, function, userpool):
event_triggers = self.Trigger
if isinstance(self.Trigger, string_types):
event_triggers = [self.Trigger]

# TODO can these be conditional?

properties = userpool.get('Properties', None)
if properties is None:
properties = {}
userpool['Properties'] = properties

lambda_config = properties.get('LambdaConfig', None)
if lambda_config is None:
lambda_config = {}
properties['LambdaConfig'] = lambda_config

for event_trigger in event_triggers:
if event_trigger not in lambda_config:
lambda_config[event_trigger] = function.get_runtime_attr("arn")
else:
raise InvalidEventException(
self.relative_id,
'Cognito trigger "{trigger}" defined multiple times.'.format(
trigger=self.Trigger))
return userpool
3 changes: 2 additions & 1 deletion samtranslator/translator/verify_logical_id.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
'AWS::S3::Bucket': 'AWS::S3::Bucket',
'AWS::SNS::Topic': 'AWS::SNS::Topic',
'AWS::DynamoDB::Table': 'AWS::Serverless::SimpleTable',
'AWS::CloudFormation::Stack': 'AWS::Serverless::Application'
'AWS::CloudFormation::Stack': 'AWS::Serverless::Application',
'AWS::Cognito::UserPool': 'AWS::Cognito::UserPool'
}


Expand Down
36 changes: 36 additions & 0 deletions samtranslator/validator/sam_schema/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,39 @@
],
"type": "object"
},
"AWS::Serverless::Function.CognitoEvent": {
"additionalProperties": false,
"properties": {
"UserPool": {
"anyOf": [
{
"type": "string"
},
{
"type": "object"
}
]
},
"Trigger": {
"anyOf": [
{
"type": "string"
},
{
"items": {
"type": "string"
},
"type": "array"
}
]
}
},
"required": [
"UserPool",
"Trigger"
],
"type": "object"
},
"AWS::Serverless::Function.DynamoDBEvent": {
"additionalProperties": false,
"properties": {
Expand Down Expand Up @@ -443,6 +476,9 @@
},
{
"$ref": "#/definitions/AWS::Serverless::Function.AlexaSkillEvent"
},
{
"$ref": "#/definitions/AWS::Serverless::Function.CognitoEvent"
}
]
},
Expand Down
25 changes: 25 additions & 0 deletions tests/translator/input/cognito_userpool_with_event.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
Resources:
UserPool:
Type: AWS::Cognito::UserPool
Properties:
LambdaConfig:
PreAuthentication: "Test"
ImplicitApiFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: s3://sam-demo-bucket/member_portal.zip
Handler: index.gethtml
Runtime: nodejs4.3
Events:
OneTrigger:
Type: Cognito
Properties:
UserPool:
Ref: UserPool
Trigger: PreSignUp
TwoTrigger:
Type: Cognito
Properties:
UserPool:
Ref: UserPool
Trigger: [Test1, Test2]
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
Resources:
UserPool:
Type: AWS::Cognito::UserPool

ImplicitApiFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: s3://sam-demo-bucket/member_portal.zip
Handler: index.gethtml
Runtime: nodejs4.3
Events:
OneTrigger:
Type: Cognito
Properties:
UserPool:
Ref: UserPool
Trigger: PreSignUp
TwoTrigger:
Type: Cognito
Properties:
UserPool:
Ref: UserPool
Trigger: PreSignUp
Loading