AWSTemplateFormatVersion: 2010-09-09
Transform: AWS::Serverless-2016-10-31
Description: Handling Redirections@Edge using CloudFront and Lambda@Edge

Parameters:
  UserName:
    Type: String
    Description: The username to be used to login.
    Default: adminuser
  DeployUserInterface:
    Description: Deploy the user interface to assist in rule definition and maintenance.
    Default: yes
    Type: String
    AllowedValues:
      - yes
      - no
    ConstraintDescription: must specify yes or no.
  UIPrefix:
    Type: String
    Description: The folder where the user interface would be deployed in the bucket.
    Default: ui

Metadata:
  AWS::CloudFormation::Interface:
    ParameterGroups:
      -
        Label:
          default: "User Interface (optional)"
        Parameters:
          - DeployUserInterface
          - UserName
          - UIPrefix
    ParameterLabels:
      DeployUserInterface:
        default: "Deploy User Interface?"

Conditions:
  DeployUICondition: !Equals [ !Ref DeployUserInterface, yes ]

Mappings:
    SourceCode:
      General:
        S3Bucket: 'CODE_BUCKET'
        LambdaPrefix: 'redirection/lambda/latest'
        UIPrefix: 'redirection/ui/latest'

Resources:
  RuleBucket:
    Type: 'AWS::S3::Bucket'
    Properties:
      VersioningConfiguration:
        Status: Enabled
      CorsConfiguration:
        CorsRules:
          -
            AllowedHeaders:
              - '*'
            AllowedMethods:
              - 'GET'
              - 'PUT'
              - 'POST'
              - 'DELETE'
            AllowedOrigins:
              - '*'
            MaxAge: 3000

  RuleBucketPolicy:
    Type: 'AWS::S3::BucketPolicy'
    Properties:
      PolicyDocument:
        Statement:
          - Action:
              - s3:GetObject
            Effect: Allow
            Principal:
              AWS: !GetAtt OriginRequestFunctionRole.Arn
            Resource: !Sub arn:aws:s3:::${RuleBucket}/*
      Bucket: !Ref RuleBucket

  OriginRequestFunctionRole:
    Type: 'AWS::IAM::Role'
    Properties:
      Path: /service-role/
      ManagedPolicyArns:
        - 'arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole'
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          Action:
            - 'sts:AssumeRole'
          Effect: Allow
          Principal:
            Service:
              - lambda.amazonaws.com
              - edgelambda.amazonaws.com

  OriginRequestFunction:
    Type: 'AWS::Lambda::Function'
    Properties:
      Code:
        S3Bucket: !Join ["-", [!FindInMap ["SourceCode", "General", "S3Bucket"], !Ref "AWS::Region"]]
        S3Key: !Join ["/", [!FindInMap ["SourceCode", "General", "LambdaPrefix"],  "origin-request-function.zip"]]
      MemorySize: 128
      Handler: index.handler
      Role: !GetAtt OriginRequestFunctionRole.Arn
      Timeout: 5
      Runtime: nodejs12.x

  OriginRequestFunctionVersion:
    Type: "AWS::Lambda::Version"
    Properties:
      FunctionName: !Ref OriginRequestFunction
      Description: "A version of OriginRequestFunction"

  DeployFunctionRole:
    Type: 'AWS::IAM::Role'
    Condition: DeployUICondition
    Properties:
      Path: /service-role/
      ManagedPolicyArns:
        - 'arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole'
        - 'arn:aws:iam::aws:policy/AmazonCognitoPowerUser'
      Policies:
        - PolicyName: CustomPolicy
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Action:
                  - 's3:*'
                Resource: !Sub 'arn:aws:s3:::${RuleBucket}/*'
                Effect: Allow
              - Action:
                  - 's3:*'
                Resource: !Join ["/",[!Join [":",['arn','aws','s3','','',!Join ["-", [!FindInMap ["SourceCode", "General", "S3Bucket"], !Ref "AWS::Region"]]]],'*']]
                Effect: Allow
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          Action:
            - 'sts:AssumeRole'
          Effect: Allow
          Principal:
            Service:
              - lambda.amazonaws.com
              - s3.amazonaws.com

  DeployFunction:
    Type: 'AWS::Lambda::Function'
    Condition: DeployUICondition
    Properties:
      Code:
        S3Bucket: !Join ["-", [!FindInMap ["SourceCode", "General", "S3Bucket"], !Ref "AWS::Region"]]
        S3Key: !Join ["/", [!FindInMap ["SourceCode", "General", "LambdaPrefix"],  "deploy-function.zip"]]
      MemorySize: 512
      Environment:
        Variables:
          RuleBucket: !Ref RuleBucket
          UIPrefix: !Ref UIPrefix
          SourceUIFileBucket: !Join ["-", [!FindInMap ["SourceCode", "General", "S3Bucket"], !Ref "AWS::Region"]]
          SourceUIFilePath: !Join ["/", [!FindInMap ["SourceCode", "General", "UIPrefix"],  "ui.zip"]]
      Handler: index.handler
      Role: !GetAtt DeployFunctionRole.Arn
      Timeout: 300
      Runtime: nodejs12.x

  DeployArtifacts:
    Type: 'Custom::LambdaCallout'
    Condition: DeployUICondition
    Properties:
      UserPoolClient: !Ref UserPoolClient
      UserPool: !Ref UserPool
      IdentityPool: !Ref IdentityPool
      UserName: !Ref UserName
      ServiceToken: !GetAtt
        - DeployFunction
        - Arn

  UserPool:
    Type: 'AWS::Cognito::UserPool'
    Condition: DeployUICondition
    Properties:
      AutoVerifiedAttributes:
        - email
      UserPoolName: Rule_Manager_UserPool
      MfaConfiguration: 'OFF'
      Schema:
        - AttributeDataType: String
          Required: true
          Name: name
          Mutable: true

  CognitoUnAuthorizedRole:
    Type: 'AWS::IAM::Role'
    Condition: DeployUICondition
    Properties:
      Policies:
        - PolicyName: CognitoUnauthorizedPolicy
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Action:
                  - 'mobileanalytics:GetEvents'
                  - 'cognito-sync:*'
                Resource: '*'
                Effect: Allow
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Action:
              - 'sts:AssumeRoleWithWebIdentity'
            Effect: Allow
            Condition:
              StringEquals:
                'cognito-identity.amazonaws.com:aud': !Ref IdentityPool
              'ForAnyValue:StringLike':
                'cognito-identity.amazonaws.com:amr': unauthenticated
            Principal:
              Federated: cognito-identity.amazonaws.com

  UserPoolClient:
    Type: 'AWS::Cognito::UserPoolClient'
    Condition: DeployUICondition
    Properties:
      GenerateSecret: false
      ClientName: Rule_Manager_ApplicationClient
      UserPoolId: !Ref UserPool

  CognitoAuthorizedRole:
    Type: 'AWS::IAM::Role'
    Condition: DeployUICondition
    Properties:
      Policies:
        - PolicyName: CognitoAuthorizedPolicy
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Action:
                  - 'mobileanalytics:PutEvents'
                  - 'cognito-sync:*'
                  - 'cognito-identity:*'
                  - 's3:*'
                Resource: '*'
                Effect: Allow
              - Action:
                  - 'lambda:InvokeFunction'
                Resource: '*'
                Effect: Allow
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Action:
              - 'sts:AssumeRoleWithWebIdentity'
            Effect: Allow
            Condition:
              StringEquals:
                'cognito-identity.amazonaws.com:aud': !Ref IdentityPool
              'ForAnyValue:StringLike':
                'cognito-identity.amazonaws.com:amr': authenticated
            Principal:
              Federated: cognito-identity.amazonaws.com

  IdentityPool:
    Type: 'AWS::Cognito::IdentityPool'
    Condition: DeployUICondition
    Properties:
      IdentityPoolName: Rule_Manager_IdentityPool
      AllowUnauthenticatedIdentities: false
      CognitoIdentityProviders:
        - ClientId: !Ref UserPoolClient
          ProviderName: !GetAtt
            - UserPool
            - ProviderName

  IdentityPoolRoleMapping:
    Type: 'AWS::Cognito::IdentityPoolRoleAttachment'
    Condition: DeployUICondition
    Properties:
      IdentityPoolId: !Ref IdentityPool
      Roles:
        unauthenticated: !GetAtt CognitoUnAuthorizedRole.Arn
        authenticated: !GetAtt CognitoAuthorizedRole.Arn

  StaticBucket:
    Type: AWS::S3::Bucket

  StaticBucketPolicy:
    Type: AWS::S3::BucketPolicy
    Properties:
       Bucket: !Ref StaticBucket
       PolicyDocument:
         Statement:
             - Sid: PolicyForCloudFrontPrivateContent
               Effect: Allow
               Principal:
                 CanonicalUser: !GetAtt OriginAccessIdentity.S3CanonicalUserId
               Action:
                 - 's3:GetObject'
               Resource: !Sub arn:aws:s3:::${StaticBucket}/*

  OriginAccessIdentity:
    Type: 'AWS::CloudFront::CloudFrontOriginAccessIdentity'
    Metadata:
      Comment: !Sub "${AWS::StackName} - Access private S3 bucket content only through CloudFront"
    Properties:
      CloudFrontOriginAccessIdentityConfig:
        Comment: !Sub "${AWS::StackName} - Access private S3 bucket content only through CloudFront"

  SiteContentDistribution:
    Type: AWS::CloudFront::Distribution
    Properties:
      DistributionConfig:
        Origins:
        - DomainName: !Sub ${StaticBucket}.s3.amazonaws.com
          Id: myS3Origin
          S3OriginConfig:
            OriginAccessIdentity: !Sub "origin-access-identity/cloudfront/${OriginAccessIdentity}"
          OriginCustomHeaders:
            -
              HeaderName: rules_bucket
              HeaderValue: !Ref RuleBucket
            -
              HeaderName: rules_file
              HeaderValue: redirector.json
        Enabled: 'true'
        Comment: !Sub "${AWS::StackName} - distribution for vod content delivery"
        DefaultRootObject: index.html
        DefaultCacheBehavior:
          TargetOriginId: myS3Origin
          LambdaFunctionAssociations:
            - EventType: 'origin-request'
              LambdaFunctionARN: !Ref OriginRequestFunctionVersion
          ViewerProtocolPolicy: allow-all
          DefaultTTL: '300'
          SmoothStreaming: 'false'
          Compress: 'true'
          ForwardedValues:
            QueryString: 'true'
            Cookies:
              Forward: 'none'
        PriceClass: PriceClass_All
        ViewerCertificate:
          CloudFrontDefaultCertificate: 'true'

Outputs:
  UserName:
    Condition: DeployUICondition
    Value: !GetAtt DeployArtifacts.UserName
  TemporaryPassword:
    Condition: DeployUICondition
    Value: !GetAtt DeployArtifacts.Password
  UserInterface:
    Condition: DeployUICondition
    Value: !Join ["/",["https://s3.amazonaws.com",!Ref RuleBucket, !Ref UIPrefix,'index.html']]
  SiteContentDistribution:
    Value: !Join ["/",['https:/',!GetAtt SiteContentDistribution.DomainName]]