#!/usr/share/ucs-test/runner bash
# shellcheck shell=bash
## desc: Change of expired password at UMC logon (with password complexity)
## roles:
##  - domaincontroller_master
## bugs: [37916]
## tags: [skip_admember]
## packages:
##  - univention-directory-manager-tools
##  - univention-management-console
## exposure: dangerous

# shellcheck source=../../lib/base.sh
. "$TESTLIBPATH/base.sh" || exit 137
# shellcheck source=../../lib/user.sh
. "$TESTLIBPATH/user.sh" || exit 137
# shellcheck source=../../lib/udm.sh
. "$TESTLIBPATH/udm.sh" || exit 137
# shellcheck source=../../lib/random.sh
. "$TESTLIBPATH/random.sh" || exit 137
# shellcheck source=../../lib/undo.sh
. "$TESTLIBPATH/undo.sh" || exit 137
# shellcheck source=../../lib/ucr.sh
. "$TESTLIBPATH/ucr.sh" || exit 137

## INITIAL_PASSWORD is chosen by user_create:
INITIAL_PASSWORD=univention
PASSWORD=Noitnevinu.1
SHORT_PASSWORD=Test
SIMPLE_PASSWORD=chocolate
SIMILAR_PASSWORD=Noitnevinu.2
COMPLEX_PASSWORD="fnGHX5%§d0"

RETVAL=100
TARGET="localhost"
COOKIEJAR="$(mktemp)" && undo rm "$COOKIEJAR"

test_username=$(user_randomname)
user_create "$test_username" &&
	undo user_remove "$test_username" ||
	fail_fast 140 "cannot create user $test_username"

test_userdn=$(user_dn "$test_username")




if ! checkpkg "univention-samba4"; then
	echo "### Preparation: Activate pwQualityCheck in policies/pwhistory"
	echo "## Note: non-Samba4 DCs require this to activate univention.password.Check (for check_cracklib.py)"
	orig_pwQualityCheck=$(udm policies/pwhistory list --filter name=default-settings | sed -n 's/^  pwQualityCheck: //p')

	reset_pwpolicy() {
		if [ -n "$orig_pwQualityCheck" -a "$orig_pwQualityCheck" != "None" ]; then
			udm_modify "policies/pwhistory" "" "" "" "default-settings" \
				--set pwQualityCheck="$orig_pwQualityCheck"
		else
			udm_modify "policies/pwhistory" "" "" "" "default-settings" \
				--remove pwQualityCheck
		fi
	}

	udm_modify "policies/pwhistory" "" "" "" "default-settings" \
		--set pwQualityCheck=TRUE \
		&& undo reset_pwpolicy \
		|| fail_fast 140 "cannot adjust policies/pwhistory"

	## Active Directory password comlexity is a bit different, but this ok to test check_cracklib.py:
	ucr set password/quality/credit/lower=1 \
		password/quality/credit/upper=1 \
		password/quality/credit/other=1 \
		password/quality/credit/digits=1 \
		&& undo ucr_restore
fi


parse_status() {
	python3 -c "$(printf 'import sys, json;\ntry:\n\tstdin = sys.stdin.read(); print(json.loads(stdin).get("status"));\nexcept ValueError:\n\tprint("Failed to decode", repr(stdin)); raise')"
}

umc_request() {
	info "Executing: curl -s -H 'Accept: application/json; q=1, */*' -H 'Accept-Language: en-US' --cookie-jar '$COOKIEJAR' $@"
	out=$(curl -s -H 'Accept: application/json; q=1, */*' -H 'Accept-Language: en-US' -H 'X-Requested-With: XMLHttpRequest' --cookie-jar "$COOKIEJAR" "$@" 2>/dev/stderr)
	info "Response was: $out"
	echo "$out"
}

wait_samba_attr() {
	if [ ! -x "$(which univention-s4search)" ] || [ ! -f "/var/lib/samba/private/sam.ldb" ]; then
		return
	fi
	local cn=$1
	local attr=$2
	local value=$3
	for i in $(seq 1 24); do
		if univention-s4search cn="$cn" "$attr" | grep "^$attr: $value$"; then
			echo "$attr for $cn is $value"
			return
		else
			echo "$attr for $cn not yet $value"
			sleep 5
		fi
	done
}

## simulate expiration of the user password, (required! otherwise UMC doesn't inititate password reset)
echo "### Preparation: simulate password expiry"
udm_modify "users/user" "" "" "" "$test_username" --set pwdChangeNextLogin=1

wait_for_replication_and_postrun
wait_samba_attr "$test_username" "pwdLastSet" "0"

echo "### Preparation: set fresh complex password via UMC login password change dialog"
output=$(umc_request -H "Content-Type:application/json" -d "{\"options\":{\"username\":\"$test_username\",\"password\":\"$INITIAL_PASSWORD\",\"new_password\":\"$PASSWORD\"}}" "http://$TARGET/univention/auth" | parse_status)
if [ "200" != "$output" ]; then
	fail_fast 110 "Unexpected output returned by UMC during password change: $output"
fi

wait_for_replication_and_postrun

echo "### Preparation: confirm UMC login with fresh complex password"
output=$(umc_request -H Content-Type:application/json -d "{\"options\":{\"username\":\"$test_username\",\"password\":\"$PASSWORD\"}}" "http://$TARGET/univention/auth" | parse_status)
if [ "200" != "$output" ]; then
	fail_fast 110 "Unexpected output returned by UMC after initial password change: $output"
fi




echo "### Basic test: UMC login with invalid user"
output=$(umc_request -H Content-Type:application/json -d "{\"options\":{\"username\":\"UNKNOWN$test_username\",\"password\":\"$PASSWORD\"}}" "http://$TARGET/univention/auth" | parse_status)
if [ "401" != "$output" ]; then
	fail_fast 110 "UMC authentication with invalid user succeeded!"
fi

echo "### Basic test: UMC login with invalid password"
output=$(umc_request -H Content-Type:application/json -d "{\"options\":{\"username\":\"$test_username\",\"password\":\"INVALID$PASSWORD\"}}" "http://$TARGET/univention/auth" | parse_status)
if [ "401" != "$output" ]; then
	fail_fast 110 "UMC authentication with invalid password succeeded!"
fi





## simulate expiration of the user password again, otherwise UMC doesn't inititate password reset
echo "### Preparation: simulate password expiry"
udm_modify "users/user" "" "" "" "$test_username" --set pwdChangeNextLogin=1

wait_for_replication_and_postrun
wait_samba_attr "$test_username" "pwdLastSet" "0"

echo "### Basic test: UMC login against expired password"
output=$(umc_request -H Content-Type:application/json -d "{\"options\":{\"username\":\"$test_username\",\"password\":\"$PASSWORD\"}}" "http://$TARGET/univention/auth")
if [ "401" != "$(parse_status <<<"$output")" ]; then
	fail_fast 110 "UMC authentication against expired password succeeded!"
fi
if [ "$(echo "$output" | python3 -c "import json, sys; output = json.load(sys.stdin); print(output.get('message'))")" != "The password has expired and must be renewed." ]; then
	fail_test 110 "unexpected message returned by UMC: $output, expected: The password has expired"
fi




if [ -x "$(which samba-tool)" ]; then
	samba-tool domain passwordsettings show
fi

echo "### Test: Try setting short password in UMC login password change dialog"
output=$(umc_request -H Content-Type:application/json -d "{\"options\":{\"username\":\"$test_username\",\"password\":\"$PASSWORD\",\"new_password\":\"$SHORT_PASSWORD\"}}" "http://$TARGET/univention/auth")
if [ -z "$output" ]; then
	fail_fast 110 "'UMC allowed setting a too short password!"
fi

TOO_SHORT_MESSAGE="Changing password failed. The password is too short."
if checkpkg "univention-samba4"; then
	TOO_SHORT_MESSAGE="$TOO_SHORT_MESSAGE The password must consist of at least 8 characters."
fi
if [ "$(echo "$output" | python3 -c "import json, sys; output = json.load(sys.stdin); print(output.get('message'))")" != "$TOO_SHORT_MESSAGE" ]; then
	fail_test 110 "unexpected message returned by UMC while trying to set short password: $output"
fi
if [ "$(echo "$output" | python3 -c "import json, sys; output = json.load(sys.stdin); print(output['status'])")" != "401" ]; then
	fail_test 110 "unexpected status returned by UMC while trying to set short password: $output"
fi





echo "### Test: Try setting simple password in UMC login password change dialog"
output=$(umc_request -H Content-Type:application/json -d "{\"options\":{\"username\":\"$test_username\",\"password\":\"$PASSWORD\",\"new_password\":\"$SIMPLE_PASSWORD\"}}" "http://$TARGET/univention/auth")
if [ -z "$output" ]; then
	fail_fast 110 "'UMC allowed setting a too simple password!"
fi

if checkpkg "univention-samba4"; then
	expected_message="Changing password failed. The password is too simple."
else
	expected_message="Changing password failed. The password is based on a dictionary word."
fi
if [ "$(echo "$output" | python3 -c "import json, sys; output = json.load(sys.stdin); print(output.get('message'))")" != "$expected_message" ]; then
	fail_test 110 "unexpected message returned by UMC while trying to set simple password: $output"
fi
if [ "$(echo "$output" | python3 -c "import json, sys; output = json.load(sys.stdin); print(output['status'])")" != "401" ]; then
	fail_test 110 "unexpected status returned by UMC while trying to set simple password: $output"
fi





echo "### Test: Set complex but similar password in UMC login password change dialog (should work currently)"
output=$(umc_request -H Content-Type:application/json -d "{\"options\":{\"username\":\"$test_username\",\"password\":\"$PASSWORD\",\"new_password\":\"$SIMILAR_PASSWORD\"}}" "http://$TARGET/univention/auth" | parse_status)
if [ "200" != "$output" ]; then
	fail_fast 110 "'UMC didn't allowed setting a similar complex password!"
fi

wait_for_replication_and_postrun

# try it multiple times, sometimes s4/drs/... take a while
sleep 15
echo "### Test: Confirm UMC login with new password"
for i in $(seq  10); do
	output=$(umc_request -H Content-Type:application/json -d "{\"options\":{\"username\":\"$test_username\",\"password\":\"$SIMILAR_PASSWORD\"}}" "http://$TARGET/univention/auth" | parse_status)
	test $output = "200" && break
	sleep 5
done

if [ "200" != "$output" ]; then
	fail_test 110 "Unexpected output returned by UMC after password change: $output"
fi





### SIMILAR_PASSWORD check is currently enough
#
# ## simulate expiration of the user password again, otherwise UMC doesn't inititate password reset
# echo "### Preparation: simulate password expiry"
# udm_modify "users/user" "" "" "" "$test_username" --set pwdChangeNextLogin=1
#
# wait_for_replication_and_postrun
# 
# echo "### Test: Set complex password in UMC login password change dialog"
# output=$(umc_request -d "{\"options\":{\"username\":\"$test_username\",\"password\":\"$PASSWORD\",\"new_password\":\"$COMPLEX_PASSWORD\"}}" -H "Content-Type:application/json" "http://$TARGET/univention/auth")
# if [ -n "$output" ]; then
# 	fail_fast 110 "Unexpected output returned by UMC during password change: $output"
# fi
# 
# wait_for_replication_and_postrun
# 
# echo "### Test: Finally confirm UMC login with new password"
# output=$(umc_request -d "{\"options\":{\"username\":\"$test_username\",\"password\":\"$COMPLEX_PASSWORD\"}}" -H "Content-Type:application/json" "http://$TARGET/univention/auth")
# if [ -n "$output" ]; then
# 	fail_test 110 "Unexpected output returned by UMC after password change: $output"
# fi



exit "$RETVAL"
