#!/bin/bash

################################################################################
# Institute of Communication and Computer Systems (ICCS)
# i-sense Research Group
#
#        Command line testing of ICT Platform REST Services.
#   Usage: ./TestModuleResources.sh INPUTFILE [USERNAME PASSWORD CLIENTID]
#   Example: ./TestModuleResources.sh EcoCreditCalculatorResources.txt
#   Requirements: Bash, GNU Wget
#
# Authors:
#    Miltiadis Koutsokeras, miltos.koutsokeras@iccs.gr
################################################################################


# For some reason the JSON parsing library TickTick should be here, before
# anything. The typical _SELF* variables do not work when we include this.
source "$(dirname "$(readlink -e "${BASH_SOURCE[0]}")")/libs/ticktick.sh"


### Includes
# Nothing


### Constants
readonly INPUTFILE="${1:-Not Set}" # input file
readonly USERNAME="${2:-Not Set}"  # user name
readonly PASSWORD="${3:-Not Set}"  # user password
readonly CLIENTID="${4:-Not Set}"  # client ID
readonly REALM="${5:-CIRC4LIFE}"
readonly SERVER_URL="${6:-https://circ4life.iccs.gr}"
readonly OPENIDCONNECTURL="${SERVER_URL}/auth/realms/${REALM}/.well-known/uma2-configuration"
readonly WGETOPTIONS="--no-verbose --server-response --content-on-error=on"


### Global variables
# Nothing

### Functions
# Print the script usage and exit
print_usage_and_exit( ) {
    echo "Usage: TestModuleResources.sh INPUTFILE [USERNAME PASSWORD CLIENTID REALM SERVER_URL]"
    echo "Use GNU Wget HTTP command line client to test a list of REST resource
endpoints. The Access Control Manager protected endpoints can be accessed by
providing the user credentials and registered client ID."
    echo "Required arguments:"
    echo "INPUTFILE: Text file with REST resources to test. Check folders production for some examples."
    echo
    echo "Optional arguments:"
    echo "USERNAME: Username to use for accessing protected resources."
    echo "PASSWORD: Password for specified USERNAME."
    echo "CLIENTID: The client ID as expected by the Access Control Manager.
This the client's application unique identification. The authorization server
must already have this registered to trusted clients. Failing to provide a
registered client ID will result in failure of authentication, even with
correct user credentials. The Client ID is provided to the client application
after their registration on the authorization server from the administrator."
    echo "REALM: The Access Control Manager Realm (default: CIRC4LIFE)."
    echo "SERVER_URL: The Access Control Manager server URL (default: https://circ4life.iccs.gr)."
    exit 1
}

echoGreen( ) {
    echo -e "\e[32m${*}\e[0m"
}

echoRed( ) {
    echo -e "\e[31m${*}\e[0m"
}

echoYellow( ) {
    echo -e "\e[33m${*}\e[0m"
}

echoWgetExitCode( ) {
    case ${1} in
        0) { echoGreen "No problems occurred."; } ;;
        1) { echoRed "Generic error code."; } ;;
        2) { echoRed "Parse error---for instance, when parsing command-line options, the .wgetrc or .netrc..."; } ;;
        3) { echoRed "File I/O error."; } ;;
        4) { echoRed "Network failure."; } ;;
        6) { echoRed "Username/password authentication failure."; } ;;
        5) { echoRed "SSL verification failure."; } ;;
        7) { echoRed "Protocol errors."; } ;;
        8) { echoRed "Server issued an error response."; } ;;
    esac
}

# Print an error message and exit
error_and_exit( ) {
    echoRed "ERROR: ${*}"
    exit 1
}

HTTPStatusCode( ) {
    cat "${1}" | awk '/^\s*HTTP/{ print $2 }'
}

# Perform a HTTP GET request with the help of GNU Wget
GETrequest( ) {
    local acceptmediatype="${1}"
    local resourceurl="${2}"
    local tokentype="${3}"
    local token="${4}"

    if [[ "${acceptmediatype}" =~ image/ ]]
    then
        outfile="$(mktemp -u ${TEMPDIR:-/tmp}/image.png.XXXXXXXXXX)"
    else
        outfile="-"
    fi

    local stderrfile="$(mktemp -u ${TEMPDIR:-/tmp}/stderr.XXXXXXXXXX)"

    echo "---------------------------------------------------------------------"
    echoYellow "RESPONSE"
    if [ "x${tokentype}x" = "xx" ]
    then
        output="$(wget ${WGETOPTIONS} --method=GET -O${outfile} --header="Accept: ${acceptmediatype}" "${resourceurl}" 2>"${stderrfile}")"
        exit_code=${?}
    elif [ "x${tokentype}x" = "xbearerx" ]
    then
        output="$(wget ${WGETOPTIONS} --method=GET -O${outfile} --header="Accept: ${acceptmediatype}" --header="Authorization: Bearer ${token}" "${resourceurl}" 2>"${stderrfile}")"
        exit_code=${?}
    else
        echo "Unknown Open ID token type: ${tokentype:-EMPTY}"
        return 1
    fi
    echoGreen "$(cat "${stderrfile}")"
    echo
    if [[ "${acceptmediatype}" =~ image/ ]]
    then
        echoYellow "OUTPUT: ${outfile}"
    else
        echoYellow "OUTPUT: ${output}"
    fi
    echo "---------------------------------------------------------------------"
    echo -n "  Response status: "
    echoWgetExitCode ${exit_code}
    echo -n "  Response HTTP code: "
    HTTPStatusCode "${stderrfile}"
    echo

    rm -f "${stderrfile}"

    return 0
}

# Perform a HTTP POST request with the help of GNU Wget
POSTrequest( ) {
    local acceptmediatype="${1}"
    local resourceurl="${2}"
    local tokentype="${3}"
    local token="${4}"
    local postmediatype="${5}"

    if [[ "${acceptmediatype}" =~ image/ ]]
    then
        outfile="$(mktemp -u ${TEMPDIR:-/tmp}/image.png.XXXXXXXXXX)"
    else
        outfile="-"
    fi

    local stderrfile="$(mktemp -u ${TEMPDIR:-/tmp}/stderr.XXXXXXXXXX)"

    if grep -q '__REPLACE_WITH_REFRESH_TOKEN__' "${6}"
    then
        echoYellow "Found __REPLACE_WITH_REFRESH_TOKEN__ in POST file ${6}, will replace it with current refresh token."
        local postfile="$(mktemp -u ${TEMPDIR:-/tmp}/postfile.XXXXXXXXXX)"
        cp -v "${6}" "${postfile}"
        sed -i "s/__REPLACE_WITH_REFRESH_TOKEN__/${LOGINREFRESHTOKEN}/" "${postfile}"
    else
        local postfile="${6}"
    fi

    echo "---------------------------------------------------------------------"
    echoYellow "RESPONSE"
    if [ "x${tokentype}x" = "xx" ]
    then
        if [ -f "${postfile}" ]
        then
            output="$(wget ${WGETOPTIONS} -O${outfile} --header="Accept: ${acceptmediatype}" --header="Content-Type: ${postmediatype}" --post-file="${postfile}" "${resourceurl}"  2>"${stderrfile}")"
            exit_code=${?}
        else
            output="$(wget ${WGETOPTIONS} -O${outfile} --header="Accept: ${acceptmediatype}" --post-data="" "${resourceurl}"  2>"${stderrfile}")"
            exit_code=${?}
        fi
    elif [ "x${tokentype}x" = "xbearerx" ]
    then
        if [ -f "${postfile}" ]
        then
            output="$(wget ${WGETOPTIONS} -O${outfile} --header="Accept: ${acceptmediatype}" --header="Authorization: Bearer ${token}" --header="Content-Type: ${postmediatype}" --post-file="${postfile}" "${resourceurl}"  2>"${stderrfile}")"
            exit_code=${?}
        else
            output="$(wget ${WGETOPTIONS} -O${outfile} --header="Accept: ${acceptmediatype}" --header="Authorization: Bearer ${token}" --post-data="" "${resourceurl}"  2>"${stderrfile}")"
            exit_code=${?}
        fi
    else
        echo "Unknown Open ID token type: ${tokentype:-EMPTY}"
        return 1
    fi
    echoGreen "$(cat "${stderrfile}")"
    echo
    if [[ "${acceptmediatype}" =~ image/ ]]
    then
        echoYellow "OUTPUT: ${outfile}"
    else
        echoYellow "OUTPUT: ${output}"
        # Parse the output if it is JSON and keep some variables
        if tickParse "${output}" 1>/dev/null 2>&1
        then
            LOGINREFRESHTOKEN=``refreshtoken``
        fi
    fi
    echo "---------------------------------------------------------------------"
    echo -n "  Response status: "
    echoWgetExitCode ${exit_code}
    echo -n "  Response HTTP code: "
    HTTPStatusCode "${stderrfile}"
    echo

    rm -f "${stderrfile}"

    return 0
}

# Connect to Open ID Connect Discovery URL and retrieve token and logout
# endpoints
getOpenIDEndpoints( ) {
    # We need to get the token endpoint from the OpenID Connect Discovery URL
    echo "Retrieving Open ID Connect Discovery data from ${OPENIDCONNECTURL}..."
    local OPENIDCONNECTJSON="$(wget ${WGETOPTIONS} -O- ${OPENIDCONNECTURL})"
    echo "Open ID Connect Discovery data:"
    echo "---------------------------------------------------------------------"
    echo "${OPENIDCONNECTJSON}"
    echo "---------------------------------------------------------------------"

    # Parse the JSON response
    tickParse "${OPENIDCONNECTJSON}" || return 1

    OPENIDCONNECTTOKEN="``token_endpoint``"
    OPENIDCONNECTLOGOUT="``end_session_endpoint``"

    echo "Open ID Connect Discovery endpoints:"
    echo "---------------------------------------------------------------------"
    echoYellow "Token:  ${OPENIDCONNECTTOKEN}"
    echoYellow "Logout: ${OPENIDCONNECTLOGOUT}"
    echo "---------------------------------------------------------------------"

    # All OK
    return 0
}

# Login with USERNAME/PASSWORD and get a Open ID Token back.
getOpenIDToken( ) {
    local REQUESTCONTENTTYPE="application/x-www-form-urlencoded"
    local REQUESTPOSTDATA="grant_type=password&client_id=${CLIENTID}&username=${USERNAME}&password=${PASSWORD}"
    local LOGINRESPONSEJSON="$(wget ${WGETOPTIONS} -O- --header="Content-Type: ${REQUESTCONTENTTYPE}" --post-data="${REQUESTPOSTDATA}" ${OPENIDCONNECTTOKEN})"
    echo "Open ID Connect Login response:"
    echo "---------------------------------------------------------------------"
    echo "${LOGINRESPONSEJSON}"
    echo "---------------------------------------------------------------------"

    # Parse the JSON response
    tickParse "${LOGINRESPONSEJSON}" || return 1

    OPENIDTOKENTYPE="``token_type``"
    OPENIDTOKEN="``access_token``"
    OPENIDREFRESHTOKEN="``refresh_token``"

    echo "Returned token"
    echo "---------------------------------------------------------------------"
    echoYellow "Access Token Type: ${OPENIDTOKENTYPE}"
    echoYellow "Access Token: ${OPENIDTOKEN}"
    echo "---------------------------------------------------------------------"

    return 0
}

# Perform a logout
performLogout( ) {
    local resourceurl="${1}"
    local tokentype="${2}"
    local token="${3}"
    local refreshtoken="${4}"
    local REQUESTCONTENTTYPE="application/x-www-form-urlencoded"
    local REQUESTPOSTDATA="client_id=${CLIENTID}&refresh_token=${refreshtoken}"

    echo "Performing logout with access token ${tokentype}: ${token}"
    if [ "x${tokentype}x" = "xbearerx" ]
    then
        output="$(wget ${WGETOPTIONS} -O- --header="Authorization: Bearer ${token}" --header="Content-Type: ${REQUESTCONTENTTYPE}" --post-data="${REQUESTPOSTDATA}" ${resourceurl})"
        exit_code=${?}
    else
        echo "Unknown Open ID token type: ${tokentype:-EMPTY}"
        return 1
    fi
    echo
    echo "OUTPUT: ${output}"
    echo "---------------------------------------------------------------------"
    echo -n "  Response status: "
    echoWgetExitCode ${exit_code}
    echo

    return 0
}


### Main program
if [ ! -f "${INPUTFILE}" ]
then
    print_usage_and_exit
fi

if [ "x${USERNAME}x" = "xNot Setx" ] \
&& [ "x${PASSWORD}x" = "xNot Setx" ] \
&& [ "x${CLIENTID}x" = "xNot Setx" ]
then
    echo "User credentials and Client ID not set, continuing with unauthorized requests."
else
    echo "User credentials and Client ID: ${USERNAME} ${PASSWORD} ${CLIENTID}"
    # Find the Access Control Manager endpoints for login/logout and login
    getOpenIDEndpoints || error_and_exit "Cannot get Open ID Connect endpoints!"
    getOpenIDToken || error_and_exit "Cannot login as ${USERNAME} to get Open ID token!"
fi

echoGreen "####################################################################"
echoGreen "#### TESTING MODULE WITH FILE ${INPUTFILE}"

echoYellow "CLIENTID: ${CLIENTID}"
echoYellow "REALM: ${REALM}"
echoYellow "SERVER_URL: ${SERVER_URL}"

# Remove the comment and empty lines and read the rest one by one
grep -v '^#\|^$' "${INPUTFILE}" | while IFS= read -r line
do
    echo
    echoYellow "#### Processing resource [${line}] ####"
    IFS='|' read -r -a fields <<< "${line}";
    method="${fields[0]}"
    mediatype="${fields[1]}"
    resource="${fields[2]}"
    posttype="${fields[3]}"
    postdata="${fields[4]}"
    echo "---------------------------------------------------------------------"
    echoYellow "REQUEST"
    echo "  HTTP method: ${method}"
    echo "  Media type: ${mediatype}"
    echo "  Resource URL: ${resource}"
    echo "  POST type: ${posttype:-Not Available}"
    echo "  POST data: ${postdata:-Not Available}"
    echo "---------------------------------------------------------------------"
    if [ "x${method}x" = "xGETx" ]
    then
        GETrequest "${mediatype}" "${resource}" "${OPENIDTOKENTYPE}" "${OPENIDTOKEN}"
    elif [ "x${method}x" = "xPOSTx" ]
    then
        # POST data file is a relative path to this script's location
        POSTrequest "${mediatype}" "${resource}" "${OPENIDTOKENTYPE}" "${OPENIDTOKEN}" "${posttype}" "${postdata}"
    else
    	echoRed  "ERROR: Unknown HTTP method ${method}"
    fi
done

echoGreen "#### END OF MODULE TESTING"
echoGreen "####################################################################"

if [ "x${USERNAME}x" = "xNot Setx" ] \
&& [ "x${PASSWORD}x" = "xNot Setx" ] \
&& [ "x${CLIENTID}x" = "xNot Setx" ]
then
    echo "User credentials and Client ID not set, no need to log out."
else
    performLogout "${OPENIDCONNECTLOGOUT}" "${OPENIDTOKENTYPE}" "${OPENIDTOKEN}" "${OPENIDREFRESHTOKEN}"
fi
