#!/bin/bash
# This file is in the public domain.
# Runs tests with a 'hanging' Anastasis provider.

set -eu
#set -x

# Exit, with status code "skip" (no 'real' failure)
function exit_skip() {
    echo " SKIP: $1"
    exit 77
}

# Exit, with error message (hard failure)
function exit_fail() {
    echo " FAIL: $1"
    exit 1
}

# Cleanup to run whenever we exit
function cleanup()
{
    for n in $(jobs -p)
    do
        kill -SIGCONT $n # in case suspended...
        kill $n 2> /dev/null || true
    done
    rm -rf $CONF $R1FILE $R2FILE $B1FILE $B2FILE $TMP_DIR
    wait
}

function sync_providers() {
  infile=$1
  outfile=$2
  echo "Synchronizing providers"
  # Sync with providers (up to 3 providers aren't synced here)
  for x in 1 2 3; do
    echo "Synchronizing providers (round $x)"
    #anastasis-reducer sync_providers < $infile > $outfile 2> /dev/null || true
    anastasis-reducer sync_providers < $infile > $outfile || true
    CODE=$(jq -r -e ".code // 0" < $outfile)
    # ANASTASIS_REDUCER_PROVIDERS_ALREADY_SYNCED
    # FIXME: Temporary workaround for C reducer. See #7227.
    if test "$CODE" = "8420"; then
      # restore previous non-error state
      cat $infile > $outfile
      break
    fi
    # ANASTASIS_REDUCER_ACTION_INVALID
    if test "$CODE" = "8400"; then
      # restore previous non-error state
      cat $infile > $outfile
      break
    fi
    if test "$CODE" != "0"; then
      exit_fail "Expected no error or 8420/8400, got $CODE"
    fi
    cat $outfile > $infile
  done
  echo "Providers synced."
}


CONF_1="test_anastasis_reducer_free_1.conf"
CONF_2="test_anastasis_reducer_free_2.conf"
CONF_3="test_anastasis_reducer_free_3.conf"
CONF_4="test_anastasis_reducer_free_4.conf"


# Configuration file will be edited, so we create one
# from the template.
CONF=$(mktemp test_reducerXXXXXX.conf)
cp test_reducer.conf $CONF

TMP_DIR=`mktemp -d keys-tmp-XXXXXX`
B1FILE=`mktemp test_reducer_stateB1XXXXXX`
B2FILE=`mktemp test_reducer_stateB2XXXXXX`
R1FILE=`mktemp test_reducer_stateR1XXXXXX`
R2FILE=`mktemp test_reducer_stateR2XXXXXX`
export B1FILE
export B2FILE
export R1FILE
export R2FILE

# Install cleanup handler (except for kill -9)
trap cleanup EXIT

# Check we can actually run
echo -n "Testing for jq"
jq -h > /dev/null || exit_skip "jq required"
echo " FOUND"
echo -n "Testing for timeout"
timeout --help > /dev/null || exit_skip "timeout required"
echo " FOUND"
echo -n "Testing for anastasis-reducer ..."
anastasis-reducer -h > /dev/null || exit_skip "anastasis-reducer required"
echo " FOUND"

echo -n "Testing for anastasis-httpd"
anastasis-httpd -h >/dev/null </dev/null || exit_skip " MISSING"
echo " FOUND"

echo -n "Initialize anastasis database ..."
# Name of the Postgres database we will use for the script.
# Will be dropped, do NOT use anything that might be used
# elsewhere
TARGET_DB_1=`anastasis-config -c $CONF_1 -s stasis-postgres -o CONFIG | sed -e "s/^postgres:\/\/\///"`
TARGET_DB_2=`anastasis-config -c $CONF_2 -s stasis-postgres -o CONFIG | sed -e "s/^postgres:\/\/\///"`
TARGET_DB_3=`anastasis-config -c $CONF_3 -s stasis-postgres -o CONFIG | sed -e "s/^postgres:\/\/\///"`
TARGET_DB_4=`anastasis-config -c $CONF_4 -s stasis-postgres -o CONFIG | sed -e "s/^postgres:\/\/\///"`

dropdb $TARGET_DB_1 >/dev/null 2>/dev/null || true
createdb $TARGET_DB_1 || exit_skip "Could not create database $TARGET_DB_1"
anastasis-dbinit -c $CONF_1 2> anastasis-dbinit_1.log
dropdb $TARGET_DB_2 >/dev/null 2>/dev/null || true
createdb $TARGET_DB_2 || exit_skip "Could not create database $TARGET_DB_2"
anastasis-dbinit -c $CONF_2 2> anastasis-dbinit_2.log
dropdb $TARGET_DB_3 >/dev/null 2>/dev/null || true
createdb $TARGET_DB_3 || exit_skip "Could not create database $TARGET_DB_3"
anastasis-dbinit -c $CONF_3 2> anastasis-dbinit_3.log
dropdb $TARGET_DB_4 >/dev/null 2>/dev/null || true
createdb $TARGET_DB_4 || exit_skip "Could not create database $TARGET_DB_4"
anastasis-dbinit -c $CONF_4 2> anastasis-dbinit_4.log

echo " OK"

echo -n "Launching anastasis services ..."
PREFIX="" #valgrind
$PREFIX anastasis-httpd -c $CONF_1 2> anastasis-httpd_1.log &
PPID_1=$!
$PREFIX anastasis-httpd -c $CONF_2 2> anastasis-httpd_2.log &
PPID_2=$!
$PREFIX anastasis-httpd -c $CONF_3 2> anastasis-httpd_3.log &
PPID_3=$!
$PREFIX anastasis-httpd -c $CONF_4 2> anastasis-httpd_4.log &
PPID_4=$!
export PPID_1
export PPID_2
export PPID_3
export PPID_4

echo -n "Waiting for anastasis services ..."

# Wait for anastasis services to be available
for n in `seq 1 50`
do
    echo -n "."
    sleep 0.1
    OK=0
   # anastasis_01
    wget --tries=1 --timeout=1 http://localhost:8086/ -o /dev/null -O /dev/null >/dev/null || continue
    # anastasis_02
    wget --tries=1 --timeout=1 http://localhost:8087/ -o /dev/null -O /dev/null >/dev/null || continue
    # anastasis_03
    wget --tries=1 --timeout=1 http://localhost:8088/ -o /dev/null -O /dev/null >/dev/null || continue
    # anastasis_04
    wget --tries=1 --timeout=1 http://localhost:8089/ -o /dev/null -O /dev/null >/dev/null || continue
    OK=1
    break
done

if [ 1 != $OK ]
then
    exit_skip "Failed to launch anastasis services"
fi
echo "OK"

echo -n "Running backup logic ...,"
anastasis-reducer -b > $B1FILE
echo -n "."
anastasis-reducer -a \
  '{"continent": "Demoworld"}' \
  select_continent < $B1FILE > $B2FILE
echo -n "."
anastasis-reducer -a \
  '{"country_code": "xx"}' \
  select_country < $B2FILE > $B1FILE
echo -n "."

kill -SIGSTOP $PPID_4
START=`date '+%s'`
timeout 10 anastasis-reducer -L DEBUG -a \
  '{"identity_attributes": {
    "full_name": "Max Musterman",
    "sq_number": "4",
    "birthdate": "2000-01-01"}}' \
        enter_user_attributes < $B1FILE > $B2FILE || true
END=`date '+%s'`
DELTA=`expr $END - $START`
kill -SIGCONT $PPID_4

if test $DELTA -ge 5
then
    exit_fail "Reducer hangs on suspended provider in 'enter_user_attributes'"
fi

cat $B2FILE > $B1FILE
echo -n ","
sync_providers $B1FILE $B2FILE
echo -n ","
# "91GPWWR" encodes "Hans"
anastasis-reducer -a \
  '{"authentication_method": {
    "type": "question",
    "instructions": "What is your name?",
    "challenge": "91GPWWR"
    } }' \
  add_authentication < $B2FILE > $B1FILE
echo -n "."
# "64S36" encodes "123"
anastasis-reducer -a \
  '{"authentication_method": {
    "type": "question",
    "instructions": "How old are you?",
    "challenge": "64S36"
    } }' \
  add_authentication < $B1FILE > $B2FILE
echo -n "."
# "9NGQ4WR" encodes "Mars"
anastasis-reducer -a \
  '{"authentication_method": {
    "type": "question",
    "instructions": "Where do you live?",
    "challenge": "9NGQ4WR"
    } }' \
  add_authentication < $B2FILE > $B1FILE
echo -n "."
# Finished adding authentication methods
anastasis-reducer \
    next < $B1FILE > $B2FILE


echo -n ","
# Finished policy review
anastasis-reducer \
  next < $B2FILE > $B1FILE
echo -n "."

# Note: 'secret' must here be a Crockford base32-encoded value
anastasis-reducer -a \
  '{"secret": { "value" : "VERYHARDT0GVESSSECRET", "mime" : "text/plain" }}' \
  enter_secret < $B1FILE > $B2FILE
anastasis-reducer next $B2FILE $B1FILE
echo " OK"

echo -n "Final backup checks ..."
STATE=`jq -r -e .backup_state < $B1FILE`
if test "$STATE" != "BACKUP_FINISHED"
then
    exit_fail "Expected new state to be 'BACKUP_FINISHED', got '$STATE'"
fi

jq -r -e .core_secret < $B1FILE > /dev/null && exit_fail "'core_secret' was not cleared upon success"

echo " OK"


echo -n "Running recovery basic logic ..."
anastasis-reducer -r > $R1FILE
anastasis-reducer -a \
  '{"continent": "Demoworld"}' \
  select_continent < $R1FILE > $R2FILE
anastasis-reducer -a \
  '{"country_code": "xx" }' \
  select_country < $R2FILE > $R1FILE
anastasis-reducer -a '{"identity_attributes": { "full_name": "Max Musterman", "sq_number": "4", "birthdate": "2000-01-01" }}' enter_user_attributes < $R1FILE > $R2FILE


STATE=`jq -r -e .recovery_state < $R2FILE`
if test "$STATE" != "SECRET_SELECTING"
then
    exit_fail "Expected new state to be 'SECRET_SELECTING', got '$STATE'"
fi
echo " OK"

echo -n "Adding provider (to ensure it is loaded)"
anastasis-reducer -a '{"provider_url" : "http://localhost:8086/" }' add_provider < $R2FILE > $R1FILE
echo " OK"

echo -n "Selecting secret to recover"
anastasis-reducer -a '{"attribute_mask": 0, "providers" : [ { "version": 1, "url" : "http://localhost:8086/" } ] }' select_version < $R1FILE > $R2FILE

STATE=`jq -r -e .recovery_state < $R2FILE`
if test "$STATE" != "CHALLENGE_SELECTING"
then
    exit_fail "Expected new state to be 'CHALLENGE_SELECTING', got '$STATE'"
fi
echo " OK"

cat $R2FILE > $R1FILE
sync_providers $R1FILE $R2FILE

echo -n "Running challenge logic ..."

cat $R2FILE | jq .

UUID0=`jq -r -e .recovery_information.challenges[0].uuid < $R2FILE`
UUID1=`jq -r -e .recovery_information.challenges[1].uuid < $R2FILE`
UUID2=`jq -r -e .recovery_information.challenges[2].uuid < $R2FILE`
UUID0Q=`jq -r -e .recovery_information.challenges[0].instructions < $R2FILE`
UUID1Q=`jq -r -e .recovery_information.challenges[1].instructions < $R2FILE`
UUID2Q=`jq -r -e .recovery_information.challenges[2].instructions < $R2FILE`

if test "$UUID2Q" = 'How old are you?'
then
    AGE_UUID=$UUID2
elif test "$UUID1Q" = 'How old are you?'
then
    AGE_UUID=$UUID1
else
    AGE_UUID=$UUID0
fi

if test "$UUID2Q" = 'What is your name?'
then
    NAME_UUID=$UUID2
elif test "$UUID1Q" = 'What is your name?'
then
    NAME_UUID=$UUID1
else
    NAME_UUID=$UUID0
fi

anastasis-reducer -a \
  "$(jq -n '
    {
        uuid: $UUID
    }' \
    --arg UUID "$NAME_UUID"
  )" \
  select_challenge < $R2FILE > $R1FILE

anastasis-reducer -a '{"answer": "Hans"}' \
  solve_challenge < $R1FILE > $R2FILE

anastasis-reducer -a \
  "$(jq -n '
    {
        uuid: $UUID
    }' \
    --arg UUID "$AGE_UUID"
  )" \
  select_challenge < $R2FILE > $R1FILE

anastasis-reducer -a '{"answer": "123"}' \
  solve_challenge < $R1FILE > $R2FILE

echo " OK"

echo -n "Checking recovered secret ..."
# finally: check here that we recovered the secret...

STATE=$(jq -r -e .recovery_state < "$R2FILE")
if test "$STATE" != "RECOVERY_FINISHED"
then
    jq -e . $R2FILE
    exit_fail "Expected new state to be 'RECOVERY_FINISHED', got '$STATE'"
fi

SECRET=$(jq -r -e .core_secret.value < "$R2FILE")
if test "$SECRET" != "VERYHARDT0GVESSSECRET"
then
    jq -e . "$R2FILE"
    exit_fail "Expected recovered secret to be 'VERYHARDT0GVESSSECRET', got '$SECRET'"
fi

MIME=$(jq -r -e .core_secret.mime < "$R2FILE")
if test "$MIME" != "text/plain"
then
    jq -e . $R2FILE
    exit_fail "Expected recovered mime to be 'text/plain', got '$MIME'"
fi

echo " OK"

exit 0
