#!/usr/bin/env bash
#
# Cookbook Name:: aws-parallelcluster
#
# Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the
# License. A copy of the License is located at
#
# http://aws.amazon.com/apache2.0/
#
# or in the "LICENSE.txt" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
# OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions and
# limitations under the License.

# This script must be executed by the user who want to connect to DCV to obtain a Session Token.
#
# It performs the following steps:
# 1. Asks the ParallelCluster DCV External Authenticator for a Request Token and the Access File name
# 2. Creates the "access file" in the AUTHORIZATION_FILE_DIR folder (to confirm user identity)
# 3. Asks the ParallelCluster DCV External Authenticator for a SessionToken (the real token to access to the DCV session)
# 4. Returns DCV Session Id, DCV Server Port and the Session Token

# Requirements:
# jq, curl, awk, xargs, shuf, cat, grep, systemctl

# Usage:
# The script requires the ParallelCluster shared folder as input parameter
# The ParallelCluster shared folder will be used as session storage folder for DCV.
# It enables the clients to share files while connected to the session.
# ./pcluster_dcv_connect.sh "/shared"


# Returns the sessionid, the port and the tokenid (256 character long).
# Example: mysession 8443 adfsaklcxzvsadkhfgsdkhjfag-__bafbdajshsdjfh

AUTHORIZATION_FILE_DIR="/var/spool/parallelcluster/pcluster_dcv_authenticator"
DCV_SESSION_FOLDER="${HOME}/.parallelcluster/dcv"
LOG_FILE_PATH="/var/log/parallelcluster/pcluster_dcv_connect.log"
LOG_FILE_MAX_SIZE=5242880 # 5MB


_fail() {
  message=$1
  >&2 echo "ERROR: ${message}"
  exit 1
}

_validate_json() {
  json_param=$1
  message=$2

  if [[ -z "${json_param}" ]]; then
      _fail "${message}. Empty JSON."
  fi
  if ! jq -e . >/dev/null 2>&1 <<<"${json_param}"; then
      _fail "${message}. Wrong JSON."
  fi
}

_atomic_touch() {
    file_path="$1"

    _umask=$(umask)
    umask 0022 && touch "${file_path}"
    umask ${_umask}

    if [[ ! -f "${file_path}" ]]; then
        _fail "Unable to create the file in the $(dirname "${file_path}") folder. Exiting.."
    fi
}

_log() {
    text=$1

    [[ -f "${LOG_FILE_PATH}" ]] || _atomic_touch "${LOG_FILE_PATH}"

    # create a backup of the file if the size exceeds the LOG_FILE_MAX_SIZE
    file_size=$(du -b ${LOG_FILE_PATH} | awk '{print $1}')
    if [[ ${file_size} -gt ${LOG_FILE_MAX_SIZE} ]]; then
        mv "${LOG_FILE_PATH}" "${LOG_FILE_PATH}.1"
    fi

    # append log
    log_time=$(date "+%Y-%m-%d %H:%M:%S")
    echo "[${log_time}]: ${text}" >> "${LOG_FILE_PATH}"
}

_create_dcv_session() {
    dcv_session_file="$1"
    shared_folder_path="$2"

    _log "Creating a new Amazon DCV session..."
    # Generate a random session id
    sessionid=$(shuf -zer -n20  {A..Z} {a..z} {0..9})
    echo "${sessionid}" > "${dcv_session_file}"
    dcv create-session --type virtual --storage-root "${shared_folder_path}" "${sessionid}"
    _log "Amazon DCV session created successfully."

    echo "${sessionid}"
}

main() {
    _log "--- Initializing Amazon DCV authentication... ---"

    if [[ -z "$1" ]]; then
        _fail "The script requires the shared folder as input parameter."
    fi

    shared_folder_path="$1"
    user=$(whoami)
    os=$(< /etc/chef/dna.json jq -r .cluster.base_os)
    _log "Input parameters: user: ${user}, OS: ${os}, shared_folder_path: ${shared_folder_path}."

    if ! [[ "${os}" =~ ^(alinux2|alinux2023|ubuntu2204|ubuntu2404|rhel8|rocky8|rhel9|rocky9)$ ]]; then
        _fail "OS not supported."
    fi

    if ! systemctl is-active --quiet dcvserver; then
        _fail "Amazon DCV service is not active on the given instance."
    fi

    # Create a session with session storage enabled.
    mkdir -p "${DCV_SESSION_FOLDER}"
    dcv_session_file="${DCV_SESSION_FOLDER}/dcv_session_$(hostname)"
    if [[ ! -e ${dcv_session_file} ]]; then
        sessionid=$(_create_dcv_session "${dcv_session_file}" "${shared_folder_path}")
    else
        sessionid=$(cat "${dcv_session_file}")
        _log "Reusing existing Amazon DCV session for the current user."

        # number of session can either be 0 or 1
        number_of_sessions=$(dcv list-sessions |& grep "${user}" | grep -c "${sessionid}")
        if (( number_of_sessions == 0 )); then
            # There is no running session (e.g. the system has been rebooted)
            _log "There are no running sessions for the current user, creating a new one.."
            sessionid=$(_create_dcv_session "${dcv_session_file}" "${shared_folder_path}")
        fi
    fi

    # xargs to remove eventual whitespaces
    dcv_server_port=$(grep web-port= /etc/dcv/dcv.conf| awk -F'=' '{ print $2 }' | xargs)
    ext_auth_port=$((dcv_server_port + 1))

    # Retrieve Request Token and Access File name
    _log "Retrieving Request Token and Access File name.."
    user_token_request=$(/usr/bin/curl --retry 3 --max-time 5 -s -k -X GET -G "https://localhost:${ext_auth_port}" -d action=requestToken -d authUser="${user}" -d sessionID="${sessionid}")
    _validate_json "${user_token_request}" "Unable to obtain the Request Token from the Amazon DCV external authenticator."
    request_token=$(echo "${user_token_request}" | jq -r .requestToken)
    access_file=$(echo "${user_token_request}" | jq -r .accessFile)
    _log "Request Token and Access File name obtained successfully."

    # Create the access file in the AUTHORIZATION_FILE_DIR with 644 permissions
    # This is used by the external authenticator to verify the user declares himself as who he really is
    _log "Creating access file.."
    _atomic_touch "${AUTHORIZATION_FILE_DIR}/${access_file}"
    _log "Access file created successfully."


    # Retrieve Session Token
    _log "Retrieving Session Token.."
    session_token_request=$(/usr/bin/curl --retry 3 --max-time 5 -s -k -X GET -G "https://localhost:${ext_auth_port}" -d action=sessionToken -d requestToken="${request_token}")
    _validate_json "${session_token_request}" "Unable to obtain the Session Token from the Amazon DCV external authenticator."
    session_token=$(echo "${session_token_request}" | jq -r .sessionToken)
    _log "Session Token obtained successfully."

    if [[ -z "${dcv_server_port}" ]]; then
      dcv_server_port=8443
    fi

    _log "--- Amazon DCV authentication performed successfully. ---"
    echo "PclusterDcvServerPort=${dcv_server_port} PclusterDcvSessionId=${sessionid} PclusterDcvSessionToken=${session_token}"
}

main "$@"
