From 8fb36030a54bd7b4d26c3b2b23e7e27c44d9747f Mon Sep 17 00:00:00 2001 From: Marcial Rosales Date: Wed, 6 Aug 2025 10:33:49 +0200 Subject: [PATCH] Add spring authorization server for testing purposes with Selenium. And ci job to build the docker image --- .../workflows/authorization-server-make.yaml | 37 +++ .github/workflows/test-authnz.yaml | 22 +- .../workflows/test-management-ui-for-pr.yaml | 22 +- .github/workflows/test-management-ui.yaml | 20 +- selenium/authorization-server/.gitattributes | 2 + selenium/authorization-server/.gitignore | 33 +++ .../.mvn/wrapper/maven-wrapper.properties | 19 ++ selenium/authorization-server/dockerfile | 12 + selenium/authorization-server/mvnw | 259 ++++++++++++++++++ selenium/authorization-server/mvnw.cmd | 149 ++++++++++ selenium/authorization-server/pom.xml | 59 ++++ .../AudienceAuthority.java | 37 +++ .../AuthorizationServerApplication.java | 13 + .../ClientController.java | 20 ++ .../authorization_server/ScopeAuthority.java | 38 +++ .../authorization_server/SecurityConfig.java | 186 +++++++++++++ .../SimpleCORSFilter.java | 52 ++++ .../UsersConfiguration.java | 93 +++++++ .../src/main/resources/application.yml | 73 +++++ .../AuthorizationServerApplicationTests.java | 13 + selenium/bin/components/fakeportal | 8 +- selenium/bin/components/spring | 49 ++++ selenium/bin/components/uaa | 5 +- selenium/bin/gen-spring-yml | 20 ++ selenium/bin/suite_template | 24 +- selenium/fakeportal/app.js | 12 +- .../test/oauth/env.docker.fakeportal.spring | 1 + selenium/test/oauth/env.docker.fakeportal.uaa | 1 + .../test/oauth/env.local.fakeportal.spring | 1 + selenium/test/oauth/env.local.fakeportal.uaa | 1 + selenium/test/oauth/env.spring | 4 + selenium/test/oauth/env.uaa | 1 + 32 files changed, 1237 insertions(+), 49 deletions(-) create mode 100644 .github/workflows/authorization-server-make.yaml create mode 100644 selenium/authorization-server/.gitattributes create mode 100644 selenium/authorization-server/.gitignore create mode 100644 selenium/authorization-server/.mvn/wrapper/maven-wrapper.properties create mode 100644 selenium/authorization-server/dockerfile create mode 100755 selenium/authorization-server/mvnw create mode 100644 selenium/authorization-server/mvnw.cmd create mode 100644 selenium/authorization-server/pom.xml create mode 100644 selenium/authorization-server/src/main/java/com/rabbitmq/authorization_server/AudienceAuthority.java create mode 100644 selenium/authorization-server/src/main/java/com/rabbitmq/authorization_server/AuthorizationServerApplication.java create mode 100644 selenium/authorization-server/src/main/java/com/rabbitmq/authorization_server/ClientController.java create mode 100644 selenium/authorization-server/src/main/java/com/rabbitmq/authorization_server/ScopeAuthority.java create mode 100644 selenium/authorization-server/src/main/java/com/rabbitmq/authorization_server/SecurityConfig.java create mode 100644 selenium/authorization-server/src/main/java/com/rabbitmq/authorization_server/SimpleCORSFilter.java create mode 100644 selenium/authorization-server/src/main/java/com/rabbitmq/authorization_server/UsersConfiguration.java create mode 100644 selenium/authorization-server/src/main/resources/application.yml create mode 100644 selenium/authorization-server/src/test/java/com/rabbitmq/authorization_server/AuthorizationServerApplicationTests.java create mode 100755 selenium/bin/components/spring create mode 100755 selenium/bin/gen-spring-yml create mode 100644 selenium/test/oauth/env.docker.fakeportal.spring create mode 100644 selenium/test/oauth/env.docker.fakeportal.uaa create mode 100644 selenium/test/oauth/env.local.fakeportal.spring create mode 100644 selenium/test/oauth/env.local.fakeportal.uaa create mode 100644 selenium/test/oauth/env.spring diff --git a/.github/workflows/authorization-server-make.yaml b/.github/workflows/authorization-server-make.yaml new file mode 100644 index 0000000000..11e9cda40b --- /dev/null +++ b/.github/workflows/authorization-server-make.yaml @@ -0,0 +1,37 @@ +name: Spring Authorization Server docker image (make) + +on: + push: + branches: + - main + paths: + - .github/workflows/authorization-server-make.yaml + - selenium/authorization-server + pull_request: + paths: + - .github/workflows/authorization-server-make.yaml + - selenium/authorization-server + +env: + REGISTRY_IMAGE: pivotalrabbitmq/spring-authorization-server + IMAGE_TAG: 0.0.11 +jobs: + docker: + runs-on: ubuntu-latest + steps: + + - name: CHECKOUT REPOSITORY + uses: actions/checkout@v4 + + - name: Login to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_PASSWORD }} + + - name: Build and push + uses: docker/build-push-action@v6 + with: + context: selenium/authorization-server + push: true + tags: ${{ env.REGISTRY_IMAGE }}:${{ env.IMAGE_TAG }} diff --git a/.github/workflows/test-authnz.yaml b/.github/workflows/test-authnz.yaml index 17ce8225cb..9347416fa6 100644 --- a/.github/workflows/test-authnz.yaml +++ b/.github/workflows/test-authnz.yaml @@ -74,20 +74,22 @@ jobs: - name: Run Suites id: tests run: | - IMAGE_TAG=$(find PACKAGES/rabbitmq-server-generic-unix-*.tar.xz | awk -F 'PACKAGES/rabbitmq-server-generic-unix-|.tar.xz' '{print $2}') - CONF_DIR_PREFIX="$(mktemp -d)" RABBITMQ_DOCKER_IMAGE=pivotalrabbitmq/rabbitmq:$IMAGE_TAG \ - ${SELENIUM_DIR}/run-suites.sh full-suite-authnz-messaging - echo "SELENIUM_ARTIFACTS=$CONF_DIR_PREFIX" >> "$GITHUB_OUTPUT" - + export IMAGE_TAG=$(find PACKAGES/rabbitmq-server-generic-unix-*.tar.xz | awk -F 'PACKAGES/rabbitmq-server-generic-unix-|.tar.xz' '{print $2}') + export CONF_DIR_PREFIX="$(mktemp -d)" + export RABBITMQ_DOCKER_IMAGE=pivotalrabbitmq/rabbitmq:$IMAGE_TAG + echo "Running selenium tests with " + echo " - CONF_DIR_PREFIX: ${CONF_DIR_PREFIX}" + echo " - IMAGE_TAG: ${IMAGE_TAG}" + echo " - RABBITMQ_DOCKER_IMAGE: ${RABBITMQ_DOCKER_IMAGE}" + echo "SELENIUM_ARTIFACTS=${CONF_DIR_PREFIX}" >> $GITHUB_ENV + ${SELENIUM_DIR}/run-suites.sh full-suite-authnz-messaging + - name: Upload Test Artifacts - if: always() + if: ${{ failure() && steps.tests.outcome == 'failure' }} uses: actions/upload-artifact@v4.3.2 - env: - SELENIUM_ARTIFACTS: ${{ steps.tests.outputs.SELENIUM_ARTIFACTS }} with: name: test-artifacts-${{ matrix.browser }}-${{ matrix.erlang_version }} - path: | - $SELENIUM_ARTIFACTS/* + path: ${{ env.SELENIUM_ARTIFACTS }}/* summary-selenium: needs: diff --git a/.github/workflows/test-management-ui-for-pr.yaml b/.github/workflows/test-management-ui-for-pr.yaml index 6b82138cca..90400c4b7b 100644 --- a/.github/workflows/test-management-ui-for-pr.yaml +++ b/.github/workflows/test-management-ui-for-pr.yaml @@ -62,17 +62,19 @@ jobs: - name: Run short UI suites on a standalone rabbitmq server id: tests run: | - IMAGE_TAG=$(find PACKAGES/rabbitmq-server-generic-unix-*.tar.xz | awk -F 'PACKAGES/rabbitmq-server-generic-unix-|.tar.xz' '{print $2}') - CONF_DIR_PREFIX="$(mktemp -d)" RABBITMQ_DOCKER_IMAGE=pivotalrabbitmq/rabbitmq:$IMAGE_TAG \ - ${SELENIUM_DIR}/run-suites.sh short-suite-management-ui - echo "SELENIUM_ARTIFACTS=$CONF_DIR_PREFIX" >> "$GITHUB_OUTPUT" - + export IMAGE_TAG=$(find PACKAGES/rabbitmq-server-generic-unix-*.tar.xz | awk -F 'PACKAGES/rabbitmq-server-generic-unix-|.tar.xz' '{print $2}') + export CONF_DIR_PREFIX="$(mktemp -d)" + export RABBITMQ_DOCKER_IMAGE=pivotalrabbitmq/rabbitmq:$IMAGE_TAG + echo "Running selenium tests with " + echo " - CONF_DIR_PREFIX: ${CONF_DIR_PREFIX}" + echo " - IMAGE_TAG: ${IMAGE_TAG}" + echo " - RABBITMQ_DOCKER_IMAGE: ${RABBITMQ_DOCKER_IMAGE}" + echo "SELENIUM_ARTIFACTS=${CONF_DIR_PREFIX}" >> $GITHUB_ENV + ${SELENIUM_DIR}/run-suites.sh short-suite-management-ui + - name: Upload Test Artifacts - if: ${{ failure() && steps.tests.outcome == 'failed' }} + if: ${{ failure() && steps.tests.outcome == 'failure' }} uses: actions/upload-artifact@v4 - env: - SELENIUM_ARTIFACTS: ${{ steps.tests.outputs.SELENIUM_ARTIFACTS }} with: name: test-artifacts-${{ matrix.browser }}-${{ matrix.erlang_version }} - path: | - $SELENIUM_ARTIFACTS/* + path: ${{ env.SELENIUM_ARTIFACTS }}/* diff --git a/.github/workflows/test-management-ui.yaml b/.github/workflows/test-management-ui.yaml index d240f327da..1768c0cbf5 100644 --- a/.github/workflows/test-management-ui.yaml +++ b/.github/workflows/test-management-ui.yaml @@ -66,17 +66,19 @@ jobs: - name: Run full UI suite on a 3-node rabbitmq cluster id: tests run: | - IMAGE_TAG=$(find PACKAGES/rabbitmq-server-generic-unix-*.tar.xz | awk -F 'PACKAGES/rabbitmq-server-generic-unix-|.tar.xz' '{print $2}') - CONF_DIR_PREFIX="$(mktemp -d)" RABBITMQ_DOCKER_IMAGE=pivotalrabbitmq/rabbitmq:$IMAGE_TAG \ - ${SELENIUM_DIR}/run-suites.sh full-suite-management-ui - echo "SELENIUM_ARTIFACTS=$CONF_DIR_PREFIX" >> "$GITHUB_OUTPUT" + export IMAGE_TAG=$(find PACKAGES/rabbitmq-server-generic-unix-*.tar.xz | awk -F 'PACKAGES/rabbitmq-server-generic-unix-|.tar.xz' '{print $2}') + export CONF_DIR_PREFIX="$(mktemp -d)" + export RABBITMQ_DOCKER_IMAGE=pivotalrabbitmq/rabbitmq:$IMAGE_TAG + echo "Running selenium tests with " + echo " - CONF_DIR_PREFIX: ${CONF_DIR_PREFIX}" + echo " - IMAGE_TAG: ${IMAGE_TAG}" + echo " - RABBITMQ_DOCKER_IMAGE: ${RABBITMQ_DOCKER_IMAGE}" + echo "SELENIUM_ARTIFACTS=${CONF_DIR_PREFIX}" >> $GITHUB_ENV + ${SELENIUM_DIR}/run-suites.sh full-suite-management-ui - name: Upload Test Artifacts - if: ${{ failure() && steps.tests.outcome == 'failed' }} + if: ${{ failure() && steps.tests.outcome == 'failure' }} uses: actions/upload-artifact@v4.3.2 - env: - SELENIUM_ARTIFACTS: ${{ steps.run-suites.outputs.SELENIUM_ARTIFACTS }} with: name: test-artifacts-${{ matrix.browser }}-${{ matrix.erlang_version }} - path: | - $SELENIUM_ARTIFACTS/* + path: ${{ env.SELENIUM_ARTIFACTS }}/* diff --git a/selenium/authorization-server/.gitattributes b/selenium/authorization-server/.gitattributes new file mode 100644 index 0000000000..3b41682ac5 --- /dev/null +++ b/selenium/authorization-server/.gitattributes @@ -0,0 +1,2 @@ +/mvnw text eol=lf +*.cmd text eol=crlf diff --git a/selenium/authorization-server/.gitignore b/selenium/authorization-server/.gitignore new file mode 100644 index 0000000000..667aaef0c8 --- /dev/null +++ b/selenium/authorization-server/.gitignore @@ -0,0 +1,33 @@ +HELP.md +target/ +.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ diff --git a/selenium/authorization-server/.mvn/wrapper/maven-wrapper.properties b/selenium/authorization-server/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000000..d58dfb70ba --- /dev/null +++ b/selenium/authorization-server/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,19 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +wrapperVersion=3.3.2 +distributionType=only-script +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.9/apache-maven-3.9.9-bin.zip diff --git a/selenium/authorization-server/dockerfile b/selenium/authorization-server/dockerfile new file mode 100644 index 0000000000..a37ac795bb --- /dev/null +++ b/selenium/authorization-server/dockerfile @@ -0,0 +1,12 @@ +FROM maven:3.9.9-eclipse-temurin-24-alpine as builder +WORKDIR /home/app/authorization-server +COPY ./ . +RUN mvn -Dmaven.test.skip=true clean package + +FROM openjdk:24-jdk +EXPOSE 8080 +ENTRYPOINT ["java","-jar","/authorization-server.jar"] +ARG JAR_FILE=target/*.jar +COPY --from=builder /home/app/authorization-server/target/*.jar authorization-server.jar + + diff --git a/selenium/authorization-server/mvnw b/selenium/authorization-server/mvnw new file mode 100755 index 0000000000..19529ddf8c --- /dev/null +++ b/selenium/authorization-server/mvnw @@ -0,0 +1,259 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Apache Maven Wrapper startup batch script, version 3.3.2 +# +# Optional ENV vars +# ----------------- +# JAVA_HOME - location of a JDK home dir, required when download maven via java source +# MVNW_REPOURL - repo url base for downloading maven distribution +# MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +# MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output +# ---------------------------------------------------------------------------- + +set -euf +[ "${MVNW_VERBOSE-}" != debug ] || set -x + +# OS specific support. +native_path() { printf %s\\n "$1"; } +case "$(uname)" in +CYGWIN* | MINGW*) + [ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")" + native_path() { cygpath --path --windows "$1"; } + ;; +esac + +# set JAVACMD and JAVACCMD +set_java_home() { + # For Cygwin and MinGW, ensure paths are in Unix format before anything is touched + if [ -n "${JAVA_HOME-}" ]; then + if [ -x "$JAVA_HOME/jre/sh/java" ]; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACCMD="$JAVA_HOME/jre/sh/javac" + else + JAVACMD="$JAVA_HOME/bin/java" + JAVACCMD="$JAVA_HOME/bin/javac" + + if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then + echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2 + echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2 + return 1 + fi + fi + else + JAVACMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v java + )" || : + JAVACCMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v javac + )" || : + + if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then + echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2 + return 1 + fi + fi +} + +# hash string like Java String::hashCode +hash_string() { + str="${1:-}" h=0 + while [ -n "$str" ]; do + char="${str%"${str#?}"}" + h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296)) + str="${str#?}" + done + printf %x\\n $h +} + +verbose() { :; } +[ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; } + +die() { + printf %s\\n "$1" >&2 + exit 1 +} + +trim() { + # MWRAPPER-139: + # Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds. + # Needed for removing poorly interpreted newline sequences when running in more + # exotic environments such as mingw bash on Windows. + printf "%s" "${1}" | tr -d '[:space:]' +} + +# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties +while IFS="=" read -r key value; do + case "${key-}" in + distributionUrl) distributionUrl=$(trim "${value-}") ;; + distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;; + esac +done <"${0%/*}/.mvn/wrapper/maven-wrapper.properties" +[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in ${0%/*}/.mvn/wrapper/maven-wrapper.properties" + +case "${distributionUrl##*/}" in +maven-mvnd-*bin.*) + MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ + case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in + *AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;; + :Darwin*x86_64) distributionPlatform=darwin-amd64 ;; + :Darwin*arm64) distributionPlatform=darwin-aarch64 ;; + :Linux*x86_64*) distributionPlatform=linux-amd64 ;; + *) + echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2 + distributionPlatform=linux-amd64 + ;; + esac + distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip" + ;; +maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;; +*) MVN_CMD="mvn${0##*/mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;; +esac + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +[ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}" +distributionUrlName="${distributionUrl##*/}" +distributionUrlNameMain="${distributionUrlName%.*}" +distributionUrlNameMain="${distributionUrlNameMain%-bin}" +MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}" +MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")" + +exec_maven() { + unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || : + exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD" +} + +if [ -d "$MAVEN_HOME" ]; then + verbose "found existing MAVEN_HOME at $MAVEN_HOME" + exec_maven "$@" +fi + +case "${distributionUrl-}" in +*?-bin.zip | *?maven-mvnd-?*-?*.zip) ;; +*) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;; +esac + +# prepare tmp dir +if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then + clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; } + trap clean HUP INT TERM EXIT +else + die "cannot create temp dir" +fi + +mkdir -p -- "${MAVEN_HOME%/*}" + +# Download and Install Apache Maven +verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +verbose "Downloading from: $distributionUrl" +verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" + +# select .zip or .tar.gz +if ! command -v unzip >/dev/null; then + distributionUrl="${distributionUrl%.zip}.tar.gz" + distributionUrlName="${distributionUrl##*/}" +fi + +# verbose opt +__MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR='' +[ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v + +# normalize http auth +case "${MVNW_PASSWORD:+has-password}" in +'') MVNW_USERNAME='' MVNW_PASSWORD='' ;; +has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;; +esac + +if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then + verbose "Found wget ... using wget" + wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl" +elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then + verbose "Found curl ... using curl" + curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl" +elif set_java_home; then + verbose "Falling back to use Java to download" + javaSource="$TMP_DOWNLOAD_DIR/Downloader.java" + targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName" + cat >"$javaSource" <<-END + public class Downloader extends java.net.Authenticator + { + protected java.net.PasswordAuthentication getPasswordAuthentication() + { + return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() ); + } + public static void main( String[] args ) throws Exception + { + setDefault( new Downloader() ); + java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() ); + } + } + END + # For Cygwin/MinGW, switch paths to Windows format before running javac and java + verbose " - Compiling Downloader.java ..." + "$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java" + verbose " - Running Downloader.java ..." + "$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")" +fi + +# If specified, validate the SHA-256 sum of the Maven distribution zip file +if [ -n "${distributionSha256Sum-}" ]; then + distributionSha256Result=false + if [ "$MVN_CMD" = mvnd.sh ]; then + echo "Checksum validation is not supported for maven-mvnd." >&2 + echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + elif command -v sha256sum >/dev/null; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c >/dev/null 2>&1; then + distributionSha256Result=true + fi + elif command -v shasum >/dev/null; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then + distributionSha256Result=true + fi + else + echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2 + echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + fi + if [ $distributionSha256Result = false ]; then + echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2 + echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2 + exit 1 + fi +fi + +# unzip and move +if command -v unzip >/dev/null; then + unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip" +else + tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar" +fi +printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/mvnw.url" +mv -- "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME" + +clean || : +exec_maven "$@" diff --git a/selenium/authorization-server/mvnw.cmd b/selenium/authorization-server/mvnw.cmd new file mode 100644 index 0000000000..249bdf3822 --- /dev/null +++ b/selenium/authorization-server/mvnw.cmd @@ -0,0 +1,149 @@ +<# : batch portion +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Apache Maven Wrapper startup batch script, version 3.3.2 +@REM +@REM Optional ENV vars +@REM MVNW_REPOURL - repo url base for downloading maven distribution +@REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +@REM MVNW_VERBOSE - true: enable verbose log; others: silence the output +@REM ---------------------------------------------------------------------------- + +@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0) +@SET __MVNW_CMD__= +@SET __MVNW_ERROR__= +@SET __MVNW_PSMODULEP_SAVE=%PSModulePath% +@SET PSModulePath= +@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @( + IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B) +) +@SET PSModulePath=%__MVNW_PSMODULEP_SAVE% +@SET __MVNW_PSMODULEP_SAVE= +@SET __MVNW_ARG0_NAME__= +@SET MVNW_USERNAME= +@SET MVNW_PASSWORD= +@IF NOT "%__MVNW_CMD__%"=="" (%__MVNW_CMD__% %*) +@echo Cannot start maven from wrapper >&2 && exit /b 1 +@GOTO :EOF +: end batch / begin powershell #> + +$ErrorActionPreference = "Stop" +if ($env:MVNW_VERBOSE -eq "true") { + $VerbosePreference = "Continue" +} + +# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties +$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl +if (!$distributionUrl) { + Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" +} + +switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) { + "maven-mvnd-*" { + $USE_MVND = $true + $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip" + $MVN_CMD = "mvnd.cmd" + break + } + default { + $USE_MVND = $false + $MVN_CMD = $script -replace '^mvnw','mvn' + break + } +} + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +if ($env:MVNW_REPOURL) { + $MVNW_REPO_PATTERN = if ($USE_MVND) { "/org/apache/maven/" } else { "/maven/mvnd/" } + $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace '^.*'+$MVNW_REPO_PATTERN,'')" +} +$distributionUrlName = $distributionUrl -replace '^.*/','' +$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$','' +$MAVEN_HOME_PARENT = "$HOME/.m2/wrapper/dists/$distributionUrlNameMain" +if ($env:MAVEN_USER_HOME) { + $MAVEN_HOME_PARENT = "$env:MAVEN_USER_HOME/wrapper/dists/$distributionUrlNameMain" +} +$MAVEN_HOME_NAME = ([System.Security.Cryptography.MD5]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join '' +$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME" + +if (Test-Path -Path "$MAVEN_HOME" -PathType Container) { + Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME" + Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" + exit $? +} + +if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) { + Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl" +} + +# prepare tmp dir +$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile +$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir" +$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null +trap { + if ($TMP_DOWNLOAD_DIR.Exists) { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } + } +} + +New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null + +# Download and Install Apache Maven +Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +Write-Verbose "Downloading from: $distributionUrl" +Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" + +$webclient = New-Object System.Net.WebClient +if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) { + $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD) +} +[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 +$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null + +# If specified, validate the SHA-256 sum of the Maven distribution zip file +$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum +if ($distributionSha256Sum) { + if ($USE_MVND) { + Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." + } + Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash + if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) { + Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property." + } +} + +# unzip and move +Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null +Rename-Item -Path "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" -NewName $MAVEN_HOME_NAME | Out-Null +try { + Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null +} catch { + if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) { + Write-Error "fail to move MAVEN_HOME" + } +} finally { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } +} + +Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" diff --git a/selenium/authorization-server/pom.xml b/selenium/authorization-server/pom.xml new file mode 100644 index 0000000000..1901790ea3 --- /dev/null +++ b/selenium/authorization-server/pom.xml @@ -0,0 +1,59 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 3.5.0 + + + com.rabbitmq + authorization-server + 0.0.11 + authorization-server + Authorization Server for Selenium + + + + + + + + + + + + + + + 24 + + + + org.springframework.boot + spring-boot-starter + + + + org.springframework.boot + spring-boot-starter-test + test + + + org.springframework.boot + spring-boot-starter-oauth2-authorization-server + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/selenium/authorization-server/src/main/java/com/rabbitmq/authorization_server/AudienceAuthority.java b/selenium/authorization-server/src/main/java/com/rabbitmq/authorization_server/AudienceAuthority.java new file mode 100644 index 0000000000..3d497e40bd --- /dev/null +++ b/selenium/authorization-server/src/main/java/com/rabbitmq/authorization_server/AudienceAuthority.java @@ -0,0 +1,37 @@ +package com.rabbitmq.authorization_server; + +import org.springframework.security.authentication.AbstractAuthenticationToken; +import java.util.List; + +import org.springframework.security.core.GrantedAuthority; + +public class AudienceAuthority implements GrantedAuthority { + + private String authority; + + + public AudienceAuthority(String value) { + this.authority = value; + } + + public static AudienceAuthority aud(String value) { + return new AudienceAuthority(value); + } + + @Override + public String getAuthority() { + return authority; + } + + @Override + public String toString() { + return "Audience:" + authority; + } + + public static List getAll(AbstractAuthenticationToken principal) { + return principal.getAuthorities() + .stream().filter(a -> a instanceof AudienceAuthority) + .map(a -> a.getAuthority()).toList(); + } + +} \ No newline at end of file diff --git a/selenium/authorization-server/src/main/java/com/rabbitmq/authorization_server/AuthorizationServerApplication.java b/selenium/authorization-server/src/main/java/com/rabbitmq/authorization_server/AuthorizationServerApplication.java new file mode 100644 index 0000000000..853a77f92a --- /dev/null +++ b/selenium/authorization-server/src/main/java/com/rabbitmq/authorization_server/AuthorizationServerApplication.java @@ -0,0 +1,13 @@ +package com.rabbitmq.authorization_server; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class AuthorizationServerApplication { + + public static void main(String[] args) { + SpringApplication.run(AuthorizationServerApplication.class, args); + } + +} diff --git a/selenium/authorization-server/src/main/java/com/rabbitmq/authorization_server/ClientController.java b/selenium/authorization-server/src/main/java/com/rabbitmq/authorization_server/ClientController.java new file mode 100644 index 0000000000..00474a941d --- /dev/null +++ b/selenium/authorization-server/src/main/java/com/rabbitmq/authorization_server/ClientController.java @@ -0,0 +1,20 @@ +package com.rabbitmq.authorization_server; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; +import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class ClientController { + + @Autowired + private RegisteredClientRepository registeredClientRepository; + + @GetMapping("/api/client") + public RegisteredClient findClientById(@RequestParam String clientId) { + return registeredClientRepository.findByClientId(clientId); + } +} \ No newline at end of file diff --git a/selenium/authorization-server/src/main/java/com/rabbitmq/authorization_server/ScopeAuthority.java b/selenium/authorization-server/src/main/java/com/rabbitmq/authorization_server/ScopeAuthority.java new file mode 100644 index 0000000000..3688ce773b --- /dev/null +++ b/selenium/authorization-server/src/main/java/com/rabbitmq/authorization_server/ScopeAuthority.java @@ -0,0 +1,38 @@ +package com.rabbitmq.authorization_server; + +import java.util.List; +import java.util.Set; + +import org.springframework.security.authentication.AbstractAuthenticationToken; +import org.springframework.security.core.GrantedAuthority; + +public class ScopeAuthority implements GrantedAuthority { + + private String authority; + + public ScopeAuthority(String value) { + this.authority = value; + } + + public static ScopeAuthority scope(String value) { + return new ScopeAuthority(value); + } + + @Override + public String getAuthority() { + return authority; + } + + @Override + public String toString() { + return "Scope:" + authority; + } + + public static List getAuthorites(AbstractAuthenticationToken principal) { + return principal.getAuthorities() + .stream() + .filter(a -> a instanceof ScopeAuthority) + .map(a -> a.getAuthority()).toList(); + } + +} \ No newline at end of file diff --git a/selenium/authorization-server/src/main/java/com/rabbitmq/authorization_server/SecurityConfig.java b/selenium/authorization-server/src/main/java/com/rabbitmq/authorization_server/SecurityConfig.java new file mode 100644 index 0000000000..e695a5bd2a --- /dev/null +++ b/selenium/authorization-server/src/main/java/com/rabbitmq/authorization_server/SecurityConfig.java @@ -0,0 +1,186 @@ +package com.rabbitmq.authorization_server; + +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.interfaces.RSAPrivateKey; +import java.security.interfaces.RSAPublicKey; +import java.util.UUID; +import java.util.Collection; +import java.util.List; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.annotation.Order; +import org.springframework.http.MediaType; +import org.springframework.security.authentication.AbstractAuthenticationToken; +import org.springframework.security.config.Customizer; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.oauth2.jwt.JwtDecoder; +import org.springframework.security.oauth2.server.authorization.OAuth2TokenType; +import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration; +import org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer; +import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings; +import org.springframework.security.oauth2.server.authorization.token.JwtEncodingContext; +import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenCustomizer; +import org.springframework.security.provisioning.InMemoryUserDetailsManager; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint; +import org.springframework.security.web.util.matcher.MediaTypeRequestMatcher; +import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenClaimsContext; + +import org.springframework.security.oauth2.core.AuthorizationGrantType; + +import com.nimbusds.jose.jwk.JWKSet; +import com.nimbusds.jose.jwk.RSAKey; +import com.nimbusds.jose.jwk.source.ImmutableJWKSet; +import com.nimbusds.jose.jwk.source.JWKSource; +import com.nimbusds.jose.proc.SecurityContext; + +@Configuration +@EnableWebSecurity +public class SecurityConfig { + + @Bean + @Order(1) + public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) + throws Exception { + OAuth2AuthorizationServerConfigurer authorizationServerConfigurer = + OAuth2AuthorizationServerConfigurer.authorizationServer(); + + http + .securityMatcher(authorizationServerConfigurer.getEndpointsMatcher()) + .with(authorizationServerConfigurer, (authorizationServer) -> + authorizationServer + .oidc(Customizer.withDefaults()) // Enable OpenID Connect 1.0 + ) + .authorizeHttpRequests((authorize) -> + authorize + .anyRequest().authenticated() + ) + // Redirect to the login page when not authenticated from the + // authorization endpoint + .exceptionHandling((exceptions) -> exceptions + .defaultAuthenticationEntryPointFor( + new LoginUrlAuthenticationEntryPoint("/login"), + new MediaTypeRequestMatcher(MediaType.TEXT_HTML) + ) + ); + + return http.build(); + } + + @Bean + @Order(2) + public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) + throws Exception { + http + .authorizeHttpRequests((authorize) -> authorize + .anyRequest().authenticated() + ) + // Form login handles the redirect to the login page from the + // authorization server filter chain + .formLogin(Customizer.withDefaults()); + + return http.build(); + } + + @Bean + public UserDetailsService userDetailsService(UsersConfiguration users) { + return new InMemoryUserDetailsManager(users.getUserDetails()); + } + + @Bean + public JWKSource jwkSource() { + KeyPair keyPair = generateRsaKey(); + RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic(); + RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate(); + RSAKey rsaKey = new RSAKey.Builder(publicKey) + .privateKey(privateKey) + .keyID(UUID.randomUUID().toString()) + .build(); + JWKSet jwkSet = new JWKSet(rsaKey); + return new ImmutableJWKSet<>(jwkSet); + } + + private static KeyPair generateRsaKey() { + KeyPair keyPair; + try { + KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA"); + keyPairGenerator.initialize(2048); + keyPair = keyPairGenerator.generateKeyPair(); + } + catch (Exception ex) { + throw new IllegalStateException(ex); + } + return keyPair; + } + + Logger logger = LoggerFactory.getLogger(SecurityConfig.class); + + @Bean + public OAuth2TokenCustomizer accessTokenCustomizer() { + logger.info("Creating accessTokenCustomizer ..."); + return (context) -> { + logger.info("Calling accessTokenCustomizer with tokenType: {}", context.getTokenType().getValue()); + AbstractAuthenticationToken principal = context.getPrincipal(); + logger.info("registered client: {}", context.getRegisteredClient()); + logger.info("principal : {}", principal); + logger.info("token format : {} ", + context.getRegisteredClient().getTokenSettings().getAccessTokenFormat().getValue()); + logger.info("authorities : {}", principal.getAuthorities()); + logger.info("authorized scopes : {}", context.getAuthorizedScopes()); + + if (AuthorizationGrantType.CLIENT_CREDENTIALS.equals(context.getAuthorizationGrantType())) { + Collection extra_scope = context.getRegisteredClient().getScopes(); + logger.info("granting extra_scope: {}", extra_scope); + context.getClaims() + .claim("extra_scope", extra_scope); + } else { + Collection extra_scope = ScopeAuthority.getAuthorites(principal); + List audience = AudienceAuthority.getAll(principal); + logger.info("granting extra_scope: {}", extra_scope); + logger.info("granting audience: {}", audience); + context.getClaims() + .audience(audience) + .claim("extra_scope", extra_scope); + } + }; + } + @Bean + public OAuth2TokenCustomizer jwtTokenCustomizer() { + logger.info("Creating jwtTokenCustomizer ..."); + return (context) -> { + logger.info("Calling jwtTokenCustomizer with tokenType: {}", context.getTokenType().getValue()); + if (OAuth2TokenType.ACCESS_TOKEN.equals(context.getTokenType())) { + AbstractAuthenticationToken principal = context.getPrincipal(); + logger.info("registered client: {}", context.getRegisteredClient()); + logger.info("principal : {}", principal); + logger.info("token format : {} ", + context.getRegisteredClient().getTokenSettings().getAccessTokenFormat().getValue()); + logger.info("authorities : {}", principal.getAuthorities()); + logger.info("authorized scopes : {}", context.getAuthorizedScopes()); + + context.getClaims() + .audience(AudienceAuthority.getAll(principal)) + .claim("extra_scope", ScopeAuthority.getAuthorites(principal)); + } + }; + } + + + @Bean + public JwtDecoder jwtDecoder(JWKSource jwkSource) { + return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource); + } + + @Bean + public AuthorizationServerSettings authorizationServerSettings() { + return AuthorizationServerSettings.builder().build(); + } + +} diff --git a/selenium/authorization-server/src/main/java/com/rabbitmq/authorization_server/SimpleCORSFilter.java b/selenium/authorization-server/src/main/java/com/rabbitmq/authorization_server/SimpleCORSFilter.java new file mode 100644 index 0000000000..08c8e10251 --- /dev/null +++ b/selenium/authorization-server/src/main/java/com/rabbitmq/authorization_server/SimpleCORSFilter.java @@ -0,0 +1,52 @@ +package com.rabbitmq.authorization_server; + +import java.io.IOException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; + +import jakarta.servlet.Filter; +import jakarta.servlet.FilterChain; +import jakarta.servlet.FilterConfig; +import jakarta.servlet.ServletException; +import jakarta.servlet.ServletRequest; +import jakarta.servlet.ServletResponse; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +@Component +@Order(Ordered.HIGHEST_PRECEDENCE) +public class SimpleCORSFilter implements Filter { + + @Autowired + public SimpleCORSFilter() { + } + + @Override + public void init(FilterConfig fc) throws ServletException { + + } + + @Override + public void doFilter(ServletRequest req, ServletResponse resp, + FilterChain chain) throws IOException, ServletException { + HttpServletResponse response = (HttpServletResponse) resp; + HttpServletRequest request = (HttpServletRequest) req; + response.setHeader("Access-Control-Allow-Origin", "*"); + response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE"); + response.setHeader("Access-Control-Max-Age", "3600"); + response.setHeader("Access-Control-Allow-Headers", "x-requested-with, authorization, Content-Type, Authorization, credential, X-XSRF-TOKEN"); + + if ("OPTIONS".equalsIgnoreCase(request.getMethod())) { + response.setStatus(HttpServletResponse.SC_OK); + } else { + chain.doFilter(req, resp); + } + } + + @Override + public void destroy() { + } + +} diff --git a/selenium/authorization-server/src/main/java/com/rabbitmq/authorization_server/UsersConfiguration.java b/selenium/authorization-server/src/main/java/com/rabbitmq/authorization_server/UsersConfiguration.java new file mode 100644 index 0000000000..c567fcb874 --- /dev/null +++ b/selenium/authorization-server/src/main/java/com/rabbitmq/authorization_server/UsersConfiguration.java @@ -0,0 +1,93 @@ +package com.rabbitmq.authorization_server; + +import java.util.List; +import java.util.stream.Stream; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.userdetails.User; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.stereotype.Component; + +import static com.rabbitmq.authorization_server.AudienceAuthority.aud; +import static com.rabbitmq.authorization_server.ScopeAuthority.scope; + +@Component +@ConfigurationProperties(prefix = "spring.security.oauth2") +public class UsersConfiguration { + + private List users; + + public UsersConfiguration() { + } + + @Override + public String toString() { + return "UsersConfiguration [users=" + users + "]"; + } + + public List getUserDetails() { + return users.stream().map(u -> + User.withDefaultPasswordEncoder() + .username(u.getUsername()) + .password(u.getPassword()) + .authorities(u.getAuthorities()) + .build()).toList(); + } + + public static class ConfigUser { + + private String username; + private String password; + private List scopes; + private List audiencies; + + public ConfigUser() { + } + + public void setUsername(String username) { + this.username = username; + } + public void setPassword(String password) { + this.password = password; + } + public void setScopes(List scopes) { + this.scopes = scopes; + } + public void setAudiencies(List audiencies) { + this.audiencies = audiencies; + } + public String getUsername() { + return username; + } + public String getPassword() { + return password; + } + public List getScopes() { + return scopes; + } + public List getAudiencies() { + return audiencies; + } + public List getAuthorities() { + return Stream.concat(scopes.stream().map(s -> scope(s)), + audiencies.stream().map(s -> aud(s))).toList(); + } + + @Override + public String toString() { + return "User [username=" + username + ", password=" + password + ", scopes=" + scopes + ", audiencies=" + + audiencies + "]"; + } + + + } + + public List getUsers() { + return users; + } + + public void setUsers(List users) { + this.users = users; + } +} diff --git a/selenium/authorization-server/src/main/resources/application.yml b/selenium/authorization-server/src/main/resources/application.yml new file mode 100644 index 0000000000..a22f1c2ae9 --- /dev/null +++ b/selenium/authorization-server/src/main/resources/application.yml @@ -0,0 +1,73 @@ +logging.level: + com.rabbitmq.authorization_server: DEBUG + +server: + port: 8443 + ssl: + bundle: spring-authorizationserver + +spring: + ssl: + bundle: + jks: + spring-authorizationserver: + key: + alias: server-spring-tls + password: foobar + keystore: + location: ../test/authnz-msg-protocols/spring/server_spring.jks + password: foobar + type: PKCS12 + security: + oauth2: + users: + - username: rabbit_admin + password: rabbit_admin + scopes: + - openid + - profile + - rabbitmq.tag:administrator + audiencies: + - rabbitmq + authorizationserver: + client: + producer: + registration: + provider: spring + client-id: producer + client-secret: "{noop}producer" + authorization-grant-types: + - client_credentials + client-authentication-methods: + - client_secret_post + scopes: + - openid + - profile + - rabbitmq.tag:management + - rabbitmq.configure:*/* + - rabbitmq.read:*/* + - rabbitmq.write:*/* + client-name: producer + token: + access-token-format: reference + rabbitmq_client_code: + registration: + provider: spring + client-id: rabbitmq_client_code + authorization-grant-types: + - authorization_code + require-proof-key: true + client-authentication-methods: + - none + redirect-uris: + - "https://localhost:15671/js/oidc-oauth/login-callback.html" + post-logout-redirect-uris: + - "https://localhost:15671/" + scopes: + - openid + - profile + - rabbitmq.tag:administrator + - rabbitmq.tag:management + client-name: rabbitmq_client_code + + \ No newline at end of file diff --git a/selenium/authorization-server/src/test/java/com/rabbitmq/authorization_server/AuthorizationServerApplicationTests.java b/selenium/authorization-server/src/test/java/com/rabbitmq/authorization_server/AuthorizationServerApplicationTests.java new file mode 100644 index 0000000000..eae8ff4316 --- /dev/null +++ b/selenium/authorization-server/src/test/java/com/rabbitmq/authorization_server/AuthorizationServerApplicationTests.java @@ -0,0 +1,13 @@ +package com.rabbitmq.authorization_server; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class AuthorizationServerApplicationTests { + + @Test + void contextLoads() { + } + +} diff --git a/selenium/bin/components/fakeportal b/selenium/bin/components/fakeportal index b0693b85a3..46889080a9 100644 --- a/selenium/bin/components/fakeportal +++ b/selenium/bin/components/fakeportal @@ -32,6 +32,8 @@ init_fakeportal() { print "> CLIENT_ID: ${CLIENT_ID}" print "> CLIENT_SECRET: ${CLIENT_SECRET}" print "> RABBITMQ_URL: ${RABBITMQ_URL}" + print "> IDP_TOKEN_ENDPOINT: ${IDP_TOKEN_ENDPOINT}" + print "> IDP: ${IDP}" } start_fakeportal() { begin "Starting fakeportal ..." @@ -48,11 +50,11 @@ start_fakeportal() { --env PORT=3000 \ --env RABBITMQ_URL="${RABBITMQ_URL_FOR_FAKEPORTAL}" \ --env PROXIED_RABBITMQ_URL="${RABBITMQ_URL}" \ - --env UAA_URL="${UAA_URL_FOR_FAKEPORTAL}" \ + --env IDP_TOKEN_ENDPOINT="${IDP_TOKEN_ENDPOINT}" \ --env CLIENT_ID="${CLIENT_ID}" \ --env CLIENT_SECRET="${CLIENT_SECRET}" \ - --env NODE_EXTRA_CA_CERTS=/etc/uaa/ca_uaa_certificate.pem \ - -v ${TEST_CONFIG_DIR}/uaa:/etc/uaa \ + --env NODE_EXTRA_CA_CERTS=/etc/${IDP}/ca_${IDP}_certificate.pem \ + -v ${TEST_CONFIG_DIR}/${IDP}:/etc/${IDP} \ -v ${FAKEPORTAL_DIR}:/code/fakeportal \ mocha-test:${mocha_test_tag} run fakeportal diff --git a/selenium/bin/components/spring b/selenium/bin/components/spring new file mode 100755 index 0000000000..05e0523dfb --- /dev/null +++ b/selenium/bin/components/spring @@ -0,0 +1,49 @@ +#!/usr/bin/env bash + +SPRING_DOCKER_IMAGE=${SPRING_DOCKER_IMAGE:-pivotalrabbitmq/spring-authorization-server:0.0.10} + +ensure_spring() { + if docker ps | grep spring &> /dev/null; then + print "spring already running ..." + else + start_spring + fi +} +init_spring() { + SPRING_CONFIG_DIR=${TEST_CONFIG_PATH}/spring + SPRING_URL=${SPRING_URL:-$OAUTH_PROVIDER_URL} + + print "> SPRING_CONFIG_DIR: ${SPRING_CONFIG_DIR}" + print "> SPRING_URL: ${SPRING_URL}" + print "> SPRING_DOCKER_IMAGE: ${SPRING_DOCKER_IMAGE}" + + generate-ca-server-client-kpi spring $SPRING_CONFIG_DIR + generate-server-keystore-if-required spring $SPRING_CONFIG_DIR +} +start_spring() { + begin "Starting spring ..." + + init_spring + kill_container_if_exist spring + + MOUNT_SPRING_CONF_DIR=$CONF_DIR/spring + + mkdir -p $MOUNT_SPRING_CONF_DIR + ${BIN_DIR}/gen-spring-yml ${SPRING_CONFIG_DIR} $ENV_FILE $MOUNT_SPRING_CONF_DIR/application.yml + print "> EFFECTIVE SPRING_CONFIG_FILE: $MOUNT_SPRING_CONF_DIR/application.yml" + cp ${SPRING_CONFIG_DIR}/*.pem $MOUNT_SPRING_CONF_DIR + cp ${SPRING_CONFIG_DIR}/*.jks $MOUNT_SPRING_CONF_DIR + + docker run \ + --detach \ + --name spring \ + --net ${DOCKER_NETWORK} \ + --publish 8080:8080 \ + --publish 8443:8443 \ + -v ${MOUNT_SPRING_CONF_DIR}:/config \ + ${SPRING_DOCKER_IMAGE} + + wait_for_oidc_endpoint spring $SPRING_URL $MOUNT_SPRING_CONF_DIR/ca_spring_certificate.pem + end "spring is ready" + +} diff --git a/selenium/bin/components/uaa b/selenium/bin/components/uaa index 2a91fb468a..ec0cac19f6 100644 --- a/selenium/bin/components/uaa +++ b/selenium/bin/components/uaa @@ -24,7 +24,7 @@ start_uaa() { begin "Starting UAA ..." init_uaa - kill_container_if_exist uaa + kill_container_if_exist uaa MOUNT_UAA_CONF_DIR=$CONF_DIR/uaa @@ -44,6 +44,7 @@ start_uaa() { --env JAVA_OPTS="-Djava.security.policy=unlimited -Djava.security.egd=file:/dev/./urandom" \ ${UAA_DOCKER_IMAGE} - wait_for_oidc_endpoint uaa $UAA_URL + wait_for_message uaa "Server startup in" 20 10 + wait_for_oidc_endpoint uaa $UAA_URL $MOUNT_UAA_CONF_DIR/ca_uaa_certificate.pem end "UAA is ready" } diff --git a/selenium/bin/gen-spring-yml b/selenium/bin/gen-spring-yml new file mode 100755 index 0000000000..c32d8fb1ee --- /dev/null +++ b/selenium/bin/gen-spring-yml @@ -0,0 +1,20 @@ +#!/usr/bin/env bash +SCRIPT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + +#set -x + +SPRING_PATH=${1:?First parameter is the directory env and config files are relative to} +ENV_FILE=${2:?Second parameter is a comma-separated list of .env file which has exported template variables} +FINAL_CONFIG_FILE=${3:?Forth parameter is the name of the final config file. It is relative to where this script is run from} + +source $ENV_FILE + +parentdir="$(dirname "$FINAL_CONFIG_FILE")" +mkdir -p $parentdir + +echo "" > $FINAL_CONFIG_FILE + +for f in $($SCRIPT/find-template-files "${PROFILES}" $SPRING_PATH "application" "yml") +do + envsubst < $f >> $FINAL_CONFIG_FILE +done diff --git a/selenium/bin/suite_template b/selenium/bin/suite_template index 3d46d26ee4..97f6b0152d 100644 --- a/selenium/bin/suite_template +++ b/selenium/bin/suite_template @@ -154,16 +154,17 @@ build_mocha_image() { } kill_container_if_exist() { - if docker stop $1 &> /dev/null; then - docker rm $1 &> /dev/null - fi + docker kill $1 &> /dev/null + docker rm $1 &> /dev/null } wait_for_message() { - attemps_left=10 + delay=${3:-5} + attemps_left=${4:-10} + while ! docker logs $1 2>&1 | grep -q "$2"; do - sleep 5 - print "Waiting 5sec for $1 to start ($attemps_left attempts left )..." + sleep $delay + print "Waiting $delay sec for $1 to start ($attemps_left attempts left )..." ((attemps_left--)) if [[ "$attemps_left" -lt 1 ]]; then print "Timed out waiting" @@ -185,17 +186,20 @@ wait_for_oidc_endpoint() { wait_for_oidc_endpoint_local() { NAME=$1 BASE_URL=$2 - CURL_ARGS="-k --tlsv1.2 -L --fail " - DELAY_BETWEEN_ATTEMPTS=5 + CURL_ARGS="--tlsv1.2 -L --fail " + DELAY_BETWEEN_ATTEMPTS=10 if [[ $# -eq 3 ]]; then CURL_ARGS="$CURL_ARGS --cacert $3" DELAY_BETWEEN_ATTEMPTS=10 + else + CURL_ARGS="$CURL_ARGS -k " fi max_retry=15 counter=0 print "Waiting for OIDC discovery endpoint $NAME ... (BASE_URL: $BASE_URL)" until (curl $CURL_ARGS ${BASE_URL}/.well-known/openid-configuration >/dev/null 2>&1) do + echo "Failed $?" sleep $DELAY_BETWEEN_ATTEMPTS [[ counter -eq $max_retry ]] && print "Failed!" && exit 1 print "Trying again. Try #$counter" @@ -208,7 +212,7 @@ wait_for_oidc_endpoint_docker() { BASE_URL=$2 CURL_ARGS="-k --tlsv1.2 -L --fail " DOCKER_ARGS="--rm --net ${DOCKER_NETWORK} " - DELAY_BETWEEN_ATTEMPTS=5 + DELAY_BETWEEN_ATTEMPTS=10 if [[ $# -gt 2 ]]; then DOCKER_ARGS="$DOCKER_ARGS -v $3:/tmp/ca_certificate.pem" CURL_ARGS="$CURL_ARGS --cacert /tmp/ca_certificate.pem" @@ -469,7 +473,7 @@ do_generate-ca-server-client-kpi() { cd $ROOT/tls-gen/basic cp openssl.cnf openssl.cnf.bak if [ -f "$FOLDER/openssl.cnf.in" ]; then - cp $FOLDER/openssl.cnf.in >> openssl.cnf + cat $FOLDER/openssl.cnf.in >> openssl.cnf fi if [[ ! -z "${DEBUG}" ]]; then print "Used this openssl.conf" diff --git a/selenium/fakeportal/app.js b/selenium/fakeportal/app.js index 5b8d422d03..0de515cdfc 100644 --- a/selenium/fakeportal/app.js +++ b/selenium/fakeportal/app.js @@ -7,7 +7,7 @@ const rabbitmq_url = process.env.RABBITMQ_URL; const proxied_rabbitmq_url = process.env.PROXIED_RABBITMQ_URL; const client_id = process.env.CLIENT_ID; const client_secret = process.env.CLIENT_SECRET; -const uaa_url = process.env.UAA_URL; +const idp_token_endpoint = process.env.IDP_TOKEN_ENDPOINT; const port = process.env.PORT || 3000; app.engine('.html', require('ejs').__express); @@ -28,7 +28,10 @@ app.get('/favicon.ico', (req, res) => res.status(204)); app.listen(port); -console.log('Express started on port ' + port); +console.log('Express started on port ' + port + " using ") +console.log(" - idp_token_endpoint: " + idp_token_endpoint) +console.log(" - rabbitmq_url: " + rabbitmq_url) +console.log(" - proxied_rabbitmq_url: " + proxied_rabbitmq_url) function default_if_blank(value, defaultValue) { if (typeof value === "undefined" || value === null || value == "") { @@ -40,14 +43,13 @@ function default_if_blank(value, defaultValue) { function access_token(id, secret) { const req = new XMLHttpRequest(); - const url = uaa_url + '/oauth/token'; + const url = idp_token_endpoint const params = 'client_id=' + id + '&client_secret=' + secret + '&grant_type=client_credentials' + - '&token_format=jwt' + '&response_type=token'; - console.debug("Sending " + url + " with params "+ params); + console.debug("Sending " + url + " with params " + params); req.open('POST', url, false); req.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); diff --git a/selenium/test/oauth/env.docker.fakeportal.spring b/selenium/test/oauth/env.docker.fakeportal.spring new file mode 100644 index 0000000000..fe8494e5bb --- /dev/null +++ b/selenium/test/oauth/env.docker.fakeportal.spring @@ -0,0 +1 @@ +export IDP_TOKEN_ENDPOINT=https://spring:8443/oauth2/token diff --git a/selenium/test/oauth/env.docker.fakeportal.uaa b/selenium/test/oauth/env.docker.fakeportal.uaa new file mode 100644 index 0000000000..123af93eca --- /dev/null +++ b/selenium/test/oauth/env.docker.fakeportal.uaa @@ -0,0 +1 @@ +export IDP_TOKEN_ENDPOINT=https://uaa:8443/oauth/token diff --git a/selenium/test/oauth/env.local.fakeportal.spring b/selenium/test/oauth/env.local.fakeportal.spring new file mode 100644 index 0000000000..fe8494e5bb --- /dev/null +++ b/selenium/test/oauth/env.local.fakeportal.spring @@ -0,0 +1 @@ +export IDP_TOKEN_ENDPOINT=https://spring:8443/oauth2/token diff --git a/selenium/test/oauth/env.local.fakeportal.uaa b/selenium/test/oauth/env.local.fakeportal.uaa new file mode 100644 index 0000000000..123af93eca --- /dev/null +++ b/selenium/test/oauth/env.local.fakeportal.uaa @@ -0,0 +1 @@ +export IDP_TOKEN_ENDPOINT=https://uaa:8443/oauth/token diff --git a/selenium/test/oauth/env.spring b/selenium/test/oauth/env.spring new file mode 100644 index 0000000000..e0d13ba264 --- /dev/null +++ b/selenium/test/oauth/env.spring @@ -0,0 +1,4 @@ +export OAUTH_SERVER_CONFIG_DIR=${OAUTH_SERVER_CONFIG_BASEDIR}/oauth/spring +export OAUTH_SCOPES="openid profile" +export OAUTH_CLIENT_ID=rabbitmq_client_code +export IDP=spring diff --git a/selenium/test/oauth/env.uaa b/selenium/test/oauth/env.uaa index 506e68ac66..6c14da27b2 100644 --- a/selenium/test/oauth/env.uaa +++ b/selenium/test/oauth/env.uaa @@ -2,3 +2,4 @@ export OAUTH_SIGNING_KEY_ID=legacy-token-key export OAUTH_SERVER_CONFIG_DIR=${OAUTH_SERVER_CONFIG_BASEDIR}/oauth/uaa export OAUTH_CLIENT_SECRET=rabbitmq_client_code export OAUTH_SCOPES="openid profile rabbitmq.*" +export IDP=uaa