a1bac0f764
Co-authored-by: Michael Hoennig <michael@hoennig.de> Reviewed-on: https://dev.hostsharing.net/hostsharing/hs.hsadmin.ng/pulls/220 Reviewed-by: Marc Sandlus <hsh-marcsandlus@noreply.dev.hostsharing.net> Co-authored-by: Michael Hoennig <michael.hoennig@hostsharing.net> Co-committed-by: Michael Hoennig <michael.hoennig@hostsharing.net>
282 lines
9.7 KiB
Bash
Executable File
282 lines
9.7 KiB
Bash
Executable File
#!/bin/bash
|
|
|
|
if [ "$1" == "--show-password" ]; then
|
|
HSADMINNG_JWT_SHOW_PASSWORD=yes
|
|
shift
|
|
else
|
|
HSADMINNG_JWT_SHOW_PASSWORD=
|
|
fi
|
|
|
|
if [ "$1" == "--trace" ]; then
|
|
function trace() {
|
|
echo "$*" >&2
|
|
}
|
|
function doCurl() {
|
|
set -x
|
|
if [ -z "$HSADMINNG_JWT_ASSUME" ]; then
|
|
curl --no-progress-meter --show-error --fail-with-body \
|
|
--header "Authorization: Bearer $HSADMINNG_JWT_TOKEN" \
|
|
"$@"
|
|
else
|
|
curl --no-progress-meter --show-error --fail-with-body \
|
|
--header "Authorization: Bearer $HSADMINNG_JWT_TOKEN" \
|
|
--header "assumed-roles: $HSADMINNG_JWT_ASSUME" \
|
|
"$@"
|
|
fi
|
|
set +x
|
|
echo
|
|
}
|
|
shift
|
|
else
|
|
function trace() {
|
|
: # noop
|
|
}
|
|
function doCurl() {
|
|
if [ -z "$HSADMINNG_JWT_ASSUME" ]; then
|
|
curl --fail-with-body --header "Authorization: Bearer $HSADMINNG_JWT_TOKEN" "$@"
|
|
else
|
|
curl --fail-with-body \
|
|
--header "Authorization: Bearer $HSADMINNG_JWT_TOKEN" \
|
|
--header "assumed-roles: $HSADMINNG_JWT_ASSUME" \
|
|
"$@"
|
|
echo
|
|
fi
|
|
}
|
|
fi
|
|
|
|
export HSADMINNG_JWT_ASSUME_HEADER
|
|
if [ -f ~/.cas-curl-assume ]; then
|
|
HSADMINNG_JWT_ASSUME="$(cat ~/.cas-curl-assume)"
|
|
else
|
|
HSADMINNG_JWT_ASSUME=
|
|
fi
|
|
|
|
if [ -z "$HSADMINNG_JWT_TOKEN_URL" ]; then
|
|
cat >&2 <<EOF
|
|
ERROR: environment incomplete
|
|
|
|
please set the following environment variables:
|
|
export HSADMINNG_JWT_TOKEN_URL=https://login.hostsharing.net/oauth/token
|
|
export HSADMINNG_JWT_USERNAME=<<optionally, your username, or leave empty after '='>>
|
|
export HSADMINNG_JWT_PASSWORD=<<optionally, your password, or leave empty after '='>>
|
|
EOF
|
|
exit 1
|
|
fi
|
|
|
|
function jwtCurlDocumentation() {
|
|
cat <<EOF
|
|
curl-wrapper utilizing JWT-authentication for hsadmin-ng
|
|
usage: $0 [--trace] [--show-password] <<command>> [parameters]
|
|
|
|
commands:
|
|
EOF
|
|
# filters out help texts (containing double-# and following lines with leading single-#) from the commands itself
|
|
# (the '' makes sure that this line is not found, just the lines with actual help texts)
|
|
sed -n '/#''#/ {x; p; x; s/#''#//; p; :a; n; /^[[:space:]]*#/!b; s/^[[:space:]]*#//; p; ba}' <$0
|
|
}
|
|
|
|
function jwtLogin() {
|
|
# JWT token exists and not expired?
|
|
if [ -f ~/.jwt-token ]; then
|
|
# Check if token is still valid (simple expiry check - you might want to add JWT parsing for more precise validation)
|
|
if find ~/.jwt-token -type f -size +0c -mmin -55 2>/dev/null | grep -q .; then
|
|
HSADMINNG_JWT_TOKEN=$(<~/.jwt-token)
|
|
return
|
|
fi
|
|
fi
|
|
|
|
if [ -z "$HSADMINNG_JWT_USERNAME" ]; then
|
|
read -e -p "Username: " HSADMINNG_JWT_USERNAME
|
|
fi
|
|
|
|
if [ -z "$HSADMINNG_JWT_PASSWORD" ]; then
|
|
read -s -e -p "Password: " HSADMINNG_JWT_PASSWORD
|
|
fi
|
|
|
|
if [ "$HSADMINNG_JWT_SHOW_PASSWORD" == "yes" ]; then
|
|
HSADMINNG_JWT_PASSWORD_DISPLAY=$HSADMINNG_JWT_PASSWORD
|
|
else
|
|
HSADMINNG_JWT_PASSWORD_DISPLAY="<<password hidden - use --show-password to show>>"
|
|
fi
|
|
|
|
# OAuth2 Resource Owner Password Credentials Grant (public client)
|
|
trace "+ curl --no-progress-meter --fail-with-body --show-error -X POST \
|
|
-H 'Content-Type: application/x-www-form-urlencoded' \
|
|
-d \"grant_type=password&client_id=$HSADMINNG_JWT_CLIENT_ID&client_secret=$HSADMINNG_JWT_CLIENT_SECRET&username=$HSADMINNG_JWT_USERNAME&password=$HSADMINNG_JWT_PASSWORD_DISPLAY\" \
|
|
$HSADMINNG_JWT_TOKEN_URL -o ~/.jwt-token.response"
|
|
|
|
JWT_RESPONSE=$(curl --no-progress-meter --fail-with-body --show-error -X POST \
|
|
-H 'Content-Type: application/x-www-form-urlencoded' \
|
|
-d "grant_type=password&client_id=$HSADMINNG_JWT_CLIENT_ID&client_secret=$HSADMINNG_JWT_CLIENT_SECRET&username=$HSADMINNG_JWT_USERNAME&password=$HSADMINNG_JWT_PASSWORD_DISPLAY" \
|
|
$HSADMINNG_JWT_TOKEN_URL 2>&1 | tee ~/.jwt-token.response)
|
|
|
|
# Extract access token from JSON response
|
|
HSADMINNG_JWT_TOKEN=$(echo "$JWT_RESPONSE" | grep -o '"access_token":"[^"]*' | cut -d'"' -f4)
|
|
|
|
if [ -z "$HSADMINNG_JWT_TOKEN" ]; then
|
|
echo "ERROR: could not get JWT access token: $JWT_RESPONSE" >&2
|
|
echo >&2
|
|
exit 1
|
|
fi
|
|
|
|
echo "$HSADMINNG_JWT_TOKEN" > ~/.jwt-token
|
|
trace "JWT Token acquired (length: ${#HSADMINNG_JWT_TOKEN})"
|
|
}
|
|
|
|
function jwtLogout() {
|
|
rm -f ~/.jwt-token ~/.jwt-token.response
|
|
}
|
|
|
|
function jwtValidate() {
|
|
if [ ! -f ~/.jwt-token ]; then
|
|
echo "ERROR: no JWT token found, please login first" >&2
|
|
exit 1
|
|
fi
|
|
|
|
HSADMINNG_JWT_TOKEN=$(<~/.jwt-token)
|
|
|
|
# Simple JWT validation - decode payload (base64 decode the middle part)
|
|
JWT_PAYLOAD=$(echo "$HSADMINNG_JWT_TOKEN" | cut -d'.' -f2)
|
|
# Add padding if needed for base64 decoding
|
|
case $((${#JWT_PAYLOAD} % 4)) in
|
|
2) JWT_PAYLOAD="${JWT_PAYLOAD}==" ;;
|
|
3) JWT_PAYLOAD="${JWT_PAYLOAD}=" ;;
|
|
esac
|
|
|
|
if command -v base64 >/dev/null 2>&1; then
|
|
JWT_DECODED=$(echo "$JWT_PAYLOAD" | base64 -d 2>/dev/null)
|
|
if [ $? -eq 0 ]; then
|
|
OAUTH_USER=$(echo "$JWT_DECODED" | grep -o '"sub":"[^"]*' | cut -d'"' -f4)
|
|
if [ -z "$OAUTH_USER" ]; then
|
|
OAUTH_USER=$(echo "$JWT_DECODED" | grep -o '"preferred_username":"[^"]*' | cut -d'"' -f4)
|
|
fi
|
|
if [ -z "$OAUTH_USER" ]; then
|
|
OAUTH_USER=$(echo "$JWT_DECODED" | grep -o '"username":"[^"]*' | cut -d'"' -f4)
|
|
fi
|
|
|
|
if [ -n "$OAUTH_USER" ]; then
|
|
echo "OAuth User: $OAUTH_USER"
|
|
# Check expiry
|
|
EXP=$(echo "$JWT_DECODED" | grep -o '"exp":[0-9]*' | cut -d':' -f2)
|
|
if [ -n "$EXP" ]; then
|
|
CURRENT_TIME=$(date +%s)
|
|
if [ "$EXP" -lt "$CURRENT_TIME" ]; then
|
|
echo "WARNING: JWT token has expired" >&2
|
|
else
|
|
echo "Token expires: $(date -d "@$EXP" 2>/dev/null || date -r "$EXP" 2>/dev/null || echo "unknown")"
|
|
fi
|
|
fi
|
|
else
|
|
echo "Could not extract user from JWT token" >&2
|
|
fi
|
|
else
|
|
echo "Could not decode JWT token" >&2
|
|
fi
|
|
else
|
|
echo "JWT token present but cannot validate (base64 command not available)" >&2
|
|
fi
|
|
}
|
|
|
|
function jwtToken() {
|
|
if [ ! -f ~/.jwt-token ]; then
|
|
echo "ERROR: no JWT token found, please login first" >&2
|
|
exit 1
|
|
fi
|
|
HSADMINNG_JWT_TOKEN=$(<~/.jwt-token)
|
|
echo "$HSADMINNG_JWT_TOKEN"
|
|
}
|
|
|
|
case "${1,,}" in
|
|
|
|
# -- generic commands --------------------------------------------------------------------------
|
|
|
|
""|"-h"|"--help"|"help") ## prints documentation about commands and options
|
|
jwtCurlDocumentation
|
|
exit
|
|
;;
|
|
|
|
"env") ## prints all related HSADMINNG_JWT_... environment variables; use '--show-password' to show the password as well
|
|
# example: jwt-curl -show-password env
|
|
echo "export HSADMINNG_JWT_TOKEN_URL=$HSADMINNG_JWT_TOKEN_URL"
|
|
echo "export HSADMINNG_JWT_CLIENT_ID=$HSADMINNG_JWT_CLIENT_ID"
|
|
echo "export HSADMINNG_JWT_CLIENT_SECRET=$HSADMINNG_JWT_CLIENT_SECRET"
|
|
echo "export HSADMINNG_JWT_USERNAME=$HSADMINNG_JWT_USERNAME"
|
|
if [ "$HSADMINNG_JWT_SHOW_PASSWORD" == "yes" ]; then
|
|
echo "export HSADMINNG_JWT_PASSWORD=$HSADMINNG_JWT_PASSWORD"
|
|
elif [ -z "$HSADMINNG_JWT_PASSWORD" ]; then
|
|
echo "export HSADMINNG_JWT_PASSWORD=#<<not given>>"
|
|
else
|
|
echo "export HSADMINNG_JWT_PASSWORD=#<<given, but hidden - add --show-password to show>>"
|
|
fi
|
|
;;
|
|
|
|
# --- authentication-related commands ------------------------------------------------------------
|
|
|
|
"login") ## reads username+password and fetches JWT access token (bypasses HSADMINNG_JWT_USERNAME+HSADMINNG_JWT_PASSWORD)
|
|
# example: jwt-curl login
|
|
jwtLogout
|
|
export HSADMINNG_JWT_USERNAME=
|
|
export HSADMINNG_JWT_PASSWORD=
|
|
jwtLogin
|
|
;;
|
|
"assume") ## assumes the given comma-separated roles
|
|
# example using object-id-name: jwt-curl assume 'hs_office.relation#ExampleMandant-with-PARTNER-ExamplePartner:AGENT'
|
|
# example using object-uuid: jwt-curl assume 'hs_office.relation#1d3bc468-c5c8-11ef-9d0d-4751ecfda2b7:AGENT'
|
|
shift
|
|
if [ -z "$1" ]; then
|
|
echo "ERROR: requires comma-separated list of roles to assume" >&2
|
|
exit 1
|
|
fi
|
|
echo "$1" >~/.cas-curl-assume
|
|
;;
|
|
"unassume") ## do not assume any particular role anymore, use the plain user as RBAC subject
|
|
rm -f ~/.cas-curl-assume
|
|
;;
|
|
"token") ## prints the current JWT access token
|
|
jwtToken
|
|
;;
|
|
"validate") ## validates current JWT token and prints currently logged in user
|
|
jwtValidate
|
|
;;
|
|
"logout") ## logout, deletes JWT token
|
|
jwtLogout
|
|
;;
|
|
|
|
# --- HTTP-commands ----------------------------------------------------------------------
|
|
|
|
"get") ## HTTP GET, add URL as parameter
|
|
# example: jwt-curl GET http://localhost:8080/api/hs/office/partners/P-10003 | jq
|
|
# hint: '| jq' is just for human-readable formatted JSON output
|
|
shift
|
|
jwtLogin
|
|
doCurl "$*"
|
|
;;
|
|
"post") ## HTTP POST, add curl options to specify the request body and the URL as last parameter
|
|
# example: jwt-curl POST \
|
|
# -d '{ "prefix":"ttt", "reference":80001, "adminUserName":"admin@ttt.example.com" }' \
|
|
# http://localhost:8080/api/test/customers | jq
|
|
# hint: '| jq' is just for human-readable formatted JSON output
|
|
shift
|
|
jwtLogin
|
|
doCurl --header "Content-Type: application/json" -X POST "$@"
|
|
;;
|
|
"patch") ## HTTP PATCH, add curl options to specify the request body and the URL as last parameterparameter
|
|
# example: jwt-curl PATCH \
|
|
# -d '{ "reference":80002 }' \
|
|
# http://localhost:8080/api/test/customers/ae90ac2a-4728-4ca9-802e-a0d0108b2324 | jq
|
|
# hint: '| jq' is just for human-readable formatted JSON output
|
|
shift
|
|
jwtLogin
|
|
doCurl --header "Content-Type: application/json" -X POST "$*"
|
|
;;
|
|
|
|
# --- unknown command --------------------------------------------------------------------
|
|
*)
|
|
cat >&2 <<EOF
|
|
unknown command: '$1'
|
|
valid commands: help, login, logout, validate, get, post, patch, delete
|
|
EOF
|
|
exit 1
|
|
;;
|
|
esac
|