######### Apache License ########################################
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.

# Permission is hereby granted, free of charge, to any person obtaining a copy of
# this software and associated documentation files (the "Software"), to deal in
# the Software without restriction, including without limitation the rights to
# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
# the Software, and to permit persons to whom the Software is furnished to do so.

# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
# FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.


#################################################################################

AWSTemplateFormatVersion: '2010-09-09'
Description: Complete setup for custom plugin with resolved cyclic dependencies

Metadata:
   AWS::CloudFormation::Interface:
    ParameterGroups:
      - Label:
          default: "Identity Center Configuration"
        Parameters:
          - IdcInstanceArn
      - Label:
          default: "Lambda Function Configuration"
        Parameters:
          - LambdaName
      - Label:
          default: "Email Configuration"
        Parameters:
          - Fromemailaddress
          - Toemailaddress

Parameters:
  # Identity Center Configuration
  IdcInstanceArn:
    Type: String
    Description: "ARN of the Identity Center (IDC) Instance"    
    Default: "arn:aws:sso:::instance/ssoins-11111111111"

  # Lambda Function Configuration
  LambdaName:
    Type: String
    Default: email-lambda
    Description: Name of the Lambda function
    MinLength: 1
    MaxLength: 64
   
  # Email Configuration
  Toemailaddress:
    Type: String
    Default: "abc@gmail.com"
    AllowedPattern: '[^@]+@[^@]+\.[^@]+'
    Description: Email address to receive email
   
  Fromemailaddress:
    Type: String
    Default: "xyz@gmail.com"
    AllowedPattern: '[^@]+@[^@]+\.[^@]+'
    Description: Email address to send email

###############################################################################################

          #################          RESOURCES            #######################

##############################################################################################

Resources:


################# VPC for hosting Lambda  #######################


  VPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: 10.0.0.0/16
      EnableDnsHostnames: true
      EnableDnsSupport: true
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName}-VPC

  VPCFlowLogsRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: vpc-flow-logs.amazonaws.com
            Action: sts:AssumeRole
      Policies:
        - PolicyName: FlowLogsPolicy
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - logs:CreateLogGroup
                  - logs:CreateLogStream
                  - logs:PutLogEvents
                  - logs:DescribeLogGroups
                  - logs:DescribeLogStreams
                Resource: !Sub arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:${AWS::StackName}-VPCFlowLogs:*

  MyKMSKey:
    Type: AWS::KMS::Key
    Properties:
      Description: KMS key for S3 bucket encryption and VPC Flow Logs
      EnableKeyRotation: true
      KeyPolicy:
        Version: '2012-10-17'
        Id: key-default-1
        Statement:        
          - Sid: Enable IAM User Permissions
            Effect: Allow
            Principal:
              AWS: !Sub 'arn:aws:iam::${AWS::AccountId}:root'
            Action: 'kms:*'
            Resource: '*'
          - Sid: AllowCloudWatchLogs
            Effect: Allow
            Principal:
              Service: !Sub 'logs.${AWS::Region}.amazonaws.com'
            Action:
              - kms:Encrypt*
              - kms:Decrypt*
              - kms:ReEncrypt*
              - kms:GenerateDataKey*
              - kms:Describe*
            Resource: '*'
          - Sid: AllowQBusinessService
            Effect: Allow
            Principal:
              Service:
                - qbusiness.amazonaws.com
                - kendra.amazonaws.com
            Action:
              - kms:Decrypt
              - kms:GenerateDataKey
              - kms:DescribeKey
            Resource: '*'

  VPCFlowLogsGroup:
    Type: AWS::Logs::LogGroup
    Properties:
      LogGroupName: !Sub ${AWS::StackName}-VPCFlowLogs
      RetentionInDays: 30
      KmsKeyId: !GetAtt MyKMSKey.Arn

  VPCFlowLog:
    Type: AWS::EC2::FlowLog
    Properties:
      DeliverLogsPermissionArn: !GetAtt VPCFlowLogsRole.Arn
      LogGroupName: !Ref VPCFlowLogsGroup
      ResourceId: !Ref VPC
      ResourceType: VPC
      TrafficType: ALL
      MaxAggregationInterval: 60
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName}-VPCFlowLog

  PrivateSubnet1:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      CidrBlock: 10.0.1.0/24
      AvailabilityZone: !Select [0, !GetAZs '']
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName}-PrivateSubnet1

  PrivateSubnet2:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      CidrBlock: 10.0.2.0/24
      AvailabilityZone: !Select [1, !GetAZs '']
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName}-PrivateSubnet2

  LambdaSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Security group for Lambda function
      VpcId: !Ref VPC
      SecurityGroupEgress:
         - IpProtocol: tcp
           FromPort: 587
           ToPort: 587
           CidrIp: 0.0.0.0/0
           Description: "Allow SMTP traffic to SES through NAT Gateway"
         - IpProtocol: tcp
           FromPort: 465
           ToPort: 465
           CidrIp: 0.0.0.0/0
           Description: "Allow SMTPS traffic to SES through NAT Gateway"
         - IpProtocol: tcp
           FromPort: 443
           ToPort: 443
           CidrIp: 0.0.0.0/0
           Description: "Allow HTTPS traffic to AWS services through NAT Gateway"
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName}-Lambda-SG

  InternetGateway:
    Type: AWS::EC2::InternetGateway

  AttachGateway:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      VpcId: !Ref VPC
      InternetGatewayId: !Ref InternetGateway

  PublicSubnet1:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      CidrBlock: 10.0.3.0/24
      AvailabilityZone: !Select [0, !GetAZs '']
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName}-PublicSubnet1

  NatGatewayEIP:
    Type: AWS::EC2::EIP
    DependsOn: AttachGateway
    Properties:
      Domain: vpc

  NatGateway:
    Type: AWS::EC2::NatGateway
    Properties:
      AllocationId: !GetAtt NatGatewayEIP.AllocationId
      SubnetId: !Ref PublicSubnet1

  PublicRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName}-Public

  PublicRoute:
    Type: AWS::EC2::Route
    DependsOn: AttachGateway
    Properties:
      RouteTableId: !Ref PublicRouteTable
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref InternetGateway

  PublicSubnet1RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PublicSubnet1
      RouteTableId: !Ref PublicRouteTable

  PrivateRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName}-Private

  PrivateRoute:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref PrivateRouteTable
      DestinationCidrBlock: 0.0.0.0/0
      NatGatewayId: !Ref NatGateway

  PrivateSubnet1RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PrivateSubnet1
      RouteTableId: !Ref PrivateRouteTable

  PrivateSubnet2RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PrivateSubnet2
      RouteTableId: !Ref PrivateRouteTable


################# Amazon Q data source S3 bucket and logging bucket  #######################


  S3AccessLogsBucket:
    Type: AWS::S3::Bucket
    Properties:
      PublicAccessBlockConfiguration:
        BlockPublicAcls: true
        BlockPublicPolicy: true
        IgnorePublicAcls: true
        RestrictPublicBuckets: true
      BucketEncryption:
        ServerSideEncryptionConfiguration:
          - ServerSideEncryptionByDefault:
              SSEAlgorithm: 'aws:kms'
              KMSMasterKeyID: !GetAtt MyKMSKey.Arn
      VersioningConfiguration:
        Status: Enabled

      Tags:
        - Key: Purpose
          Value: S3AccessLogs
        - Key: ComplianceException
          Value: LoggingBucket

  S3AccessLogsBucketPolicy:
    Type: AWS::S3::BucketPolicy
    Properties:
      Bucket: !Ref S3AccessLogsBucket
      PolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Sid: S3ServerAccessLogsPolicy
            Effect: Allow
            Principal:
              Service: logging.s3.amazonaws.com
            Action: s3:PutObject
            Resource: !Sub '${S3AccessLogsBucket.Arn}/*'
            Condition:
              ArnLike:
                aws:SourceArn: !Sub 'arn:aws:s3:::${AmazonQDataSourceBucket}'
              StringEquals:
                aws:SourceAccount: !Ref 'AWS::AccountId'
  AmazonQDataSourceBucket:
    Type: AWS::S3::Bucket
    Properties:
      PublicAccessBlockConfiguration:
        BlockPublicAcls: true
        BlockPublicPolicy: true
        IgnorePublicAcls: true
        RestrictPublicBuckets: true
      ObjectLockEnabled: true
      BucketEncryption:
        ServerSideEncryptionConfiguration:
          - ServerSideEncryptionByDefault:
              SSEAlgorithm: 'aws:kms'
              KMSMasterKeyID: !GetAtt MyKMSKey.Arn
      LoggingConfiguration:
        DestinationBucketName: !Ref S3AccessLogsBucket
        LogFilePrefix: 'data-source-bucket-logs/'
      VersioningConfiguration:
        Status: Enabled

  AmazonQDataSourceBucketPolicy:
    Type: AWS::S3::BucketPolicy
    Properties:
      Bucket: !Ref AmazonQDataSourceBucket
      PolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Sid: EnforceTLSRequestsOnly
            Effect: Deny
            Principal: '*'
            Action: 's3:*'
            Resource:
              - !Sub '${AmazonQDataSourceBucket.Arn}/*'
              - !GetAtt AmazonQDataSourceBucket.Arn
            Condition:
              Bool:
                'aws:SecureTransport': false
          - Sid: EnforceEncryptionInTransit
            Effect: Deny
            Principal: '*'
            Action: 's3:*'
            Resource:
              - !Sub '${AmazonQDataSourceBucket.Arn}/*'
              - !GetAtt AmazonQDataSourceBucket.Arn
            Condition:
              NumericLessThan:
                's3:TlsVersion': 1.2
          - Sid: DenyIncorrectEncryptionHeader
            Effect: Deny
            Principal: '*'
            Action: 's3:PutObject'
            Resource: !Sub '${AmazonQDataSourceBucket.Arn}/*'
            Condition:
              StringNotEquals:
                's3:x-amz-server-side-encryption-aws-kms-key-id': !GetAtt MyKMSKey.Arn


################# Cognito User Pool for API Gateway authentication  #######################

  CognitoUserPool:
    Type: AWS::Cognito::UserPool
    Properties:
      UserPoolName: !Sub "${AWS::StackName}-EmailUserPool"
      UsernameAttributes:
        - email
      AutoVerifiedAttributes:
        - email

  CognitoResourceServer:
    Type: AWS::Cognito::UserPoolResourceServer
    Properties:
      Identifier: "email"
      Name: "email"
      UserPoolId: !Ref CognitoUserPool
      Scopes:
        - ScopeName: "read"
          ScopeDescription: "Read access to email"

  CognitoUserPoolClient:
    Type: AWS::Cognito::UserPoolClient
    DependsOn: CognitoResourceServer
    Properties:
      ClientName: EmailUserPoolClient
      UserPoolId: !Ref CognitoUserPool
      GenerateSecret: true
      AllowedOAuthFlowsUserPoolClient: true
      AllowedOAuthFlows:
        - code
      AllowedOAuthScopes:
        - email
        - openid
        - profile
        - 'email/read'
      CallbackURLs:
        - !Sub '${QBusinessWebExperience.DefaultEndpoint}oauth/callback'  
      SupportedIdentityProviders:
        - COGNITO

  CognitoAuthorizer:
    Type: AWS::ApiGateway::Authorizer
    Properties:
      Name: CognitoAuthorizer
      Type: COGNITO_USER_POOLS
      IdentitySource: method.request.header.Authorization
      ProviderARNs:
        - !GetAtt CognitoUserPool.Arn
      RestApiId: !Ref ApiGatewayRestApi

  CognitoDomain:
    Type: AWS::Cognito::UserPoolDomain
    DependsOn: CognitoResourceServer
    Properties:
      Domain: !Sub '${AWS::AccountId}-${AWS::StackName}-email-domain'
      UserPoolId: !Ref CognitoUserPool


################# Amazon Q Business Apps  #######################

  QBusinessApp:
    Type: AWS::QBusiness::Application
    Properties:
      DisplayName: qbusiness-email-app
      Description: qbusiness email demo app
      IdentityCenterInstanceArn: !Ref IdcInstanceArn
      RoleArn: !Sub arn:aws:iam::${AWS::AccountId}:role/aws-service-role/qbusiness.amazonaws.com/AWSServiceRoleForQBusiness

  QBusinessIndex:
    Type: AWS::QBusiness::Index
    Properties:
      ApplicationId: !Ref QBusinessApp
      CapacityConfiguration: 
        Units: 1
      DisplayName: QBusinessIndexExample
      Type: ENTERPRISE

  QBusinessRetriever:
    Type: AWS::QBusiness::Retriever
    Properties:
      ApplicationId: !Ref QBusinessApp
      Configuration: 
        NativeIndexConfiguration: 
          IndexId: !GetAtt QBusinessIndex.IndexId
      DisplayName: QBusinessExampleRetriever
      Type: NATIVE_INDEX

  QBizDataSourceRole:
    Type: 'AWS::IAM::Role'
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Sid: 'AllowsAmazonQToAssumeRoleForServicePrincipal'
            Effect: Allow
            Principal:
              Service: 
                - qbusiness.amazonaws.com
            Action: 'sts:AssumeRole'
      ManagedPolicyArns:
        - !Ref QBizKMSPolicy
        - !Ref QBizS3Policy
        - !Ref QBizDocumentPolicy
        - !Ref QBizNetworkPolicy
      
  
  QBizKMSPolicy:
    Type: 'AWS::IAM::ManagedPolicy'
    Properties:
      Description: 'Allows Amazon Q to decrypt secrets and S3 objects'
      PolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Sid: 'AllowsAmazonQToDecryptSecretAndS3Objects'
            Effect: Allow
            Action:
              - 'kms:Decrypt'
              - 'kms:GenerateDataKey'
            Resource:
              - !GetAtt MyKMSKey.Arn

  QBizS3Policy:
    Type: 'AWS::IAM::ManagedPolicy'
    Properties:
      Description: 'Allows Amazon Q to access S3'
      PolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Sid: 'AllowsAmazonQToGetObjectfromS3'
            Effect: Allow
            Action:
              - 's3:GetObject'
            Resource:
              - !Sub 'arn:aws:s3:::${AmazonQDataSourceBucket}/*'
            Condition:
              StringEquals:
                'aws:ResourceAccount': !Sub '${AWS::AccountId}'
          - Sid: 'AllowsAmazonQToListS3Buckets'
            Effect: Allow
            Action:
              - 's3:ListBucket'
            Resource:
              - !Sub 'arn:aws:s3:::${AmazonQDataSourceBucket}'
            Condition:
              StringEquals:
                'aws:ResourceAccount': !Sub '${AWS::AccountId}'

  QBizDocumentPolicy:
    Type: 'AWS::IAM::ManagedPolicy'
    Properties:
      Description: 'Allows Amazon Q to manage documents and groups'
      PolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Sid: 'AllowsAmazonQToIngestDocuments'
            Effect: Allow
            Action:
              - 'qbusiness:BatchPutDocument'
              - 'qbusiness:BatchDeleteDocument'
            Resource:
              - !Sub 'arn:aws:qbusiness:${AWS::Region}:${AWS::AccountId}:*'
          - Sid: 'AllowsAmazonQToCallPrincipalMappingAPIs'
            Effect: Allow
            Action:
              - 'qbusiness:PutGroup'
              - 'qbusiness:CreateUser'
              - 'qbusiness:DeleteGroup'
              - 'qbusiness:UpdateUser'
              - 'qbusiness:ListGroups'
            Resource:
              - !Sub 'arn:aws:qbusiness:${AWS::Region}:${AWS::AccountId}:application/*'

  QBizNetworkPolicy:
    Type: 'AWS::IAM::ManagedPolicy'
    Properties:
      Description: 'Allows Amazon Q to manage network interfaces'
      PolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Sid: 'AllowsAmazonQToCreateAndDeleteENI'
            Effect: Allow
            Action:
              - 'ec2:CreateNetworkInterface'
              - 'ec2:DeleteNetworkInterface'
            Resource:
              - !Sub 'arn:aws:ec2:${AWS::Region}:${AWS::AccountId}:subnet/*'
              - !Sub 'arn:aws:ec2:${AWS::Region}:${AWS::AccountId}:security-group/*'
              - !Sub 'arn:aws:ec2:${AWS::Region}:${AWS::AccountId}:network-interface/*'
            Condition:
              StringLike:
                'aws:RequestTag/AMAZON_Q': !Sub 'qbusiness_${AWS::AccountId}_*'
              ForAllValues:StringEquals:
                'aws:TagKeys':
                  - 'AMAZON_Q'
          - Sid: 'AllowsAmazonQToCreateTags'
            Effect: Allow
            Action:
              - 'ec2:CreateTags'
            Resource:
              - !Sub 'arn:aws:ec2:${AWS::Region}:${AWS::AccountId}:network-interface/*'

  QBusinessWebExperience:
    Type: AWS::QBusiness::WebExperience
    DependsOn: QBizWebexperienceRole
    Properties:
      ApplicationId: !Ref QBusinessApp
      RoleArn: !GetAtt QBizWebexperienceRole.Arn

  QBizWebexperienceRole:
    Type: 'AWS::IAM::Role'
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Sid: 'QBusinessTrustPolicy'
            Effect: 'Allow'
            Principal:
              Service:
                - 'application.qbusiness.amazonaws.com'
            Action:
              - 'sts:AssumeRole'
              - 'sts:SetContext'
            Condition:
              StringEquals:
                'aws:SourceAccount': !Sub '${AWS::AccountId}'
              ArnEquals:
                'aws:SourceArn': !Sub 'arn:aws:qbusiness:${AWS::Region}:${AWS::AccountId}:application/*'
      ManagedPolicyArns:
        - !Ref QBizConversationPolicy
        - !Ref QBizKMSPolicy
        - !Ref QBizSTSPolicy
        - !Ref QBizAppsBasicPolicy
        - !Ref QBizAppsAdvancedPolicy

  QBizConversationPolicy:
    Type: 'AWS::IAM::ManagedPolicy'
    Properties:
      Description: 'Allows Amazon Q conversation and basic operations'
      PolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Sid: QBusinessConversationPermission
            Effect: Allow
            Action:
              - qbusiness:Chat
              - qbusiness:ChatSync
              - qbusiness:ListMessages
              - qbusiness:ListConversations
              - qbusiness:DeleteConversation
              - qbusiness:PutFeedback
              - qbusiness:GetWebExperience
              - qbusiness:GetApplication
              - qbusiness:ListPlugins
              - qbusiness:GetChatControlsConfiguration
              - qbusiness:ListIndices
              - qbusiness:ListDataSources
            Resource: !Sub 'arn:aws:qbusiness:${AWS::Region}:${AWS::AccountId}:application/*'

  QBizSTSPolicy:
    Type: 'AWS::IAM::ManagedPolicy'
    Properties:
      Description: 'Allows STS operations for Amazon Q'
      PolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Sid: QBusinessSetContextPermissions
            Effect: Allow
            Action:
              - sts:SetContext
            Resource:
              - 'arn:aws:sts::*:self'
            Condition:
              StringLike:
                'aws:CalledViaLast': 'qbusiness.amazonaws.com'

  QBizAppsBasicPolicy:
    Type: 'AWS::IAM::ManagedPolicy'
    Properties:
      Description: 'Allows basic QApps operations'
      PolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Sid: QAppsResourceAgnosticPermissions
            Effect: Allow
            Action:
              - qapps:CreateQApp
              - qapps:PredictQApp
              - qapps:PredictProblemStatementFromConversation
              - qapps:PredictQAppFromProblemStatement
              - qapps:ListQApps
              - qapps:ListLibraryItems
              - qapps:CreateSubscriptionToken
            Resource: !Sub 'arn:aws:qbusiness:${AWS::Region}:${AWS::AccountId}:application/*'
          - Sid: QAppsAppUniversalPermissions
            Effect: Allow
            Action:
              - qapps:DisassociateQAppFromUser
            Resource: !Sub 'arn:aws:qapps:${AWS::Region}:${AWS::AccountId}:application/*'

  QBizAppsAdvancedPolicy:
    Type: 'AWS::IAM::ManagedPolicy'
    Properties:
      Description: 'Allows advanced QApps operations'
      PolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Sid: QAppsAppOwnerPermissions
            Effect: Allow
            Action:
              - qapps:GetQApp
              - qapps:CopyQApp
              - qapps:UpdateQApp
              - qapps:DeleteQApp
              - qapps:ImportDocument
              - qapps:ImportDocumentToQApp
              - qapps:CreateLibraryItem
              - qapps:UpdateLibraryItem
              - qapps:StartQAppSession
            Resource: !Sub 'arn:aws:qapps:${AWS::Region}:${AWS::AccountId}:application/*'
            Condition:
              StringEqualsIgnoreCase:
                'qapps:UserIsAppOwner': 'true'

################# Lambda function to send emails  #######################

  LambdaExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: lambda.amazonaws.com
            Action: sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
        - arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole
      Policies:
        - PolicyName: SESAccess
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action: 
                  - ses:SendEmail
                  - ses:SendRawEmail
                  - ses:SendTemplatedEmail
                Resource: !Sub 'arn:aws:ses:${AWS::Region}:${AWS::AccountId}:*'

  LambdaFunction:
    Type: AWS::Lambda::Function
    Properties:
      FunctionName: !Ref LambdaName
      Description: A Lambda function to send email
      Handler: lambda_function.lambda_handler
      Role: !GetAtt LambdaExecutionRole.Arn
      Code:
        S3Bucket: aws-blogs-artifacts-public
        S3Key: ML-17431/Lambda/sendEmail.zip
      Runtime: python3.12
      Timeout: 30
      MemorySize: 128
      ReservedConcurrentExecutions: 100
      VpcConfig:
        SecurityGroupIds: 
          - !Ref LambdaSecurityGroup
        SubnetIds:
          - !Ref PrivateSubnet1
          - !Ref PrivateSubnet2


################# API Gateway resources  #######################

  ApiGatewayRestApi:
    Type: AWS::ApiGateway::RestApi
  
    Properties:
      Name: !Sub 'sendEmailAPI_${AWS::AccountId}'
      Description: An API Gateway to expose the Lambda function

  ApiGatewayMethod:
    Type: AWS::ApiGateway::Method
    Properties:
      RestApiId: !Ref ApiGatewayRestApi
      ResourceId: !GetAtt ApiGatewayRestApi.RootResourceId
      HttpMethod: POST
      AuthorizationType: COGNITO_USER_POOLS
      AuthorizerId: !Ref CognitoAuthorizer
      AuthorizationScopes:
        - !Sub "email/read"
      ApiKeyRequired: false
      Integration:
        PassthroughBehavior: WHEN_NO_MATCH
        ContentHandling: CONVERT_TO_TEXT
        Type: AWS_PROXY
        IntegrationHttpMethod: POST
        Uri: !Sub
          - arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${lambdaFunctionArn}/invocations
          - lambdaFunctionArn: !GetAtt LambdaFunction.Arn

  ApiGatewayLogGroup:
    Type: AWS::Logs::LogGroup
    DependsOn: MyKMSKey
    DeletionPolicy: Retain
    UpdateReplacePolicy: Retain
    Properties:
      LogGroupName: !Sub 'API-Gateway-Log-Group-${AWS::StackName}'
      RetentionInDays: 30 
      KmsKeyId: !GetAtt MyKMSKey.Arn

  ApiGatewayDeployment:
    Type: AWS::ApiGateway::Deployment
    DependsOn: ApiGatewayMethod
    Properties:
      RestApiId: !Ref ApiGatewayRestApi
      
  ApiGatewayStage:
    Type: AWS::ApiGateway::Stage
    Properties:
      DeploymentId: !Ref ApiGatewayDeployment
      RestApiId: !Ref ApiGatewayRestApi
      StageName: 'dev'
      AccessLogSetting:
        DestinationArn: !GetAtt ApiGatewayLogGroup.Arn
        Format: '{"requestId":"$context.requestId", "ip": "$context.identity.sourceIp", "caller":"$context.identity.caller", "user":"$context.identity.user", "requestTime":"$context.requestTime", "httpMethod":"$context.httpMethod", "resourcePath":"$context.resourcePath", "status":"$context.status", "protocol":"$context.protocol", "responseLength":"$context.responseLength" }'
      MethodSettings:
        - ResourcePath: '/'
          HttpMethod: 'POST'
          MetricsEnabled: true
          DataTraceEnabled: true
          LoggingLevel: INFO

  ApiUsagePlan:
    Type: 'AWS::ApiGateway::UsagePlan'
    DependsOn: ApiGatewayStage
    Properties:
      ApiStages: 
        - ApiId: !Ref ApiGatewayRestApi
          Stage: !Ref ApiGatewayStage
      Description: 'Usage plan for API'
      Quota:
        Limit: 1000
        Period: MONTH
      Throttle:
        RateLimit: 10
        BurstLimit: 20
      UsagePlanName: !Sub '${AWS::StackName}-usage-plan'

  ApiGatewayLambdaPermission:
    Type: AWS::Lambda::Permission
    Properties:
      Action: lambda:InvokeFunction
      Principal: apigateway.amazonaws.com
      FunctionName: !GetAtt LambdaFunction.Arn
      SourceArn: !Sub
        - arn:${AWS::Partition}:execute-api:${AWS::Region}:${AWS::AccountId}:${ApiId}/*/*/*
        - ApiId: !Ref ApiGatewayRestApi


################# Amazon SES identities  #######################

  ToEmail:
    Type: AWS::SES::EmailIdentity
    Properties:
      EmailIdentity: !Ref Toemailaddress

  FromEmail:
    Type: AWS::SES::EmailIdentity
    Properties:
      EmailIdentity: !Ref Fromemailaddress

################# Lambda function to updte KMS key policy after resources are created  #######################

  UpdateKMSKeyPolicyFunction:
    Type: AWS::Lambda::Function
    Properties:
      Handler: index.handler
      Role: !GetAtt UpdateKMSKeyPolicyRole.Arn
      ReservedConcurrentExecutions: 1
      VpcConfig:
        SecurityGroupIds: 
          - !Ref LambdaSecurityGroup
        SubnetIds:
          - !Ref PrivateSubnet1
          - !Ref PrivateSubnet2
      Code:
        ZipFile: |
          import boto3
          import json
          import cfnresponse

          def handler(event, context):
              if event['RequestType'] in ['Create', 'Update']:
                  try:
                      kms = boto3.client('kms')
                      response = kms.get_key_policy(KeyId=event['ResourceProperties']['KMSKeyId'], PolicyName='default')
                      policy = json.loads(response['Policy'])
                      policy['Statement'].append({
                          'Sid': 'AllowQBusinessAccess',
                          'Effect': 'Allow',
                          'Principal': {'AWS': event['ResourceProperties']['QBizDataSourceRoleArn']},
                          'Action': ['kms:Decrypt', 'kms:DescribeKey', 'kms:GenerateDataKey'],
                          'Resource': '*'
                      })
                      policy['Statement'].append({
                          'Sid': 'AllowKendraSession',
                          'Effect': 'Allow',
                          'Principal': {'AWS': f"arn:aws:sts::{event['ResourceProperties']['QBizDataSourceRoleArn'].split(':')[4]}:assumed-role/{event['ResourceProperties']['QBizDataSourceRoleArn'].split('/')[-1]}/KendraCustomerSession"},
                          'Action': ['kms:Decrypt', 'kms:DescribeKey', 'kms:GenerateDataKey'],
                          'Resource': '*'
                      })
                      kms.put_key_policy(KeyId=event['ResourceProperties']['KMSKeyId'], PolicyName='default', Policy=json.dumps(policy))
                      cfnresponse.send(event, context, cfnresponse.SUCCESS, {})
                  except Exception as e:
                      print(e)
                      cfnresponse.send(event, context, cfnresponse.FAILED, {})
              else:
                  cfnresponse.send(event, context, cfnresponse.SUCCESS, {})
      Runtime: python3.8
      Timeout: 60

  UpdateKMSKeyPolicyRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: 
                - lambda.amazonaws.com
            Action: 
              - sts:AssumeRole
      ManagedPolicyArns:
       - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
       - arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole
      Policies:
        - PolicyName: UpdateKMSKeyPolicy
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - kms:GetKeyPolicy
                  - kms:PutKeyPolicy
                Resource: !GetAtt MyKMSKey.Arn

  UpdateKMSKeyPolicy:
    Type: Custom::UpdateKMSKeyPolicy
    DependsOn: 
      - MyKMSKey
      - QBizDataSourceRole
    Properties:
      ServiceToken: !GetAtt UpdateKMSKeyPolicyFunction.Arn
      KMSKeyId: !Ref MyKMSKey
      QBizDataSourceRoleArn: !GetAtt QBizDataSourceRole.Arn

################# Amazon Q Business application data source  #######################

  DataSourceS3:
    Type: "AWS::QBusiness::DataSource"
    Properties:
      ApplicationId: !Ref QBusinessApp
      IndexId: !GetAtt QBusinessIndex.IndexId
      DisplayName: MyS3DataSource
      RoleArn: !GetAtt 'QBizDataSourceRole.Arn'
      Configuration:
        type: S3
        syncMode: FULL_CRAWL
        connectionConfiguration:
          repositoryEndpointMetadata:
            BucketName: !Ref AmazonQDataSourceBucket
        repositoryConfigurations:
          document:
            fieldMappings:
              - dataSourceFieldName: content
                indexFieldName: document_content
                indexFieldType: STRING

################# Outputs  #######################

Outputs:
  QBizWebexperienceURL:
    Description: "URL of the Amazon Q Web Experience"
    Value: !GetAtt 'QBusinessWebExperience.DefaultEndpoint'
    Export:
      Name: !Sub "${AWS::StackName}-WebExperienceURL"

  LambdaFunctionArn:
    Description: "ARN of the created Lambda function"
    Value: !GetAtt LambdaFunction.Arn
    Export:
      Name: !Sub "${AWS::StackName}-LambdaFunctionArn"

  ApiGatewayInvokeURL:
    Description: "URL of the API Gateway endpoint"
    Value: !Sub "https://${ApiGatewayRestApi}.execute-api.${AWS::Region}.amazonaws.com/dev"
    Export:
      Name: !Sub "${AWS::StackName}-ApiEndpoint"

  S3BucketName:
    Description: "Name of the S3 bucket for storing data"
    Value: !Ref AmazonQDataSourceBucket
    Export:
      Name: !Sub "${AWS::StackName}-S3BucketName"

  CognitoUserPoolId:
    Description: "ID of the Cognito User Pool"
    Value: !Ref CognitoUserPool
    Export:
      Name: !Sub "${AWS::StackName}-UserPoolId"
