#!/bin/bash
# Copyright 2019-2021 Chad D. Lemmen http://www.lemmen.com
#
# UK Making Tax Digital (MTD)
# https://developer.service.hmrc.gov.uk/api-documentation
 
#
# Modification History
#
# 2021-09-07 CDL - Gov-Client-Multi-Factor must be percent encoded.
#
# 2021-06-11 CDL - Do not percent encode Gov-Client-Local-IPs-Timestamp
#                  the specification does not call for it and the header
#                  was being rejected when encoded.

# Put the passed options into an array
passopt=($(echo " $*" | grep -o " -[[:alpha:]]"))


# Show available options
read -d '' showhelp <<EOD

Usage: $0 [options]

  -a <access_token>   OAuth 2.0 Access Token 
  -b <begin_date>     Date from which to return obligations YYYY-MM-DD
  -c <auth_code>      Authorization Code
  -e <end_date>       Date to which to return obligations YYYY-MM-DD
  -f <file>           JSON file to read
  -g <test_scenario>  Gov-Test-Scenario header e.g. QUARTERLY_NONE_MET
  -h                  Show help
  -i <client_id>      Application Client ID
  -o                  Retrieve VAT obligations
  -r                  Submit VAT Return
  -s <client_secret>  Application Client Secret
  -t                  Get OAuth 2.0 access token
  -u                  Get authorization URL
  -v <vrn>            VAT Registration Number

EOD

if [ -z "$1" ]; then # No options passed
  echo "$0: try '$0 -h' for more information"
  exit
fi

# Get runtime options...
while getopts 'a:b:c:e:f:g:hi:oprs:tuv:' opt; do
  case "$opt" in
    a)
      accesstoken="$OPTARG"
      ;;
    b)
      begindate="$OPTARG"
      ;;
    c)
      authcode="$OPTARG"
      ;;
    e)
      enddate="$OPTARG"
      ;;
    f)
      jsonfile="$OPTARG"
      ;;
    g)
      gov_test_scenario="$OPTARG"
      ;;
    h)
      echo
      echo "$showhelp"
      echo
      exit
      ;;
    i)
      clientid="$OPTARG"
      ;;
    o)
      obligations=true
      ;;
    p)
      testfraudheaders=true
      ;;
    r)
      submitreturn=true
      ;;
    s)
      clientsecret="$OPTARG"
      ;;
    t)
      gettoken=true
      ;;
    u)
      getauthurl=true
      ;;
    v)
      vrn="$OPTARG"
      ;;
    *)
      exit # getopts reports the illegal option
      ;;
  esac
done


check_required_options() {
  local option=$1
  local requires=($2)

  if [[ "${passopt[@]}" =~ "$option" ]]; then
    for r in "${requires[@]}"; do
      if [[ ! "${passopt[@]}" =~ "$r" ]]; then
        echo "Option ${option} requires option(s) ${requires[@]}" 
        exit 1
      fi
    done
  fi
}

# Some options require other options
check_required_options -u "-i"
check_required_options -t "-c -i -s"
check_required_options -o "-a -v -b -e"
check_required_options -r "-a -v -f"
check_required_options -p "-a"


# Production base URL
baseurl='https://api.service.hmrc.gov.uk'
# To use the test system export SS_MTD_TEST=1
if [ -n "$SS_MTD_TEST" ]; then
  # Sandbox base URL
  baseurl='https://test-api.service.hmrc.gov.uk'
fi


percent_encode() {
  # percent encode colon and remove trailing comma
  sed -e 's/:/%3A/g' -e 's/,$//' <<< "$1"
}


# https://developer.service.hmrc.gov.uk/api-documentation/docs/fraud-prevention
function fraud_headers() {
  # Application connection method header
  # DESKTOP_APP_DIRECT
  # Installed desktop application connecting directly to HMRC

  ssname=Stansoft
  ssver=7.26
  arrhead=("Gov-Client-Connection-Method: DESKTOP_APP_DIRECT")

  # UTC format yyyy-MM-ddThh:mm:ss.sssZ
  # 2021-06-11 CDL
  # Do not percent encode
  #timestamp=$(percent_encode $(date -u +%FT%T.%3NZ))
  timestamp=$(date -u +%FT%T.%3NZ)

  # The file should be readable by everyone
  idfile=/tmp/.stansoft-id
  for a in {1..2}; do
    if [ -e "$idfile" ]; then
      devid=$(< "$idfile")
    fi
    if [ -z "$devid" ]; then
      uuidgen > "$idfile"
      chmod 666 "$idfile"
      continue
    fi
    break
  done

  arrhead+=("Gov-Client-Device-ID: $devid")
  arrhead+=("Gov-Client-User-IDs: os=$(logname)")

  timezone=UTC$(date +%:z)
  arrhead+=("Gov-Client-Timezone: $timezone")

  # IP and MAC addresses need to be percent encoded. A colon is %3A.
  # Get the local ip addresses
  arrip=($(/sbin/ifconfig | grep 'inet' | awk '{print $2}'))
  if [ ${#arrip[@]} -eq 0 ]; then # array is empty
    arrip=($(hostname -I))
  fi
  for i in "${arrip[@]}"; do
    iplist+="$i,"
  done 
  # Remove the trailing comma from the list and percent encode colons
  #iplist=$(sed 's/:/%3A/g' <<< "${iplist%?}") 
  iplist=$(percent_encode "$iplist")
  arrhead+=("Gov-Client-Local-IPs: $iplist")
  arrhead+=("Gov-Client-Local-IPs-Timestamp: $timestamp")

  # Get the local MAC addresses
  arrmac=($(/sbin/ifconfig | grep 'ether' | awk '{print $2}')) 
  for i in "${arrmac[@]}"; do
    maclist+="$i,"
  done
  # Remove the trailing comma from the list and percent encode colons
  #maclist=$(sed 's/:/%3A/g' <<< "${maclist%?}")
  maclist=$(percent_encode "${maclist%?}")
  arrhead+=("Gov-Client-MAC-Addresses: $maclist") 

  # Get the screen information
  # This will get the dimensions of the screen the terminal is running on,
  # but this will only work if X is running and X11 forwarding is used.
  width=$(xwininfo -root 2>/dev/null | grep Width | awk '{print $2}')
  height=$(xwininfo -root 2>/dev/null | grep Height | awk '{print $2}')
  depth=$(xwininfo -root 2>/dev/null | grep Depth | awk '{print $2}')
  if [ -z "$width" ]; then
    width=724
    height=436
    depth=24
  fi
  arrhead+=("Gov-Client-Screens: width=$width&height=$height&scaling-factor=1&colour-depth=$depth")

  # Window size in pixels
  # stty size, shows rows and columns and Stansoft screens use 80x24
  arrhead+=("Gov-Client-Window-Size: width=724&height=436")
  # We could use {vendor,name,version} if we need version
  # Spaces need to be percent encoded, but we will just take the
  # first and last array elements.
  amfg=($(cat /sys/devices/virtual/dmi/id/board_{vendor,name} 2>/dev/null))
  if [ ${#amfg[@]} -lt 2 ]; then
    # Not all motherboards have board_{vendor,name} so get uname info
    amfg=($(uname -sm))
  fi
  #arrhead+=("Gov-Client-User-Agent: $(uname -o) ("${amfg[0]}"/"${amfg[-1]}")")
  arrhead+=("Gov-Client-User-Agent: os-family=$(uname -s)&os-version=$(uname -r)&device-manufacturer=$(uname -m)&device-model=$(uname -m)")
  arrhead+=("Gov-Vendor-Version: $ssname=$ssver")

  ref=$(tr -cd '[:alnum:]' <<< /dev/urandom | head -c8)

  # 2021-09-07 CDL percent encode timestamp for Gov-Client-Multi-Factor
  arrhead+=("Gov-Client-Multi-Factor: type=OTHER&timestamp=$(percent_encode $timestamp)&unique-reference=$ref")
  arrhead+=("Gov-Vendor-License-IDs: $ssname=$(echo $ssname$(uname -n) | md5sum | cut -d' ' -f1)")
  arrhead+=("Gov-Vendor-Product-Name: $ssname")

  # Add -H delimiters to the headers
  for i in "${arrhead[@]}"; do
    fraudhead+=(-H "$i")
  done

  # some versions of bash didn't work to call function as $(fraud_headers)
  #echo "${fraudhead[@]}"
}


# function to transfer data from or to a json endpoint
endpoint_transfer() {

fraud_headers

case "$1" in
  authorize_url)
      endpoint="/oauth/authorize?response_type=code"
      parameter=("&client_id=$clientid"
                 "&scope=write:vat+read:vat"
                 "&redirect_uri=urn:ietf:wg:oauth:2.0:oob")
      endpoint="$endpoint"$(printf '%s' "${parameter[@]}")
      headers=("${fraudhead[@]}")
      # using an array because of the spaces
      curlopts=(-ILs -o /dev/null -w '%{url_effective} \n%{http_code}')
      ;;
  token)
      endpoint="/oauth/token"
      parameter="client_secret=${clientsecret}&client_id=${clientid}&grant_type=authorization_code&redirect_uri=urn:ietf:wg:oauth:2.0:oob&code=${authcode}"
      header=("${fraudhead[@]}")
      curlopts=(-X POST -s -w '\n%{http_code}' --data "$parameter")
      ;;
  retrieve_vat_obligations)
      endpoint="/organisations/vat/$vrn/obligations"
      parameter="from=${begindate}&to=${enddate}"
      headers=("${fraudhead[@]}" \
               -H "Accept: application/vnd.hmrc.1.0+json" \
               -H "Authorization: Bearer $accesstoken" \
               -H "Gov-Test-Scenario: $gov_test_scenario")
      curlopts=(-G -s -w '\n%{http_code}' -d "$parameter")
      ;;
  submit_vat_return)
      endpoint="/organisations/vat/$vrn/returns"
      headers=("${fraudhead[@]}" \
               -H "Accept: application/vnd.hmrc.1.0+json" \
               -H "Content-Type: application/json" \
               -H "Authorization: Bearer $accesstoken")
      curlopts=(-s -w '\n%{http_code}' -d @"$jsonfile")
      ;;
  test_fraud_headers)
      endpoint="/test/fraud-prevention-headers/validate"
      headers=("${fraudhead[@]}")
      headers+=(-H "Accept: application/vnd.hmrc.1.0+json")
      headers+=(-H "Authorization: Bearer $accesstoken")
      curlopts=(-s -w '\n%{http_code}')
      ;;
  *)
      echo "Unknown endpoint type"
      exit
      ;;
esac

# debug
if [ -n "$SS_MTD_TEST" ] && [ -t 1 ]; then # stdout is a terminal
  echo curl "${curlopts[@]}" "${headers[@]}" "${baseurl}${endpoint}"
fi
response=$(curl "${curlopts[@]}" "${headers[@]}" "${baseurl}${endpoint}")
response=(${response[@]}) # convert to array
jsonresp=${response[@]::${#response[@]}-1} # get all elements except last
httpcode=${response[-1]} # get last element (last line)

# The authorize endpoint does not return a JSON response
# for either an HTTP error or the auth url
if [ "$1" = "authorize_url" ]; then
  return
fi

# HTTP status codes used by HMRC
# 200 to 299 if it succeeded
# 400 to 499 if it failed because of a client error by your application
# 500 to 599 if it failed because of an error on our server

jq '.' <<< "$jsonresp"
#echo "$jsonresp"

if [ $httpcode -ge 400 -a $httpcode -le 599 ]; then
  exit 1
fi
if [ $httpcode -ge 200 -a $httpcode -le 299 ]; then
  return
fi
}


# Getting an OAuth 2.0 access token
#
# Send your user to our authorization endpoint
# so they can request authorization. 
# The user will need to copy/paste the returned url
# into their web browser and then copy/paste the
# authorization code back into the application.
# The authorization code, if authorization is successful
# is a single-use token that expires 10 minutes after it's issued.
if [ "$getauthurl" == "true" ]; then
  endpoint_transfer authorize_url

  if [ $httpcode -ge 400 ]; then
    # No auth url was returned, show HTTP error code
    echo HTTP $httpcode
    ret=1
  else
    # Return the authorization URL
    echo "$jsonresp"
    ret=0
  fi

  exit $ret
fi


# Exchange authorization code for access token 
# within 10 minutes before it expires.
# A user's access_token expires after 4 hours.
if [ "$gettoken" == "true" ]; then
  endpoint_transfer token
fi


# Retrieve VAT obligations. This endpoint is one of the two mandatory
# endpoints to use to achieve the minimum level of compliance.
# This endpoint is user-restricted - it requires an Authorization header
# containing an OAuth 2.0 Bearer Token with the read:vat scope.
if [ "$obligations" == "true" ]; then
  endpoint_transfer retrieve_vat_obligations
fi


# Submit VAT return for period. This endpoint is one of the two mandatory
# endpoints to use to achieve the minimum level of compliance. 
# This endpoint is user-restricted - it requires an Authorization header
# containing an OAuth 2.0 Bearer Token with the write:vat scope
if [ "$submitreturn" == "true" ]; then
  endpoint_transfer submit_vat_return
fi


# Test fraud prevention headers endpoint
if [ "$testfraudheaders" == "true" ]; then
  endpoint_transfer test_fraud_headers
fi

