---
AWSTemplateFormatVersion: '2010-09-09'
Description: A basic template for creating a Lambda-backed API Gateway for use as
  a custom identity provider in AWS Transfer. By default, it authenticates
  a single user in a single server using a hardcoded SSH key or password. You can
  update the Lambda function code to do something different after deployment.
Parameters:
  CreateServer:
    AllowedValues:
      - 'true'
      - 'false'
    Type: String
    Description: Whether this stack creates a server internally or not. If a server is created internally,
      the customer identity provider is automatically associated with it.
    Default: 'true'
  UserName:
    Type: String
    Description: Name of the user this identity provider will authenticate. Default
      is 'myuser'. You can also update it in the Lambda function code after deployment.
    Default: myuser
  UserHomeDirectory:
    Type: String
    Description: Home directory of the user. Default is '/'.
      You can also update it in the Lambda function code after deployment.
    Default: "/"
  UserRoleArn:
    Type: String
    Description: IAM role of the user. Default is 'arn:aws:iam::000000000000:role/MyUserS3AccessRole'.
      You can also update it in the Lambda function code after deployment.
    Default: "arn:aws:iam::000000000000:role/MyUserS3AccessRole"
  UserPassword:
    Type: String
    Description: Password of the user. Default is 'MySuperSecretPassword'. You
      can also update it in the Lambda function code after deployment.
    Default: MySuperSecretPassword
  UserPublicKey1:
    Type: String
    Description: SSH public key of the user. Default is 'ssh-rsa myrsapubkey'.
      You can also update it in the Lambda function code after deployment.
    Default: ssh-rsa myrsapubkey
Conditions:
  CreateServer:
    Fn::Equals:
      - Ref: CreateServer
      - 'true'
  NotCreateServer:
    Fn::Not:
      - Condition: CreateServer
Outputs:
  ServerId:
    Value:
      Fn::GetAtt: TransferServer.ServerId
    Condition: CreateServer
  StackArn:
    Value:
      Ref: AWS::StackId
  TransferIdentityProviderUrl:
    Description: URL to pass to AWS Transfer CreateServer call as part of optional IdentityProviderDetails
    Value:
      Fn::Join:
      - ''
      - - https://
        - Ref: CustomIdentityProviderApi
        - .execute-api.
        - Ref: AWS::Region
        - '.'
        - Ref: AWS::URLSuffix
        - '/'
        - Ref: ApiStage
    Condition: NotCreateServer
  TransferIdentityProviderInvocationRole:
    Description: IAM Role to pass to AWS Transfer CreateServer call as part of optional IdentityProviderDetails
    Value:
      Fn::GetAtt: TransferIdentityProviderRole.Arn
    Condition: NotCreateServer
Resources:
  TransferServer:
    Type: AWS::Transfer::Server
    Condition: CreateServer
    Properties:
      EndpointType: PUBLIC
      IdentityProviderDetails:
        InvocationRole:
          Fn::GetAtt: TransferIdentityProviderRole.Arn
        Url:
          Fn::Join:
            - ''
            - - https://
              - Ref: CustomIdentityProviderApi
              - .execute-api.
              - Ref: AWS::Region
              - '.'
              - Ref: AWS::URLSuffix
              - '/'
              - Ref: ApiStage
      IdentityProviderType: API_GATEWAY
      LoggingRole:
        Fn::GetAtt: CloudWatchLoggingRole.Arn
  CloudWatchLoggingRole:
    Description: IAM role used by Transfer  to log API requests to CloudWatch
    Type: AWS::IAM::Role
    Condition: CreateServer
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - transfer.amazonaws.com
            Action:
              - sts:AssumeRole
      Policies:
        - PolicyName: TransferLogsPolicy
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - logs:CreateLogGroup
                  - logs:CreateLogStream
                  - logs:DescribeLogStreams
                  - logs:PutLogEvents
                Resource:
                  Fn::Sub: '*'
  CustomIdentityProviderApi:
    Type: AWS::ApiGateway::RestApi
    Properties:
      Name: Transfer Custom Identity Provider basic template API
      Description: API used for GetUserConfig requests
      FailOnWarnings: true
      EndpointConfiguration:
        Types:
        - REGIONAL
  LambdaExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
        - Effect: Allow
          Principal:
            Service:
            - lambda.amazonaws.com
          Action:
          - sts:AssumeRole
      ManagedPolicyArns:
      - Fn::Sub: arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
  ApiCloudWatchLogsRole:
    Description: IAM role used by API Gateway to log API requests to CloudWatch
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
        - Effect: Allow
          Principal:
            Service:
            - apigateway.amazonaws.com
          Action:
          - sts:AssumeRole
      Policies:
      - PolicyName: ApiGatewayLogsPolicy
        PolicyDocument:
          Version: '2012-10-17'
          Statement:
          - Effect: Allow
            Action:
            - logs:CreateLogGroup
            - logs:CreateLogStream
            - logs:DescribeLogGroups
            - logs:DescribeLogStreams
            - logs:DescribeQueries
            - logs:FilterLogEvents
            - logs:GetLogEvents
            - logs:GetLogGroupFields
            - logs:GetLogRecord
            - logs:GetQueryResults
            - logs:PutLogEvents
            - logs:StartQuery
            - logs:StopQuery
            Resource: "*"
  ApiLoggingAccount:
    Type: AWS::ApiGateway::Account
    DependsOn:
    - CustomIdentityProviderApi
    Properties:
      CloudWatchRoleArn:
        Fn::GetAtt: ApiCloudWatchLogsRole.Arn
  ApiStage:
    Type: AWS::ApiGateway::Stage
    Properties:
      DeploymentId:
        Ref: ApiDeployment202008
      MethodSettings:
      - DataTraceEnabled: false
        HttpMethod: "*"
        LoggingLevel: INFO
        ResourcePath: "/*"
      RestApiId:
        Ref: CustomIdentityProviderApi
      StageName: prod
  ApiDeployment202008:
    DependsOn:
    - GetUserConfigRequest
    Type: AWS::ApiGateway::Deployment
    Properties:
      RestApiId:
        Ref: CustomIdentityProviderApi
  TransferIdentityProviderRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
        - Effect: Allow
          Principal:
            Service: transfer.amazonaws.com
          Action:
          - sts:AssumeRole
      Policies:
      - PolicyName: TransferCanInvokeThisApi
        PolicyDocument:
          Version: '2012-10-17'
          Statement:
          - Effect: Allow
            Action:
            - execute-api:Invoke
            Resource:
              Fn::Sub: arn:${AWS::Partition}:execute-api:${AWS::Region}:${AWS::AccountId}:${CustomIdentityProviderApi}/prod/GET/*
      - PolicyName: TransferCanReadThisApi
        PolicyDocument:
          Version: '2012-10-17'
          Statement:
          - Effect: Allow
            Action:
            - apigateway:GET
            Resource: "*"
  GetUserConfigLambda:
    Type: AWS::Lambda::Function
    Properties:
      Code:
        ZipFile:
          Fn::Sub: |
            'use strict';

            // GetUserConfig Lambda

            exports.handler = (event, context, callback) => {
              console.log("Username:", event.username, "ServerId: ", event.serverId);

              var response;
              // Check if the username presented for authentication is correct. This doesn't check the value of the serverId, only that it is provided.
              if (event.password == '' && ( event.protocol == 'FTP' || event.protocol == 'FTPS')) {
                // Return HTTP status 200 but with no role in the response to indicate authentication failure
                response = {};
              } else if (event.serverId !== "" && event.username == '${UserName}') {
                response = {
                  Role: '${UserRoleArn}', // The user will be authenticated if and only if the Role field is not blank
                  Policy: '', // Optional JSON blob to further restrict this user's permissions
                  HomeDirectory: '${UserHomeDirectory}' // Not required, defaults to '/'
                };

                // Check if password is provided
                if (event.password == "") {
                  // If no password provided, return the user's SSH public key
                  response['PublicKeys'] = [ "${UserPublicKey1}" ];
                // Check if password is correct
                } else if (event.password !== '${UserPassword}') {
                  // Return HTTP status 200 but with no role in the response to indicate authentication failure
                  response = {};
                }
              } else {
                // Return HTTP status 200 but with no role in the response to indicate authentication failure
                response = {};
              }
              callback(null, response);
            };
      Description: A function to provide IAM roles and policies for given user and serverId.
      Handler: index.handler
      Role:
        Fn::GetAtt: LambdaExecutionRole.Arn
      Runtime: nodejs20.x
  GetUserConfigLambdaPermission:
    Type: AWS::Lambda::Permission
    Properties:
      Action: lambda:invokeFunction
      FunctionName:
        Fn::GetAtt: GetUserConfigLambda.Arn
      Principal: apigateway.amazonaws.com
      SourceArn:
        Fn::Sub: arn:${AWS::Partition}:execute-api:${AWS::Region}:${AWS::AccountId}:${CustomIdentityProviderApi}/*
  ServersResource:
    Type: AWS::ApiGateway::Resource
    Properties:
      RestApiId:
        Ref: CustomIdentityProviderApi
      ParentId:
        Fn::GetAtt:
        - CustomIdentityProviderApi
        - RootResourceId
      PathPart: servers
  ServerIdResource:
    Type: AWS::ApiGateway::Resource
    Properties:
      RestApiId:
        Ref: CustomIdentityProviderApi
      ParentId:
        Ref: ServersResource
      PathPart: "{serverId}"
  UsersResource:
    Type: AWS::ApiGateway::Resource
    Properties:
      RestApiId:
        Ref: CustomIdentityProviderApi
      ParentId:
        Ref: ServerIdResource
      PathPart: users
  UserNameResource:
    Type: AWS::ApiGateway::Resource
    Properties:
      RestApiId:
        Ref: CustomIdentityProviderApi
      ParentId:
        Ref: UsersResource
      PathPart: "{username}"
  GetUserConfigResource:
    Type: AWS::ApiGateway::Resource
    Properties:
      RestApiId:
        Ref: CustomIdentityProviderApi
      ParentId:
        Ref: UserNameResource
      PathPart: config
  GetUserConfigRequest:
    Type: AWS::ApiGateway::Method
    DependsOn: GetUserConfigResponseModel
    Properties:
      AuthorizationType: AWS_IAM
      HttpMethod: GET
      Integration:
        Type: AWS
        IntegrationHttpMethod: POST
        Uri:
          Fn::Join:
            - ""
            - - "arn:"
              - Ref: AWS::Partition
              - ":apigateway:"
              - Ref: AWS::Region
              - ":lambda:path/2015-03-31/functions/"
              - Fn::GetAtt:
                - GetUserConfigLambda
                - Arn
              - "/invocations"
        IntegrationResponses:
        - StatusCode: 200
        RequestTemplates:
          application/json: |
            {
              "username": "$util.urlDecode($input.params('username'))",
              "password": "$util.escapeJavaScript($util.base64Decode($input.params('PasswordBase64'))).replaceAll("\\'","'")",
              "protocol": "$input.params('protocol')",
              "serverId": "$input.params('serverId')",
              "sourceIp": "$input.params('sourceIp')"
            }
      RequestParameters:
        method.request.header.PasswordBase64: false
        method.request.querystring.protocol: false
        method.request.querystring.sourceIp: false
      ResourceId:
        Ref: GetUserConfigResource
      RestApiId:
        Ref: CustomIdentityProviderApi
      MethodResponses:
      - StatusCode: 200
        ResponseModels:
          application/json: UserConfigResponseModel
  GetUserConfigResponseModel:
    Type: AWS::ApiGateway::Model
    Properties:
      RestApiId:
        Ref: CustomIdentityProviderApi
      ContentType: application/json
      Description: API response for GetUserConfig
      Name: UserConfigResponseModel
      Schema:
        "$schema": http://json-schema.org/draft-04/schema#
        title: UserUserConfig
        type: object
        properties:
          HomeDirectory:
            type: string
          Role:
            type: string
          Policy:
            type: string
          PublicKeys:
            type: array
            items:
              type: string
