Repository: aws/serverless-application-model

Issue:
Cognito User Pool SMS configuration problem
**Description:**
When trying to create a Cognito user pool using SAM templates, SAM throws the error

> Failed to create the changeset: Waiter ChangeSetCreateComplete failed: Waiter encountered a terminal failure state Status: FAILED. Reason: Transform AWS::Serverless-2016-10-31 failed with: Invalid Serverless Application Specification document. Number of errors found: 1. Resource with id [CognitoUserPool] is invalid. Type of property 'SmsConfiguration' is invalid.

when specifying [SmsConfiguration](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cognito-userpool.html#cfn-cognito-userpool-smsconfiguration) property.
In the template, there is also a Lambda trigger that has Cognito configured as an event source.
After looking through the project and doing some tests, I believe the error could appear in the samtranslator module:
`'SmsConfiguration': PropertyType(False, list_of(dict)),`
From the CloudFormation docs, [SmsConfiguration](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cognito-userpool.html#cfn-cognito-userpool-smsconfiguration) seems to be a simple dict, but in the code snippet above, it is validated as a list of dicts.
Indeed, if I modify the corresponding part of the template from a mapping to a YAML list consisting of a single object, validation passes, but when the stack is created by CloudFormation, it fails with 
> Property validation failure: [Value of property {/SmsConfiguration} does not match type {Object}]

which is consistent with the type of the property specified in the CloudFormation docs.

**Steps to reproduce the issue:**
1. Create a SAM template with a Cognito user pool configured to use SMS MFA and a Lambda trigger associated.
```yaml
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
  Example YAML.
Globals:
  Function:
    Timeout: 3
    Handler: lambda_function.lambda_handler
    Runtime: python3.6
    MemorySize: 128
Resources:
  PreSignupValidationLambda:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: src/pre_signup_validation/
      Events:
        CognitoTrigger:
          Type: Cognito
          Properties:
            UserPool: !Ref CognitoUserPool
            Trigger: PreSignUp
  CognitoUserPool:
    Type: 'AWS::Cognito::UserPool'
    Properties:
      AutoVerifiedAttributes:
        - phone_number
      MfaConfiguration: OPTIONAL
      Schema:
        - AttributeDataType: String
          DeveloperOnlyAttribute: false
          Mutable: false
          Name: sub
          Required: true
          StringAttributeConstraints:
            MaxLength: 2048
            MinLength: 0
        - AttributeDataType: String
          DeveloperOnlyAttribute: false
          Mutable: true
          Name: email
          Required: true
          StringAttributeConstraints:
            MaxLength: 2048
            MinLength: 0
        - AttributeDataType: String
          DeveloperOnlyAttribute: false
          Mutable: true
          Name: phone_number
          Required: true
          StringAttributeConstraints:
            MaxLength: 2048
            MinLength: 0
      SmsConfiguration:
        ExternalId: 'xxx-xxx-xxx'
        SnsCallerArn: !GetAtt CognitoSMSRole.Arn
      UsernameAttributes:
        - email
        - phone_number
      UserPoolName: Customers
  CognitoSMSRole:
    Type: 'AWS::IAM::Role'
    Properties:
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service: 'cognito-idp.amazonaws.com'
            Action:
              - 'sts:AssumeRole'
            Condition:
              StringEquals:
                'sts:ExternalId': 'xxx-xxx-xxx'
      Policies:
        - PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - 'sns:Publish'
                Resource:
                  - '*'
          PolicyName: CognitoSendSMS
      RoleName: CognitoSMSRole
```
2. Write a basic Lambda function in ```<template_location>/src/pre_signup_validation/lambda_function.py```
```python
def lambda_handler(event: dict, context: dict):
    return event
```
3. Run (Commands from the AWS Toolkit for PyCharm when trying to deploy application)
```bash
sam build --template template.yaml --build-dir build --use-container
```
```bash
sam package --template-file build/template.yaml --output-template-file build/packaged-template.yaml --s3-bucket <your_s3_bucket>
```
```bash
sam deploy --template-file build/packaged-template.yaml --stack-name test --no-execute-changeset
```

**Observed result:**
SAM validates the SmsConfiguration parameter of Cognito user pools as a list of type dict.
**Expected result:**
Validation should be consistent with CloudFormation specification.


Discussion:
Why was this closed @karakter98 ? Did you resolve your problem? I'm in the process of converting a CloudFormation template to a SAM template and experiencing the same error.

Resolving patch:
diff --git a/samtranslator/model/cognito.py b/samtranslator/model/cognito.py
--- a/samtranslator/model/cognito.py
+++ b/samtranslator/model/cognito.py
@@ -20,7 +20,7 @@ class CognitoUserPool(Resource):
         "Policies": PropertyType(False, is_type(dict)),
         "Schema": PropertyType(False, list_of(dict)),
         "SmsAuthenticationMessage": PropertyType(False, is_str()),
-        "SmsConfiguration": PropertyType(False, list_of(dict)),
+        "SmsConfiguration": PropertyType(False, is_type(dict)),
         "SmsVerificationMessage": PropertyType(False, is_str()),
         "UsernameAttributes": PropertyType(False, list_of(is_str())),
         "UsernameConfiguration": PropertyType(False, is_type(dict)),
diff --git a/samtranslator/parser/parser.py b/samtranslator/parser/parser.py
--- a/samtranslator/parser/parser.py
+++ b/samtranslator/parser/parser.py
@@ -18,7 +18,7 @@ def parse(self, sam_template, parameter_values, sam_plugins):
 
     @staticmethod
     def validate_datatypes(sam_template):
-        """Validates the datatype within the template """
+        """Validates the datatype within the template"""
         if (
             "Resources" not in sam_template
             or not isinstance(sam_template["Resources"], dict)


Test patch:
diff --git a/tests/translator/input/congito_userpool_with_sms_configuration.yaml b/tests/translator/input/congito_userpool_with_sms_configuration.yaml
new file mode 100644
--- /dev/null
+++ b/tests/translator/input/congito_userpool_with_sms_configuration.yaml
@@ -0,0 +1,23 @@
+Resources:
+  HelloWorldFunction:
+    Type: AWS::Serverless::Function
+    Properties:
+      CodeUri: s3://bucket/key
+      Handler: app.handler
+      Runtime: nodejs14.x
+      Events:
+        CognitoUserPoolPreSignup:
+          Type: Cognito
+          Properties:
+            UserPool:
+              Ref: MyCognitoUserPool
+            Trigger:
+              PreSignUp
+
+  MyCognitoUserPool:
+      Type: AWS::Cognito::UserPool
+      Properties:
+        UserPoolName: PreSignup
+        SmsConfiguration:
+          SnsCallerArn: !GetAtt UserPoolRole.Arn
+          ExternalId: !Ref ExternalId
diff --git a/tests/translator/output/aws-cn/congito_userpool_with_sms_configuration.json b/tests/translator/output/aws-cn/congito_userpool_with_sms_configuration.json
new file mode 100644
--- /dev/null
+++ b/tests/translator/output/aws-cn/congito_userpool_with_sms_configuration.json
@@ -0,0 +1,98 @@
+{
+  "Resources": {
+    "MyCognitoUserPool": {
+      "Type": "AWS::Cognito::UserPool",
+      "Properties": {
+        "LambdaConfig": {
+          "PreSignUp": {
+            "Fn::GetAtt": [
+              "HelloWorldFunction",
+              "Arn"
+            ]
+          }
+        },
+        "SmsConfiguration": {
+          "SnsCallerArn": {
+            "Fn::GetAtt": [
+              "UserPoolRole",
+              "Arn"
+            ]
+          },
+          "ExternalId": {
+            "Ref": "ExternalId"
+          }
+        },
+        "UserPoolName": "PreSignup"
+      }
+    },
+    "HelloWorldFunction": {
+      "Type": "AWS::Lambda::Function",
+      "Properties": {
+        "Code": {
+          "S3Bucket": "bucket",
+          "S3Key": "key"
+        },
+        "Handler": "app.handler",
+        "Role": {
+          "Fn::GetAtt": [
+            "HelloWorldFunctionRole",
+            "Arn"
+          ]
+        },
+        "Runtime": "nodejs14.x",
+        "Tags": [
+          {
+            "Key": "lambda:createdBy",
+            "Value": "SAM"
+          }
+        ]
+      }
+    },
+    "HelloWorldFunctionRole": {
+      "Type": "AWS::IAM::Role",
+      "Properties": {
+        "AssumeRolePolicyDocument": {
+          "Version": "2012-10-17",
+          "Statement": [
+            {
+              "Action": [
+                "sts:AssumeRole"
+              ],
+              "Effect": "Allow",
+              "Principal": {
+                "Service": [
+                  "lambda.amazonaws.com"
+                ]
+              }
+            }
+          ]
+        },
+        "ManagedPolicyArns": [
+          "arn:aws-cn:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
+        ],
+        "Tags": [
+          {
+            "Key": "lambda:createdBy",
+            "Value": "SAM"
+          }
+        ]
+      }
+    },
+    "HelloWorldFunctionCognitoPermission": {
+      "Type": "AWS::Lambda::Permission",
+      "Properties": {
+        "Action": "lambda:InvokeFunction",
+        "FunctionName": {
+          "Ref": "HelloWorldFunction"
+        },
+        "Principal": "cognito-idp.amazonaws.com",
+        "SourceArn": {
+          "Fn::GetAtt": [
+            "MyCognitoUserPool",
+            "Arn"
+          ]
+        }
+      }
+    }
+  }
+}
diff --git a/tests/translator/output/aws-us-gov/congito_userpool_with_sms_configuration.json b/tests/translator/output/aws-us-gov/congito_userpool_with_sms_configuration.json
new file mode 100644
--- /dev/null
+++ b/tests/translator/output/aws-us-gov/congito_userpool_with_sms_configuration.json
@@ -0,0 +1,98 @@
+{
+  "Resources": {
+    "MyCognitoUserPool": {
+      "Type": "AWS::Cognito::UserPool",
+      "Properties": {
+        "LambdaConfig": {
+          "PreSignUp": {
+            "Fn::GetAtt": [
+              "HelloWorldFunction",
+              "Arn"
+            ]
+          }
+        },
+        "SmsConfiguration": {
+          "SnsCallerArn": {
+            "Fn::GetAtt": [
+              "UserPoolRole",
+              "Arn"
+            ]
+          },
+          "ExternalId": {
+            "Ref": "ExternalId"
+          }
+        },
+        "UserPoolName": "PreSignup"
+      }
+    },
+    "HelloWorldFunction": {
+      "Type": "AWS::Lambda::Function",
+      "Properties": {
+        "Code": {
+          "S3Bucket": "bucket",
+          "S3Key": "key"
+        },
+        "Handler": "app.handler",
+        "Role": {
+          "Fn::GetAtt": [
+            "HelloWorldFunctionRole",
+            "Arn"
+          ]
+        },
+        "Runtime": "nodejs14.x",
+        "Tags": [
+          {
+            "Key": "lambda:createdBy",
+            "Value": "SAM"
+          }
+        ]
+      }
+    },
+    "HelloWorldFunctionRole": {
+      "Type": "AWS::IAM::Role",
+      "Properties": {
+        "AssumeRolePolicyDocument": {
+          "Version": "2012-10-17",
+          "Statement": [
+            {
+              "Action": [
+                "sts:AssumeRole"
+              ],
+              "Effect": "Allow",
+              "Principal": {
+                "Service": [
+                  "lambda.amazonaws.com"
+                ]
+              }
+            }
+          ]
+        },
+        "ManagedPolicyArns": [
+          "arn:aws-us-gov:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
+        ],
+        "Tags": [
+          {
+            "Key": "lambda:createdBy",
+            "Value": "SAM"
+          }
+        ]
+      }
+    },
+    "HelloWorldFunctionCognitoPermission": {
+      "Type": "AWS::Lambda::Permission",
+      "Properties": {
+        "Action": "lambda:InvokeFunction",
+        "FunctionName": {
+          "Ref": "HelloWorldFunction"
+        },
+        "Principal": "cognito-idp.amazonaws.com",
+        "SourceArn": {
+          "Fn::GetAtt": [
+            "MyCognitoUserPool",
+            "Arn"
+          ]
+        }
+      }
+    }
+  }
+}
diff --git a/tests/translator/output/congito_userpool_with_sms_configuration.json b/tests/translator/output/congito_userpool_with_sms_configuration.json
new file mode 100644
--- /dev/null
+++ b/tests/translator/output/congito_userpool_with_sms_configuration.json
@@ -0,0 +1,98 @@
+{
+  "Resources": {
+    "MyCognitoUserPool": {
+      "Type": "AWS::Cognito::UserPool",
+      "Properties": {
+        "LambdaConfig": {
+          "PreSignUp": {
+            "Fn::GetAtt": [
+              "HelloWorldFunction",
+              "Arn"
+            ]
+          }
+        },
+        "SmsConfiguration": {
+          "SnsCallerArn": {
+            "Fn::GetAtt": [
+              "UserPoolRole",
+              "Arn"
+            ]
+          },
+          "ExternalId": {
+            "Ref": "ExternalId"
+          }
+        },
+        "UserPoolName": "PreSignup"
+      }
+    },
+    "HelloWorldFunction": {
+      "Type": "AWS::Lambda::Function",
+      "Properties": {
+        "Code": {
+          "S3Bucket": "bucket",
+          "S3Key": "key"
+        },
+        "Handler": "app.handler",
+        "Role": {
+          "Fn::GetAtt": [
+            "HelloWorldFunctionRole",
+            "Arn"
+          ]
+        },
+        "Runtime": "nodejs14.x",
+        "Tags": [
+          {
+            "Key": "lambda:createdBy",
+            "Value": "SAM"
+          }
+        ]
+      }
+    },
+    "HelloWorldFunctionRole": {
+      "Type": "AWS::IAM::Role",
+      "Properties": {
+        "AssumeRolePolicyDocument": {
+          "Version": "2012-10-17",
+          "Statement": [
+            {
+              "Action": [
+                "sts:AssumeRole"
+              ],
+              "Effect": "Allow",
+              "Principal": {
+                "Service": [
+                  "lambda.amazonaws.com"
+                ]
+              }
+            }
+          ]
+        },
+        "ManagedPolicyArns": [
+          "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
+        ],
+        "Tags": [
+          {
+            "Key": "lambda:createdBy",
+            "Value": "SAM"
+          }
+        ]
+      }
+    },
+    "HelloWorldFunctionCognitoPermission": {
+      "Type": "AWS::Lambda::Permission",
+      "Properties": {
+        "Action": "lambda:InvokeFunction",
+        "FunctionName": {
+          "Ref": "HelloWorldFunction"
+        },
+        "Principal": "cognito-idp.amazonaws.com",
+        "SourceArn": {
+          "Fn::GetAtt": [
+            "MyCognitoUserPool",
+            "Arn"
+          ]
+        }
+      }
+    }
+  }
+}
diff --git a/tests/translator/test_translator.py b/tests/translator/test_translator.py
--- a/tests/translator/test_translator.py
+++ b/tests/translator/test_translator.py
@@ -254,6 +254,7 @@ class TestTranslatorEndToEnd(AbstractTestTranslator):
     @parameterized.expand(
         itertools.product(
             [
+                "congito_userpool_with_sms_configuration",
                 "cognito_userpool_with_event",
                 "s3_with_condition",
                 "function_with_condition",