diff --git a/.aliases b/.aliases index 2d6b0146..96a0b970 100644 --- a/.aliases +++ b/.aliases @@ -74,11 +74,15 @@ function importLegacyData() { alias gw-importHostingAssets='importLegacyData importHostingAssets' function gradlewBootRun() { - local port=${1:-8080} - shift + local serverPort=${1:-8080}; shift + local managementPort=${2:-$((serverPort + 1))}; shift local additional_args="$@" - echo gw bootRun --args="--spring.profiles.active=dev,fakeCasAuthenticator,complete,test-data --server.port=${port} ${additional_args}" - ./gradlew bootRun --args="--spring.profiles.active=dev,fakeCasAuthenticator,complete,test-data --server.port=${port} ${additional_args}" + unset HSADMINNG_JWT_ISSUER + unset HSADMINNG_JWT_JWKS_URL + unset HSADMINNG_JWT_TOKEN_URL + set -x + ./gradlew bootRun --args="--spring.profiles.active=dev,fake-jwt,complete,test-data --server.port=${serverPort} --management.server.port=${managementPort} ${additional_args}" + set +x } alias gw-bootRun=gradlewBootRun @@ -97,7 +101,7 @@ alias pg-sql-restore='gunzip --stdout | docker exec -i hsadmin-ng-postgres psql alias fp='grep -r '@Accepts' src | sed -e 's/^.*@/@/g' | sort -u | wc -l' -alias gw-spotless='./gradlew spotlessApply -x pitest -x test -x :processResources' +alias gw-spotless='./gradlew compile spotlessApply -x pitest -x test -x :processResources' alias gw-check='. .aliases; . .tc-environment; gw test check -x pitest' # HOWTO: run all 'normal' tests (by default without scenario+import-tests): `gw-test` @@ -143,7 +147,7 @@ function _gwTest() { alias gw-test=_gwTest alias howto=bin/howto -alias cas-curl=bin/cas-curl +alias jwt-curl=bin/jwt-curl # etc/docker-compose.yml limits CPUs+MEM and includes a PostgreSQL config for analysing slow queries alias gw-importHostingAssets-in-docker-compose=' diff --git a/.tc-environment b/.tc-environment index 194e6d52..665a2123 100644 --- a/.tc-environment +++ b/.tc-environment @@ -3,6 +3,7 @@ source .unset-environment export HSADMINNG_POSTGRES_RESTRICTED_USERNAME=restricted export HSADMINNG_POSTGRES_ADMIN_USERNAME=admin export HSADMINNG_SUPERUSER=import-superuser@hostsharing.net -export HSADMINNG_CAS_SERVER= +export HSADMINNG_OFFICE_DATA_SQL_FILE +export HSADMINNG_JWT_TOKEN_URL=http://localhost:8080/fake-jwt/token export LANG=en_US.UTF-8 diff --git a/.unset-environment b/.unset-environment index 69a50ee3..4fc29b63 100644 --- a/.unset-environment +++ b/.unset-environment @@ -5,5 +5,10 @@ unset HSADMINNG_POSTGRES_RESTRICTED_USERNAME unset HSADMINNG_SUPERUSER unset HSADMINNG_MIGRATION_DATA_PATH unset HSADMINNG_OFFICE_DATA_SQL_FILE -unset HSADMINNG_CAS_SERVER= + +unset HSADMINNG_JWT_ISSUER +unset HSADMINNG_JWT_JWKS_URL +unset HSADMINNG_JWT_USERNAME +unset HSADMINNG_JWT_PASSWORD +unset HSADMINNG_JWT_TOKEN_URL diff --git a/README.md b/README.md index 0e553c08..a31e3a46 100644 --- a/README.md +++ b/README.md @@ -87,13 +87,10 @@ If you have at least Docker and the Java JDK installed in appropriate versions a # if the container has been built already and you want to keep the data, run this: pg-sql-start -Next, compile and run the application on `localhost:8080` and the management server on `localhost:8081`: +Next, compile and run the application with in dev-mode with all modules, test-data and fake-JWT-authentication:: - # this disables CAS-authentication, for using the REST-API with CAS-authentication, see `bin/cas-curl`. - export HSADMINNG_CAS_SERVER= - - # this runs the application with test-data and all modules: - gw bootRun --args='--spring.profiles.active=dev,fakeCasAuthenticator,complete,test-data' + # on `localhost:8080` and the management server on `localhost:8081`: + gw-bootRun # there is also an alias which takes an optional port as an argument: gw-bootRun 8888 @@ -101,53 +98,75 @@ Next, compile and run the application on `localhost:8080` and the management ser The meaning of these profiles is: - **dev**: the PostgreSQL users are created via Liquibase -- **fakeCasAuthenticator**: The username is simply taken from whatever is after "Bearer " in the "Authorization" header. +- **fake-jwt**: the app starts with a build-in fake OAuth2/JWT server - **complete**: all modules are started - **test-data**: some test data inserted -Now we can access the REST API, e.g. using curl: +Now we can access the REST API, e.g. using curl. But you need to use JWT authentication. +To make this a bit easier to handle, we use `bin/jwt-curl` (or `jwt-curl` alias). - # the following command should reply with "pong": - curl -f -s http://localhost:8080/api/ping +Make sure you replace `8080` with the port you used to run the application.` + + # the following command does not need authentication and should reply with "pinged ...". + curl http://localhost:8080/api/ping + + # but when you try endpoints which need authentication, you will get a 401 error: + curl http://localhost:8080/api/pong + + # For the follinging commands we need to be authenticated by a valid JWT token. + # To make JWT handling a bit easier, there is a wrapper scropt `jwt-curl`. + # Make sure the following variable is set to the fake JWT issuer: + export HSADMINNG_JWT_TOKEN_URL=http://localhost:8080/fake-jwt/token + + # optionally, you can set the username and password to in env-vars as well: + export HSADMINNG_JWT_USERNAME=superuser-alex@hostsharing.net + export HSADMINNG_JWT_PASSWORD=whatever-as-its-not-checked-by-fake-jwt-auth + + # also optionally, you can login explicitly: + jwt-curl login + + # now, the following command should reply with "ponged ... superuser-alex@hostsharing.net": + jwt-curl GET http://localhost:8080/api/pong # the following command should return a JSON array with just all customers: - curl -f -s\ - -H 'Authorization: Bearer superuser-alex@hostsharing.net' \ - http://localhost:8080/api/test/customers \ + jwt-curl GET http://localhost:8080/api/test/customers \ | jq # just if `jq` is installed, to prettyprint the output # the following command should return a JSON array with just all packages visible for the admin of the customer yyy: - curl -f -s\ - -H 'Authorization: Bearer superuser-alex@hostsharing.net' -H 'assumed-roles: rbactest.customer#yyy:ADMIN' \ - http://localhost:8080/api/test/packages \ + jwt-curl ASSUME 'rbactest.customer#yyy:ADMIN' + jwt-curl GET http://localhost:8080/api/test/packages \ | jq + jwt-curl UNASSUME # add a new customer - curl -f -s\ - -H 'Authorization: Bearer superuser-alex@hostsharing.net' -H "Content-Type: application/json" \ + jwt-curl POST \ -d '{ "prefix":"ttt", "reference":80001, "adminUserName":"admin@ttt.example.com" }' \ - -X POST http://localhost:8080/api/test/customers \ + http://localhost:8080/api/test/customers \ | jq If you wonder who 'superuser-alex@hostsharing.net' and 'superuser-fran@hostsharing.net' are and where the data comes from: -Mike and Sven are just example global admin accounts as part of the example data which is automatically inserted in Testcontainers and Development environments. -Also try for example 'admin@xxx.example.com' or 'unknown@example.org'. +Alex and Fran are just example global admin accounts as part of the example data which is automatically inserted in Testcontainers and Development environments. +Also, for example, try 'admin@xxx.example.com' or 'unknown@example.org'. If you want a formatted JSON output, you can pipe the result to `jq` or similar. -And to see the full, currently implemented, API, open http://localhost:8080/swagger-ui/index.html). -For a locally running app without CAS-authentication (export HSADMINNG_CAS_SERVER=''), -authorize using the name of the subject (e.g. "superuser-alex@hostsharing.net" in case of test-data). -Otherwise, use a valid CAS-ticket. +And to see the full, currently implemented, API, open http://localhost:8080/swagger-ui/index.html. -If you want to run the application with real CAS-Authentication: +If you want to run the application with real (OAuth2) JWT-authentication: - # set the CAS-SERVER-Root, also see `bin/cas-curl`. - export HSADMINNG_CAS_SERVER=https://login.hostsharing.net # or whatever your CAS-Server-URL you want to use + # set the JWT-issuer URI, e.g. + export HSADMINNG_JWT_ISSUER=https://login.hshsngdev.hs-example.de/realms/HSAdminDEV - # run the application against the real CAS authenticator - gw bootRun --args='--spring.profiles.active=dev,realCasAuthenticator,complete,test-data' + # and the JWT JWKS callback URI: + export HSADMINNG_JWT_JWKS_URL=https://login.hshsngdev.hs-example.de/realms/HSAdminDEV/.well-known/openid-configuration + # as well as the JWT token endpoint URI: + export HSADMINNG_JWT_TOKEN_URL=https://login.hshsngdev.hs-example.de/realms/HSAdminDEV/protocol/openid-connect/token + + # run the application against the specified JWT authenticator, do NOT add the 'fake-jwt' profile: + gw bootRun --args='--spring.profiles.active=dev,complete,test-data' + +Also run `bin/jwt-curl` (or the alias `jwt-curl`) without any parameters to see the available commands. ### PostgreSQL Server @@ -156,7 +175,7 @@ You might amend the port and user settings in `src/main/resources/application.ym But the easiest way to run PostgreSQL is via Docker. -Initially, pull an image compatible to current PostgreSQL version of Hostsharing: +Initially, pull an image compatible to the current PostgreSQL version of Hostsharing: docker pull postgres:15.5-bookworm @@ -674,7 +693,7 @@ howto Add `--args='--spring.profiles.active=...` with the wanted profile selector: ```sh -gw bootRun --args='--spring.profiles.active=fakeCasAuthenticator,external-db,only-prod-schema,without-test-data' +gw bootRun --args='--spring.profiles.active=external-db,only-prod-schema,without-test-data' ``` These profiles mean: @@ -712,7 +731,7 @@ If it's selected, just hit the *bug*-symbol next to it. If you frequently need to run with a fresh database and a clean build, you can use this: ```sh -export HSADMINNG_CAS_SERVER= +# replace `gw bootRun` by the proper command as described above gw clean && pg-sql-reset && sleep 5 && gw bootRun' 2>&1 | tee log ``` @@ -851,9 +870,16 @@ This port can be changed in Or on the command line, add ` --server.port=...` to the `--args` parameter of the `bootRun` task, e.g.: ```sh -gw bootRun --args='--spring.profiles.active=dev,fakeCasAuthenticator,complete,test-data --server.port=8888' +gw bootRun --args='--spring.profiles.active=dev,fake-jwt,complete,test-data --server.port=8888' ``` +or, for local development, simply: + +```sh +gw-bootRun 8888 +``` + + ### How to Use a Persistent Database for Integration Tests? Usually, the `DataJpaTest` integration tests run against a database in a temporary docker container. @@ -888,7 +914,7 @@ Therefore, during initial development, it's good approach just to amend the exis ```shell pg-sql-reset -gw bootRun +gw bootRun # with the proper command line arguments ``` **⚠** diff --git a/bin/cas-curl b/bin/cas-curl deleted file mode 100755 index af9cf541..00000000 --- a/bin/cas-curl +++ /dev/null @@ -1,261 +0,0 @@ -#!/bin/bash - -if [ "$2" == "--show-password" ]; then - HSADMINNG_CAS_SHOW_PASSWORD=yes - shift -else - HSADMINNG_CAS_SHOW_PASSWORD= -fi - -if [ "$1" == "--trace" ]; then - function trace() { - echo "$*" >&2 - } - function doCurl() { - set -x - if [ -z "$HSADMINNG_CAS_ASSUME" ]; then - curl --fail-with-body \ - --header "Authorization: $HSADMINNG_CAS_TICKET" \ - "$@" - else - curl --fail-with-body \ - --header "Authorization: $HSADMINNG_CAS_TICKET" \ - --header "assumed-roles: $HSADMINNG_CAS_ASSUME" \ - "$@" - fi - set +x - } - shift -else - function trace() { - : # noop - } - function doCurl() { - curl --fail-with-body --header "Authorization: $HSADMINNG_CAS_TICKET" "$@" - } -fi - -export HSADMINNG_CAS_ASSUME_HEADER -if [ -f ~/.cas-curl-assume ]; then - HSADMINNG_CAS_ASSUME="$(cat ~/.cas-curl-assume)" -else - HSADMINNG_CAS_ASSUME= -fi - -if [ -z "$HSADMINNG_CAS_LOGIN" ] || [ -z "$HSADMINNG_CAS_VALIDATE" ] || \ - [ -z "$HSADMINNG_CAS_SERVICE_ID" ]; then - cat >&2 <> - export HSADMINNG_CAS_SERVICE_ID=https://hsadminng.hostsharing.net:443/ -EOF - exit 1 -fi - -function casCurlDocumentation() { - cat <> [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 casLogin() { - # ticket granting ticket exists and not expired? - if find ~/.cas-login-tgt -type f -size +0c -mmin -60 2>/dev/null | grep -q .; then - return - fi - - if [ -z "$HSADMINNG_CAS_USERNAME" ]; then - read -e -p "Username: " HSADMINNG_CAS_USERNAME - fi - - if [ -z "$HSADMINNG_CAS_PASSWORD" ]; then - read -s -e -p "Password: " HSADMINNG_CAS_PASSWORD - fi - - if [ "$HSADMINNG_CAS_SHOW_PASSWORD" == "--show-password" ]; then - HSADMINNG_CAS_PASSWORD_DISPLAY=$HSADMINNG_CAS_PASSWORD - else - HSADMINNG_CAS_PASSWORD_DISPLAY="<