diff --git a/.dockerignore b/.dockerignore index 7adad677..e420573a 100644 --- a/.dockerignore +++ b/.dockerignore @@ -9,3 +9,5 @@ roman.iml frontend/node_modules frontend/build *.iml +!backend/target/surefire-reports +!backend/target/test.result \ No newline at end of file diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 00000000..42a03abf --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1 @@ +* @wireapp/integrations \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 94f0971e..8f67a423 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,12 +1,8 @@ name: CI on: - push: - branches-ignore: - - master - - staging - pull_request: + types: [ opened, synchronize ] concurrency: group: ${{ github.workflow }}-${{ github.ref }} @@ -24,6 +20,16 @@ jobs: - name: Set up Docker Buildx uses: docker/setup-buildx-action@v1 + - name: Run tests + run: make docker-run-tests + + - name: Publish test report + uses: EnricoMi/publish-unit-test-result-action/composite@v2.7 + if: always() + with: + files: | + backend/target/reports/TEST-*.xml + - name: Build image id: docker_build uses: docker/build-push-action@v2 @@ -36,10 +42,11 @@ jobs: # Send webhook to Wire using Slack Bot - name: Webhook to Wire - uses: 8398a7/action-slack@v2 + uses: 8398a7/action-slack@v3 with: status: ${{ job.status }} author_name: Docker CI pipeline + fields: repo,message,commit,author,action,eventName,ref,workflow env: SLACK_WEBHOOK_URL: ${{ secrets.WEBHOOK_CI }} # Send message only if previous step failed diff --git a/.github/workflows/prod.yml b/.github/workflows/prod.yml index 70c66e3d..22246d73 100644 --- a/.github/workflows/prod.yml +++ b/.github/workflows/prod.yml @@ -185,10 +185,11 @@ jobs: # Send webhook to Wire using Slack Bot - name: Webhook to Wire - uses: 8398a7/action-slack@v2 + uses: 8398a7/action-slack@v3 with: status: ${{ job.status }} author_name: ${{ env.SERVICE_NAME }} Quay Production Publish + fields: repo,message,commit,author,action,eventName,ref,workflow env: SLACK_WEBHOOK_URL: ${{ secrets.WEBHOOK_RELEASE }} # Send message only if previous step failed diff --git a/.github/workflows/quay.yml b/.github/workflows/quay.yml index 65891433..0fa5ee74 100644 --- a/.github/workflows/quay.yml +++ b/.github/workflows/quay.yml @@ -46,10 +46,11 @@ jobs: release_version=${{ github.event.inputs.tag }} # Send webhook to Wire using Slack Bot - name: Webhook to Wire - uses: 8398a7/action-slack@v2 + uses: 8398a7/action-slack@v3 with: status: ${{ job.status }} author_name: Roman Quay Custom Tag Pipeline + fields: repo,message,commit,author,action,eventName,ref,workflow env: SLACK_WEBHOOK_URL: ${{ secrets.WEBHOOK_CI }} # Send message only if previous step failed diff --git a/.github/workflows/staging.yml b/.github/workflows/staging.yml index 871bdf39..4234afd4 100644 --- a/.github/workflows/staging.yml +++ b/.github/workflows/staging.yml @@ -166,10 +166,11 @@ jobs: # Send webhook to Wire using Slack Bot - name: Webhook to Wire - uses: 8398a7/action-slack@v2 + uses: 8398a7/action-slack@v3 with: status: ${{ job.status }} author_name: ${{ env.SERVICE_NAME }} Quay Staging Publish + fields: repo,message,commit,author,action,eventName,ref,workflow env: SLACK_WEBHOOK_URL: ${{ secrets.WEBHOOK_RELEASE }} # Send message only if previous step failed diff --git a/Dockerfile.UnitTests b/Dockerfile.UnitTests new file mode 100644 index 00000000..6fb0d3dc --- /dev/null +++ b/Dockerfile.UnitTests @@ -0,0 +1,12 @@ +FROM --platform=linux/x86_64 wirebot/cryptobox:1.4.0 AS test-stage +WORKDIR /app + +COPY . ./ +WORKDIR /app/backend + +# in case of error, write test output status code to /tmp/test.result and exit with 0 for later stages checks. +RUN echo "0" > /tmp/test.result +RUN ./mvnw test -Dmaven.test.skip=false 2>&1 || echo $? > /tmp/test.result || echo "Tests failed" + +FROM scratch AS export-stage +COPY --from=test-stage /app/backend/target/surefire-reports/TEST-*.xml /tmp/test.result / \ No newline at end of file diff --git a/Makefile b/Makefile index 37741581..9921cf4a 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,8 @@ +docker-run-tests: + ./test.sh + db: - docker-compose up -d db + docker compose up -d db docker-build: docker build -t eu.gcr.io/wire-bot/roman . diff --git a/README.md b/README.md index 153035f3..51979393 100644 --- a/README.md +++ b/README.md @@ -83,7 +83,7 @@ result. In order to receive events via _Websocket_ connect to: ``` -wss://proxy.services.wire.com/api/await/`` +wss://proxy.services.wire.com/await/`` ``` ### Events that are sent as HTTP `POST` to your endpoint (Webhook or Websocket) @@ -334,7 +334,7 @@ or Full description: https://proxy.services.wire.com/swagger#!/default/post -**Note:** `token` that comes with `conversation.init` events is _lifelong_. It should be stored for later usage. `token` +**Note:** `token` that comes with `conversation.bot_request` events is _lifelong_. It should be stored for later usage. `token` that comes with other event types has lifespan of 20 seconds. ### Bot Examples @@ -353,7 +353,7 @@ The best way how to run Roman is to use Docker, another option is to run the Rom - In order to actually being able to connect to the Wire backend, Roman's endpoints needs to run on HTTPS. - You need a PostgreSQL instance with an empty database and credentials. - In order to run it as a Docker container, you need to have Docker installed. -- In order to run it natively on JVM, you need to have JVM 11 installed + all necessary libraries +- In order to run it natively on JVM, you need to have JVM 17 installed + all necessary libraries for [Cryptobox4j](https://github.com/wireapp/cryptobox4j). ### Configuration @@ -455,7 +455,7 @@ env variables. See [Configuration section](#configuration) how to obtain them. As previously mentioned, Wire recommends running the Roman as a docker container. However, you can run it natively on the JVM as well. -Please note that Roman requires JVM >= 11. To run it natively, one needs to +Please note that Roman requires JVM >= 17. To run it natively, one needs to install [Cryptobox4j](https://github.com/wireapp/cryptobox4j) and other cryptographic libraries. You can use [Docker Build Image](https://github.com/wireapp/cryptobox4j/blob/master/dockerfiles/Dockerfile.cryptobox) @@ -464,17 +464,17 @@ as an inspiration what needs to be installed and what environment variables need Also, don't forget to read the [Configuration section](#configuration) and set all necessary environment variables for the Roman itselgf. -First, it is necessary to build the application: +First, it is necessary to build the application under `backend` directory: ```bash -# Maven and JVM 11 is required -mvn package -DskipTests +# Maven and JVM 17 is required +./mvnw package -DskipTests ``` Then to run it like that: ```bash -# JVM 11 required +# JVM 17 required java -jar target/roman.jar server roman.yaml ``` @@ -557,6 +557,24 @@ docker-compose -f docker-compose.prod.yml --env-file .env.prod up -d 10. All set! You can go to `https://roman.example.com/swagger` and start using Roman. +## Running Roman tests suite + +Tests run with the help of docker containers to setup all requirements. +To run the test suite just run: + +```bash +make docker-run-tests +``` + +or directly: +```bash +./test.sh +``` + ## Comprehensive tutorial how to onboard new bot Step-by-step guide, how to create a simple bot for Roman - [onboarding.md](docs/onboarding.md). + +## Comprehensive tutorial how to update the public key after the certificate renewal + +Step-by-step guide, how to update bots public key manually - [public-key-refresh.md](docs/public-key-refresh.md). diff --git a/backend/.mvn/wrapper/maven-wrapper.properties b/backend/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 00000000..642d572c --- /dev/null +++ b/backend/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,2 @@ +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip +wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar diff --git a/backend/mvnw b/backend/mvnw new file mode 100755 index 00000000..a16b5431 --- /dev/null +++ b/backend/mvnw @@ -0,0 +1,310 @@ +#!/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 +# +# https://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. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven Start Up Batch script +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# M2_HOME - location of maven2's installed home dir +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "`uname`" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + export JAVA_HOME="`/usr/libexec/java_home`" + else + export JAVA_HOME="/Library/Java/Home" + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=`java-config --jre-home` + fi +fi + +if [ -z "$M2_HOME" ] ; then + ## resolve links - $0 may be a link to maven's home + PRG="$0" + + # need this for relative symlinks + while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG="`dirname "$PRG"`/$link" + fi + done + + saveddir=`pwd` + + M2_HOME=`dirname "$PRG"`/.. + + # make it fully qualified + M2_HOME=`cd "$M2_HOME" && pwd` + + cd "$saveddir" + # echo Using m2 at $M2_HOME +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --unix "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --unix "$CLASSPATH"` +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$M2_HOME" ] && + M2_HOME="`(cd "$M2_HOME"; pwd)`" + [ -n "$JAVA_HOME" ] && + JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="`which javac`" + if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=`which readlink` + if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then + if $darwin ; then + javaHome="`dirname \"$javaExecutable\"`" + javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + else + javaExecutable="`readlink -f \"$javaExecutable\"`" + fi + javaHome="`dirname \"$javaExecutable\"`" + javaHome=`expr "$javaHome" : '\(.*\)/bin'` + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + 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" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="`which java`" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=`cd "$wdir/.."; pwd` + fi + # end of workaround + done + echo "${basedir}" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' < "$1")" + fi +} + +BASE_DIR=`find_maven_basedir "$(pwd)"` +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found .mvn/wrapper/maven-wrapper.jar" + fi +else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." + fi + if [ -n "$MVNW_REPOURL" ]; then + jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + else + jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + fi + while IFS="=" read key value; do + case "$key" in (wrapperUrl) jarUrl="$value"; break ;; + esac + done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" + if [ "$MVNW_VERBOSE" = true ]; then + echo "Downloading from: $jarUrl" + fi + wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" + if $cygwin; then + wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` + fi + + if command -v wget > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found wget ... using wget" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget "$jarUrl" -O "$wrapperJarPath" + else + wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" + fi + elif command -v curl > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found curl ... using curl" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl -o "$wrapperJarPath" "$jarUrl" -f + else + curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f + fi + + else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Falling back to using Java to download" + fi + javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaClass=`cygpath --path --windows "$javaClass"` + fi + if [ -e "$javaClass" ]; then + if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Compiling MavenWrapperDownloader.java ..." + fi + # Compiling the Java class + ("$JAVA_HOME/bin/javac" "$javaClass") + fi + if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + # Running the downloader + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Running MavenWrapperDownloader.java ..." + fi + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} +if [ "$MVNW_VERBOSE" = true ]; then + echo $MAVEN_PROJECTBASEDIR +fi +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --path --windows "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --windows "$CLASSPATH"` + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` +fi + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +exec "$JAVACMD" \ + $MAVEN_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/backend/mvnw.cmd b/backend/mvnw.cmd new file mode 100755 index 00000000..c8d43372 --- /dev/null +++ b/backend/mvnw.cmd @@ -0,0 +1,182 @@ +@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 https://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 Maven Start Up Batch script +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM M2_HOME - location of maven2's installed home dir +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" +if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + +FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) +) else ( + if not "%MVNW_REPOURL%" == "" ( + SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %DOWNLOAD_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) +) +@REM End of extension + +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + +%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" +if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%" == "on" pause + +if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% + +exit /B %ERROR_CODE% diff --git a/backend/pom.xml b/backend/pom.xml index 3bfcc69c..0aad5c25 100644 --- a/backend/pom.xml +++ b/backend/pom.xml @@ -6,7 +6,7 @@ com.wire.bots roman - 1.19.0 + 1.19.3 Roman Wire Bot API Proxy @@ -28,8 +28,8 @@ true - 3.6.0 - 4.0.0 + 3.6.4 + 4.0.7 0.11.5 5.8.2 11.0.18 @@ -67,10 +67,18 @@ io.dropwizard dropwizard-servlets + + io.dropwizard + dropwizard-jdbi3 + + + io.dropwizard + dropwizard-client + com.smoketurner dropwizard-swagger - 4.0.0-1 + 4.0.5-1 @@ -102,12 +110,12 @@ com.fasterxml.jackson.core jackson-databind - 2.15.1 + 2.17.1 jakarta.xml.bind jakarta.xml.bind-api - 4.0.1 + 4.0.2 io.jsonwebtoken @@ -178,6 +186,21 @@ + + org.apache.maven.plugins + maven-surefire-plugin + 3.2.2 + + + org.apache.maven.surefire + surefire-junit-platform + 3.2.2 + + + + -Djava.library.path=/wire/cryptobox/dist/lib + + org.apache.maven.plugins maven-compiler-plugin diff --git a/backend/roman.yaml b/backend/roman.yaml index fe24d4b3..e585d373 100644 --- a/backend/roman.yaml +++ b/backend/roman.yaml @@ -22,7 +22,7 @@ logging: "com.wire.bots.logger": ${LOG_LEVEL:-INFO} swagger: - # make sure that this settings is the same as "server.rootPath" + # make sure that these settings is the same as "server.rootPath" uriPrefix: /api title: Roman Swagger description: Roman - Wire Bots Proxy @@ -42,10 +42,10 @@ jerseyClient: retries: 3 userAgent: roman tls: - protocol: TLSv1.2 + protocol: TLSv1.3 provider: SunJSSE supportedProtocols: - - TLSv1.1 + - TLSv1.3 - TLSv1.2 database: @@ -71,3 +71,5 @@ romanPubKeyBase64: ${ROMAN_PUB_KEY_BASE64:-} # optional enabling of CORS - in format a.domain.com,another.domain.com allowedCors: ${ALLOWED_CORS} + +healthchecks: false diff --git a/backend/src/main/java/com/wire/bots/roman/ImageProcessor.java b/backend/src/main/java/com/wire/bots/roman/ImageProcessor.java index 1888373a..6771dce4 100644 --- a/backend/src/main/java/com/wire/bots/roman/ImageProcessor.java +++ b/backend/src/main/java/com/wire/bots/roman/ImageProcessor.java @@ -1,29 +1,30 @@ package com.wire.bots.roman; import com.wire.bots.roman.resources.Picture; -import com.wire.xenon.assets.ImagePreview; +import javax.imageio.ImageIO; import java.awt.*; import java.awt.image.BufferedImage; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; public class ImageProcessor { - private static final int MEDIUM_DIMENSION = 2896; public static Picture getMediumImage(Picture picture) throws Exception { - return getScaledImage(picture, MEDIUM_DIMENSION); + return getScaledImage(picture); } - private static Boolean shouldScaleOriginalSize(double width, double height, double dimension) { - final double maxPixelCount = 1.3 * dimension * dimension; - return (width > 1.3 * dimension || height > 1.3 * dimension) + private static Boolean shouldScaleOriginalSize(double width, double height) { + final double maxPixelCount = 1.3 * (double) ImageProcessor.MEDIUM_DIMENSION * (double) ImageProcessor.MEDIUM_DIMENSION; + return (width > 1.3 * (double) ImageProcessor.MEDIUM_DIMENSION || height > 1.3 * (double) ImageProcessor.MEDIUM_DIMENSION) && width * height > maxPixelCount; } - private static Size getScaledSize(double origWidth, double origHeight, double dimension) { + private static Size getScaledSize(double origWidth, double origHeight) { Size ret = new Size(); - double op1 = Math.min(dimension / origWidth, dimension / origHeight); - double op2 = dimension / Math.sqrt(origWidth * origHeight); + double op1 = Math.min((double) ImageProcessor.MEDIUM_DIMENSION / origWidth, (double) ImageProcessor.MEDIUM_DIMENSION / origHeight); + double op2 = (double) ImageProcessor.MEDIUM_DIMENSION / Math.sqrt(origWidth * origHeight); double scale = Math.max(op1, op2); double width = Math.ceil(scale * origWidth); ret.width = width; @@ -52,37 +53,29 @@ private static BufferedImage resizeImage(BufferedImage originalImage, return resizedImage; } - // todo. fixme, xenon to expose getMimeType type and getImageData - private static Picture getScaledImage(ImagePreview image, int dimension) throws Exception { -// String resultImageType; -// switch ("image/jpeg") { -// case "image/jpeg": -// resultImageType = "jpg"; -// break; -// case "image/png": -// resultImageType = "png"; -// break; -// default: -// throw new IllegalArgumentException("Unsupported mime type"); -// } -// -// int origWidth = image.getWidth(); -// int origHeight = image.getHeight(); -// -// BufferedImage resultImage = ImageIO.read(new ByteArrayInputStream(image.getImageData())); -// -// if (shouldScaleOriginalSize(origWidth, origHeight, dimension)) { -// Size scaledSize = getScaledSize(origWidth, origHeight, dimension); -// resultImage = resizeImage(resultImage, (int) scaledSize.width, -// (int) scaledSize.height); -// } -// -// try (ByteArrayOutputStream resultStream = new ByteArrayOutputStream()) { -// ImageIO.write(resultImage, resultImageType, resultStream); -// resultStream.flush(); -// return new Picture(resultStream.toByteArray(), image.getMimeType()); -// } - return null; + private static Picture getScaledImage(Picture image) throws Exception { + String resultImageType = switch (image.getMimeType()) { + case "image/jpeg" -> "jpg"; + case "image/png" -> "png"; + default -> throw new IllegalArgumentException("Unsupported mime type"); + }; + + int origWidth = image.getWidth(); + int origHeight = image.getHeight(); + + BufferedImage resultImage = ImageIO.read(new ByteArrayInputStream(image.getImageData())); + + if (shouldScaleOriginalSize(origWidth, origHeight)) { + Size scaledSize = getScaledSize(origWidth, origHeight); + resultImage = resizeImage(resultImage, (int) scaledSize.width, + (int) scaledSize.height); + } + + try (ByteArrayOutputStream resultStream = new ByteArrayOutputStream()) { + ImageIO.write(resultImage, resultImageType, resultStream); + resultStream.flush(); + return new Picture(resultStream.toByteArray(), image.getMimeType()); + } } private static class Size { diff --git a/backend/src/main/java/com/wire/bots/roman/MessageHandler.java b/backend/src/main/java/com/wire/bots/roman/MessageHandler.java index 6027f9e7..34421c92 100644 --- a/backend/src/main/java/com/wire/bots/roman/MessageHandler.java +++ b/backend/src/main/java/com/wire/bots/roman/MessageHandler.java @@ -381,6 +381,7 @@ public void onBotRemoved(UUID botId, SystemMessage msg) { OutgoingMessage message = new OutgoingMessage(); message.botId = botId; message.type = "conversation.bot_removed"; + message.conversationId = msg.conversation.id; if (!send(message)) Logger.info("onBotRemoved: failed to deliver message"); diff --git a/backend/src/main/java/com/wire/bots/roman/ProviderClient.java b/backend/src/main/java/com/wire/bots/roman/ProviderClient.java index 5e5336fc..d36e91a1 100644 --- a/backend/src/main/java/com/wire/bots/roman/ProviderClient.java +++ b/backend/src/main/java/com/wire/bots/roman/ProviderClient.java @@ -15,6 +15,7 @@ import org.glassfish.jersey.logging.LoggingFeature; import java.io.ByteArrayOutputStream; +import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.UUID; @@ -160,7 +161,7 @@ public Response updateServiceURL(NewCookie zprovider, UUID serviceId, String pas .put(Entity.entity(updateService, MediaType.APPLICATION_JSON)); } - public String uploadProfilePicture(Cookie cookie, byte[] image, String mimeType) throws Exception { + public AssetKey uploadProfilePicture(Cookie cookie, byte[] image, String mimeType) throws Exception { final boolean isPublic = true; final String retention = "eternal"; String strMetadata = String.format("{\"public\": %s, \"retention\": \"%s\"}", isPublic, retention); @@ -193,21 +194,21 @@ public String uploadProfilePicture(Cookie cookie, byte[] image, String mimeType) os.write(image); os.write("\r\n--frontier--\r\n".getBytes(StandardCharsets.UTF_8)); - Response response = providerTarget + try (Response response = providerTarget .path("provider") .path("assets") - .request(MediaType.APPLICATION_JSON_TYPE) + .request(MediaType.APPLICATION_JSON) .cookie(cookie) - .post(Entity.entity(os.toByteArray(), "multipart/mixed; boundary=frontier")); - - if (response.getStatus() >= 400) { - Logger.warning(response.readEntity(String.class)); - return null; - } + .post(Entity.entity(os.toByteArray(), "multipart/mixed; boundary=frontier"))) { - AssetKey assetKey = response.readEntity(AssetKey.class); + if (response.getStatus() >= 400) { + String msg = response.readEntity(String.class); + Logger.warning("Error uploading asset: %s, status: %d", msg, response.getStatus()); + throw new IOException(response.getStatusInfo().getReasonPhrase()); + } - return assetKey.id; + return response.readEntity(AssetKey.class); + } } @JsonInclude(JsonInclude.Include.NON_NULL) diff --git a/backend/src/main/java/com/wire/bots/roman/Sender.java b/backend/src/main/java/com/wire/bots/roman/Sender.java index 4622a895..6640e07c 100644 --- a/backend/src/main/java/com/wire/bots/roman/Sender.java +++ b/backend/src/main/java/com/wire/bots/roman/Sender.java @@ -8,7 +8,17 @@ import com.wire.bots.roman.model.Mention; import com.wire.lithium.ClientRepo; import com.wire.xenon.WireClient; -import com.wire.xenon.assets.*; +import com.wire.xenon.assets.AssetBase; +import com.wire.xenon.assets.AudioAsset; +import com.wire.xenon.assets.AudioPreview; +import com.wire.xenon.assets.ButtonActionConfirmation; +import com.wire.xenon.assets.Calling; +import com.wire.xenon.assets.FileAsset; +import com.wire.xenon.assets.FileAssetPreview; +import com.wire.xenon.assets.ImageAsset; +import com.wire.xenon.assets.ImagePreview; +import com.wire.xenon.assets.MessageText; +import com.wire.xenon.assets.Poll; import com.wire.xenon.backend.models.Conversation; import com.wire.xenon.exceptions.MissingStateException; import com.wire.xenon.models.AssetKey; @@ -230,6 +240,7 @@ private void setAssetMetadata(AssetBase asset, AssetMeta meta) { asset.setAssetToken(meta.assetToken); asset.setSha256(Base64.getDecoder().decode(meta.sha256)); asset.setOtrKey(Base64.getDecoder().decode(meta.otrKey)); + asset.setDomain(meta.domain); } private AssetKey uploadAssetData(WireClient wireClient, AssetBase asset) throws Exception { diff --git a/backend/src/main/java/com/wire/bots/roman/model/AssetMeta.java b/backend/src/main/java/com/wire/bots/roman/model/AssetMeta.java index 569002a4..fe353702 100644 --- a/backend/src/main/java/com/wire/bots/roman/model/AssetMeta.java +++ b/backend/src/main/java/com/wire/bots/roman/model/AssetMeta.java @@ -2,7 +2,6 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; - import jakarta.validation.constraints.NotNull; @JsonIgnoreProperties(ignoreUnknown = true) @@ -21,4 +20,8 @@ public class AssetMeta { @JsonProperty @NotNull public String otrKey; + + @JsonProperty + @NotNull + public String domain; } diff --git a/backend/src/main/java/com/wire/bots/roman/resources/BroadcastResource.java b/backend/src/main/java/com/wire/bots/roman/resources/BroadcastResource.java index a93b387d..fc1afddd 100644 --- a/backend/src/main/java/com/wire/bots/roman/resources/BroadcastResource.java +++ b/backend/src/main/java/com/wire/bots/roman/resources/BroadcastResource.java @@ -34,6 +34,7 @@ @Api @Path("/broadcast") @Produces(MediaType.APPLICATION_JSON) +@Consumes(MediaType.APPLICATION_JSON) public class BroadcastResource { private final Sender sender; private final BotsDAO botsDAO; diff --git a/backend/src/main/java/com/wire/bots/roman/resources/ConversationResource.java b/backend/src/main/java/com/wire/bots/roman/resources/ConversationResource.java index 6398be9d..58b6c06f 100644 --- a/backend/src/main/java/com/wire/bots/roman/resources/ConversationResource.java +++ b/backend/src/main/java/com/wire/bots/roman/resources/ConversationResource.java @@ -13,10 +13,7 @@ import io.swagger.annotations.*; import jakarta.validation.Valid; import jakarta.validation.constraints.NotNull; -import jakarta.ws.rs.GET; -import jakarta.ws.rs.POST; -import jakarta.ws.rs.Path; -import jakarta.ws.rs.Produces; +import jakarta.ws.rs.*; import jakarta.ws.rs.container.ContainerRequestContext; import jakarta.ws.rs.core.Context; import jakarta.ws.rs.core.MediaType; @@ -30,6 +27,7 @@ @Api @Path("/conversation") @Produces(MediaType.APPLICATION_JSON) +@Consumes(MediaType.APPLICATION_JSON) public class ConversationResource { private final Sender sender; diff --git a/backend/src/main/java/com/wire/bots/roman/resources/Picture.java b/backend/src/main/java/com/wire/bots/roman/resources/Picture.java index 475191be..33eb2b51 100644 --- a/backend/src/main/java/com/wire/bots/roman/resources/Picture.java +++ b/backend/src/main/java/com/wire/bots/roman/resources/Picture.java @@ -8,6 +8,7 @@ public class Picture extends ImagePreview { private boolean aPublic; private final byte[] imageData; + private final String mimeType; private int width; private int height; private String retention; @@ -16,6 +17,7 @@ public Picture(byte[] image, String mimeType) { super(UUID.randomUUID(), mimeType); this.imageData = image; + this.mimeType = mimeType; } public void setPublic(boolean aPublic) { @@ -45,4 +47,8 @@ public int getHeight() { public void setHeight(int height) { this.height = height; } + + public String getMimeType() { + return mimeType; + } } diff --git a/backend/src/main/java/com/wire/bots/roman/resources/ProviderResource.java b/backend/src/main/java/com/wire/bots/roman/resources/ProviderResource.java index 93a69d32..ecedead7 100644 --- a/backend/src/main/java/com/wire/bots/roman/resources/ProviderResource.java +++ b/backend/src/main/java/com/wire/bots/roman/resources/ProviderResource.java @@ -12,22 +12,23 @@ import com.wire.xenon.tools.Logger; import io.dropwizard.validation.ValidationMethod; import io.swagger.annotations.*; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotNull; +import jakarta.ws.rs.Consumes; import jakarta.ws.rs.POST; import jakarta.ws.rs.Path; import jakarta.ws.rs.Produces; -import org.hibernate.validator.constraints.Length; -import org.jdbi.v3.core.Jdbi; - -import jakarta.validation.Valid; -import jakarta.validation.constraints.NotNull; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; +import org.hibernate.validator.constraints.Length; +import org.jdbi.v3.core.Jdbi; import static com.wire.bots.roman.Tools.generateToken; @Api @Path("/") @Produces(MediaType.APPLICATION_JSON) +@Consumes(MediaType.APPLICATION_JSON) public class ProviderResource { private final ProviderClient providerClient; diff --git a/backend/src/main/java/com/wire/bots/roman/resources/ServiceResource.java b/backend/src/main/java/com/wire/bots/roman/resources/ServiceResource.java index bb4e72d0..eb4c9804 100644 --- a/backend/src/main/java/com/wire/bots/roman/resources/ServiceResource.java +++ b/backend/src/main/java/com/wire/bots/roman/resources/ServiceResource.java @@ -11,6 +11,7 @@ import com.wire.bots.roman.model.Provider; import com.wire.bots.roman.model.Service; import com.wire.xenon.backend.models.ErrorMessage; +import com.wire.xenon.models.AssetKey; import com.wire.xenon.tools.Logger; import io.dropwizard.validation.ValidationMethod; import io.swagger.annotations.*; @@ -39,6 +40,7 @@ @Api @Path("/service") @Produces(MediaType.APPLICATION_JSON) +@Consumes(MediaType.APPLICATION_JSON) public class ServiceResource { private static final String PROFILE_KEY = "3-1-c9262f6f-892f-40d5-9349-fbeb62c8aba4"; @@ -98,9 +100,9 @@ public Response create(@ApiParam(hidden = true) @CookieParam(Z_ROMAN) String tok if (image != null) { String mimeType = "image/png"; Picture mediumImage = ImageProcessor.getMediumImage(new Picture(image, mimeType)); - String key = providerClient.uploadProfilePicture(cookie, mediumImage.getImageData(), mimeType); - service.assets.get(0).key = key; - service.assets.get(1).key = key; + AssetKey assetKey = providerClient.uploadProfilePicture(cookie, mediumImage.getImageData(), mimeType); + service.assets.get(0).key = assetKey.id; + service.assets.get(1).key = assetKey.id; } } } @@ -213,8 +215,8 @@ public Response update(@Context ContainerRequestContext context, byte[] image = Base64.getDecoder().decode(payload.avatar); String mimeType = "image/jpeg"; Picture mediumImage = ImageProcessor.getMediumImage(new Picture(image, mimeType)); - String key = providerClient.uploadProfilePicture(cookie, mediumImage.getImageData(), mimeType); - providerClient.updateServiceAvatar(cookie, provider.serviceId, key); + AssetKey assetKey = providerClient.uploadProfilePicture(cookie, mediumImage.getImageData(), mimeType); + providerClient.updateServiceAvatar(cookie, provider.serviceId, assetKey.id); } provider = providersDAO.get(providerId); diff --git a/backend/src/main/java/com/wire/bots/roman/resources/UsersResource.java b/backend/src/main/java/com/wire/bots/roman/resources/UsersResource.java index 3431e413..04783f05 100644 --- a/backend/src/main/java/com/wire/bots/roman/resources/UsersResource.java +++ b/backend/src/main/java/com/wire/bots/roman/resources/UsersResource.java @@ -9,20 +9,18 @@ import com.wire.xenon.backend.models.User; import com.wire.xenon.tools.Logger; import io.swagger.annotations.*; - -import jakarta.ws.rs.GET; -import jakarta.ws.rs.Path; -import jakarta.ws.rs.PathParam; -import jakarta.ws.rs.Produces; +import jakarta.ws.rs.*; import jakarta.ws.rs.container.ContainerRequestContext; import jakarta.ws.rs.core.Context; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; + import java.util.UUID; @Api @Path("/users") @Produces(MediaType.APPLICATION_JSON) +@Consumes(MediaType.APPLICATION_JSON) public class UsersResource { private final ClientRepo repo; diff --git a/backend/src/test/java/com/wire/bots/roman/integrations/BroadcastResourceTest.java b/backend/src/test/java/com/wire/bots/roman/integrations/BroadcastResourceTest.java index 800aacd1..79291cb1 100644 --- a/backend/src/test/java/com/wire/bots/roman/integrations/BroadcastResourceTest.java +++ b/backend/src/test/java/com/wire/bots/roman/integrations/BroadcastResourceTest.java @@ -4,7 +4,11 @@ import com.wire.bots.roman.Const; import com.wire.bots.roman.DAO.ProvidersDAO; import com.wire.bots.roman.Tools; -import com.wire.bots.roman.model.*; +import com.wire.bots.roman.model.AssetMeta; +import com.wire.bots.roman.model.Attachment; +import com.wire.bots.roman.model.Config; +import com.wire.bots.roman.model.IncomingMessage; +import com.wire.bots.roman.model.Report; import com.wire.lithium.models.NewBotResponseModel; import com.wire.xenon.backend.models.Conversation; import com.wire.xenon.backend.models.NewBot; @@ -19,6 +23,7 @@ import org.jdbi.v3.core.Jdbi; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.io.TempDir; @@ -29,21 +34,16 @@ import java.util.Random; import java.util.UUID; -import static io.dropwizard.testing.ConfigOverride.config; +import static com.wire.bots.roman.resources.dummies.Const.ROMAN_TEST_CONFIG; import static org.assertj.core.api.Assertions.assertThat; @ExtendWith(DropwizardExtensionsSupport.class) public class BroadcastResourceTest { private static final String BOT_CLIENT_DUMMY = "bot_client_dummy"; - private static final String CONFIG = "roman-test.yml"; @TempDir static Path tempDir; static final DropwizardAppExtension SUPPORT = new DropwizardAppExtension<>( - Application.class, CONFIG, - new ResourceConfigurationSourceProvider(), - config("database.url", () -> "jdbc:h2:" + tempDir.resolve("database.h2")), - config("key", "TcZA2Kq4GaOcIbQuOvasrw34321cZAfLW4Ga54fsds43hUuOdcdm42"), - config("apiHost", "http://localhost:8090")); + Application.class, ROMAN_TEST_CONFIG, new ResourceConfigurationSourceProvider()); private Client client; private Jdbi jdbi; @@ -61,6 +61,7 @@ public void afterClass() { } @Test + @Disabled("Fix when broadcast is testable") public void broadcastTest() throws InterruptedException { final Random random = new Random(); final UUID botId = UUID.randomUUID(); @@ -159,4 +160,4 @@ private NewBotResponseModel newBotFromBE(UUID botId, UUID userId, UUID convId, S return res.readEntity(NewBotResponseModel.class); } -} +} \ No newline at end of file diff --git a/backend/src/test/java/com/wire/bots/roman/integrations/DatabaseTest.java b/backend/src/test/java/com/wire/bots/roman/integrations/DatabaseTest.java index eacbccb5..2c59191c 100644 --- a/backend/src/test/java/com/wire/bots/roman/integrations/DatabaseTest.java +++ b/backend/src/test/java/com/wire/bots/roman/integrations/DatabaseTest.java @@ -1,179 +1,174 @@ -//package com.wire.bots.roman.integrations; -// -//import com.fasterxml.jackson.core.JsonProcessingException; -//import com.fasterxml.jackson.databind.ObjectMapper; -//import com.wire.bots.roman.Application; -//import com.wire.bots.roman.DAO.BroadcastDAO; -//import com.wire.bots.roman.DAO.OutgoingMessageDAO; -//import com.wire.bots.roman.DAO.ProvidersDAO; -//import com.wire.bots.roman.model.Attachment; -//import com.wire.bots.roman.model.Config; -//import com.wire.bots.roman.model.OutgoingMessage; -//import com.wire.bots.roman.model.Provider; -//import io.dropwizard.testing.ConfigOverride; -//import io.dropwizard.testing.DropwizardTestSupport; -//import org.jdbi.v3.core.Jdbi; -//import org.junit.After; -//import org.junit.Before; -//import org.junit.Test; -// -//import java.util.List; -//import java.util.UUID; -// -//import static org.assertj.core.api.Assertions.assertThat; -// -//public class DatabaseTest { -// private static final DropwizardTestSupport SUPPORT = new DropwizardTestSupport<>( -// Application.class, "roman.yaml", -// ConfigOverride.config("key", "TcZA2Kq4GaOcIbQuOvasrw34321cZAfLW4Ga54fsds43hUuOdcdm42"), -// ConfigOverride.config("romanPubKeyBase64", "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3xtHqyZPlb0lxlnP0rNA\n" + -// "JVmAjB1Tenl11brkkKihcJNRAYrnrT/6sPX4u2lVn/aPncUTjN8omL47MBct7qYV\n" + -// "1VY4a5beOyNiVL0ZjZMuh07aL9Z2A4cu67tKZrCoGttn3jpSVlqoOtwEgW+Tpgpm\n" + -// "KojcRC4DDXEZTEvRoi0RLzAyWCH/8hwWzXR7J082zmn0Ur211QVbOJN/62PAIWyj\n" + -// "l5bLglp00AY5OnBHgRNwwRkBJIJLwgNm8u9+0ZplqmMGd3C/QFNngCOeRvFe+5g4\n" + -// "qfO4/FOlbkM2kYFAi5KUowfG7cdMQELI+fe4v7yNsgrbMKhnIiLtDIU4wiQIRjbr\n" + -// "ZwIDAQAB")); -// private Jdbi jdbi; -// -// @Before -// public void beforeClass() throws Exception { -// SUPPORT.before(); -// Application app = SUPPORT.getApplication(); -// jdbi = app.getJdbi(); -// } -// -// @After -// public void afterClass() { -// SUPPORT.after(); -// } -// -// -// @Test -// public void testProviderDAO() { -// final ProvidersDAO providersDAO = jdbi.onDemand(ProvidersDAO.class); -// -// final UUID providerId = UUID.randomUUID(); -// final String name = "name"; -// final String email = "email@wire.com"; -// final String hash = "hash"; -// final String password = "password"; -// final int insert = providersDAO.insert(name, providerId, email, hash, password); -// assertThat(insert).isEqualTo(1); -// -// Provider provider = providersDAO.get(providerId); -// assertThat(provider).isNotNull(); -// assertThat(provider.name).isEqualTo(name); -// assertThat(provider.hash).isEqualTo(hash); -// assertThat(provider.password).isEqualTo(password); -// assertThat(provider.id).isEqualTo(providerId); -// assertThat(provider.email).isEqualTo(email); -// -// provider = providersDAO.get(email); -// assertThat(provider).isNotNull(); -// assertThat(provider.name).isEqualTo(name); -// assertThat(provider.hash).isEqualTo(hash); -// assertThat(provider.password).isEqualTo(password); -// assertThat(provider.id).isEqualTo(providerId); -// assertThat(provider.email).isEqualTo(email); -// -// final String url = "url"; -// final String auth = "auth"; -// final UUID serviceId = UUID.randomUUID(); -// final String service_name = "service name"; -// final String prefix = "/"; -// -// int update = providersDAO.update(providerId, url, auth, serviceId, service_name, prefix); -// assertThat(update).isEqualTo(1); -// -// provider = providersDAO.getByAuth(auth); -// assertThat(provider).isNotNull(); -// assertThat(provider.serviceAuth).isEqualTo(auth); -// assertThat(provider.serviceUrl).isEqualTo(url); -// assertThat(provider.serviceId).isEqualTo(serviceId); -// assertThat(provider.serviceName).isEqualTo(service_name); -// assertThat(provider.commandPrefix).isEqualTo(prefix); -// -// final String newURL = "newURL"; -// update = providersDAO.updateUrl(providerId, newURL); -// assertThat(update).isEqualTo(1); -// -// provider = providersDAO.get(providerId); -// assertThat(provider).isNotNull(); -// assertThat(provider.serviceUrl).isEqualTo(newURL); -// -// final String newName = "new service name"; -// update = providersDAO.updateServiceName(providerId, newName); -// assertThat(update).isEqualTo(1); -// -// provider = providersDAO.get(providerId); -// assertThat(provider).isNotNull(); -// assertThat(provider.serviceName).isEqualTo(newName); -// -// final String newPrefix = "@"; -// update = providersDAO.updateServicePrefix(providerId, newPrefix); -// assertThat(update).isEqualTo(1); -// -// provider = providersDAO.get(providerId); -// assertThat(provider).isNotNull(); -// assertThat(provider.commandPrefix).isEqualTo(newPrefix); -// -// final int deleteService = providersDAO.deleteService(providerId); -// provider = providersDAO.get(providerId); -// -// } -// -// @Test -// public void testBroadcastDAO() { -// final BroadcastDAO broadcastDAO = jdbi.onDemand(BroadcastDAO.class); -// -// final UUID providerId = UUID.randomUUID(); -// final UUID broadcastId = UUID.randomUUID(); -// final UUID botId = UUID.randomUUID(); -// final UUID messageId = UUID.randomUUID(); -// -// final int insert1 = broadcastDAO.insert(broadcastId, botId, providerId, messageId, 0); -// assertThat(insert1).isEqualTo(1); -// -// int insertStatus = broadcastDAO.insertStatus(messageId, 1); -// assertThat(insertStatus).isEqualTo(1); -// insertStatus = broadcastDAO.insertStatus(messageId, 2); -// assertThat(insertStatus).isEqualTo(1); -// insertStatus = broadcastDAO.insertStatus(messageId, 3); -// assertThat(insertStatus).isEqualTo(1); -// -// final UUID get = broadcastDAO.getBroadcastId(providerId); -// assertThat(get).isNotNull(); -// assertThat(get).isEqualTo(broadcastId); -// -// final List report = broadcastDAO.report(broadcastId); -// -// final UUID broadcastId2 = UUID.randomUUID(); -// final UUID botId2 = UUID.randomUUID(); -// final UUID messageId2 = UUID.randomUUID(); -// final int insert2 = broadcastDAO.insert(broadcastId2, botId2, providerId, messageId2, 0); -// assertThat(insert2).isEqualTo(1); -// -// final UUID get2 = broadcastDAO.getBroadcastId(providerId); -// assertThat(get2).isNotNull(); -// assertThat(get2).isEqualTo(broadcastId2); -// } -// -// @Test -// public void testOutgoingMessageDAO() throws JsonProcessingException { -// final ObjectMapper mapper = new ObjectMapper(); -// final OutgoingMessageDAO outgoingMessageDAO = jdbi.onDemand(OutgoingMessageDAO.class); -// OutgoingMessage message = new OutgoingMessage(); -// message.messageId = UUID.randomUUID(); -// message.token = "token"; -// message.attachment = new Attachment(); -// message.attachment.data = "data"; -// -// outgoingMessageDAO.insert(message.messageId, mapper.writeValueAsString(message)); -// -// final OutgoingMessage challenge = outgoingMessageDAO.get(message.messageId); -// assertThat(challenge).isNotNull(); -// assertThat(challenge.messageId).isEqualTo(message.messageId); -// -// outgoingMessageDAO.delete(message.messageId); -// } -//} +package com.wire.bots.roman.integrations; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.wire.bots.roman.Application; +import com.wire.bots.roman.DAO.BroadcastDAO; +import com.wire.bots.roman.DAO.OutgoingMessageDAO; +import com.wire.bots.roman.DAO.ProvidersDAO; +import com.wire.bots.roman.model.Attachment; +import com.wire.bots.roman.model.Config; +import com.wire.bots.roman.model.OutgoingMessage; +import com.wire.bots.roman.model.Provider; +import io.dropwizard.configuration.ResourceConfigurationSourceProvider; +import io.dropwizard.testing.DropwizardTestSupport; +import io.dropwizard.testing.junit5.DropwizardExtensionsSupport; +import org.jdbi.v3.core.Jdbi; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +import java.util.List; +import java.util.UUID; + +import static com.wire.bots.roman.resources.dummies.Const.ROMAN_TEST_CONFIG; +import static org.assertj.core.api.Assertions.assertThat; + +@ExtendWith(DropwizardExtensionsSupport.class) +public class DatabaseTest { + private static final DropwizardTestSupport SUPPORT = new DropwizardTestSupport<>( + Application.class, ROMAN_TEST_CONFIG, new ResourceConfigurationSourceProvider() + ); + private Jdbi jdbi; + + @BeforeEach + public void beforeClass() throws Exception { + SUPPORT.before(); + Application app = SUPPORT.getApplication(); + jdbi = app.getJdbi(); + } + + @AfterEach + public void afterClass() { + SUPPORT.after(); + } + + @Test + public void testProviderDAO() { + final ProvidersDAO providersDAO = jdbi.onDemand(ProvidersDAO.class); + + final UUID providerId = UUID.randomUUID(); + final String name = "name"; + final String email = "email@wire.com"; + final String hash = "hash"; + final String password = "password"; + final int insert = providersDAO.insert(name, providerId, email, hash, password); + assertThat(insert).isEqualTo(1); + + Provider provider = providersDAO.get(providerId); + assertThat(provider).isNotNull(); + assertThat(provider.name).isEqualTo(name); + assertThat(provider.hash).isEqualTo(hash); + assertThat(provider.password).isEqualTo(password); + assertThat(provider.id).isEqualTo(providerId); + assertThat(provider.email).isEqualTo(email); + + provider = providersDAO.get(email); + assertThat(provider).isNotNull(); + assertThat(provider.name).isEqualTo(name); + assertThat(provider.hash).isEqualTo(hash); + assertThat(provider.password).isEqualTo(password); + assertThat(provider.id).isEqualTo(providerId); + assertThat(provider.email).isEqualTo(email); + + final String url = "url"; + final String auth = "auth"; + final UUID serviceId = UUID.randomUUID(); + final String service_name = "service name"; + final String prefix = "/"; + + int update = providersDAO.update(providerId, url, auth, serviceId, service_name, prefix); + assertThat(update).isEqualTo(1); + + provider = providersDAO.getByAuth(auth); + assertThat(provider).isNotNull(); + assertThat(provider.serviceAuth).isEqualTo(auth); + assertThat(provider.serviceUrl).isEqualTo(url); + assertThat(provider.serviceId).isEqualTo(serviceId); + assertThat(provider.serviceName).isEqualTo(service_name); + assertThat(provider.commandPrefix).isEqualTo(prefix); + + final String newURL = "newURL"; + update = providersDAO.updateUrl(providerId, newURL); + assertThat(update).isEqualTo(1); + + provider = providersDAO.get(providerId); + assertThat(provider).isNotNull(); + assertThat(provider.serviceUrl).isEqualTo(newURL); + + final String newName = "new service name"; + update = providersDAO.updateServiceName(providerId, newName); + assertThat(update).isEqualTo(1); + + provider = providersDAO.get(providerId); + assertThat(provider).isNotNull(); + assertThat(provider.serviceName).isEqualTo(newName); + + final String newPrefix = "@"; + update = providersDAO.updateServicePrefix(providerId, newPrefix); + assertThat(update).isEqualTo(1); + + provider = providersDAO.get(providerId); + assertThat(provider).isNotNull(); + assertThat(provider.commandPrefix).isEqualTo(newPrefix); + + final int deleteService = providersDAO.deleteService(providerId); + provider = providersDAO.get(providerId); + } + + @Test + public void testBroadcastDAO() { + final BroadcastDAO broadcastDAO = jdbi.onDemand(BroadcastDAO.class); + + final UUID providerId = UUID.randomUUID(); + final UUID broadcastId = UUID.randomUUID(); + final UUID botId = UUID.randomUUID(); + final UUID messageId = UUID.randomUUID(); + + final int insert1 = broadcastDAO.insert(broadcastId, botId, providerId, messageId, 0); + assertThat(insert1).isEqualTo(1); + + int insertStatus = broadcastDAO.insertStatus(messageId, 1); + assertThat(insertStatus).isEqualTo(1); + insertStatus = broadcastDAO.insertStatus(messageId, 2); + assertThat(insertStatus).isEqualTo(1); + insertStatus = broadcastDAO.insertStatus(messageId, 3); + assertThat(insertStatus).isEqualTo(1); + + final UUID get = broadcastDAO.getBroadcastId(providerId); + assertThat(get).isNotNull(); + assertThat(get).isEqualTo(broadcastId); + + final List report = broadcastDAO.report(broadcastId); + + final UUID broadcastId2 = UUID.randomUUID(); + final UUID botId2 = UUID.randomUUID(); + final UUID messageId2 = UUID.randomUUID(); + final int insert2 = broadcastDAO.insert(broadcastId2, botId2, providerId, messageId2, 0); + assertThat(insert2).isEqualTo(1); + + final UUID get2 = broadcastDAO.getBroadcastId(providerId); + assertThat(get2).isNotNull(); + assertThat(get2).isEqualTo(broadcastId2); + } + + @Test + public void testOutgoingMessageDAO() throws JsonProcessingException { + final ObjectMapper mapper = new ObjectMapper(); + final OutgoingMessageDAO outgoingMessageDAO = jdbi.onDemand(OutgoingMessageDAO.class); + OutgoingMessage message = new OutgoingMessage(); + message.messageId = UUID.randomUUID(); + message.token = "token"; + message.attachment = new Attachment(); + message.attachment.data = "data"; + + outgoingMessageDAO.insert(message.messageId, mapper.writeValueAsString(message)); + + final OutgoingMessage challenge = outgoingMessageDAO.get(message.messageId); + assertThat(challenge).isNotNull(); + assertThat(challenge.messageId).isEqualTo(message.messageId); + + outgoingMessageDAO.delete(message.messageId); + } +} \ No newline at end of file diff --git a/backend/src/test/java/com/wire/bots/roman/resources/dummies/Const.java b/backend/src/test/java/com/wire/bots/roman/resources/dummies/Const.java index c1401c49..35e81b29 100644 --- a/backend/src/test/java/com/wire/bots/roman/resources/dummies/Const.java +++ b/backend/src/test/java/com/wire/bots/roman/resources/dummies/Const.java @@ -8,4 +8,6 @@ public class Const { public static final UUID CONV_ID = UUID.fromString("21aa4705-ae33-4824-8302-c160a06dc657"); public static final UUID MSG_ID = UUID.fromString("51aa4705-ae33-4824-8302-c160a06dc657"); + public static final String ROMAN_TEST_CONFIG = "roman-test.yml"; + } diff --git a/backend/src/test/resources/roman-test.yml b/backend/src/test/resources/roman-test.yml index 4fbf1e5a..e36747e2 100644 --- a/backend/src/test/resources/roman-test.yml +++ b/backend/src/test/resources/roman-test.yml @@ -34,13 +34,6 @@ swagger: - https - http -assets: - mappings: - /assets: / - overrides: - # the default assumes you have build frontend by "npm run build" - /: ${FRONTEND_PATH:-../frontend/build} - jerseyClient: timeout: 40s connectionTimeout: 40s @@ -56,23 +49,23 @@ jerseyClient: - TLSv1.2 database: - driverClass: org.h2.Driver - user: sa - password: sa - url: jdbc:h2:./target/test + driverClass: org.postgresql.Driver + user: roman + password: roman + url: jdbc:postgresql://localhost:5432/roman token: dummy # used to sign JWT -key: ${APP_KEY:-} +key: ${APP_KEY:-TcZA2Kq4GaOcIbQuOvasrw34321cZAfLW4Ga54fsds43hUuOdcdm42} # the public URL of the Roman instance, should end with "/api" as all Roman endpoints # are served starting with /api -domain: ${PROXY_DOMAIN:-https://proxy.services.wire.com/api} +domain: http://localhost:8080/api # URL of the Wire Backend -apiHost: ${WIRE_API_HOST:-https://prod-nginz-https.wire.com} +apiHost: https://staging-nginz-https.zinfra.io/v4 # TLS public key of "domain" in base64 format - used to pin certificates in Wire backend # for the bot -romanPubKeyBase64: ${ROMAN_PUB_KEY_BASE64:-default} +romanPubKeyBase64: MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3xtHqyZPlb0lxlnP0rNAJVmAjB1Tenl11brkkKihcJNRAYrnrT/6sPX4u2lVn/aPncUTjN8omL47MBct7qYV1VY4a5beOyNiVL0ZjZMuh07aL9Z2A4cu67tKZrCoGttn3jpSVlqoOtwEgW+TpgpmKojcRC4DDXEZTEvRoi0RLzAyWCH/8hwWzXR7J082zmn0Ur211QVbOJN/62PAIWyjl5bLglp00AY5OnBHgRNwwRkBJIJLwgNm8u9+0ZplqmMGd3C/QFNngCOeRvFe+5g4qfO4/FOlbkM2kYFAi5KUowfG7cdMQELI+fe4v7yNsgrbMKhnIiLtDIU4wiQIRjbrZwIDAQAB # optional enabling of CORS - in format a.domain.com,another.domain.com -allowedCors: ${ALLOWED_CORS} +allowedCors: ${ALLOWED_CORS} \ No newline at end of file diff --git a/docs/onboarding.md b/docs/onboarding.md index 15076bd7..a1bb253a 100644 --- a/docs/onboarding.md +++ b/docs/onboarding.md @@ -190,8 +190,8 @@ Now when we have a functional bot with a public URL, we need to register it in W } ``` -From this JSON you will need `service_code` *(in our case `41c5e09f-4867-43d9-b74f-12b27148469a:96917b38-b5d3-4217-bee8-d77569b3c4dc`)* -for the following steps. +From this JSON you will need the `service_code` *(in our case `41c5e09f-4867-43d9-b74f-12b27148469a:96917b38-b5d3-4217-bee8-d77569b3c4dc`)* +for the next steps. The `service_code` is nothing but the combination of your `ProviderID` and `ServiceID` separated by `:`. `ServiceID` is the id of the service you have just created. `ProviderID` and `ServiceID` are needed in the next step. ## 5. Whitelisting the Bot diff --git a/docs/public-key-refresh.md b/docs/public-key-refresh.md new file mode 100644 index 00000000..45bd0919 --- /dev/null +++ b/docs/public-key-refresh.md @@ -0,0 +1,50 @@ +# Update bot public key + +When registering a service with the Wire backend, the service needs to specify with certificate it will use for incoming TLS connections. The Wire backend will verify that the certificates match when making TLS requests to the service, and otherwise abort any connection if it doesn’t match. + +Periodically, the certificates need to be updated. + +## Requirements + +- Wire Provider account. +- Roman instance *(you can use public Roman running [here](https://proxy.services.wire.com/))*, in this guide we will be + using [internal staging Roman](https://roman.integrations.zinfra.io/swagger) with URL `https://roman.integrations.zinfra.io`. + - Please note that Wire Staging environment is accessible only for the Wire development team and if you create the bot here, you won't + be able to use it unless you have access to that environment. +- A bot already fully onboarded and running. If you don't have one, please check the [onboarding guide](onboarding.md). + +You will need to make some API calls, use your favourite tool for that, e.g. [Postman](https://www.postman.com/) or [curl](https://curl.se/). +The following examples will use `curl` commands, but you can easily adapt them to your tool of choice. + +## Commands +```bash +curl --request POST \ + --url https://prod-nginz-https.wire.com/v6/provider/login \ + --header 'content-type: application/json' \ + --data '{ + "email": "$PROVIDER_EMAIL", + "password": "$PROVIDER_PASSWORD" +}' +``` +Then take the `Set-cookie` value from the response headers and call: + + +```bash +curl --request GET \ +--url https://prod-nginz-https.wire.com/v6/provider/services \ +--header 'cookie: $COOKIE' +``` +This will give you the list of services you have on the provider you have registered. + +Then for each service you want to update with a new public key: +```bash +curl --request PUT \ +--url https://prod-nginz-https.wire.com/v6/provider/services/${SERVICE_ID}/connection \ +--header 'content-type: application/json' \ +--header 'cookie: $COOKIE' \ +--data '{ +"password": "$PROVIDER_PASSWORD", +"public_keys": [ +"-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3xtHqyZPlb0lxlnP0rNA\nJVmAjB1Tenl11brkkKihcJNRAYrnrT/6sPX4u2lVn/aPncUTjN8omL47MBct7qYV\n1VY4a5beOyNiVL0ZjZMuh07aL9Z2A4cu67tKZrCoGttn3jpSVlqoOtwEgW+Tpgpm\nKojcRC4DDXEZTEvRoi0RLzAyWCH/8hwWzXR7J082zmn0Ur211QVbOJN/62PAIWyj\nl5bLglp00AY5OnBHgRNwwRkBJIJLwgNm8u9+0ZplqmMGd3C/QFNngCOeRvFe+5g4\nqfO4/FOlbkM2kYFAi5KUowfG7cdMQELI+fe4v7yNsgrbMKhnIiLtDIU4wiQIRjbr\nZwIDAQAB\n-----END PUBLIC KEY-----\n" +] +``` diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 038a5988..39c184d8 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -9060,9 +9060,9 @@ } }, "node_modules/follow-redirects": { - "version": "1.15.2", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", - "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", + "version": "1.15.4", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.4.tgz", + "integrity": "sha512-Cr4D/5wlrb0z9dgERpUL3LrmPKVDsETIJhaCMeDfuFYcqa5bldGV6wBsAN6X/vxlXQtFBMrXdXxdL8CbDTGniw==", "funding": [ { "type": "individual", @@ -10462,9 +10462,9 @@ } }, "node_modules/ip": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.8.tgz", - "integrity": "sha512-PuExPYUiu6qMBQb4l06ecm6T6ujzhmh+MeJcW9wa89PoAz5pvd4zPgN5WJV104mb6S2T1AwNIAaB70JNrLQWhg==" + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.9.tgz", + "integrity": "sha512-cyRxvOEpNHNtchU3Ln9KC/auJgup87llfQpQ+t5ghoC/UhL16SWzbueiCsdTnWmqAWl7LadfuwhlqmtOaqMHdQ==" }, "node_modules/ip-regex": { "version": "2.1.0", diff --git a/frontend/src/app/pages/home/components/ServiceAccess.tsx b/frontend/src/app/pages/home/components/ServiceAccess.tsx index 646ac448..a98981f1 100644 --- a/frontend/src/app/pages/home/components/ServiceAccess.tsx +++ b/frontend/src/app/pages/home/components/ServiceAccess.tsx @@ -23,7 +23,7 @@ export default function ServiceAccessInfo(accessInfo: ServiceAccessInfoProps) { InputProps={{ readOnly: true }} - helperText={'Service code is used to enable the service in the team settings.'} + helperText={'Service code is made of ProviderID and ServiceID separated by a colon.'} /> diff --git a/test.sh b/test.sh new file mode 100755 index 00000000..7fd23cf9 --- /dev/null +++ b/test.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env sh +set -x + +echo "1/4) Starting test environment..." +docker compose up -d db + +echo "2/4) Running tests..." +DOCKER_BUILDKIT=1 docker build --network host --target export-stage --output backend/target/reports -f Dockerfile.UnitTests . + +echo "3/4) Cleaning up test environment..." +docker compose stop + +echo "4/4) Evaluating tests result exit status..." +EXIT_CODE=`cat backend/target/reports/test.result` +exit $EXIT_CODE \ No newline at end of file