diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000000000000000000000000000000000000..79bc9f5ac4941dd57916e41ce9656b913fe38e30 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,41 @@ +name: Maven build with tests +on: [ push, pull_request ] +jobs: + build: + name: JDK ${{ matrix.java }} + runs-on: ubuntu-latest + strategy: + matrix: + java: [ '11', '17' ] + steps: + - uses: actions/checkout@v3 + - name: Set up JDK ${{ matrix.java }} + uses: actions/setup-java@v3 + with: + distribution: 'zulu' + java-version: ${{ matrix.java }} + - uses: actions/cache@v3 + with: + path: ~/.m2/repository + key: ${{ runner.os }}-maven-jdk${{ matrix.java }}-${{ hashFiles('**/pom.xml') }} + restore-keys: | + ${{ runner.os }}-maven-jdk${{ matrix.java }}- + - name: Build with Maven + run: mvn -B clean install -Djava.version=${{matrix.java}} + release: + name: Release + needs: [build] + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + fetch-depth: 0 + - name: Setup Node.js + uses: actions/setup-node@v3 + with: + node-version: 16 + - name: Release + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: npm install && npx semantic-release diff --git a/.gitignore b/.gitignore index 5ff6309b7199129c1afe4f4ec1906e640bec48c6..946c6348f05a527874476e2915d098a141676496 100644 --- a/.gitignore +++ b/.gitignore @@ -3,11 +3,15 @@ target/ !**/src/main/**/target/ !**/src/test/**/target/ +# semantic +node_modules + ### IntelliJ IDEA ### .idea/modules.xml .idea/jarRepositories.xml .idea/compiler.xml .idea/libraries/ +.idea *.iws *.iml *.ipr @@ -35,4 +39,4 @@ build/ .vscode/ ### Mac OS ### -.DS_Store \ No newline at end of file +.DS_Store diff --git a/.mvn/wrapper/maven-wrapper.jar b/.mvn/wrapper/maven-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..bf82ff01c6cdae4a1bb754a6e062954d77ac5c11 Binary files /dev/null and b/.mvn/wrapper/maven-wrapper.jar differ diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000000000000000000000000000000000000..996a98a60604ef16f371c1489fd4e578debc244c --- /dev/null +++ b/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,18 @@ +# 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. +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.0/apache-maven-3.6.0-bin.zip +wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar diff --git a/.releaserc b/.releaserc new file mode 100644 index 0000000000000000000000000000000000000000..a05f894c3b2a684dc839a39ce77649b3f2a0f091 --- /dev/null +++ b/.releaserc @@ -0,0 +1,20 @@ +{ + "branches": ["master"], + "plugins": [ + "@semantic-release/commit-analyzer", + "@semantic-release/release-notes-generator", + ["@semantic-release/exec", { + "prepareCmd": "./update-versions.sh ${nextRelease.version} && ./mvnw clean package" + }], + "@semantic-release/changelog", + ["@semantic-release/git", { + "assets": ["CHANGELOG.md", "pom.xml"], + "message": "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}" + }], + ["@semantic-release/github", { + "assets": [ + {"path": "target/perun-ga4gh-broker.war"} + ] + }] + ] +} \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/mvnw b/mvnw new file mode 100755 index 0000000000000000000000000000000000000000..b7f064624f8911a9d50912d2c3e163a1eb685209 --- /dev/null +++ b/mvnw @@ -0,0 +1,287 @@ +#!/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.1.1 +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# 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 /usr/local/etc/mavenrc ] ; then + . /usr/local/etc/mavenrc + fi + + 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 + JAVA_HOME="`/usr/libexec/java_home`"; export JAVA_HOME + else + JAVA_HOME="/Library/Java/Home"; export JAVA_HOME + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=`java-config --jre-home` + fi +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -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 "$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="`\\unset -f command; \\command -v 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 + +# 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 + printf '%s' "$(cd "$basedir"; pwd)" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' < "$1")" + fi +} + +BASE_DIR=$(find_maven_basedir "$(dirname $0)") +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}; export MAVEN_PROJECTBASEDIR +if [ "$MVNW_VERBOSE" = true ]; then + echo $MAVEN_PROJECTBASEDIR +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 + wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar" + else + wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar" + fi + while IFS="=" read key value; do + case "$key" in (wrapperUrl) wrapperUrl="$value"; break ;; + esac + done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" + if [ "$MVNW_VERBOSE" = true ]; then + echo "Downloading from: $wrapperUrl" + 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 + QUIET="--quiet" + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found wget ... using wget" + QUIET="" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget $QUIET "$wrapperUrl" -O "$wrapperJarPath" + else + wget $QUIET --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" + fi + [ $? -eq 0 ] || rm -f "$wrapperJarPath" + elif command -v curl > /dev/null; then + QUIET="--silent" + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found curl ... using curl" + QUIET="" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl $QUIET -o "$wrapperJarPath" "$wrapperUrl" -f -L + else + curl $QUIET --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L + fi + [ $? -eq 0 ] || rm -f "$wrapperJarPath" + else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Falling back to using Java to download" + fi + javaSource="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" + javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaSource=`cygpath --path --windows "$javaSource"` + javaClass=`cygpath --path --windows "$javaClass"` + fi + if [ -e "$javaSource" ]; then + if [ ! -e "$javaClass" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Compiling MavenWrapperDownloader.java ..." + fi + # Compiling the Java class + ("$JAVA_HOME/bin/javac" "$javaSource") + fi + if [ -e "$javaClass" ]; 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 +########################################################################################## + +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 "$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 \ + $MAVEN_DEBUG_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/mvnw.cmd b/mvnw.cmd new file mode 100644 index 0000000000000000000000000000000000000000..cba1f040dc3c8ca3445f24904051943a296b59bf --- /dev/null +++ b/mvnw.cmd @@ -0,0 +1,187 @@ +@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.1.1 +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@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 "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %* +if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\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 WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar" + +FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET WRAPPER_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 WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %WRAPPER_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('%WRAPPER_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 "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat" +if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\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% + +cmd /C exit /B %ERROR_CODE% diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000000000000000000000000000000000000..dc43bd3fc6c53dc084f875f3e07b708e7ae6b760 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,4423 @@ +{ + "name": "perun-ga4gh-broker", + "requires": true, + "lockfileVersion": 1, + "dependencies": { + "@babel/code-frame": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", + "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", + "dev": true, + "requires": { + "@babel/highlight": "^7.18.6" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.18.6.tgz", + "integrity": "sha512-MmetCkz9ej86nJQV+sFCxoGGrUbU3q02kgLciwkrt9QqEB7cP39oKEY0PakknEO0Gu20SskMRi+AYZ3b1TpN9g==", + "dev": true + }, + "@babel/highlight": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", + "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.18.6", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "@colors/colors": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", + "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", + "dev": true, + "optional": true + }, + "@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + } + }, + "@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true + }, + "@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "requires": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + } + }, + "@octokit/auth-token": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-3.0.0.tgz", + "integrity": "sha512-MDNFUBcJIptB9At7HiV7VCvU3NcL4GnfCQaP8C5lrxWrRPMJBnemYtehaKSOlaM7AYxeRyj9etenu8LVpSpVaQ==", + "dev": true, + "requires": { + "@octokit/types": "^6.0.3" + } + }, + "@octokit/core": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@octokit/core/-/core-4.0.4.tgz", + "integrity": "sha512-sUpR/hc4Gc7K34o60bWC7WUH6Q7T6ftZ2dUmepSyJr9PRF76/qqkWjE2SOEzCqLA5W83SaISymwKtxks+96hPQ==", + "dev": true, + "requires": { + "@octokit/auth-token": "^3.0.0", + "@octokit/graphql": "^5.0.0", + "@octokit/request": "^6.0.0", + "@octokit/request-error": "^3.0.0", + "@octokit/types": "^6.0.3", + "before-after-hook": "^2.2.0", + "universal-user-agent": "^6.0.0" + } + }, + "@octokit/endpoint": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-7.0.0.tgz", + "integrity": "sha512-Kz/mIkOTjs9rV50hf/JK9pIDl4aGwAtT8pry6Rpy+hVXkAPhXanNQRxMoq6AeRgDCZR6t/A1zKniY2V1YhrzlQ==", + "dev": true, + "requires": { + "@octokit/types": "^6.0.3", + "is-plain-object": "^5.0.0", + "universal-user-agent": "^6.0.0" + } + }, + "@octokit/graphql": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-5.0.0.tgz", + "integrity": "sha512-1ZZ8tX4lUEcLPvHagfIVu5S2xpHYXAmgN0+95eAOPoaVPzCfUXJtA5vASafcpWcO86ze0Pzn30TAx72aB2aguQ==", + "dev": true, + "requires": { + "@octokit/request": "^6.0.0", + "@octokit/types": "^6.0.3", + "universal-user-agent": "^6.0.0" + } + }, + "@octokit/openapi-types": { + "version": "12.10.1", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-12.10.1.tgz", + "integrity": "sha512-P+SukKanjFY0ZhsK6wSVnQmxTP2eVPPE8OPSNuxaMYtgVzwJZgfGdwlYjf4RlRU4vLEw4ts2fsE2icG4nZ5ddQ==", + "dev": true + }, + "@octokit/plugin-paginate-rest": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-3.0.0.tgz", + "integrity": "sha512-fvw0Q5IXnn60D32sKeLIxgXCEZ7BTSAjJd8cFAE6QU5qUp0xo7LjFUjjX1J5D7HgN355CN4EXE4+Q1/96JaNUA==", + "dev": true, + "requires": { + "@octokit/types": "^6.39.0" + } + }, + "@octokit/plugin-request-log": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@octokit/plugin-request-log/-/plugin-request-log-1.0.4.tgz", + "integrity": "sha512-mLUsMkgP7K/cnFEw07kWqXGF5LKrOkD+lhCrKvPHXWDywAwuDUeDwWBpc69XK3pNX0uKiVt8g5z96PJ6z9xCFA==", + "dev": true + }, + "@octokit/plugin-rest-endpoint-methods": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-6.1.2.tgz", + "integrity": "sha512-sAfSKtLHNq0UQ2iFuI41I6m5SK6bnKFRJ5kUjDRVbmQXiRVi4aQiIcgG4cM7bt+bhSiWL4HwnTxDkWFlKeKClA==", + "dev": true, + "requires": { + "@octokit/types": "^6.40.0", + "deprecation": "^2.3.1" + } + }, + "@octokit/request": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-6.2.0.tgz", + "integrity": "sha512-7IAmHnaezZrgUqtRShMlByJK33MT9ZDnMRgZjnRrRV9a/jzzFwKGz0vxhFU6i7VMLraYcQ1qmcAOin37Kryq+Q==", + "dev": true, + "requires": { + "@octokit/endpoint": "^7.0.0", + "@octokit/request-error": "^3.0.0", + "@octokit/types": "^6.16.1", + "is-plain-object": "^5.0.0", + "node-fetch": "^2.6.7", + "universal-user-agent": "^6.0.0" + } + }, + "@octokit/request-error": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-3.0.0.tgz", + "integrity": "sha512-WBtpzm9lR8z4IHIMtOqr6XwfkGvMOOILNLxsWvDwtzm/n7f5AWuqJTXQXdDtOvPfTDrH4TPhEvW2qMlR4JFA2w==", + "dev": true, + "requires": { + "@octokit/types": "^6.0.3", + "deprecation": "^2.0.0", + "once": "^1.4.0" + } + }, + "@octokit/rest": { + "version": "19.0.3", + "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-19.0.3.tgz", + "integrity": "sha512-5arkTsnnRT7/sbI4fqgSJ35KiFaN7zQm0uQiQtivNQLI8RQx8EHwJCajcTUwmaCMNDg7tdCvqAnc7uvHHPxrtQ==", + "dev": true, + "requires": { + "@octokit/core": "^4.0.0", + "@octokit/plugin-paginate-rest": "^3.0.0", + "@octokit/plugin-request-log": "^1.0.4", + "@octokit/plugin-rest-endpoint-methods": "^6.0.0" + } + }, + "@octokit/types": { + "version": "6.40.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-6.40.0.tgz", + "integrity": "sha512-MFZOU5r8SwgJWDMhrLUSvyJPtVsqA6VnbVI3TNbsmw+Jnvrktzvq2fYES/6RiJA/5Ykdwq4mJmtlYUfW7CGjmw==", + "dev": true, + "requires": { + "@octokit/openapi-types": "^12.10.0" + } + }, + "@semantic-release/changelog": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@semantic-release/changelog/-/changelog-6.0.1.tgz", + "integrity": "sha512-FT+tAGdWHr0RCM3EpWegWnvXJ05LQtBkQUaQRIExONoXjVjLuOILNm4DEKNaV+GAQyJjbLRVs57ti//GypH6PA==", + "dev": true, + "requires": { + "@semantic-release/error": "^3.0.0", + "aggregate-error": "^3.0.0", + "fs-extra": "^9.0.0", + "lodash": "^4.17.4" + } + }, + "@semantic-release/commit-analyzer": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/@semantic-release/commit-analyzer/-/commit-analyzer-9.0.2.tgz", + "integrity": "sha512-E+dr6L+xIHZkX4zNMe6Rnwg4YQrWNXK+rNsvwOPpdFppvZO1olE2fIgWhv89TkQErygevbjsZFSIxp+u6w2e5g==", + "dev": true, + "requires": { + "conventional-changelog-angular": "^5.0.0", + "conventional-commits-filter": "^2.0.0", + "conventional-commits-parser": "^3.2.3", + "debug": "^4.0.0", + "import-from": "^4.0.0", + "lodash": "^4.17.4", + "micromatch": "^4.0.2" + } + }, + "@semantic-release/error": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@semantic-release/error/-/error-3.0.0.tgz", + "integrity": "sha512-5hiM4Un+tpl4cKw3lV4UgzJj+SmfNIDCLLw0TepzQxz9ZGV5ixnqkzIVF+3tp0ZHgcMKE+VNGHJjEeyFG2dcSw==", + "dev": true + }, + "@semantic-release/exec": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@semantic-release/exec/-/exec-6.0.3.tgz", + "integrity": "sha512-bxAq8vLOw76aV89vxxICecEa8jfaWwYITw6X74zzlO0mc/Bgieqx9kBRz9z96pHectiTAtsCwsQcUyLYWnp3VQ==", + "dev": true, + "requires": { + "@semantic-release/error": "^3.0.0", + "aggregate-error": "^3.0.0", + "debug": "^4.0.0", + "execa": "^5.0.0", + "lodash": "^4.17.4", + "parse-json": "^5.0.0" + } + }, + "@semantic-release/git": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@semantic-release/git/-/git-10.0.1.tgz", + "integrity": "sha512-eWrx5KguUcU2wUPaO6sfvZI0wPafUKAMNC18aXY4EnNcrZL86dEmpNVnC9uMpGZkmZJ9EfCVJBQx4pV4EMGT1w==", + "dev": true, + "requires": { + "@semantic-release/error": "^3.0.0", + "aggregate-error": "^3.0.0", + "debug": "^4.0.0", + "dir-glob": "^3.0.0", + "execa": "^5.0.0", + "lodash": "^4.17.4", + "micromatch": "^4.0.0", + "p-reduce": "^2.0.0" + } + }, + "@semantic-release/github": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/@semantic-release/github/-/github-8.0.5.tgz", + "integrity": "sha512-9pGxRM3gv1hgoZ/muyd4pWnykdIUVfCiev6MXE9lOyGQof4FQy95GFE26nDcifs9ZG7bBzV8ue87bo/y1zVf0g==", + "dev": true, + "requires": { + "@octokit/rest": "^19.0.0", + "@semantic-release/error": "^2.2.0", + "aggregate-error": "^3.0.0", + "bottleneck": "^2.18.1", + "debug": "^4.0.0", + "dir-glob": "^3.0.0", + "fs-extra": "^10.0.0", + "globby": "^11.0.0", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.0", + "issue-parser": "^6.0.0", + "lodash": "^4.17.4", + "mime": "^3.0.0", + "p-filter": "^2.0.0", + "p-retry": "^4.0.0", + "url-join": "^4.0.0" + }, + "dependencies": { + "@semantic-release/error": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@semantic-release/error/-/error-2.2.0.tgz", + "integrity": "sha512-9Tj/qn+y2j+sjCI3Jd+qseGtHjOAeg7dU2/lVcqIQ9TV3QDaDXDYXcoOHU+7o2Hwh8L8ymL4gfuO7KxDs3q2zg==", + "dev": true + }, + "fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "requires": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + } + } + } + }, + "@semantic-release/npm": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/@semantic-release/npm/-/npm-9.0.1.tgz", + "integrity": "sha512-I5nVZklxBzfMFwemhRNbSrkiN/dsH3c7K9+KSk6jUnq0rdLFUuJt7EBsysq4Ir3moajQgFkfEryEHPqiKJj20g==", + "dev": true, + "requires": { + "@semantic-release/error": "^3.0.0", + "aggregate-error": "^3.0.0", + "execa": "^5.0.0", + "fs-extra": "^10.0.0", + "lodash": "^4.17.15", + "nerf-dart": "^1.0.0", + "normalize-url": "^6.0.0", + "npm": "^8.3.0", + "rc": "^1.2.8", + "read-pkg": "^5.0.0", + "registry-auth-token": "^4.0.0", + "semver": "^7.1.2", + "tempy": "^1.0.0" + }, + "dependencies": { + "fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "requires": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + } + } + } + }, + "@semantic-release/release-notes-generator": { + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/@semantic-release/release-notes-generator/-/release-notes-generator-10.0.3.tgz", + "integrity": "sha512-k4x4VhIKneOWoBGHkx0qZogNjCldLPRiAjnIpMnlUh6PtaWXp/T+C9U7/TaNDDtgDa5HMbHl4WlREdxHio6/3w==", + "dev": true, + "requires": { + "conventional-changelog-angular": "^5.0.0", + "conventional-changelog-writer": "^5.0.0", + "conventional-commits-filter": "^2.0.0", + "conventional-commits-parser": "^3.2.3", + "debug": "^4.0.0", + "get-stream": "^6.0.0", + "import-from": "^4.0.0", + "into-stream": "^6.0.0", + "lodash": "^4.17.4", + "read-pkg-up": "^7.0.0" + } + }, + "@tootallnate/once": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", + "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", + "dev": true + }, + "@types/minimist": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.2.tgz", + "integrity": "sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==", + "dev": true + }, + "@types/normalize-package-data": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz", + "integrity": "sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==", + "dev": true + }, + "@types/parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", + "dev": true + }, + "@types/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", + "dev": true + }, + "JSONStream": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", + "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==", + "dev": true, + "requires": { + "jsonparse": "^1.2.0", + "through": ">=2.2.7 <3" + } + }, + "agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, + "requires": { + "debug": "4" + } + }, + "aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "dev": true, + "requires": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + } + }, + "ansi-escapes": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-5.0.0.tgz", + "integrity": "sha512-5GFMVX8HqE/TB+FuBJGuO5XG0WrsA6ptUqoODaT/n9mmUaZFkqnBueB4leqGBCmrUHnCnC4PCZTCd0E7QQ83bA==", + "dev": true, + "requires": { + "type-fest": "^1.0.2" + }, + "dependencies": { + "type-fest": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz", + "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==", + "dev": true + } + } + }, + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "ansicolors": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/ansicolors/-/ansicolors-0.3.2.tgz", + "integrity": "sha512-QXu7BPrP29VllRxH8GwB7x5iX5qWKAAMLqKQGWTeLWVlNHNOpVMJ91dsxQAIWXpjuW5wqvxu3Jd/nRjrJ+0pqg==", + "dev": true + }, + "argv-formatter": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/argv-formatter/-/argv-formatter-1.0.0.tgz", + "integrity": "sha512-F2+Hkm9xFaRg+GkaNnbwXNDV5O6pnCFEmqyhvfC/Ic5LbgOWjJh3L+mN/s91rxVL3znE7DYVpW0GJFT+4YBgWw==", + "dev": true + }, + "array-ify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-ify/-/array-ify-1.0.0.tgz", + "integrity": "sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng==", + "dev": true + }, + "array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true + }, + "arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==", + "dev": true + }, + "at-least-node": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", + "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", + "dev": true + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "before-after-hook": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.2.tgz", + "integrity": "sha512-3pZEU3NT5BFUo/AD5ERPWOgQOCZITni6iavr5AUw5AUwQjMlI0kzu5btnyD39AF0gUEsDPwJT+oY1ORBJijPjQ==", + "dev": true + }, + "bottleneck": { + "version": "2.19.5", + "resolved": "https://registry.npmjs.org/bottleneck/-/bottleneck-2.19.5.tgz", + "integrity": "sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw==", + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true + }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, + "camelcase-keys": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-6.2.2.tgz", + "integrity": "sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==", + "dev": true, + "requires": { + "camelcase": "^5.3.1", + "map-obj": "^4.0.0", + "quick-lru": "^4.0.1" + } + }, + "cardinal": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/cardinal/-/cardinal-2.1.1.tgz", + "integrity": "sha512-JSr5eOgoEymtYHBjNWyjrMqet9Am2miJhlfKNdqLp6zoeAh0KN5dRAcxlecj5mAJrmQomgiOBj35xHLrFjqBpw==", + "dev": true, + "requires": { + "ansicolors": "~0.3.2", + "redeyed": "~2.1.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "dev": true + }, + "cli-table3": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.2.tgz", + "integrity": "sha512-QyavHCaIC80cMivimWu4aWHilIpiDpfm3hGmqAmXVL1UsnbLuBSMd21hTX6VY4ZSDSM73ESLeF8TOYId3rBTbw==", + "dev": true, + "requires": { + "@colors/colors": "1.5.0", + "string-width": "^4.2.0" + } + }, + "cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "compare-func": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/compare-func/-/compare-func-2.0.0.tgz", + "integrity": "sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==", + "dev": true, + "requires": { + "array-ify": "^1.0.0", + "dot-prop": "^5.1.0" + } + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "conventional-changelog-angular": { + "version": "5.0.13", + "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-5.0.13.tgz", + "integrity": "sha512-i/gipMxs7s8L/QeuavPF2hLnJgH6pEZAttySB6aiQLWcX3puWDL3ACVmvBhJGxnAy52Qc15ua26BufY6KpmrVA==", + "dev": true, + "requires": { + "compare-func": "^2.0.0", + "q": "^1.5.1" + } + }, + "conventional-changelog-writer": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/conventional-changelog-writer/-/conventional-changelog-writer-5.0.1.tgz", + "integrity": "sha512-5WsuKUfxW7suLblAbFnxAcrvf6r+0b7GvNaWUwUIk0bXMnENP/PEieGKVUQrjPqwPT4o3EPAASBXiY6iHooLOQ==", + "dev": true, + "requires": { + "conventional-commits-filter": "^2.0.7", + "dateformat": "^3.0.0", + "handlebars": "^4.7.7", + "json-stringify-safe": "^5.0.1", + "lodash": "^4.17.15", + "meow": "^8.0.0", + "semver": "^6.0.0", + "split": "^1.0.0", + "through2": "^4.0.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "conventional-commits-filter": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/conventional-commits-filter/-/conventional-commits-filter-2.0.7.tgz", + "integrity": "sha512-ASS9SamOP4TbCClsRHxIHXRfcGCnIoQqkvAzCSbZzTFLfcTqJVugB0agRgsEELsqaeWgsXv513eS116wnlSSPA==", + "dev": true, + "requires": { + "lodash.ismatch": "^4.4.0", + "modify-values": "^1.0.0" + } + }, + "conventional-commits-parser": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-3.2.4.tgz", + "integrity": "sha512-nK7sAtfi+QXbxHCYfhpZsfRtaitZLIA6889kFIouLvz6repszQDgxBu7wf2WbU+Dco7sAnNCJYERCwt54WPC2Q==", + "dev": true, + "requires": { + "JSONStream": "^1.0.4", + "is-text-path": "^1.0.1", + "lodash": "^4.17.15", + "meow": "^8.0.0", + "split2": "^3.0.0", + "through2": "^4.0.0" + } + }, + "core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true + }, + "cosmiconfig": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.1.tgz", + "integrity": "sha512-a1YWNUV2HwGimB7dU2s1wUMurNKjpx60HxBB6xUM8Re+2s1g1IIfJvFR0/iCF+XHdE0GMTKTuLR32UQff4TEyQ==", + "dev": true, + "requires": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + } + }, + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "crypto-random-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", + "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==", + "dev": true + }, + "dateformat": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-3.0.3.tgz", + "integrity": "sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==", + "dev": true + }, + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "dev": true + }, + "decamelize-keys": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.0.tgz", + "integrity": "sha512-ocLWuYzRPoS9bfiSdDd3cxvrzovVMZnRDVEzAs+hWIVXGDbHxWMECij2OBuyB/An0FFW/nLuq6Kv1i/YC5Qfzg==", + "dev": true, + "requires": { + "decamelize": "^1.1.0", + "map-obj": "^1.0.0" + }, + "dependencies": { + "map-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", + "integrity": "sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==", + "dev": true + } + } + }, + "deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "dev": true + }, + "del": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/del/-/del-6.1.1.tgz", + "integrity": "sha512-ua8BhapfP0JUJKC/zV9yHHDW/rDoDxP4Zhn3AkA6/xT6gY7jYXJiaeyBZznYVujhZZET+UgcbZiQ7sN3WqcImg==", + "dev": true, + "requires": { + "globby": "^11.0.1", + "graceful-fs": "^4.2.4", + "is-glob": "^4.0.1", + "is-path-cwd": "^2.2.0", + "is-path-inside": "^3.0.2", + "p-map": "^4.0.0", + "rimraf": "^3.0.2", + "slash": "^3.0.0" + }, + "dependencies": { + "p-map": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "dev": true, + "requires": { + "aggregate-error": "^3.0.0" + } + } + } + }, + "deprecation": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz", + "integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==", + "dev": true + }, + "dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "requires": { + "path-type": "^4.0.0" + } + }, + "dot-prop": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", + "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", + "dev": true, + "requires": { + "is-obj": "^2.0.0" + } + }, + "duplexer2": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", + "integrity": "sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==", + "dev": true, + "requires": { + "readable-stream": "^2.0.2" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "env-ci": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/env-ci/-/env-ci-5.5.0.tgz", + "integrity": "sha512-o0JdWIbOLP+WJKIUt36hz1ImQQFuN92nhsfTkHHap+J8CiI8WgGpH/a9jEGHh4/TU5BUUGjlnKXNoDb57+ne+A==", + "dev": true, + "requires": { + "execa": "^5.0.0", + "fromentries": "^1.3.2", + "java-properties": "^1.0.0" + } + }, + "error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "requires": { + "is-arrayish": "^0.2.1" + } + }, + "escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + } + }, + "fast-glob": { + "version": "3.2.11", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz", + "integrity": "sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + } + }, + "fastq": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", + "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", + "dev": true, + "requires": { + "reusify": "^1.0.4" + } + }, + "figures": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", + "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.5" + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "find-versions": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/find-versions/-/find-versions-4.0.0.tgz", + "integrity": "sha512-wgpWy002tA+wgmO27buH/9KzyEOQnKsG/R0yrcjPT9BOFm0zRBVQbZ95nRGXWMywS8YR5knRbpohio0bcJABxQ==", + "dev": true, + "requires": { + "semver-regex": "^3.1.2" + } + }, + "from2": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", + "integrity": "sha512-OMcX/4IC/uqEPVgGeyfN22LJk6AZrMkRZHxcHBMBvHScDGgwTm2GT2Wkgtocyd3JfZffjj2kYUDXXII0Fk9W0g==", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "readable-stream": "^2.0.0" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "fromentries": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fromentries/-/fromentries-1.3.2.tgz", + "integrity": "sha512-cHEpEQHUg0f8XdtZCc2ZAhrHzKzT0MrFUTcvx+hfxYu7rGMDc5SKoXFh+n4YigxsHXRzc6OrCshdR1bWH6HHyg==", + "dev": true + }, + "fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "requires": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true + }, + "get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true + }, + "git-log-parser": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/git-log-parser/-/git-log-parser-1.2.0.tgz", + "integrity": "sha512-rnCVNfkTL8tdNryFuaY0fYiBWEBcgF748O6ZI61rslBvr2o7U65c2/6npCRqH40vuAhtgtDiqLTJjBVdrejCzA==", + "dev": true, + "requires": { + "argv-formatter": "~1.0.0", + "spawn-error-forwarder": "~1.0.0", + "split2": "~1.0.0", + "stream-combiner2": "~1.1.1", + "through2": "~2.0.0", + "traverse": "~0.6.6" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "split2": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-1.0.0.tgz", + "integrity": "sha512-NKywug4u4pX/AZBB1FCPzZ6/7O+Xhz1qMVbzTvvKvikjO99oPN87SkK08mEY9P63/5lWjK+wgOOgApnTg5r6qg==", + "dev": true, + "requires": { + "through2": "~2.0.0" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dev": true, + "requires": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + } + } + }, + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "requires": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + } + }, + "graceful-fs": { + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", + "dev": true + }, + "handlebars": { + "version": "4.7.7", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.7.tgz", + "integrity": "sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA==", + "dev": true, + "requires": { + "minimist": "^1.2.5", + "neo-async": "^2.6.0", + "source-map": "^0.6.1", + "uglify-js": "^3.1.4", + "wordwrap": "^1.0.0" + } + }, + "hard-rejection": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz", + "integrity": "sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==", + "dev": true + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true + }, + "hook-std": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hook-std/-/hook-std-2.0.0.tgz", + "integrity": "sha512-zZ6T5WcuBMIUVh49iPQS9t977t7C0l7OtHrpeMb5uk48JdflRX0NSFvCekfYNmGQETnLq9W/isMyHl69kxGi8g==", + "dev": true + }, + "hosted-git-info": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", + "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "dev": true, + "requires": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + } + }, + "https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dev": true, + "requires": { + "agent-base": "6", + "debug": "4" + } + }, + "human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true + }, + "ignore": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", + "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", + "dev": true + }, + "import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "requires": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "dependencies": { + "resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true + } + } + }, + "import-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/import-from/-/import-from-4.0.0.tgz", + "integrity": "sha512-P9J71vT5nLlDeV8FHs5nNxaLbrpfAV5cF5srvbZfpwpcJoM/xZR3hiv+q+SAnuSmuGbXMWud063iIMx/V/EWZQ==", + "dev": true + }, + "indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true + }, + "into-stream": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/into-stream/-/into-stream-6.0.0.tgz", + "integrity": "sha512-XHbaOAvP+uFKUFsOgoNPRjLkwB+I22JFPFe5OjTkQ0nwgj6+pSjb4NmB6VMxaPshLiOf+zcpOCBQuLwC1KHhZA==", + "dev": true, + "requires": { + "from2": "^2.3.0", + "p-is-promise": "^3.0.0" + } + }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true + }, + "is-core-module": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.9.0.tgz", + "integrity": "sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "is-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", + "dev": true + }, + "is-path-cwd": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-2.2.0.tgz", + "integrity": "sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==", + "dev": true + }, + "is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true + }, + "is-plain-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==", + "dev": true + }, + "is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", + "dev": true + }, + "is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true + }, + "is-text-path": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-text-path/-/is-text-path-1.0.1.tgz", + "integrity": "sha512-xFuJpne9oFz5qDaodwmmG08e3CawH/2ZV8Qqza1Ko7Sk8POWbkRdwIoAWVhqvq0XeUzANEhKo2n0IXUGBm7A/w==", + "dev": true, + "requires": { + "text-extensions": "^1.0.0" + } + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "issue-parser": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/issue-parser/-/issue-parser-6.0.0.tgz", + "integrity": "sha512-zKa/Dxq2lGsBIXQ7CUZWTHfvxPC2ej0KfO7fIPqLlHB9J2hJ7rGhZ5rilhuufylr4RXYPzJUeFjKxz305OsNlA==", + "dev": true, + "requires": { + "lodash.capitalize": "^4.2.1", + "lodash.escaperegexp": "^4.1.2", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.uniqby": "^4.7.0" + } + }, + "java-properties": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/java-properties/-/java-properties-1.0.2.tgz", + "integrity": "sha512-qjdpeo2yKlYTH7nFdK0vbZWuTCesk4o63v5iVOlhMQPfuIZQfW/HI35SjfhA+4qpg36rnFSvUK5b1m+ckIblQQ==", + "dev": true + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", + "dev": true + }, + "json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", + "dev": true + }, + "jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.6", + "universalify": "^2.0.0" + } + }, + "jsonparse": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", + "integrity": "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==", + "dev": true + }, + "kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true + }, + "lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true + }, + "load-json-file": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", + "integrity": "sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0", + "strip-bom": "^3.0.0" + }, + "dependencies": { + "parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", + "dev": true, + "requires": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + } + } + } + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, + "lodash.capitalize": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/lodash.capitalize/-/lodash.capitalize-4.2.1.tgz", + "integrity": "sha512-kZzYOKspf8XVX5AvmQF94gQW0lejFVgb80G85bU4ZWzoJ6C03PQg3coYAUpSTpQWelrZELd3XWgHzw4Ck5kaIw==", + "dev": true + }, + "lodash.escaperegexp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz", + "integrity": "sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw==", + "dev": true + }, + "lodash.ismatch": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.ismatch/-/lodash.ismatch-4.4.0.tgz", + "integrity": "sha512-fPMfXjGQEV9Xsq/8MTSgUf255gawYRbjwMyDbcvDhXgV7enSZA0hynz6vMPnpAb5iONEzBHBPsT+0zes5Z301g==", + "dev": true + }, + "lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "dev": true + }, + "lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "dev": true + }, + "lodash.uniqby": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/lodash.uniqby/-/lodash.uniqby-4.7.0.tgz", + "integrity": "sha512-e/zcLx6CSbmaEgFHCA7BnoQKyCtKMxnuWrJygbwPs/AIn+IMKl66L8/s+wBUn5LRw2pZx3bUHibiV1b6aTWIww==", + "dev": true + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "map-obj": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.3.0.tgz", + "integrity": "sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==", + "dev": true + }, + "marked": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/marked/-/marked-4.0.18.tgz", + "integrity": "sha512-wbLDJ7Zh0sqA0Vdg6aqlbT+yPxqLblpAZh1mK2+AO2twQkPywvvqQNfEPVwSSRjZ7dZcdeVBIAgiO7MMp3Dszw==", + "dev": true + }, + "marked-terminal": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/marked-terminal/-/marked-terminal-5.1.1.tgz", + "integrity": "sha512-+cKTOx9P4l7HwINYhzbrBSyzgxO2HaHKGZGuB1orZsMIgXYaJyfidT81VXRdpelW/PcHEWxywscePVgI/oUF6g==", + "dev": true, + "requires": { + "ansi-escapes": "^5.0.0", + "cardinal": "^2.1.1", + "chalk": "^5.0.0", + "cli-table3": "^0.6.1", + "node-emoji": "^1.11.0", + "supports-hyperlinks": "^2.2.0" + }, + "dependencies": { + "chalk": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.0.1.tgz", + "integrity": "sha512-Fo07WOYGqMfCWHOzSXOt2CxDbC6skS/jO9ynEcmpANMoPrD+W1r1K6Vx7iNm+AQmETU1Xr2t+n8nzkV9t6xh3w==", + "dev": true + } + } + }, + "meow": { + "version": "8.1.2", + "resolved": "https://registry.npmjs.org/meow/-/meow-8.1.2.tgz", + "integrity": "sha512-r85E3NdZ+mpYk1C6RjPFEMSE+s1iZMuHtsHAqY0DT3jZczl0diWUZ8g6oU7h0M9cD2EL+PzaYghhCLzR0ZNn5Q==", + "dev": true, + "requires": { + "@types/minimist": "^1.2.0", + "camelcase-keys": "^6.2.2", + "decamelize-keys": "^1.1.0", + "hard-rejection": "^2.1.0", + "minimist-options": "4.1.0", + "normalize-package-data": "^3.0.0", + "read-pkg-up": "^7.0.1", + "redent": "^3.0.0", + "trim-newlines": "^3.0.0", + "type-fest": "^0.18.0", + "yargs-parser": "^20.2.3" + } + }, + "merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true + }, + "micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "requires": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + } + }, + "mime": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", + "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", + "dev": true + }, + "mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true + }, + "min-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", + "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", + "dev": true + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", + "dev": true + }, + "minimist-options": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-4.1.0.tgz", + "integrity": "sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==", + "dev": true, + "requires": { + "arrify": "^1.0.1", + "is-plain-obj": "^1.1.0", + "kind-of": "^6.0.3" + } + }, + "modify-values": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/modify-values/-/modify-values-1.0.1.tgz", + "integrity": "sha512-xV2bxeN6F7oYjZWTe/YPAy6MN2M+sL4u/Rlm2AHCIVGfo2p1yGmBHQ6vHehl4bRTZBdHu3TSkWdYgkwpYzAGSw==", + "dev": true + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true + }, + "nerf-dart": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/nerf-dart/-/nerf-dart-1.0.0.tgz", + "integrity": "sha512-EZSPZB70jiVsivaBLYDCyntd5eH8NTSMOn3rB+HxwdmKThGELLdYv8qVIMWvZEFy9w8ZZpW9h9OB32l1rGtj7g==", + "dev": true + }, + "node-emoji": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-1.11.0.tgz", + "integrity": "sha512-wo2DpQkQp7Sjm2A0cq+sN7EHKO6Sl0ctXeBdFZrL9T9+UywORbufTcTZxom8YqpLQt/FqNMUkOpkZrJVYSKD3A==", + "dev": true, + "requires": { + "lodash": "^4.17.21" + } + }, + "node-fetch": { + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "dev": true, + "requires": { + "whatwg-url": "^5.0.0" + } + }, + "normalize-package-data": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.3.tgz", + "integrity": "sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==", + "dev": true, + "requires": { + "hosted-git-info": "^4.0.1", + "is-core-module": "^2.5.0", + "semver": "^7.3.4", + "validate-npm-package-license": "^3.0.1" + } + }, + "normalize-url": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", + "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", + "dev": true + }, + "npm": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/npm/-/npm-8.15.0.tgz", + "integrity": "sha512-sFXrMiO07eDWUb/e5ni2yNvtz2hePKqSyukUxYcQv0QHjyXCe+zKP7af/bISjcvsgRBWGyivk5V3KCZ0vg8J3Q==", + "dev": true, + "requires": { + "@isaacs/string-locale-compare": "^1.1.0", + "@npmcli/arborist": "^5.0.4", + "@npmcli/ci-detect": "^2.0.0", + "@npmcli/config": "^4.2.0", + "@npmcli/fs": "^2.1.0", + "@npmcli/map-workspaces": "^2.0.3", + "@npmcli/package-json": "^2.0.0", + "@npmcli/run-script": "^4.1.7", + "abbrev": "~1.1.1", + "archy": "~1.0.0", + "cacache": "^16.1.1", + "chalk": "^4.1.2", + "chownr": "^2.0.0", + "cli-columns": "^4.0.0", + "cli-table3": "^0.6.2", + "columnify": "^1.6.0", + "fastest-levenshtein": "^1.0.12", + "glob": "^8.0.1", + "graceful-fs": "^4.2.10", + "hosted-git-info": "^5.0.0", + "ini": "^3.0.0", + "init-package-json": "^3.0.2", + "is-cidr": "^4.0.2", + "json-parse-even-better-errors": "^2.3.1", + "libnpmaccess": "^6.0.2", + "libnpmdiff": "^4.0.2", + "libnpmexec": "^4.0.2", + "libnpmfund": "^3.0.1", + "libnpmhook": "^8.0.2", + "libnpmorg": "^4.0.2", + "libnpmpack": "^4.0.2", + "libnpmpublish": "^6.0.2", + "libnpmsearch": "^5.0.2", + "libnpmteam": "^4.0.2", + "libnpmversion": "^3.0.1", + "make-fetch-happen": "^10.2.0", + "minipass": "^3.1.6", + "minipass-pipeline": "^1.2.4", + "mkdirp": "^1.0.4", + "mkdirp-infer-owner": "^2.0.0", + "ms": "^2.1.2", + "node-gyp": "^9.0.0", + "nopt": "^5.0.0", + "npm-audit-report": "^3.0.0", + "npm-install-checks": "^5.0.0", + "npm-package-arg": "^9.1.0", + "npm-pick-manifest": "^7.0.1", + "npm-profile": "^6.2.0", + "npm-registry-fetch": "^13.3.0", + "npm-user-validate": "^1.0.1", + "npmlog": "^6.0.2", + "opener": "^1.5.2", + "p-map": "^4.0.0", + "pacote": "^13.6.1", + "parse-conflict-json": "^2.0.2", + "proc-log": "^2.0.1", + "qrcode-terminal": "^0.12.0", + "read": "~1.0.7", + "read-package-json": "^5.0.1", + "read-package-json-fast": "^2.0.3", + "readdir-scoped-modules": "^1.1.0", + "rimraf": "^3.0.2", + "semver": "^7.3.7", + "ssri": "^9.0.1", + "tar": "^6.1.11", + "text-table": "~0.2.0", + "tiny-relative-date": "^1.3.0", + "treeverse": "^2.0.0", + "validate-npm-package-name": "^4.0.0", + "which": "^2.0.2", + "write-file-atomic": "^4.0.1" + }, + "dependencies": { + "@colors/colors": { + "version": "1.5.0", + "bundled": true, + "dev": true, + "optional": true + }, + "@gar/promisify": { + "version": "1.1.3", + "bundled": true, + "dev": true + }, + "@isaacs/string-locale-compare": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "@npmcli/arborist": { + "version": "5.3.0", + "bundled": true, + "dev": true, + "requires": { + "@isaacs/string-locale-compare": "^1.1.0", + "@npmcli/installed-package-contents": "^1.0.7", + "@npmcli/map-workspaces": "^2.0.3", + "@npmcli/metavuln-calculator": "^3.0.1", + "@npmcli/move-file": "^2.0.0", + "@npmcli/name-from-folder": "^1.0.1", + "@npmcli/node-gyp": "^2.0.0", + "@npmcli/package-json": "^2.0.0", + "@npmcli/run-script": "^4.1.3", + "bin-links": "^3.0.0", + "cacache": "^16.0.6", + "common-ancestor-path": "^1.0.1", + "json-parse-even-better-errors": "^2.3.1", + "json-stringify-nice": "^1.1.4", + "mkdirp": "^1.0.4", + "mkdirp-infer-owner": "^2.0.0", + "nopt": "^5.0.0", + "npm-install-checks": "^5.0.0", + "npm-package-arg": "^9.0.0", + "npm-pick-manifest": "^7.0.0", + "npm-registry-fetch": "^13.0.0", + "npmlog": "^6.0.2", + "pacote": "^13.6.1", + "parse-conflict-json": "^2.0.1", + "proc-log": "^2.0.0", + "promise-all-reject-late": "^1.0.0", + "promise-call-limit": "^1.0.1", + "read-package-json-fast": "^2.0.2", + "readdir-scoped-modules": "^1.1.0", + "rimraf": "^3.0.2", + "semver": "^7.3.7", + "ssri": "^9.0.0", + "treeverse": "^2.0.0", + "walk-up-path": "^1.0.0" + } + }, + "@npmcli/ci-detect": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "@npmcli/config": { + "version": "4.2.0", + "bundled": true, + "dev": true, + "requires": { + "@npmcli/map-workspaces": "^2.0.2", + "ini": "^3.0.0", + "mkdirp-infer-owner": "^2.0.0", + "nopt": "^5.0.0", + "proc-log": "^2.0.0", + "read-package-json-fast": "^2.0.3", + "semver": "^7.3.5", + "walk-up-path": "^1.0.0" + } + }, + "@npmcli/disparity-colors": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "requires": { + "ansi-styles": "^4.3.0" + } + }, + "@npmcli/fs": { + "version": "2.1.0", + "bundled": true, + "dev": true, + "requires": { + "@gar/promisify": "^1.1.3", + "semver": "^7.3.5" + } + }, + "@npmcli/git": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "requires": { + "@npmcli/promise-spawn": "^3.0.0", + "lru-cache": "^7.4.4", + "mkdirp": "^1.0.4", + "npm-pick-manifest": "^7.0.0", + "proc-log": "^2.0.0", + "promise-inflight": "^1.0.1", + "promise-retry": "^2.0.1", + "semver": "^7.3.5", + "which": "^2.0.2" + } + }, + "@npmcli/installed-package-contents": { + "version": "1.0.7", + "bundled": true, + "dev": true, + "requires": { + "npm-bundled": "^1.1.1", + "npm-normalize-package-bin": "^1.0.1" + } + }, + "@npmcli/map-workspaces": { + "version": "2.0.3", + "bundled": true, + "dev": true, + "requires": { + "@npmcli/name-from-folder": "^1.0.1", + "glob": "^8.0.1", + "minimatch": "^5.0.1", + "read-package-json-fast": "^2.0.3" + } + }, + "@npmcli/metavuln-calculator": { + "version": "3.1.1", + "bundled": true, + "dev": true, + "requires": { + "cacache": "^16.0.0", + "json-parse-even-better-errors": "^2.3.1", + "pacote": "^13.0.3", + "semver": "^7.3.5" + } + }, + "@npmcli/move-file": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "requires": { + "mkdirp": "^1.0.4", + "rimraf": "^3.0.2" + } + }, + "@npmcli/name-from-folder": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "@npmcli/node-gyp": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "@npmcli/package-json": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "requires": { + "json-parse-even-better-errors": "^2.3.1" + } + }, + "@npmcli/promise-spawn": { + "version": "3.0.0", + "bundled": true, + "dev": true, + "requires": { + "infer-owner": "^1.0.4" + } + }, + "@npmcli/run-script": { + "version": "4.1.7", + "bundled": true, + "dev": true, + "requires": { + "@npmcli/node-gyp": "^2.0.0", + "@npmcli/promise-spawn": "^3.0.0", + "node-gyp": "^9.0.0", + "read-package-json-fast": "^2.0.3", + "which": "^2.0.2" + } + }, + "@tootallnate/once": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "abbrev": { + "version": "1.1.1", + "bundled": true, + "dev": true + }, + "agent-base": { + "version": "6.0.2", + "bundled": true, + "dev": true, + "requires": { + "debug": "4" + } + }, + "agentkeepalive": { + "version": "4.2.1", + "bundled": true, + "dev": true, + "requires": { + "debug": "^4.1.0", + "depd": "^1.1.2", + "humanize-ms": "^1.2.1" + } + }, + "aggregate-error": { + "version": "3.1.0", + "bundled": true, + "dev": true, + "requires": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + } + }, + "ansi-regex": { + "version": "5.0.1", + "bundled": true, + "dev": true + }, + "ansi-styles": { + "version": "4.3.0", + "bundled": true, + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "aproba": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "archy": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "are-we-there-yet": { + "version": "3.0.0", + "bundled": true, + "dev": true, + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + } + }, + "asap": { + "version": "2.0.6", + "bundled": true, + "dev": true + }, + "balanced-match": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "bin-links": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "requires": { + "cmd-shim": "^5.0.0", + "mkdirp-infer-owner": "^2.0.0", + "npm-normalize-package-bin": "^1.0.0", + "read-cmd-shim": "^3.0.0", + "rimraf": "^3.0.0", + "write-file-atomic": "^4.0.0" + } + }, + "binary-extensions": { + "version": "2.2.0", + "bundled": true, + "dev": true + }, + "brace-expansion": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "requires": { + "balanced-match": "^1.0.0" + } + }, + "builtins": { + "version": "5.0.1", + "bundled": true, + "dev": true, + "requires": { + "semver": "^7.0.0" + } + }, + "cacache": { + "version": "16.1.1", + "bundled": true, + "dev": true, + "requires": { + "@npmcli/fs": "^2.1.0", + "@npmcli/move-file": "^2.0.0", + "chownr": "^2.0.0", + "fs-minipass": "^2.1.0", + "glob": "^8.0.1", + "infer-owner": "^1.0.4", + "lru-cache": "^7.7.1", + "minipass": "^3.1.6", + "minipass-collect": "^1.0.2", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "mkdirp": "^1.0.4", + "p-map": "^4.0.0", + "promise-inflight": "^1.0.1", + "rimraf": "^3.0.2", + "ssri": "^9.0.0", + "tar": "^6.1.11", + "unique-filename": "^1.1.1" + } + }, + "chalk": { + "version": "4.1.2", + "bundled": true, + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "chownr": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "cidr-regex": { + "version": "3.1.1", + "bundled": true, + "dev": true, + "requires": { + "ip-regex": "^4.1.0" + } + }, + "clean-stack": { + "version": "2.2.0", + "bundled": true, + "dev": true + }, + "cli-columns": { + "version": "4.0.0", + "bundled": true, + "dev": true, + "requires": { + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1" + } + }, + "cli-table3": { + "version": "0.6.2", + "bundled": true, + "dev": true, + "requires": { + "@colors/colors": "1.5.0", + "string-width": "^4.2.0" + } + }, + "clone": { + "version": "1.0.4", + "bundled": true, + "dev": true + }, + "cmd-shim": { + "version": "5.0.0", + "bundled": true, + "dev": true, + "requires": { + "mkdirp-infer-owner": "^2.0.0" + } + }, + "color-convert": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "bundled": true, + "dev": true + }, + "color-support": { + "version": "1.1.3", + "bundled": true, + "dev": true + }, + "columnify": { + "version": "1.6.0", + "bundled": true, + "dev": true, + "requires": { + "strip-ansi": "^6.0.1", + "wcwidth": "^1.0.0" + } + }, + "common-ancestor-path": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "bundled": true, + "dev": true + }, + "console-control-strings": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "debug": { + "version": "4.3.4", + "bundled": true, + "dev": true, + "requires": { + "ms": "2.1.2" + }, + "dependencies": { + "ms": { + "version": "2.1.2", + "bundled": true, + "dev": true + } + } + }, + "debuglog": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "defaults": { + "version": "1.0.3", + "bundled": true, + "dev": true, + "requires": { + "clone": "^1.0.2" + } + }, + "delegates": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "depd": { + "version": "1.1.2", + "bundled": true, + "dev": true + }, + "dezalgo": { + "version": "1.0.4", + "bundled": true, + "dev": true, + "requires": { + "asap": "^2.0.0", + "wrappy": "1" + } + }, + "diff": { + "version": "5.0.0", + "bundled": true, + "dev": true + }, + "emoji-regex": { + "version": "8.0.0", + "bundled": true, + "dev": true + }, + "encoding": { + "version": "0.1.13", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "iconv-lite": "^0.6.2" + } + }, + "env-paths": { + "version": "2.2.1", + "bundled": true, + "dev": true + }, + "err-code": { + "version": "2.0.3", + "bundled": true, + "dev": true + }, + "fastest-levenshtein": { + "version": "1.0.12", + "bundled": true, + "dev": true + }, + "fs-minipass": { + "version": "2.1.0", + "bundled": true, + "dev": true, + "requires": { + "minipass": "^3.0.0" + } + }, + "fs.realpath": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "function-bind": { + "version": "1.1.1", + "bundled": true, + "dev": true + }, + "gauge": { + "version": "4.0.4", + "bundled": true, + "dev": true, + "requires": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.3", + "console-control-strings": "^1.1.0", + "has-unicode": "^2.0.1", + "signal-exit": "^3.0.7", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.5" + } + }, + "glob": { + "version": "8.0.3", + "bundled": true, + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + } + }, + "graceful-fs": { + "version": "4.2.10", + "bundled": true, + "dev": true + }, + "has": { + "version": "1.0.3", + "bundled": true, + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-flag": { + "version": "4.0.0", + "bundled": true, + "dev": true + }, + "has-unicode": { + "version": "2.0.1", + "bundled": true, + "dev": true + }, + "hosted-git-info": { + "version": "5.0.0", + "bundled": true, + "dev": true, + "requires": { + "lru-cache": "^7.5.1" + } + }, + "http-cache-semantics": { + "version": "4.1.0", + "bundled": true, + "dev": true + }, + "http-proxy-agent": { + "version": "5.0.0", + "bundled": true, + "dev": true, + "requires": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + } + }, + "https-proxy-agent": { + "version": "5.0.1", + "bundled": true, + "dev": true, + "requires": { + "agent-base": "6", + "debug": "4" + } + }, + "humanize-ms": { + "version": "1.2.1", + "bundled": true, + "dev": true, + "requires": { + "ms": "^2.0.0" + } + }, + "iconv-lite": { + "version": "0.6.3", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + } + }, + "ignore-walk": { + "version": "5.0.1", + "bundled": true, + "dev": true, + "requires": { + "minimatch": "^5.0.1" + } + }, + "imurmurhash": { + "version": "0.1.4", + "bundled": true, + "dev": true + }, + "indent-string": { + "version": "4.0.0", + "bundled": true, + "dev": true + }, + "infer-owner": { + "version": "1.0.4", + "bundled": true, + "dev": true + }, + "inflight": { + "version": "1.0.6", + "bundled": true, + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "bundled": true, + "dev": true + }, + "ini": { + "version": "3.0.0", + "bundled": true, + "dev": true + }, + "init-package-json": { + "version": "3.0.2", + "bundled": true, + "dev": true, + "requires": { + "npm-package-arg": "^9.0.1", + "promzard": "^0.3.0", + "read": "^1.0.7", + "read-package-json": "^5.0.0", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4", + "validate-npm-package-name": "^4.0.0" + } + }, + "ip": { + "version": "1.1.8", + "bundled": true, + "dev": true + }, + "ip-regex": { + "version": "4.3.0", + "bundled": true, + "dev": true + }, + "is-cidr": { + "version": "4.0.2", + "bundled": true, + "dev": true, + "requires": { + "cidr-regex": "^3.1.1" + } + }, + "is-core-module": { + "version": "2.9.0", + "bundled": true, + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "bundled": true, + "dev": true + }, + "is-lambda": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "isexe": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "json-parse-even-better-errors": { + "version": "2.3.1", + "bundled": true, + "dev": true + }, + "json-stringify-nice": { + "version": "1.1.4", + "bundled": true, + "dev": true + }, + "jsonparse": { + "version": "1.3.1", + "bundled": true, + "dev": true + }, + "just-diff": { + "version": "5.0.3", + "bundled": true, + "dev": true + }, + "just-diff-apply": { + "version": "5.3.1", + "bundled": true, + "dev": true + }, + "libnpmaccess": { + "version": "6.0.3", + "bundled": true, + "dev": true, + "requires": { + "aproba": "^2.0.0", + "minipass": "^3.1.1", + "npm-package-arg": "^9.0.1", + "npm-registry-fetch": "^13.0.0" + } + }, + "libnpmdiff": { + "version": "4.0.4", + "bundled": true, + "dev": true, + "requires": { + "@npmcli/disparity-colors": "^2.0.0", + "@npmcli/installed-package-contents": "^1.0.7", + "binary-extensions": "^2.2.0", + "diff": "^5.0.0", + "minimatch": "^5.0.1", + "npm-package-arg": "^9.0.1", + "pacote": "^13.6.1", + "tar": "^6.1.0" + } + }, + "libnpmexec": { + "version": "4.0.8", + "bundled": true, + "dev": true, + "requires": { + "@npmcli/arborist": "^5.0.0", + "@npmcli/ci-detect": "^2.0.0", + "@npmcli/run-script": "^4.1.3", + "chalk": "^4.1.0", + "mkdirp-infer-owner": "^2.0.0", + "npm-package-arg": "^9.0.1", + "npmlog": "^6.0.2", + "pacote": "^13.6.1", + "proc-log": "^2.0.0", + "read": "^1.0.7", + "read-package-json-fast": "^2.0.2", + "walk-up-path": "^1.0.0" + } + }, + "libnpmfund": { + "version": "3.0.2", + "bundled": true, + "dev": true, + "requires": { + "@npmcli/arborist": "^5.0.0" + } + }, + "libnpmhook": { + "version": "8.0.3", + "bundled": true, + "dev": true, + "requires": { + "aproba": "^2.0.0", + "npm-registry-fetch": "^13.0.0" + } + }, + "libnpmorg": { + "version": "4.0.3", + "bundled": true, + "dev": true, + "requires": { + "aproba": "^2.0.0", + "npm-registry-fetch": "^13.0.0" + } + }, + "libnpmpack": { + "version": "4.1.2", + "bundled": true, + "dev": true, + "requires": { + "@npmcli/run-script": "^4.1.3", + "npm-package-arg": "^9.0.1", + "pacote": "^13.6.1" + } + }, + "libnpmpublish": { + "version": "6.0.4", + "bundled": true, + "dev": true, + "requires": { + "normalize-package-data": "^4.0.0", + "npm-package-arg": "^9.0.1", + "npm-registry-fetch": "^13.0.0", + "semver": "^7.3.7", + "ssri": "^9.0.0" + } + }, + "libnpmsearch": { + "version": "5.0.3", + "bundled": true, + "dev": true, + "requires": { + "npm-registry-fetch": "^13.0.0" + } + }, + "libnpmteam": { + "version": "4.0.3", + "bundled": true, + "dev": true, + "requires": { + "aproba": "^2.0.0", + "npm-registry-fetch": "^13.0.0" + } + }, + "libnpmversion": { + "version": "3.0.6", + "bundled": true, + "dev": true, + "requires": { + "@npmcli/git": "^3.0.0", + "@npmcli/run-script": "^4.1.3", + "json-parse-even-better-errors": "^2.3.1", + "proc-log": "^2.0.0", + "semver": "^7.3.7" + } + }, + "lru-cache": { + "version": "7.12.0", + "bundled": true, + "dev": true + }, + "make-fetch-happen": { + "version": "10.2.0", + "bundled": true, + "dev": true, + "requires": { + "agentkeepalive": "^4.2.1", + "cacache": "^16.1.0", + "http-cache-semantics": "^4.1.0", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.0", + "is-lambda": "^1.0.1", + "lru-cache": "^7.7.1", + "minipass": "^3.1.6", + "minipass-collect": "^1.0.2", + "minipass-fetch": "^2.0.3", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^0.6.3", + "promise-retry": "^2.0.1", + "socks-proxy-agent": "^7.0.0", + "ssri": "^9.0.0" + } + }, + "minimatch": { + "version": "5.1.0", + "bundled": true, + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" + } + }, + "minipass": { + "version": "3.3.4", + "bundled": true, + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "minipass-collect": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "requires": { + "minipass": "^3.0.0" + } + }, + "minipass-fetch": { + "version": "2.1.0", + "bundled": true, + "dev": true, + "requires": { + "encoding": "^0.1.13", + "minipass": "^3.1.6", + "minipass-sized": "^1.0.3", + "minizlib": "^2.1.2" + } + }, + "minipass-flush": { + "version": "1.0.5", + "bundled": true, + "dev": true, + "requires": { + "minipass": "^3.0.0" + } + }, + "minipass-json-stream": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "requires": { + "jsonparse": "^1.3.1", + "minipass": "^3.0.0" + } + }, + "minipass-pipeline": { + "version": "1.2.4", + "bundled": true, + "dev": true, + "requires": { + "minipass": "^3.0.0" + } + }, + "minipass-sized": { + "version": "1.0.3", + "bundled": true, + "dev": true, + "requires": { + "minipass": "^3.0.0" + } + }, + "minizlib": { + "version": "2.1.2", + "bundled": true, + "dev": true, + "requires": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + } + }, + "mkdirp": { + "version": "1.0.4", + "bundled": true, + "dev": true + }, + "mkdirp-infer-owner": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "requires": { + "chownr": "^2.0.0", + "infer-owner": "^1.0.4", + "mkdirp": "^1.0.3" + } + }, + "ms": { + "version": "2.1.3", + "bundled": true, + "dev": true + }, + "mute-stream": { + "version": "0.0.8", + "bundled": true, + "dev": true + }, + "negotiator": { + "version": "0.6.3", + "bundled": true, + "dev": true + }, + "node-gyp": { + "version": "9.0.0", + "bundled": true, + "dev": true, + "requires": { + "env-paths": "^2.2.0", + "glob": "^7.1.4", + "graceful-fs": "^4.2.6", + "make-fetch-happen": "^10.0.3", + "nopt": "^5.0.0", + "npmlog": "^6.0.0", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.2", + "which": "^2.0.2" + }, + "dependencies": { + "brace-expansion": { + "version": "1.1.11", + "bundled": true, + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "glob": { + "version": "7.2.3", + "bundled": true, + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "minimatch": { + "version": "3.1.2", + "bundled": true, + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + } + } + }, + "nopt": { + "version": "5.0.0", + "bundled": true, + "dev": true, + "requires": { + "abbrev": "1" + } + }, + "normalize-package-data": { + "version": "4.0.0", + "bundled": true, + "dev": true, + "requires": { + "hosted-git-info": "^5.0.0", + "is-core-module": "^2.8.1", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4" + } + }, + "npm-audit-report": { + "version": "3.0.0", + "bundled": true, + "dev": true, + "requires": { + "chalk": "^4.0.0" + } + }, + "npm-bundled": { + "version": "1.1.2", + "bundled": true, + "dev": true, + "requires": { + "npm-normalize-package-bin": "^1.0.1" + } + }, + "npm-install-checks": { + "version": "5.0.0", + "bundled": true, + "dev": true, + "requires": { + "semver": "^7.1.1" + } + }, + "npm-normalize-package-bin": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "npm-package-arg": { + "version": "9.1.0", + "bundled": true, + "dev": true, + "requires": { + "hosted-git-info": "^5.0.0", + "proc-log": "^2.0.1", + "semver": "^7.3.5", + "validate-npm-package-name": "^4.0.0" + } + }, + "npm-packlist": { + "version": "5.1.1", + "bundled": true, + "dev": true, + "requires": { + "glob": "^8.0.1", + "ignore-walk": "^5.0.1", + "npm-bundled": "^1.1.2", + "npm-normalize-package-bin": "^1.0.1" + } + }, + "npm-pick-manifest": { + "version": "7.0.1", + "bundled": true, + "dev": true, + "requires": { + "npm-install-checks": "^5.0.0", + "npm-normalize-package-bin": "^1.0.1", + "npm-package-arg": "^9.0.0", + "semver": "^7.3.5" + } + }, + "npm-profile": { + "version": "6.2.0", + "bundled": true, + "dev": true, + "requires": { + "npm-registry-fetch": "^13.0.1", + "proc-log": "^2.0.0" + } + }, + "npm-registry-fetch": { + "version": "13.3.0", + "bundled": true, + "dev": true, + "requires": { + "make-fetch-happen": "^10.0.6", + "minipass": "^3.1.6", + "minipass-fetch": "^2.0.3", + "minipass-json-stream": "^1.0.1", + "minizlib": "^2.1.2", + "npm-package-arg": "^9.0.1", + "proc-log": "^2.0.0" + } + }, + "npm-user-validate": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "npmlog": { + "version": "6.0.2", + "bundled": true, + "dev": true, + "requires": { + "are-we-there-yet": "^3.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^4.0.3", + "set-blocking": "^2.0.0" + } + }, + "once": { + "version": "1.4.0", + "bundled": true, + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "opener": { + "version": "1.5.2", + "bundled": true, + "dev": true + }, + "p-map": { + "version": "4.0.0", + "bundled": true, + "dev": true, + "requires": { + "aggregate-error": "^3.0.0" + } + }, + "pacote": { + "version": "13.6.1", + "bundled": true, + "dev": true, + "requires": { + "@npmcli/git": "^3.0.0", + "@npmcli/installed-package-contents": "^1.0.7", + "@npmcli/promise-spawn": "^3.0.0", + "@npmcli/run-script": "^4.1.0", + "cacache": "^16.0.0", + "chownr": "^2.0.0", + "fs-minipass": "^2.1.0", + "infer-owner": "^1.0.4", + "minipass": "^3.1.6", + "mkdirp": "^1.0.4", + "npm-package-arg": "^9.0.0", + "npm-packlist": "^5.1.0", + "npm-pick-manifest": "^7.0.0", + "npm-registry-fetch": "^13.0.1", + "proc-log": "^2.0.0", + "promise-retry": "^2.0.1", + "read-package-json": "^5.0.0", + "read-package-json-fast": "^2.0.3", + "rimraf": "^3.0.2", + "ssri": "^9.0.0", + "tar": "^6.1.11" + } + }, + "parse-conflict-json": { + "version": "2.0.2", + "bundled": true, + "dev": true, + "requires": { + "json-parse-even-better-errors": "^2.3.1", + "just-diff": "^5.0.1", + "just-diff-apply": "^5.2.0" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "proc-log": { + "version": "2.0.1", + "bundled": true, + "dev": true + }, + "promise-all-reject-late": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "promise-call-limit": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "promise-inflight": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "promise-retry": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "requires": { + "err-code": "^2.0.2", + "retry": "^0.12.0" + } + }, + "promzard": { + "version": "0.3.0", + "bundled": true, + "dev": true, + "requires": { + "read": "1" + } + }, + "qrcode-terminal": { + "version": "0.12.0", + "bundled": true, + "dev": true + }, + "read": { + "version": "1.0.7", + "bundled": true, + "dev": true, + "requires": { + "mute-stream": "~0.0.4" + } + }, + "read-cmd-shim": { + "version": "3.0.0", + "bundled": true, + "dev": true + }, + "read-package-json": { + "version": "5.0.1", + "bundled": true, + "dev": true, + "requires": { + "glob": "^8.0.1", + "json-parse-even-better-errors": "^2.3.1", + "normalize-package-data": "^4.0.0", + "npm-normalize-package-bin": "^1.0.1" + } + }, + "read-package-json-fast": { + "version": "2.0.3", + "bundled": true, + "dev": true, + "requires": { + "json-parse-even-better-errors": "^2.3.0", + "npm-normalize-package-bin": "^1.0.1" + } + }, + "readable-stream": { + "version": "3.6.0", + "bundled": true, + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "readdir-scoped-modules": { + "version": "1.1.0", + "bundled": true, + "dev": true, + "requires": { + "debuglog": "^1.0.1", + "dezalgo": "^1.0.0", + "graceful-fs": "^4.1.2", + "once": "^1.3.0" + } + }, + "retry": { + "version": "0.12.0", + "bundled": true, + "dev": true + }, + "rimraf": { + "version": "3.0.2", + "bundled": true, + "dev": true, + "requires": { + "glob": "^7.1.3" + }, + "dependencies": { + "brace-expansion": { + "version": "1.1.11", + "bundled": true, + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "glob": { + "version": "7.2.3", + "bundled": true, + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "minimatch": { + "version": "3.1.2", + "bundled": true, + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + } + } + }, + "safe-buffer": { + "version": "5.2.1", + "bundled": true, + "dev": true + }, + "safer-buffer": { + "version": "2.1.2", + "bundled": true, + "dev": true, + "optional": true + }, + "semver": { + "version": "7.3.7", + "bundled": true, + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + }, + "dependencies": { + "lru-cache": { + "version": "6.0.0", + "bundled": true, + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + } + } + }, + "set-blocking": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "signal-exit": { + "version": "3.0.7", + "bundled": true, + "dev": true + }, + "smart-buffer": { + "version": "4.2.0", + "bundled": true, + "dev": true + }, + "socks": { + "version": "2.6.2", + "bundled": true, + "dev": true, + "requires": { + "ip": "^1.1.5", + "smart-buffer": "^4.2.0" + } + }, + "socks-proxy-agent": { + "version": "7.0.0", + "bundled": true, + "dev": true, + "requires": { + "agent-base": "^6.0.2", + "debug": "^4.3.3", + "socks": "^2.6.2" + } + }, + "spdx-correct": { + "version": "3.1.1", + "bundled": true, + "dev": true, + "requires": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-exceptions": { + "version": "2.3.0", + "bundled": true, + "dev": true + }, + "spdx-expression-parse": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "requires": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-license-ids": { + "version": "3.0.11", + "bundled": true, + "dev": true + }, + "ssri": { + "version": "9.0.1", + "bundled": true, + "dev": true, + "requires": { + "minipass": "^3.1.1" + } + }, + "string-width": { + "version": "4.2.3", + "bundled": true, + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "string_decoder": { + "version": "1.3.0", + "bundled": true, + "dev": true, + "requires": { + "safe-buffer": "~5.2.0" + } + }, + "strip-ansi": { + "version": "6.0.1", + "bundled": true, + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "supports-color": { + "version": "7.2.0", + "bundled": true, + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "tar": { + "version": "6.1.11", + "bundled": true, + "dev": true, + "requires": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^3.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + } + }, + "text-table": { + "version": "0.2.0", + "bundled": true, + "dev": true + }, + "tiny-relative-date": { + "version": "1.3.0", + "bundled": true, + "dev": true + }, + "treeverse": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "unique-filename": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "requires": { + "unique-slug": "^2.0.0" + } + }, + "unique-slug": { + "version": "2.0.2", + "bundled": true, + "dev": true, + "requires": { + "imurmurhash": "^0.1.4" + } + }, + "util-deprecate": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "validate-npm-package-license": { + "version": "3.0.4", + "bundled": true, + "dev": true, + "requires": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "validate-npm-package-name": { + "version": "4.0.0", + "bundled": true, + "dev": true, + "requires": { + "builtins": "^5.0.0" + } + }, + "walk-up-path": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "wcwidth": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "requires": { + "defaults": "^1.0.3" + } + }, + "which": { + "version": "2.0.2", + "bundled": true, + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "wide-align": { + "version": "1.1.5", + "bundled": true, + "dev": true, + "requires": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, + "wrappy": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "write-file-atomic": { + "version": "4.0.1", + "bundled": true, + "dev": true, + "requires": { + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" + } + }, + "yallist": { + "version": "4.0.0", + "bundled": true, + "dev": true + } + } + }, + "npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "requires": { + "path-key": "^3.0.0" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "requires": { + "mimic-fn": "^2.1.0" + } + }, + "p-each-series": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-each-series/-/p-each-series-2.2.0.tgz", + "integrity": "sha512-ycIL2+1V32th+8scbpTvyHNaHe02z0sjgh91XXjAk+ZeXoPN4Z46DVUnzdso0aX4KckKw0FNNFHdjZ2UsZvxiA==", + "dev": true + }, + "p-filter": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-filter/-/p-filter-2.1.0.tgz", + "integrity": "sha512-ZBxxZ5sL2HghephhpGAQdoskxplTwr7ICaehZwLIlfL6acuVgZPm8yBNuRAFBGEqtD/hmUeq9eqLg2ys9Xr/yw==", + "dev": true, + "requires": { + "p-map": "^2.0.0" + } + }, + "p-is-promise": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-3.0.0.tgz", + "integrity": "sha512-Wo8VsW4IRQSKVXsJCn7TomUaVtyfjVDn3nUP7kE967BQk0CwFpdbZs0X0uk5sW9mkBa9eNM7hCMaG93WUAwxYQ==", + "dev": true + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + }, + "p-map": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz", + "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==", + "dev": true + }, + "p-reduce": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-reduce/-/p-reduce-2.1.0.tgz", + "integrity": "sha512-2USApvnsutq8uoxZBGbbWM0JIYLiEMJ9RlaN7fAzVNb9OZN0SHjjTTfIcb667XynS5Y1VhwDJVDa72TnPzAYWw==", + "dev": true + }, + "p-retry": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", + "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", + "dev": true, + "requires": { + "@types/retry": "0.12.0", + "retry": "^0.13.1" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, + "parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "requires": { + "callsites": "^3.0.0" + } + }, + "parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + } + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, + "path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true + }, + "picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true + }, + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", + "dev": true + }, + "pkg-conf": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/pkg-conf/-/pkg-conf-2.1.0.tgz", + "integrity": "sha512-C+VUP+8jis7EsQZIhDYmS5qlNtjv2yP4SNtjXK9AP1ZcTRlnSfuumaTnRfYZnYgUUYVIKqL0fRvmUGDV2fmp6g==", + "dev": true, + "requires": { + "find-up": "^2.0.0", + "load-json-file": "^4.0.0" + }, + "dependencies": { + "find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ==", + "dev": true, + "requires": { + "locate-path": "^2.0.0" + } + }, + "locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==", + "dev": true, + "requires": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + } + }, + "p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "dev": true, + "requires": { + "p-try": "^1.0.0" + } + }, + "p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg==", + "dev": true, + "requires": { + "p-limit": "^1.1.0" + } + }, + "p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==", + "dev": true + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", + "dev": true + } + } + }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true + }, + "q": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", + "integrity": "sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==", + "dev": true + }, + "queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true + }, + "quick-lru": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz", + "integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==", + "dev": true + }, + "rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dev": true, + "requires": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + } + }, + "read-pkg": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", + "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", + "dev": true, + "requires": { + "@types/normalize-package-data": "^2.4.0", + "normalize-package-data": "^2.5.0", + "parse-json": "^5.0.0", + "type-fest": "^0.6.0" + }, + "dependencies": { + "hosted-git-info": { + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", + "dev": true + }, + "normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "requires": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + }, + "type-fest": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", + "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", + "dev": true + } + } + }, + "read-pkg-up": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", + "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", + "dev": true, + "requires": { + "find-up": "^4.1.0", + "read-pkg": "^5.2.0", + "type-fest": "^0.8.1" + }, + "dependencies": { + "type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true + } + } + }, + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "redent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", + "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", + "dev": true, + "requires": { + "indent-string": "^4.0.0", + "strip-indent": "^3.0.0" + } + }, + "redeyed": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/redeyed/-/redeyed-2.1.1.tgz", + "integrity": "sha512-FNpGGo1DycYAdnrKFxCMmKYgo/mILAqtRYbkdQD8Ep/Hk2PQ5+aEAEx+IU713RTDmuBaH0c8P5ZozurNu5ObRQ==", + "dev": true, + "requires": { + "esprima": "~4.0.0" + } + }, + "registry-auth-token": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-4.2.2.tgz", + "integrity": "sha512-PC5ZysNb42zpFME6D/XlIgtNGdTl8bBOCw90xQLVMpzuuubJKYDWFAEuUNc+Cn8Z8724tg2SDhDRrkVEsqfDMg==", + "dev": true, + "requires": { + "rc": "1.2.8" + } + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true + }, + "resolve": { + "version": "1.22.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", + "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", + "dev": true, + "requires": { + "is-core-module": "^2.9.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + } + }, + "resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true + }, + "retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "dev": true + }, + "reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true + }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "requires": { + "queue-microtask": "^1.2.2" + } + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true + }, + "semantic-release": { + "version": "19.0.3", + "resolved": "https://registry.npmjs.org/semantic-release/-/semantic-release-19.0.3.tgz", + "integrity": "sha512-HaFbydST1cDKZHuFZxB8DTrBLJVK/AnDExpK0s3EqLIAAUAHUgnd+VSJCUtTYQKkAkauL8G9CucODrVCc7BuAA==", + "dev": true, + "requires": { + "@semantic-release/commit-analyzer": "^9.0.2", + "@semantic-release/error": "^3.0.0", + "@semantic-release/github": "^8.0.0", + "@semantic-release/npm": "^9.0.0", + "@semantic-release/release-notes-generator": "^10.0.0", + "aggregate-error": "^3.0.0", + "cosmiconfig": "^7.0.0", + "debug": "^4.0.0", + "env-ci": "^5.0.0", + "execa": "^5.0.0", + "figures": "^3.0.0", + "find-versions": "^4.0.0", + "get-stream": "^6.0.0", + "git-log-parser": "^1.2.0", + "hook-std": "^2.0.0", + "hosted-git-info": "^4.0.0", + "lodash": "^4.17.21", + "marked": "^4.0.10", + "marked-terminal": "^5.0.0", + "micromatch": "^4.0.2", + "p-each-series": "^2.1.0", + "p-reduce": "^2.0.0", + "read-pkg-up": "^7.0.0", + "resolve-from": "^5.0.0", + "semver": "^7.3.2", + "semver-diff": "^3.1.1", + "signale": "^1.2.1", + "yargs": "^16.2.0" + } + }, + "semver": { + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", + "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "semver-diff": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-3.1.1.tgz", + "integrity": "sha512-GX0Ix/CJcHyB8c4ykpHGIAvLyOwOobtM/8d+TQkAd81/bEjgPHrfba41Vpesr7jX/t8Uh+R3EX9eAS5be+jQYg==", + "dev": true, + "requires": { + "semver": "^6.3.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "semver-regex": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/semver-regex/-/semver-regex-3.1.4.tgz", + "integrity": "sha512-6IiqeZNgq01qGf0TId0t3NvKzSvUsjcpdEO3AQNeIjR6A2+ckTnQlDpl4qu1bjRv0RzN3FP9hzFmws3lKqRWkA==", + "dev": true + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, + "signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "signale": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/signale/-/signale-1.4.0.tgz", + "integrity": "sha512-iuh+gPf28RkltuJC7W5MRi6XAjTDCAPC/prJUpQoG4vIP3MJZ+GTydVnodXA7pwvTKb2cA0m9OFZW/cdWy/I/w==", + "dev": true, + "requires": { + "chalk": "^2.3.2", + "figures": "^2.0.0", + "pkg-conf": "^2.1.0" + }, + "dependencies": { + "figures": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", + "integrity": "sha512-Oa2M9atig69ZkfwiApY8F2Yy+tzMbazyvqv21R0NsSC8floSOC09BbT1ITWAdoMGQvJ/aZnR1KMwdx9tvHnTNA==", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.5" + } + } + } + }, + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "spawn-error-forwarder": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/spawn-error-forwarder/-/spawn-error-forwarder-1.0.0.tgz", + "integrity": "sha512-gRjMgK5uFjbCvdibeGJuy3I5OYz6VLoVdsOJdA6wV0WlfQVLFueoqMxwwYD9RODdgb6oUIvlRlsyFSiQkMKu0g==", + "dev": true + }, + "spdx-correct": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", + "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", + "dev": true, + "requires": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-exceptions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", + "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", + "dev": true + }, + "spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dev": true, + "requires": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-license-ids": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.11.tgz", + "integrity": "sha512-Ctl2BrFiM0X3MANYgj3CkygxhRmr9mi6xhejbdO960nF6EDJApTYpn0BQnDKlnNBULKiCN1n3w9EBkHK8ZWg+g==", + "dev": true + }, + "split": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz", + "integrity": "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==", + "dev": true, + "requires": { + "through": "2" + } + }, + "split2": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/split2/-/split2-3.2.2.tgz", + "integrity": "sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==", + "dev": true, + "requires": { + "readable-stream": "^3.0.0" + } + }, + "stream-combiner2": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/stream-combiner2/-/stream-combiner2-1.1.1.tgz", + "integrity": "sha512-3PnJbYgS56AeWgtKF5jtJRT6uFJe56Z0Hc5Ngg/6sI6rIt8iiMBTa9cvdyFfpMQjaVHr8dusbNeFGIIonxOvKw==", + "dev": true, + "requires": { + "duplexer2": "~0.1.0", + "readable-stream": "^2.0.2" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "requires": { + "safe-buffer": "~5.2.0" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true + }, + "strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true + }, + "strip-indent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", + "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "dev": true, + "requires": { + "min-indent": "^1.0.0" + } + }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "supports-hyperlinks": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.2.0.tgz", + "integrity": "sha512-6sXEzV5+I5j8Bmq9/vUphGRM/RJNT9SCURJLjwfOg51heRtguGWDzcaBlgAzKhQa0EVNpPEKzQuBwZ8S8WaCeQ==", + "dev": true, + "requires": { + "has-flag": "^4.0.0", + "supports-color": "^7.0.0" + }, + "dependencies": { + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true + }, + "temp-dir": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-2.0.0.tgz", + "integrity": "sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg==", + "dev": true + }, + "tempy": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tempy/-/tempy-1.0.1.tgz", + "integrity": "sha512-biM9brNqxSc04Ee71hzFbryD11nX7VPhQQY32AdDmjFvodsRFz/3ufeoTZ6uYkRFfGo188tENcASNs3vTdsM0w==", + "dev": true, + "requires": { + "del": "^6.0.0", + "is-stream": "^2.0.0", + "temp-dir": "^2.0.0", + "type-fest": "^0.16.0", + "unique-string": "^2.0.0" + }, + "dependencies": { + "type-fest": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.16.0.tgz", + "integrity": "sha512-eaBzG6MxNzEn9kiwvtre90cXaNLkmadMWa1zQMs3XORCXNbsH/OewwbxC5ia9dCxIxnTAsSxXJaa/p5y8DlvJg==", + "dev": true + } + } + }, + "text-extensions": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/text-extensions/-/text-extensions-1.9.0.tgz", + "integrity": "sha512-wiBrwC1EhBelW12Zy26JeOUkQ5mRu+5o8rpsJk5+2t+Y5vE7e842qtZDQ2g1NpX/29HdyFeJ4nSIhI47ENSxlQ==", + "dev": true + }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", + "dev": true + }, + "through2": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/through2/-/through2-4.0.2.tgz", + "integrity": "sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==", + "dev": true, + "requires": { + "readable-stream": "3" + } + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, + "tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "dev": true + }, + "traverse": { + "version": "0.6.6", + "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.6.6.tgz", + "integrity": "sha512-kdf4JKs8lbARxWdp7RKdNzoJBhGUcIalSYibuGyHJbmk40pOysQ0+QPvlkCOICOivDWU2IJo2rkrxyTK2AH4fw==", + "dev": true + }, + "trim-newlines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.1.tgz", + "integrity": "sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==", + "dev": true + }, + "type-fest": { + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.18.1.tgz", + "integrity": "sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==", + "dev": true + }, + "uglify-js": { + "version": "3.16.3", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.16.3.tgz", + "integrity": "sha512-uVbFqx9vvLhQg0iBaau9Z75AxWJ8tqM9AV890dIZCLApF4rTcyHwmAvLeEdYRs+BzYWu8Iw81F79ah0EfTXbaw==", + "dev": true, + "optional": true + }, + "unique-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", + "integrity": "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==", + "dev": true, + "requires": { + "crypto-random-string": "^2.0.0" + } + }, + "universal-user-agent": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.0.tgz", + "integrity": "sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w==", + "dev": true + }, + "universalify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "dev": true + }, + "url-join": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz", + "integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==", + "dev": true + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true + }, + "validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "requires": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "dev": true + }, + "whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dev": true, + "requires": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", + "dev": true + }, + "wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + } + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true + }, + "y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "dev": true + }, + "yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "requires": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + } + }, + "yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "dev": true + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000000000000000000000000000000000000..37434d5a84415866784cc9878b13a7f6f6b46d66 --- /dev/null +++ b/package.json @@ -0,0 +1,10 @@ +{ + "name": "perun-ga4gh-broker", + "private": true, + "devDependencies": { + "@semantic-release/changelog": "6.0.1", + "@semantic-release/git": "10.0.1", + "@semantic-release/exec": "6.0.3", + "semantic-release": "19.0.3" + } +} diff --git a/pom.xml b/pom.xml index afebcd7980df7db17711ddfabdd622e7e5b5ba3e..4544eee5fd19ae3948ad177c9cdf56eb686e085a 100644 --- a/pom.xml +++ b/pom.xml @@ -4,24 +4,32 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> - <groupId>org.example</groupId> - <artifactId>Perun-GA4GH</artifactId> - <version>1.0-SNAPSHOT</version> - <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> - <version>2.4.0</version> + <version>2.7.2</version> <relativePath /> </parent> + <groupId>cz.muni.ics</groupId> + <artifactId>Perun-GA4GH</artifactId> + <version>1.0.0</version> + + <name>perun-ga4gh-broker</name> + <description>GA4GH broker</description> + <packaging>war</packaging> + <properties> - <java.version>16</java.version> - <maven.compiler.source>16</maven.compiler.source> - <maven.compiler.target>16</maven.compiler.target> + <java.version>11</java.version> + <maven.compiler.source>${java.version}</maven.compiler.source> + <maven.compiler.target>${java.version}</maven.compiler.target> <version.springfox>3.0.0</version.springfox> <version.swagger>3.0.0</version.swagger> + <version.apache-directory-api>2.1.0</version.apache-directory-api> + <version.nimbus-jose-jwt>9.23</version.nimbus-jose-jwt> + <version.guava>31.1-jre</version.guava> + <version.bouncy-castle>1.70</version.bouncy-castle> </properties> <dependencies> @@ -30,17 +38,22 @@ <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> - <groupId>org.springframework</groupId> - <artifactId>spring-core</artifactId> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-configuration-processor</artifactId> + <optional>true</optional> + </dependency> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-oauth2-client</artifactId> </dependency> <dependency> - <groupId>org.springframework</groupId> - <artifactId>spring-context</artifactId> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> - <artifactId>spring-boot-configuration-processor</artifactId> - <optional>true</optional> + <artifactId>spring-boot-starter-tomcat</artifactId> + <scope>provided</scope> </dependency> <dependency> @@ -59,9 +72,39 @@ <artifactId>springfox-swagger-ui</artifactId> <version>${version.swagger}</version> </dependency> + + <dependency> + <groupId>org.apache.httpcomponents</groupId> + <artifactId>httpclient</artifactId> + </dependency> + <dependency> + <groupId>org.apache.directory.api</groupId> + <artifactId>api-all</artifactId> + <version>${version.apache-directory-api}</version> + </dependency> + + <dependency> + <groupId>org.bouncycastle</groupId> + <artifactId>bcpkix-jdk15on</artifactId> + <version>${version.bouncy-castle}</version> + <scope>compile</scope> + </dependency> + <dependency> + <groupId>org.bouncycastle</groupId> + <artifactId>bcprov-jdk15on</artifactId> + <version>${version.bouncy-castle}</version> + <scope>compile</scope> + </dependency> + + <dependency> + <groupId>com.google.guava</groupId> + <artifactId>guava</artifactId> + <version>${version.guava}</version> + </dependency> </dependencies> <build> + <finalName>perun-ga4gh-broker</finalName> <plugins> <plugin> <groupId>org.springframework.boot</groupId> @@ -78,4 +121,4 @@ </plugins> </build> -</project> \ No newline at end of file +</project> diff --git a/renovate.json b/renovate.json new file mode 100644 index 0000000000000000000000000000000000000000..820b73b6674d181f138beb8633234d144cfbafd9 --- /dev/null +++ b/renovate.json @@ -0,0 +1,17 @@ +{ + "extends": [ + "config:base", + ":automergeBranch", + ":automergeLinters", + ":automergeTesters", + ":enableVulnerabilityAlertsWithLabel(security)", + ":maintainLockFilesWeekly", + ":pinOnlyDevDependencies", + ":prNotPending", + ":rebaseStalePrs", + ":semanticCommits", + "npm:unpublishSafe" + ], + "stabilityDays": 3, + "transitiveRemediation": true +} diff --git a/src/main/java/cz/muni/ics/ga4gh/Application.java b/src/main/java/cz/muni/ics/ga4gh/Application.java index 8627f5e8951b9a28a6f94943b3bb35115a5dcb50..efc5f3c0b2a05cdb8f2dbd56ddc4c83bbacaf3d9 100644 --- a/src/main/java/cz/muni/ics/ga4gh/Application.java +++ b/src/main/java/cz/muni/ics/ga4gh/Application.java @@ -2,9 +2,10 @@ package cz.muni.ics.ga4gh; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; @SpringBootApplication -public class Application { +public class Application extends SpringBootServletInitializer { public static void main(String[] args) { SpringApplication.run(Application.class, args); diff --git a/src/main/java/cz/muni/ics/ga4gh/ApplicationContext.java b/src/main/java/cz/muni/ics/ga4gh/ApplicationContext.java new file mode 100644 index 0000000000000000000000000000000000000000..13e60436c7e47a75a635832e25a2feb029a96e96 --- /dev/null +++ b/src/main/java/cz/muni/ics/ga4gh/ApplicationContext.java @@ -0,0 +1,44 @@ +package cz.muni.ics.ga4gh; + +import cz.muni.ics.ga4gh.config.BrokerConfig; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.io.FileUrlResource; +import org.springframework.core.io.Resource; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; +import springfox.documentation.builders.PathSelectors; +import springfox.documentation.builders.RequestHandlerSelectors; +import springfox.documentation.spi.DocumentationType; +import springfox.documentation.spring.web.plugins.Docket; + +import java.net.MalformedURLException; + +@Configuration +public class ApplicationContext { + + private final String pathToJwkFile; + + public ApplicationContext(BrokerConfig config) { + this.pathToJwkFile = config.getPathToJwkFile(); + } + + @Bean + public Docket api() { + return new Docket(DocumentationType.SWAGGER_2) + .select() + .apis(RequestHandlerSelectors.any()) + .paths(PathSelectors.any()) + .build(); + } + + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } + + @Bean + public Resource jwks() throws MalformedURLException { + return new FileUrlResource(pathToJwkFile); + } +} diff --git a/src/main/java/cz/muni/ics/ga4gh/adapters/PerunAdapter.java b/src/main/java/cz/muni/ics/ga4gh/adapters/PerunAdapter.java new file mode 100644 index 0000000000000000000000000000000000000000..7358f9ad0080df682634f671e9455b5f5fff5ff8 --- /dev/null +++ b/src/main/java/cz/muni/ics/ga4gh/adapters/PerunAdapter.java @@ -0,0 +1,39 @@ +package cz.muni.ics.ga4gh.adapters; + +import cz.muni.ics.ga4gh.adapters.impl.PerunAdapterLdap; +import cz.muni.ics.ga4gh.adapters.impl.PerunAdapterRpc; +import cz.muni.ics.ga4gh.config.AdapterConfig; +import lombok.Getter; +import lombok.Setter; + +import java.util.Objects; + +@Getter +@Setter +public abstract class PerunAdapter implements PerunAdapterMethods { + + private final String RPC = "rpc"; + + private PerunAdapterMethods adapterPrimary; + private PerunAdapterMethods adapterFallback; + + private PerunAdapterMethodsRpc adapterRpc; + private PerunAdapterMethodsLdap adapterLdap; + + private boolean callFallback; + + public PerunAdapter(AdapterConfig config, PerunAdapterRpc adapterRpc, PerunAdapterLdap adapterLdap) { + if (config.getAdapterPrimary() != null && config.getAdapterPrimary().equalsIgnoreCase(RPC)) { + this.adapterPrimary = adapterRpc; + this.adapterFallback = adapterLdap; + } else { + this.adapterPrimary = adapterLdap; + this.adapterFallback = adapterRpc; + } + + this.adapterRpc = adapterRpc; + this.adapterLdap = adapterLdap; + + this.callFallback = Objects.requireNonNullElse(config.getCallFallback(), false); + } +} diff --git a/src/main/java/cz/muni/ics/ga4gh/adapters/PerunAdapterMethods.java b/src/main/java/cz/muni/ics/ga4gh/adapters/PerunAdapterMethods.java new file mode 100644 index 0000000000000000000000000000000000000000..85485da5b586621dd4ed63821eb1a2343cca5d64 --- /dev/null +++ b/src/main/java/cz/muni/ics/ga4gh/adapters/PerunAdapterMethods.java @@ -0,0 +1,23 @@ +package cz.muni.ics.ga4gh.adapters; + +import cz.muni.ics.ga4gh.model.Affiliation; +import cz.muni.ics.ga4gh.model.AttributeMapping; + +import java.util.List; +import java.util.Set; + +public interface PerunAdapterMethods { + + /** + * Fetch user based on his principal (extLogin and extSource) from Perun + * + * @return PerunUser with id of found user + */ + Long getPreauthenticatedUserId(String extLogin, String extSourceName); + + boolean isUserInGroup(Long userId, Long groupId); + + List<Affiliation> getGroupAffiliations(Long userId, String groupAffiliationsAttr); + + Set<Long> getUserIdsByAttributeValue(AttributeMapping attrName, String attrValue); +} diff --git a/src/main/java/cz/muni/ics/ga4gh/adapters/PerunAdapterMethodsLdap.java b/src/main/java/cz/muni/ics/ga4gh/adapters/PerunAdapterMethodsLdap.java new file mode 100644 index 0000000000000000000000000000000000000000..70c67cde5e13304ce1c26570e8cbac0d8bfc96a8 --- /dev/null +++ b/src/main/java/cz/muni/ics/ga4gh/adapters/PerunAdapterMethodsLdap.java @@ -0,0 +1,4 @@ +package cz.muni.ics.ga4gh.adapters; + +public interface PerunAdapterMethodsLdap { +} diff --git a/src/main/java/cz/muni/ics/ga4gh/adapters/PerunAdapterMethodsRpc.java b/src/main/java/cz/muni/ics/ga4gh/adapters/PerunAdapterMethodsRpc.java new file mode 100644 index 0000000000000000000000000000000000000000..f2c4d5e16d3212bcc1704673da83ec9a82cd7c57 --- /dev/null +++ b/src/main/java/cz/muni/ics/ga4gh/adapters/PerunAdapterMethodsRpc.java @@ -0,0 +1,12 @@ +package cz.muni.ics.ga4gh.adapters; + +import cz.muni.ics.ga4gh.model.Affiliation; + +import java.util.List; + +public interface PerunAdapterMethodsRpc { + + String getUserAttributeCreatedAt(Long userId, String attrName); + + List<Affiliation> getUserExtSourcesAffiliations(Long userId, String affiliationsAttr, String orgUrlAttr); +} diff --git a/src/main/java/cz/muni/ics/ga4gh/adapters/impl/PerunAdapterImpl.java b/src/main/java/cz/muni/ics/ga4gh/adapters/impl/PerunAdapterImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..fd5553bf06d4c29d4b1fb4781ce03681eab90a12 --- /dev/null +++ b/src/main/java/cz/muni/ics/ga4gh/adapters/impl/PerunAdapterImpl.java @@ -0,0 +1,72 @@ +package cz.muni.ics.ga4gh.adapters.impl; + +import cz.muni.ics.ga4gh.adapters.PerunAdapter; +import cz.muni.ics.ga4gh.config.AdapterConfig; +import cz.muni.ics.ga4gh.model.Affiliation; +import cz.muni.ics.ga4gh.model.AttributeMapping; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.List; +import java.util.Set; + +@Component +public class PerunAdapterImpl extends PerunAdapter { + + @Autowired + public PerunAdapterImpl(AdapterConfig config, PerunAdapterRpc adapterRpc, PerunAdapterLdap adapterLdap) { + super(config, adapterRpc, adapterLdap); + } + + @Override + public Long getPreauthenticatedUserId(String extLogin, String extSourceName) { + try { + return this.getAdapterPrimary().getPreauthenticatedUserId(extLogin, extSourceName); + } catch (UnsupportedOperationException e) { + if (this.isCallFallback()) { + return this.getAdapterFallback().getPreauthenticatedUserId(extLogin, extSourceName); + } else { + throw e; + } + } + } + + @Override + public boolean isUserInGroup(Long userId, Long groupId) { + try { + return this.getAdapterPrimary().isUserInGroup(userId, groupId); + } catch (UnsupportedOperationException e) { + if (this.isCallFallback()) { + return this.getAdapterFallback().isUserInGroup(userId, groupId); + } else { + throw e; + } + } + } + + @Override + public List<Affiliation> getGroupAffiliations(Long userId, String groupAffiliationsAttr) { + try { + return this.getAdapterPrimary().getGroupAffiliations(userId, groupAffiliationsAttr); + } catch (UnsupportedOperationException e) { + if (this.isCallFallback()) { + return this.getAdapterFallback().getGroupAffiliations(userId, groupAffiliationsAttr); + } else { + throw e; + } + } + } + + @Override + public Set<Long> getUserIdsByAttributeValue(AttributeMapping attrName, String attrValue) { + try { + return this.getAdapterPrimary().getUserIdsByAttributeValue(attrName, attrValue); + } catch (UnsupportedOperationException e) { + if (this.isCallFallback()) { + return this.getAdapterFallback().getUserIdsByAttributeValue(attrName, attrValue); + } else { + throw e; + } + } + } +} diff --git a/src/main/java/cz/muni/ics/ga4gh/adapters/impl/PerunAdapterLdap.java b/src/main/java/cz/muni/ics/ga4gh/adapters/impl/PerunAdapterLdap.java new file mode 100644 index 0000000000000000000000000000000000000000..2c68d7efff51f696cd731b2d4c90a9956f258de8 --- /dev/null +++ b/src/main/java/cz/muni/ics/ga4gh/adapters/impl/PerunAdapterLdap.java @@ -0,0 +1,199 @@ +package cz.muni.ics.ga4gh.adapters.impl; + +import cz.muni.ics.ga4gh.adapters.PerunAdapterMethods; +import cz.muni.ics.ga4gh.adapters.PerunAdapterMethodsLdap; +import cz.muni.ics.ga4gh.config.AttributesConfig; +import cz.muni.ics.ga4gh.connectors.PerunConnectorLdap; +import cz.muni.ics.ga4gh.model.Affiliation; +import cz.muni.ics.ga4gh.model.AttributeMapping; +import org.apache.directory.api.ldap.model.entry.Attribute; +import org.apache.directory.api.ldap.model.entry.Entry; +import org.apache.directory.api.ldap.model.message.SearchScope; +import org.apache.directory.ldap.client.api.search.FilterBuilder; +import org.apache.directory.ldap.client.template.EntryMapper; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +import static org.apache.directory.ldap.client.api.search.FilterBuilder.and; +import static org.apache.directory.ldap.client.api.search.FilterBuilder.equal; +import static org.apache.directory.ldap.client.api.search.FilterBuilder.or; + +@Component +public class PerunAdapterLdap implements PerunAdapterMethods, PerunAdapterMethodsLdap { + + public static final String OBJECT_CLASS = "objectClass"; + public static final String OU_PEOPLE = "ou=People"; + + public static final String PERUN_USER_ID = "perunUserId"; + public static final String MEMBER_OF = "memberOf"; + + public static final String PERUN_GROUP = "perunGroup"; + public static final String PERUN_USER = "perunUser"; + public static final String PERUN_GROUP_ID = "perunGroupId"; + public static final String UNIQUE_MEMBER = "uniqueMember"; + + public static final String PERUN_VO_ID = "perunVoId"; + public static final String EDU_PERSON_PRINCIPAL_NAMES = "eduPersonPrincipalNames"; + + private final PerunConnectorLdap connectorLdap; + private final Map<String, AttributeMapping> attributeMappings; + + @Autowired + PerunAdapterLdap(PerunConnectorLdap connectorLdap, AttributesConfig attributesConfig) { + this.connectorLdap = connectorLdap; + this.attributeMappings = attributesConfig.getAttributeMappings(); + } + + @Override + public Long getPreauthenticatedUserId(String extLogin, String extSourceName) { + FilterBuilder filter = and( + equal(OBJECT_CLASS, PERUN_USER), equal(EDU_PERSON_PRINCIPAL_NAMES, extLogin) + ); + + return getPerunUserId(filter); + } + + @Override + public boolean isUserInGroup(Long userId, Long groupId) { + String uniqueMemberValue = PERUN_USER_ID + '=' + userId + ',' + OU_PEOPLE + ',' + connectorLdap.getBaseDN(); + + FilterBuilder filter = and( + equal(OBJECT_CLASS, PERUN_GROUP), + equal(PERUN_GROUP_ID, String.valueOf(groupId)), + equal(UNIQUE_MEMBER, uniqueMemberValue) + ); + + EntryMapper<Long> mapper = e -> Long.parseLong(e.get(PERUN_GROUP_ID).getString()); + String[] attributes = new String[] { PERUN_GROUP_ID }; + List<Long> ids = connectorLdap.search(null, filter, SearchScope.SUBTREE, attributes, mapper); + + return ids.stream().filter(groupId::equals).count() == 1L; + } + + @Override + public List<Affiliation> getGroupAffiliations(Long userId, String groupAffiliationsAttr) { + Set<Long> userGroupIds = getGroupIdsWhereUserIsMember(userId, null); + if (userGroupIds.isEmpty()) { + return new ArrayList<>(); + } + + FilterBuilder[] groupIdFilters = new FilterBuilder[userGroupIds.size()]; + int i = 0; + + for (Long id: userGroupIds) { + groupIdFilters[i++] = equal(PERUN_GROUP_ID, String.valueOf(id)); + } + + AttributeMapping affiliationsMapping = attributeMappings.get(groupAffiliationsAttr); + FilterBuilder filterBuilder = and(equal(OBJECT_CLASS, PERUN_GROUP), or(groupIdFilters)); + String[] attributes = new String[] { affiliationsMapping.getLdapName() }; + + EntryMapper<Set<Affiliation>> mapper = e -> { + Set<Affiliation> affiliations = new HashSet<>(); + if (!checkHasAttributes(e, attributes)) { + return affiliations; + } + + Attribute a = e.get(affiliationsMapping.getLdapName()); + long linuxTime = System.currentTimeMillis() / 1000L; + a.iterator().forEachRemaining(v -> affiliations.add(new Affiliation(null, v.getString(), linuxTime))); + + return affiliations; + }; + + List<Set<Affiliation>> affiliationSets = connectorLdap.search(null, filterBuilder, SearchScope.SUBTREE, attributes, mapper); + + return affiliationSets.stream().flatMap(Set::stream).distinct().collect(Collectors.toList()); + } + + @Override + public Set<Long> getUserIdsByAttributeValue(AttributeMapping attrName, String attrValue) { + if (!StringUtils.hasText(attrName.getLdapName())) { + return new HashSet<>(); + } + + FilterBuilder filter = and( + equal(OBJECT_CLASS, PERUN_USER), + equal(attrName.getLdapName(), attrValue) + ); + + SearchScope scope = SearchScope.ONELEVEL; + String[] attributes = new String[]{PERUN_USER_ID}; + EntryMapper<Long> mapper = e -> Long.parseLong(e.get(PERUN_USER_ID).getString()); + + List<Long> result = connectorLdap.search(OU_PEOPLE, filter, scope, attributes, mapper); + + return Set.copyOf(result); + } + + private Set<Long> getGroupIdsWhereUserIsMember(Long userId, Long voId) { + String dnPrefix = getDnPrefixForUserId(userId); + String[] attributes = new String[] { MEMBER_OF }; + + EntryMapper<Set<Long>> mapper = e -> { + Set<Long> ids = new HashSet<>(); + if (checkHasAttributes(e, attributes)) { + Attribute a = e.get(MEMBER_OF); + a.iterator().forEachRemaining(id -> { + String fullVal = id.getString(); + String[] parts = fullVal.split(",", 3); + + String groupId = parts[0]; + groupId = groupId.replace(PERUN_GROUP_ID + '=', ""); + + String voIdStr = parts[1]; + voIdStr = voIdStr.replace(PERUN_VO_ID + '=', ""); + + if (voId == null || voId.equals(Long.parseLong(voIdStr))) { + ids.add(Long.parseLong(groupId)); + } + }); + } + + return ids; + }; + + Set<Long> res = connectorLdap.lookup(dnPrefix, attributes, mapper); + if (res == null) { + res = new HashSet<>(); + } + + return res; + } + + private String getDnPrefixForUserId(Long userId) { + return PERUN_USER_ID + '=' + userId + ',' + OU_PEOPLE; + } + + private boolean checkHasAttributes(Entry e, String[] attributes) { + if (e == null) { + return false; + } else if (attributes == null) { + return true; + } + + for (String attr: attributes) { + if (e.get(attr) == null) { + return false; + } + } + + return true; + } + + private Long getPerunUserId(FilterBuilder filter) { + SearchScope scope = SearchScope.ONELEVEL; + String[] attributes = new String[]{PERUN_USER_ID}; + EntryMapper<Long> mapper = e -> Long.parseLong(e.get(PERUN_USER_ID).getString()); + + return connectorLdap.searchFirst(OU_PEOPLE, filter, scope, attributes, mapper); + } +} diff --git a/src/main/java/cz/muni/ics/ga4gh/adapters/impl/PerunAdapterRpc.java b/src/main/java/cz/muni/ics/ga4gh/adapters/impl/PerunAdapterRpc.java new file mode 100644 index 0000000000000000000000000000000000000000..7747579a7929ac2552abb6740084c703959dad82 --- /dev/null +++ b/src/main/java/cz/muni/ics/ga4gh/adapters/impl/PerunAdapterRpc.java @@ -0,0 +1,283 @@ +package cz.muni.ics.ga4gh.adapters.impl; + +import com.fasterxml.jackson.databind.JsonNode; +import cz.muni.ics.ga4gh.adapters.PerunAdapterMethods; +import cz.muni.ics.ga4gh.adapters.PerunAdapterMethodsRpc; +import cz.muni.ics.ga4gh.config.AttributesConfig; +import cz.muni.ics.ga4gh.config.RpcConfig; +import cz.muni.ics.ga4gh.connectors.PerunConnectorRpc; +import cz.muni.ics.ga4gh.mappers.RpcMapper; +import cz.muni.ics.ga4gh.model.Affiliation; +import cz.muni.ics.ga4gh.model.AttributeMapping; +import cz.muni.ics.ga4gh.model.Group; +import cz.muni.ics.ga4gh.model.Member; +import cz.muni.ics.ga4gh.model.PerunAttributeValue; +import cz.muni.ics.ga4gh.model.UserExtSource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +import static cz.muni.ics.ga4gh.enums.MemberStatus.VALID; + +@Component +@Slf4j +public class PerunAdapterRpc implements PerunAdapterMethods, PerunAdapterMethodsRpc { + + public static final String EXT_LOGIN = "extLogin"; + public static final String EXT_SOURCE_NAME = "extSourceName"; + public static final String USER_EXT_SOURCE = "userExtSource"; + public static final String ATTR_NAMES = "attrNames"; + public static final String VALUE_CREATED_AT = "valueCreatedAt"; + + public static final String ID = "id"; + public static final String VO = "vo"; + public static final String USER = "user"; + public static final String MEMBER = "member"; + public static final String GROUP = "group"; + + public static final String ATTRIBUTE_NAME = "attributeName"; + public static final String ATTRIBUTE_VALUE = "attributeValue"; + + public static final String ATTRIBUTES_MANAGER = "attributesManager"; + public static final String GROUPS_MANAGER = "groupsManager"; + public static final String MEMBERS_MANAGER = "membersManager"; + public static final String USERS_MANAGER = "usersManager"; + + public static final String GET_USER_BY_EXT_SOURCE_NAME_AND_EXT_LOGIN = "getUserByExtSourceNameAndExtLogin"; + public static final String GET_USER_EXT_SOURCES = "getUserExtSources"; + public static final String GET_GROUP_BY_ID = "getGroupById"; + public static final String GET_MEMBER_BY_USER = "getMemberByUser"; + public static final String GET_MEMBERS_BY_USER = "getMembersByUser"; + public static final String GET_MEMBER_GROUPS = "getMemberGroups"; + public static final String IS_GROUP_MEMBER = "isGroupMember"; + public static final String GET_ATTRIBUTE = "getAttribute"; + public static final String GET_ATTRIBUTES = "getAttributes"; + public static final String GET_USERS_BY_ATTRIBUTE_VALUE = "getUsersByAttributeValue"; + + private final PerunConnectorRpc connectorRpc; + private final Map<String, AttributeMapping> attributeMappings; + + @Autowired + PerunAdapterRpc(PerunConnectorRpc connectorRpc, AttributesConfig attributesConfig) { + this.connectorRpc = connectorRpc; + this.attributeMappings = attributesConfig.getAttributeMappings(); + } + + @Override + public Long getPreauthenticatedUserId(String extLogin, String extSourceName) { + if (!connectorRpc.isEnabled()) { + return null; + } + + Map<String, Object> map = new LinkedHashMap<>(); + map.put(EXT_LOGIN, extLogin); + map.put(EXT_SOURCE_NAME, extSourceName); + + JsonNode response = connectorRpc.post(USERS_MANAGER, GET_USER_BY_EXT_SOURCE_NAME_AND_EXT_LOGIN, map); + + return response.get(ID) == null ? null : response.get(ID).asLong(); + } + + @Override + public boolean isUserInGroup(Long userId, Long groupId) { + if (!connectorRpc.isEnabled()) { + return false; + } + + Map<String, Object> groupParams = new LinkedHashMap<>(); + groupParams.put(ID, groupId); + JsonNode groupResponse = connectorRpc.post(GROUPS_MANAGER, GET_GROUP_BY_ID, groupParams); + Group group = RpcMapper.mapGroup(groupResponse); + + Map<String, Object> memberParams = new LinkedHashMap<>(); + memberParams.put(VO, group.getVoId()); + memberParams.put(USER, userId); + JsonNode memberResponse = connectorRpc.post(MEMBERS_MANAGER, GET_MEMBER_BY_USER, memberParams); + Member member = RpcMapper.mapMember(memberResponse); + + Map<String, Object> isGroupMemberParams = new LinkedHashMap<>(); + isGroupMemberParams.put(GROUP, groupId); + isGroupMemberParams.put(MEMBER, member.getId()); + JsonNode res = connectorRpc.post(GROUPS_MANAGER, IS_GROUP_MEMBER, isGroupMemberParams); + + return res.asBoolean(false); + } + + @Override + public List<Affiliation> getGroupAffiliations(Long userId, String groupAffiliationsAttr) { + if (!connectorRpc.isEnabled()) { + return new ArrayList<>(); + } + + List<Affiliation> affiliations = new ArrayList<>(); + List<Member> userMembers = getMembersByUser(userId); + + for (Member member : userMembers) { + if (VALID.equals(member.getStatus())) { + List<Group> memberGroups = getMemberGroups(member.getId()); + for (Group group : memberGroups) { + PerunAttributeValue attrValue = this.getGroupAttributeValue(group, groupAffiliationsAttr); + if (attrValue != null && attrValue.valueAsList() != null) { + long linuxTime = System.currentTimeMillis() / 1000L; + + for (String value : attrValue.valueAsList()) { + Affiliation affiliation = new Affiliation(null, value, linuxTime); + log.debug("found {} on group {}", value, group.getName()); + affiliations.add(affiliation); + } + } + } + } + } + + return affiliations; + } + + @Override + public String getUserAttributeCreatedAt(Long userId, String attrName) { + if (!connectorRpc.isEnabled() || attributeMappings.get(attrName) == null) { + return null; + } + + Map<String, Object> map = new LinkedHashMap<>(); + map.put(USER, userId); + map.put(ATTRIBUTE_NAME, attributeMappings.get(attrName).getRpcName()); + + JsonNode res = connectorRpc.post(ATTRIBUTES_MANAGER, GET_ATTRIBUTE, map); + + if (res == null || !res.hasNonNull(VALUE_CREATED_AT)) { + return null; + } + + return res.get(VALUE_CREATED_AT).asText(); + } + + @Override + public List<Affiliation> getUserExtSourcesAffiliations(Long userId, String affiliationsAttr, String orgUrlAttr) { + if (!connectorRpc.isEnabled()) { + return new ArrayList<>(); + } + + List<UserExtSource> userExtSources = getUserExtSources(userId); + List<Affiliation> affiliations = new ArrayList<>(); + + Map<String, AttributeMapping> attrMappings = new HashMap<>(); + attrMappings.put(affiliationsAttr, attributeMappings.get(affiliationsAttr)); + attrMappings.put(orgUrlAttr, attributeMappings.get(orgUrlAttr)); + + for (UserExtSource ues : userExtSources) { + if ("cz.metacentrum.perun.core.impl.ExtSourceIdp".equals(ues.getExtSource().getType())) { + Map<String, PerunAttributeValue> uesAttrValues = getUserExtSourceAttributeValues(ues.getId(), attrMappings); + + long asserted = ues.getLastAccess().getTime() / 1000L; + + String orgUrl = uesAttrValues.get(affiliationsAttr).valueAsString(); + String affs = uesAttrValues.get(orgUrlAttr).valueAsString(); + + if (affs != null) { + for (String aff : affs.split(";")) { + String source = ( (orgUrl != null) ? orgUrl : ues.getExtSource().getName() ); + Affiliation affiliation = new Affiliation(source, aff, asserted); + log.debug("found {} from IdP {} with orgURL {} asserted at {}", aff, ues.getExtSource().getName(), + orgUrl, asserted); + affiliations.add(affiliation); + } + } + } + } + + return affiliations; + } + + @Override + public Set<Long> getUserIdsByAttributeValue(AttributeMapping attrName, String attrValue) { + if (!connectorRpc.isEnabled()) { + return new HashSet<>(); + } + + Set<Long> result = new HashSet<>(); + + if (!StringUtils.hasText(attrName.getRpcName())) { + return result; + } + + Map<String, Object> map = new LinkedHashMap<>(); + map.put(ATTRIBUTE_NAME, attrName.getRpcName()); + map.put(ATTRIBUTE_VALUE, attrValue); + + JsonNode res = connectorRpc.post(USERS_MANAGER, GET_USERS_BY_ATTRIBUTE_VALUE, map); + + if (res != null) { + for (int i = 0; i < res.size(); i++) { + result.add(res.get(i).get(ID).asLong()); + } + } + + return result; + } + + private List<Member> getMembersByUser(Long userId) { + if (!this.connectorRpc.isEnabled()) { + return new ArrayList<>(); + } + + Map<String, Object> params = new LinkedHashMap<>(); + params.put(USER, userId); + JsonNode jsonNode = connectorRpc.post(MEMBERS_MANAGER, GET_MEMBERS_BY_USER, params); + + return RpcMapper.mapMembers(jsonNode); + } + + private List<Group> getMemberGroups(Long memberId) { + if (!this.connectorRpc.isEnabled()) { + return new ArrayList<>(); + } + + Map<String, Object> map = new LinkedHashMap<>(); + map.put(MEMBER, memberId); + + JsonNode response = connectorRpc.post(GROUPS_MANAGER, GET_MEMBER_GROUPS, map); + return RpcMapper.mapGroups(response); + } + + private PerunAttributeValue getGroupAttributeValue(Group group, String attrToFetch) { + if (attributeMappings.get(attrToFetch) == null) { + return null; + } + + Map<String, Object> map = new LinkedHashMap<>(); + map.put(GROUP, group.getId()); + map.put(ATTRIBUTE_NAME, attributeMappings.get(attrToFetch).getRpcName()); + JsonNode res = connectorRpc.post(ATTRIBUTES_MANAGER, GET_ATTRIBUTE, map); + + return RpcMapper.mapAttributeValue(res); + } + + private List<UserExtSource> getUserExtSources(Long userId) { + Map<String, Object> map = new LinkedHashMap<>(); + map.put(USER, userId); + + JsonNode response = connectorRpc.post(USERS_MANAGER, GET_USER_EXT_SOURCES, map); + return RpcMapper.mapUserExtSources(response); + } + + private Map<String, PerunAttributeValue> getUserExtSourceAttributeValues(Long uesId, Map<String, AttributeMapping> attrMappings) { + Map<String, Object> map = new LinkedHashMap<>(); + map.put(USER_EXT_SOURCE, uesId); + map.put(ATTR_NAMES, attrMappings.values().stream().map(AttributeMapping::getRpcName).collect(Collectors.toList())); + + JsonNode response = connectorRpc.post(ATTRIBUTES_MANAGER, GET_ATTRIBUTES, map); + + return RpcMapper.mapAttributes(response, attrMappings); + } +} diff --git a/src/main/java/cz/muni/ics/ga4gh/aop/LogTimes.java b/src/main/java/cz/muni/ics/ga4gh/aop/LogTimes.java new file mode 100644 index 0000000000000000000000000000000000000000..0e2e60b609f24444642190f2f357d1a0e0931b21 --- /dev/null +++ b/src/main/java/cz/muni/ics/ga4gh/aop/LogTimes.java @@ -0,0 +1,11 @@ +package cz.muni.ics.ga4gh.aop; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface LogTimes { +} diff --git a/src/main/java/cz/muni/ics/ga4gh/config/AdapterConfig.java b/src/main/java/cz/muni/ics/ga4gh/config/AdapterConfig.java new file mode 100644 index 0000000000000000000000000000000000000000..915582c127a8b73e47e6fa822ac70ef2629b0504 --- /dev/null +++ b/src/main/java/cz/muni/ics/ga4gh/config/AdapterConfig.java @@ -0,0 +1,19 @@ +package cz.muni.ics.ga4gh.config; + +import lombok.Getter; +import lombok.Setter; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +@Configuration +@EnableConfigurationProperties +@ConfigurationProperties(prefix = "adapter") +@Getter +@Setter +public class AdapterConfig { + + private String adapterPrimary; + + private Boolean callFallback; +} diff --git a/src/main/java/cz/muni/ics/ga4gh/config/AttributesConfig.java b/src/main/java/cz/muni/ics/ga4gh/config/AttributesConfig.java new file mode 100644 index 0000000000000000000000000000000000000000..469443e487bb153d774ddd238f42e3f02104f843 --- /dev/null +++ b/src/main/java/cz/muni/ics/ga4gh/config/AttributesConfig.java @@ -0,0 +1,20 @@ +package cz.muni.ics.ga4gh.config; + +import cz.muni.ics.ga4gh.model.AttributeMapping; +import lombok.Getter; +import lombok.Setter; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +import java.util.Map; + +@Configuration +@EnableConfigurationProperties +@ConfigurationProperties(prefix = "attributes") +@Getter +@Setter +public class AttributesConfig { + + private Map<String, AttributeMapping> attributeMappings; +} diff --git a/src/main/java/cz/muni/ics/ga4gh/config/BasicAuthConfig.java b/src/main/java/cz/muni/ics/ga4gh/config/BasicAuthConfig.java new file mode 100644 index 0000000000000000000000000000000000000000..5436cdb344fc796dc3fee57142d2aedc477cbbb9 --- /dev/null +++ b/src/main/java/cz/muni/ics/ga4gh/config/BasicAuthConfig.java @@ -0,0 +1,19 @@ +package cz.muni.ics.ga4gh.config; + +import lombok.Getter; +import lombok.Setter; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +@Configuration +@EnableConfigurationProperties +@ConfigurationProperties(prefix = "basic-auth") +@Getter +@Setter +public class BasicAuthConfig { + + private String username; + + private String password; +} diff --git a/src/main/java/cz/muni/ics/ga4gh/config/BrokerConfig.java b/src/main/java/cz/muni/ics/ga4gh/config/BrokerConfig.java new file mode 100644 index 0000000000000000000000000000000000000000..ac59aaffe1e37f054dce0e4a322df40ae6519958 --- /dev/null +++ b/src/main/java/cz/muni/ics/ga4gh/config/BrokerConfig.java @@ -0,0 +1,37 @@ +package cz.muni.ics.ga4gh.config; + +import lombok.Getter; +import lombok.Setter; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +import java.util.List; + +@Configuration +@EnableConfigurationProperties +@ConfigurationProperties(prefix = "broker") +@Getter +@Setter +public class BrokerConfig { + + private String bonaFideStatusAttr; + + private String bonaFideStatusRemsAttr; + + private String groupAffiliationsAttr; + + private Long termsAndPoliciesGroupId; + + private String affiliationsAttr; + + private String orgUrlAttr; + + private List<String> attributesToSearch; + + private String issuer; + + private String jku; + + private String pathToJwkFile; +} diff --git a/src/main/java/cz/muni/ics/ga4gh/config/Ga4ghConfig.java b/src/main/java/cz/muni/ics/ga4gh/config/Ga4ghConfig.java new file mode 100644 index 0000000000000000000000000000000000000000..188a1de2f5939142e892a8ca6e9c6b82992ad807 --- /dev/null +++ b/src/main/java/cz/muni/ics/ga4gh/config/Ga4ghConfig.java @@ -0,0 +1,20 @@ +package cz.muni.ics.ga4gh.config; + +import cz.muni.ics.ga4gh.model.Repo; +import lombok.Getter; +import lombok.Setter; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +import java.util.List; + +@Configuration +@EnableConfigurationProperties +@ConfigurationProperties(prefix = "ga4gh") +@Getter +@Setter +public class Ga4ghConfig { + + private List<Repo> repos; +} diff --git a/src/main/java/cz/muni/ics/ga4gh/config/LdapConfig.java b/src/main/java/cz/muni/ics/ga4gh/config/LdapConfig.java new file mode 100644 index 0000000000000000000000000000000000000000..8a451011affe76b0e95792e80963bb73245a737b --- /dev/null +++ b/src/main/java/cz/muni/ics/ga4gh/config/LdapConfig.java @@ -0,0 +1,33 @@ +package cz.muni.ics.ga4gh.config; + +import lombok.Getter; +import lombok.Setter; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +@Configuration +@EnableConfigurationProperties +@ConfigurationProperties(prefix = "ldap") +@Getter +@Setter +public class LdapConfig { + + private String host; + + private String user; + + private String password; + + private String baseDn; + + private Boolean useTls; + + private Boolean useSsl; + + private Boolean allowUntrustedSsl; + + private Long timeoutSecs; + + private int port; +} diff --git a/src/main/java/cz/muni/ics/ga4gh/config/RpcConfig.java b/src/main/java/cz/muni/ics/ga4gh/config/RpcConfig.java new file mode 100644 index 0000000000000000000000000000000000000000..74f41d82768c96b8488c389fe5c33e6fcf5cb66c --- /dev/null +++ b/src/main/java/cz/muni/ics/ga4gh/config/RpcConfig.java @@ -0,0 +1,25 @@ +package cz.muni.ics.ga4gh.config; + +import lombok.Getter; +import lombok.Setter; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +@Configuration +@EnableConfigurationProperties +@ConfigurationProperties(prefix = "rpc") +@Getter +@Setter +public class RpcConfig { + + private Boolean enabled; + + private String url; + + private String username; + + private String password; + + private String serializer; +} diff --git a/src/main/java/cz/muni/ics/ga4gh/connectors/PerunConnectorLdap.java b/src/main/java/cz/muni/ics/ga4gh/connectors/PerunConnectorLdap.java new file mode 100644 index 0000000000000000000000000000000000000000..5f4c66f2d9261532f5d27f34b1d5ef1770415569 --- /dev/null +++ b/src/main/java/cz/muni/ics/ga4gh/connectors/PerunConnectorLdap.java @@ -0,0 +1,154 @@ +package cz.muni.ics.ga4gh.connectors; + +import cz.muni.ics.ga4gh.aop.LogTimes; +import cz.muni.ics.ga4gh.config.LdapConfig; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.pool2.impl.GenericObjectPoolConfig; +import org.apache.directory.api.ldap.model.message.SearchScope; +import org.apache.directory.api.ldap.model.name.Dn; +import org.apache.directory.ldap.client.api.DefaultLdapConnectionFactory; +import org.apache.directory.ldap.client.api.DefaultPoolableLdapConnectionFactory; +import org.apache.directory.ldap.client.api.LdapConnectionConfig; +import org.apache.directory.ldap.client.api.LdapConnectionPool; +import org.apache.directory.ldap.client.api.NoVerificationTrustManager; +import org.apache.directory.ldap.client.api.search.FilterBuilder; +import org.apache.directory.ldap.client.template.EntryMapper; +import org.apache.directory.ldap.client.template.LdapConnectionTemplate; +import org.springframework.beans.factory.DisposableBean; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Repository; +import org.springframework.util.StringUtils; + +import java.util.List; +import java.util.Objects; + +@Repository +@Slf4j +@Getter +public class PerunConnectorLdap implements DisposableBean { + + private final String baseDN; + private final LdapConnectionPool pool; + private final LdapConnectionTemplate ldap; + + @Autowired + public PerunConnectorLdap(LdapConfig config) { + if (config.getHost() == null || config.getHost().trim().isEmpty()) { + throw new IllegalArgumentException("Host cannot be null or empty"); + } else if (config.getBaseDn() == null || config.getBaseDn().trim().isEmpty()) { + throw new IllegalArgumentException("baseDN cannot be null or empty"); + } + + boolean useTLS = Objects.requireNonNullElse(config.getUseTls(), false); + boolean useSSL = Objects.requireNonNullElse(config.getUseSsl(), false); + boolean allowUntrustedSsl = Objects.requireNonNullElse(config.getAllowUntrustedSsl(), false); + long timeoutSecs = Objects.requireNonNullElse(config.getTimeoutSecs(), 5L); + + + this.baseDN = config.getBaseDn(); + + LdapConnectionConfig ldapConnectionConfig = getLdapConnectionConfig(config.getHost(), config.getPort(), useTLS, useSSL, allowUntrustedSsl); + if (config.getUser() != null && !config.getUser().isEmpty()) { + log.debug("setting ldap user to {}", config.getUser()); + ldapConnectionConfig.setName(config.getUser()); + } + if (config.getPassword() != null && !config.getPassword().isEmpty()) { + log.debug("setting ldap password"); + ldapConnectionConfig.setCredentials(config.getPassword()); + } + DefaultLdapConnectionFactory factory = new DefaultLdapConnectionFactory(ldapConnectionConfig); + factory.setTimeOut(timeoutSecs * 1000L); + + GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig(); + poolConfig.setTestOnBorrow(true); + + pool = new LdapConnectionPool(new DefaultPoolableLdapConnectionFactory(factory), poolConfig); + ldap = new LdapConnectionTemplate(pool); + log.debug("initialized LDAP connector"); + } + + public String getBaseDN() { + return baseDN; + } + + private LdapConnectionConfig getLdapConnectionConfig(String host, int port, boolean useTLS, boolean useSSL, + boolean allowUntrustedSsl) { + LdapConnectionConfig config = new LdapConnectionConfig(); + config.setLdapHost(host); + config.setLdapPort(port); + config.setUseSsl(useSSL); + config.setUseTls(useTLS); + if (allowUntrustedSsl) { + config.setTrustManagers(new NoVerificationTrustManager()); + } + + return config; + } + + @Override + public void destroy() { + if (!pool.isClosed()) { + pool.close(); + } + } + + /** + * Search for the first entry that satisfies criteria. + * @param dnPrefix Prefix to be added to the base DN. (i.e. ou=People) !DO NOT END WITH A COMMA! + * @param filter Filter for entries + * @param scope Search scope + * @param attributes Attributes to be fetch for entry + * @param entryMapper Mapper of entries to the target class T + * @param <T> Class that the result should be mapped to. + * @return Found entry mapped to target class + */ + @LogTimes + public <T> T searchFirst(String dnPrefix, FilterBuilder filter, SearchScope scope, String[] attributes, + EntryMapper<T> entryMapper) + { + Dn fullDn = getFullDn(dnPrefix); + return ldap.searchFirst(fullDn, filter, scope, attributes, entryMapper); + } + + /** + * Perform lookup for the entry that satisfies criteria. + * @param dnPrefix Prefix to be added to the base DN. (i.e. ou=People) !DO NOT END WITH A COMMA! + * @param attributes Attributes to be fetch for entry + * @param entryMapper Mapper of entries to the target class T + * @param <T> Class that the result should be mapped to. + * @return Found entry mapped to target class + */ + @LogTimes + public <T> T lookup(String dnPrefix, String[] attributes, EntryMapper<T> entryMapper) { + Dn fullDn = getFullDn(dnPrefix); + return ldap.lookup(fullDn, attributes, entryMapper); + } + + /** + * Search for the entries satisfy criteria. + * @param dnPrefix Prefix to be added to the base DN. (i.e. ou=People) !DO NOT END WITH A COMMA! + * @param filter Filter for entries + * @param scope Search scope + * @param attributes Attributes to be fetch for entry + * @param entryMapper Mapper of entries to the target class T + * @param <T> Class that the result should be mapped to. + * @return List of found entries mapped to target class + */ + @LogTimes + public <T> List<T> search(String dnPrefix, FilterBuilder filter, SearchScope scope, String[] attributes, + EntryMapper<T> entryMapper) + { + Dn fullDn = getFullDn(dnPrefix); + return ldap.search(fullDn, filter, scope, attributes, entryMapper); + } + + private Dn getFullDn(String prefix) { + String dn = baseDN; + if (StringUtils.hasText(prefix)) { + dn = prefix + "," + baseDN; + } + + return ldap.newDn(dn); + } +} diff --git a/src/main/java/cz/muni/ics/ga4gh/connectors/PerunConnectorRpc.java b/src/main/java/cz/muni/ics/ga4gh/connectors/PerunConnectorRpc.java new file mode 100644 index 0000000000000000000000000000000000000000..ecacbf74255d92f37a3de5655dfdc07172458138 --- /dev/null +++ b/src/main/java/cz/muni/ics/ga4gh/connectors/PerunConnectorRpc.java @@ -0,0 +1,188 @@ +package cz.muni.ics.ga4gh.connectors; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import cz.muni.ics.ga4gh.aop.LogTimes; +import cz.muni.ics.ga4gh.config.RpcConfig; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import org.apache.http.HeaderElement; +import org.apache.http.HeaderElementIterator; +import org.apache.http.client.config.RequestConfig; +import org.apache.http.conn.ConnectionKeepAliveStrategy; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; +import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; +import org.apache.http.message.BasicHeaderElementIterator; +import org.apache.http.protocol.HTTP; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; +import org.springframework.http.client.ClientHttpRequestInterceptor; +import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; +import org.springframework.http.client.InterceptingClientHttpRequestFactory; +import org.springframework.http.client.support.BasicAuthorizationInterceptor; +import org.springframework.stereotype.Repository; +import org.springframework.util.StringUtils; +import org.springframework.web.client.HttpClientErrorException; +import org.springframework.web.client.RestTemplate; + +import javax.annotation.PostConstruct; +import java.io.IOException; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +@Repository +@Slf4j +@Getter +public class PerunConnectorRpc { + + public static final String ATTRIBUTES_MANAGER = "attributesManager"; + public static final String FACILITIES_MANAGER = "facilitiesManager"; + public static final String GROUPS_MANAGER = "groupsManager"; + public static final String MEMBERS_MANAGER = "membersManager"; + public static final String REGISTRAR_MANAGER = "registrarManager"; + public static final String SEARCHER = "searcher"; + public static final String USERS_MANAGER = "usersManager"; + public static final String VOS_MANAGER = "vosManager"; + public static final String RESOURCES_MANAGER = "resourcesManager"; + + private String perunUrl; + private String perunUser; + private String perunPassword; + private boolean isEnabled; + private String serializer; + private RestTemplate restTemplate; + + @Autowired + public PerunConnectorRpc(RpcConfig config) { + this.setPerunUrl(config.getUrl()); + this.setPerunUser(config.getUsername()); + this.setPerunPassword(config.getPassword()); + this.setEnabled(config.getEnabled()); + this.setSerializer(config.getSerializer()); + } + + public void setPerunUrl(String perunUrl) { + if (!StringUtils.hasText(perunUrl)) { + throw new IllegalArgumentException("Perun URL cannot be null or empty"); + } else if (perunUrl.endsWith("/")) { + perunUrl = perunUrl.substring(0, perunUrl.length() - 1); + } + + this.perunUrl = perunUrl; + } + + public void setPerunUser(String perunUser) { + if (!StringUtils.hasText(perunUser)) { + throw new IllegalArgumentException("Perun USER cannot be null or empty"); + } + + this.perunUser = perunUser; + } + + public void setPerunPassword(String perunPassword) { + if (!StringUtils.hasText(perunPassword)) { + throw new IllegalArgumentException("Perun PASSWORD cannot be null or empty"); + } + + this.perunPassword = perunPassword; + } + + public void setEnabled(Boolean enabled) { + this.isEnabled = Objects.requireNonNullElse(enabled, false); + } + + public void setSerializer(String serializer) { + if (!StringUtils.hasText(serializer)) { + this.serializer = "json"; + } + + this.serializer = serializer; + } + + @PostConstruct + public void postInit() { + restTemplate = new RestTemplate(); + //HTTP connection pooling, see https://howtodoinjava.com/spring-restful/resttemplate-httpclient-java-config/ + RequestConfig requestConfig = RequestConfig.custom() + .setConnectionRequestTimeout(30000) // The timeout when requesting a connection from the connection manager + .setConnectTimeout(30000) // Determines the timeout in milliseconds until a connection is established + .setSocketTimeout(60000) // The timeout for waiting for data + .build(); + + PoolingHttpClientConnectionManager poolingConnectionManager = new PoolingHttpClientConnectionManager(); + poolingConnectionManager.setMaxTotal(20); // maximum connections total + poolingConnectionManager.setDefaultMaxPerRoute(18); + + ConnectionKeepAliveStrategy connectionKeepAliveStrategy = (response, context) -> { + HeaderElementIterator it = new BasicHeaderElementIterator(response.headerIterator(HTTP.CONN_KEEP_ALIVE)); + + while (it.hasNext()) { + HeaderElement he = it.nextElement(); + String param = he.getName(); + String value = he.getValue(); + + if (value != null && param.equalsIgnoreCase("timeout")) { + return Long.parseLong(value) * 1000; + } + } + + return 20000L; + }; + + CloseableHttpClient httpClient = HttpClients.custom() + .setDefaultRequestConfig(requestConfig) + .setConnectionManager(poolingConnectionManager) + .setKeepAliveStrategy(connectionKeepAliveStrategy) + .build(); + + HttpComponentsClientHttpRequestFactory poolingRequestFactory = new HttpComponentsClientHttpRequestFactory(); + poolingRequestFactory.setHttpClient(httpClient); + //basic authentication + List<ClientHttpRequestInterceptor> interceptors = + Collections.singletonList(new BasicAuthorizationInterceptor(perunUser, perunPassword)); + InterceptingClientHttpRequestFactory authenticatingRequestFactory = new InterceptingClientHttpRequestFactory(poolingRequestFactory, interceptors); + restTemplate.setRequestFactory(authenticatingRequestFactory); + } + + /** + * Make post call to Perun RPC + * @param manager String value representing manager to be called. Use constants from this class. + * @param method Method to be called (i.e. getUserById) + * @param map Map of parameters to be passed as request body + * @return Response from Perun + */ + @LogTimes + public JsonNode post(String manager, String method, Map<String, Object> map) { + if (!this.isEnabled) { + return JsonNodeFactory.instance.nullNode(); + } + + String actionUrl = perunUrl + '/' + serializer + '/' + manager + '/' + method; + //make the call + try { + log.debug("calling {} with {}", actionUrl, map); + + return restTemplate.postForObject(actionUrl, map, JsonNode.class); + } catch (HttpClientErrorException ex) { + MediaType contentType = ex.getResponseHeaders().getContentType(); + String body = ex.getResponseBodyAsString(); + log.error("HTTP ERROR " + ex.getRawStatusCode() + " URL " + actionUrl + " Content-Type: " + contentType); + + if ("json".equals(contentType.getSubtype())) { + try { + log.error(new ObjectMapper().readValue(body, JsonNode.class).path("message").asText()); + } catch (IOException e) { + log.error("cannot parse error message from JSON", e); + } + } else { + log.error(ex.getMessage()); + } + + throw new RuntimeException("cannot connect to Perun RPC", ex); + } + } +} diff --git a/src/main/java/cz/muni/ics/ga4gh/controllers/Ga4ghBrokerController.java b/src/main/java/cz/muni/ics/ga4gh/controllers/Ga4ghBrokerController.java new file mode 100644 index 0000000000000000000000000000000000000000..c37ecf1dc12078dc375508c503a908074e486d07 --- /dev/null +++ b/src/main/java/cz/muni/ics/ga4gh/controllers/Ga4ghBrokerController.java @@ -0,0 +1,35 @@ +package cz.muni.ics.ga4gh.controllers; + +import com.fasterxml.jackson.databind.node.ArrayNode; +import cz.muni.ics.ga4gh.facade.Ga4ghBrokerFacade; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.servlet.http.HttpServletResponse; + +@RestController +@RequestMapping("/ga4gh") +public class Ga4ghBrokerController { + + private final Ga4ghBrokerFacade ga4GhBrokerFacade; + + @Autowired + public Ga4ghBrokerController(Ga4ghBrokerFacade ga4GhBrokerFacade) { + this.ga4GhBrokerFacade = ga4GhBrokerFacade; + } + + @GetMapping(value = "/{eppn}", produces = MediaType.APPLICATION_JSON_VALUE) + public ArrayNode getGa4ghPassport(@PathVariable String eppn, HttpServletResponse response) { + ArrayNode result = ga4GhBrokerFacade.getGa4ghPassport(eppn); + + if (result == null) { + response.setStatus(HttpServletResponse.SC_BAD_REQUEST); + } + + return result; + } +} diff --git a/src/main/java/cz/muni/ics/ga4gh/controllers/JwkSetPublishingEndpoint.java b/src/main/java/cz/muni/ics/ga4gh/controllers/JwkSetPublishingEndpoint.java new file mode 100644 index 0000000000000000000000000000000000000000..90bd9c7468cc7ecfd3e46417550f493cedd41635 --- /dev/null +++ b/src/main/java/cz/muni/ics/ga4gh/controllers/JwkSetPublishingEndpoint.java @@ -0,0 +1,40 @@ +package cz.muni.ics.ga4gh.controllers; + +import com.nimbusds.jose.jwk.JWK; +import com.nimbusds.jose.jwk.JWKSet; +import cz.muni.ics.ga4gh.service.JWTSigningAndValidationService; +import lombok.Getter; +import lombok.Setter; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.CrossOrigin; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.ArrayList; +import java.util.Map; + +@CrossOrigin(originPatterns = "*") +@RestController +@RequestMapping("/public") +@Getter +@Setter +public class JwkSetPublishingEndpoint { + + public static final String URL = "jwk"; + + private JWTSigningAndValidationService jwtService; + + @Autowired + public JwkSetPublishingEndpoint(JWTSigningAndValidationService jwtService) { + this.jwtService = jwtService; + } + + @RequestMapping(value = "/" + URL, produces = MediaType.APPLICATION_JSON_VALUE) + public String getJwk() { + // map from key id to key + Map<String, JWK> keys = jwtService.getAllPublicKeys(); + JWKSet jwkSet = new JWKSet(new ArrayList<>(keys.values())); + return jwkSet.toString(); + } +} diff --git a/src/main/java/cz/muni/ics/ga4gh/enums/MemberStatus.java b/src/main/java/cz/muni/ics/ga4gh/enums/MemberStatus.java new file mode 100644 index 0000000000000000000000000000000000000000..979478065d0a338839882847c313f4ee88a12ad1 --- /dev/null +++ b/src/main/java/cz/muni/ics/ga4gh/enums/MemberStatus.java @@ -0,0 +1,31 @@ +package cz.muni.ics.ga4gh.enums; + +public enum MemberStatus { + + VALID, + EXPIRED, + INVALID, + DISABLED, + SUSPENDED; + + public static MemberStatus fromString(String status) { + if (status == null) { + return null; + } + + switch (status.toUpperCase().trim()) { + case "VALID": + return VALID; + case "INVALID": + return INVALID; + case "EXPIRED": + return EXPIRED; + case "DISABLED": + return DISABLED; + case "SUSPENDED": + return SUSPENDED; + default: + return null; + } + } +} diff --git a/src/main/java/cz/muni/ics/ga4gh/exceptions/InconvertibleValueException.java b/src/main/java/cz/muni/ics/ga4gh/exceptions/InconvertibleValueException.java new file mode 100644 index 0000000000000000000000000000000000000000..581670295a7a6c24afefe64c3e1b83cd64b3aca4 --- /dev/null +++ b/src/main/java/cz/muni/ics/ga4gh/exceptions/InconvertibleValueException.java @@ -0,0 +1,24 @@ +package cz.muni.ics.ga4gh.exceptions; + +public class InconvertibleValueException extends RuntimeException { + + public InconvertibleValueException() { + super(); + } + + public InconvertibleValueException(String s) { + super(s); + } + + public InconvertibleValueException(String s, Throwable throwable) { + super(s, throwable); + } + + public InconvertibleValueException(Throwable throwable) { + super(throwable); + } + + protected InconvertibleValueException(String s, Throwable throwable, boolean b, boolean b1) { + super(s, throwable, b, b1); + } +} diff --git a/src/main/java/cz/muni/ics/ga4gh/exceptions/MissingFieldException.java b/src/main/java/cz/muni/ics/ga4gh/exceptions/MissingFieldException.java new file mode 100644 index 0000000000000000000000000000000000000000..a889a9b14f2bbe58f46828305d9d11e6b54be9ba --- /dev/null +++ b/src/main/java/cz/muni/ics/ga4gh/exceptions/MissingFieldException.java @@ -0,0 +1,24 @@ +package cz.muni.ics.ga4gh.exceptions; + +public class MissingFieldException extends RuntimeException { + + public MissingFieldException() { + super(); + } + + public MissingFieldException(String s) { + super(s); + } + + public MissingFieldException(String s, Throwable throwable) { + super(s, throwable); + } + + public MissingFieldException(Throwable throwable) { + super(throwable); + } + + protected MissingFieldException(String s, Throwable throwable, boolean b, boolean b1) { + super(s, throwable, b, b1); + } +} diff --git a/src/main/java/cz/muni/ics/ga4gh/exceptions/UserNotUniqueException.java b/src/main/java/cz/muni/ics/ga4gh/exceptions/UserNotUniqueException.java new file mode 100644 index 0000000000000000000000000000000000000000..dc3eee07defbbd50476032cf865ad47dc46ecc9f --- /dev/null +++ b/src/main/java/cz/muni/ics/ga4gh/exceptions/UserNotUniqueException.java @@ -0,0 +1,24 @@ +package cz.muni.ics.ga4gh.exceptions; + +public class UserNotUniqueException extends RuntimeException { + + public UserNotUniqueException() { + super(); + } + + public UserNotUniqueException(String s) { + super(s); + } + + public UserNotUniqueException(String s, Throwable throwable) { + super(s, throwable); + } + + public UserNotUniqueException(Throwable throwable) { + super(throwable); + } + + protected UserNotUniqueException(String s, Throwable throwable, boolean b, boolean b1) { + super(s, throwable, b, b1); + } +} diff --git a/src/main/java/cz/muni/ics/ga4gh/facade/Ga4ghBrokerFacade.java b/src/main/java/cz/muni/ics/ga4gh/facade/Ga4ghBrokerFacade.java new file mode 100644 index 0000000000000000000000000000000000000000..2c4ebf4bf6367f150af6ffaef6c2f4ec0b7d27cb --- /dev/null +++ b/src/main/java/cz/muni/ics/ga4gh/facade/Ga4ghBrokerFacade.java @@ -0,0 +1,8 @@ +package cz.muni.ics.ga4gh.facade; + +import com.fasterxml.jackson.databind.node.ArrayNode; + +public interface Ga4ghBrokerFacade { + + ArrayNode getGa4ghPassport(String eppn); +} diff --git a/src/main/java/cz/muni/ics/ga4gh/facade/impl/Ga4GhBrokerFacadeImpl.java b/src/main/java/cz/muni/ics/ga4gh/facade/impl/Ga4GhBrokerFacadeImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..bf3ce540f58b327151f50c5d818b3b7564e511e3 --- /dev/null +++ b/src/main/java/cz/muni/ics/ga4gh/facade/impl/Ga4GhBrokerFacadeImpl.java @@ -0,0 +1,23 @@ +package cz.muni.ics.ga4gh.facade.impl; + +import com.fasterxml.jackson.databind.node.ArrayNode; +import cz.muni.ics.ga4gh.facade.Ga4ghBrokerFacade; +import cz.muni.ics.ga4gh.service.Ga4ghBrokerService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +@Service +public class Ga4GhBrokerFacadeImpl implements Ga4ghBrokerFacade { + + Ga4ghBrokerService ga4GhBrokerService; + + @Autowired + public Ga4GhBrokerFacadeImpl(Ga4ghBrokerService ga4GhBrokerService) { + this.ga4GhBrokerService = ga4GhBrokerService; + } + + @Override + public ArrayNode getGa4ghPassport(String eppn) { + return ga4GhBrokerService.getGa4ghPassport(eppn); + } +} diff --git a/src/main/java/cz/muni/ics/ga4gh/jose/keystore/JWKSetKeyStore.java b/src/main/java/cz/muni/ics/ga4gh/jose/keystore/JWKSetKeyStore.java new file mode 100644 index 0000000000000000000000000000000000000000..0c03f88f75f3667425d7a48b1e87f819b2b9b7cb --- /dev/null +++ b/src/main/java/cz/muni/ics/ga4gh/jose/keystore/JWKSetKeyStore.java @@ -0,0 +1,79 @@ +package cz.muni.ics.ga4gh.jose.keystore; + +import com.nimbusds.jose.jwk.JWK; +import com.nimbusds.jose.jwk.JWKSet; +import lombok.Getter; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.io.Resource; +import org.springframework.stereotype.Component; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.text.ParseException; +import java.util.List; +import java.util.stream.Collectors; + +@Component +@Getter +public class JWKSetKeyStore { + + private JWKSet jwkSet; + private Resource location; + + @Autowired + public JWKSetKeyStore(Resource location) { + setLocation(location); + } + + public JWKSetKeyStore(JWKSet jwkSet) { + this.setJwkSet(jwkSet); + initializeJwkSet(); + } + + public void setJwkSet(JWKSet jwkSet) { + if (jwkSet == null) { + throw new IllegalArgumentException("Argument cannot be null"); + } + + this.jwkSet = jwkSet; + initializeJwkSet(); + } + + public void setLocation(Resource location) { + this.location = location; + initializeJwkSet(); + } + + public List<JWK> getKeys() { + if (jwkSet == null) { + initializeJwkSet(); + } + + return jwkSet.getKeys(); + } + + private void initializeJwkSet() { + if (jwkSet != null) { + return; + } else if (location == null) { + return; + } + + if (location.exists() && location.isReadable()) { + try (BufferedReader br = new BufferedReader( + new InputStreamReader(location.getInputStream(), StandardCharsets.UTF_8)) + ) { + String s = br.lines().collect(Collectors.joining()); + jwkSet = JWKSet.parse(s); + } catch (IOException e) { + throw new IllegalArgumentException("Key Set resource could not be read: " + location); + } catch (ParseException e) { + throw new IllegalArgumentException("Key Set resource could not be parsed: " + location); + } + } else { + throw new IllegalArgumentException("Key Set resource could not be read: " + location); + } + } +} diff --git a/src/main/java/cz/muni/ics/ga4gh/mappers/RpcMapper.java b/src/main/java/cz/muni/ics/ga4gh/mappers/RpcMapper.java new file mode 100644 index 0000000000000000000000000000000000000000..37254d6b0aa08534660a745b9af8771a7f6008c1 --- /dev/null +++ b/src/main/java/cz/muni/ics/ga4gh/mappers/RpcMapper.java @@ -0,0 +1,277 @@ +package cz.muni.ics.ga4gh.mappers; + +import com.fasterxml.jackson.databind.JsonNode; +import java.sql.Timestamp; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import cz.muni.ics.ga4gh.enums.MemberStatus; +import cz.muni.ics.ga4gh.exceptions.MissingFieldException; +import cz.muni.ics.ga4gh.model.AttributeMapping; +import cz.muni.ics.ga4gh.model.ExtSource; +import cz.muni.ics.ga4gh.model.Group; +import cz.muni.ics.ga4gh.model.Member; +import cz.muni.ics.ga4gh.model.PerunAttributeValue; +import cz.muni.ics.ga4gh.model.UserExtSource; + + +/** + * This class is mapping JsonNodes to object models. + * + * @author Dominik Frantisek Bucik <bucik@ics.muni.cz> + */ +public class RpcMapper { + + public static final String ID = "id"; + public static final String UUID = "uuid"; + public static final String PARENT_GROUP_ID = "parentGroupId"; + public static final String NAME = "name"; + public static final String DESCRIPTION = "description"; + public static final String VO_ID = "voId"; + public static final String USER_ID = "userId"; + public static final String STATUS = "status"; + public static final String TYPE = "type"; + public static final String LOGIN = "login"; + public static final String EXT_SOURCE = "extSource"; + public static final String LOA = "loa"; + public static final String LAST_ACCESS = "lastAccess"; + public static final String PERSISTENT = "persistent"; + public static final String FRIENDLY_NAME = "friendlyName"; + public static final String NAMESPACE = "namespace"; + public static final String VALUE = "value"; + + /** + * Maps JsonNode to Group model. + * + * @param json Group in JSON format from Perun to be mapped. + * @return Mapped Group object. + */ + public static Group mapGroup(JsonNode json) { + if (json == null || json.isNull()) { + return null; + } + + Long id = getRequiredFieldAsLong(json, ID); + Long parentGroupId = getFieldAsLong(json, PARENT_GROUP_ID); + String name = getFieldAsString(json, NAME); + String description = getFieldAsString(json, DESCRIPTION); + Long voId = getRequiredFieldAsLong(json, VO_ID); + String uuid = getRequiredFieldAsString(json, UUID); + + return new Group(id, parentGroupId, name, description, null, uuid, voId); + } + + /** + * Maps JsonNode to List of Groups. + * + * @param jsonArray JSON array of groups in JSON format from Perun to be mapped. + * @return List of groups. + */ + public static List<Group> mapGroups(JsonNode jsonArray) { + if (jsonArray.isNull()) { + return new ArrayList<>(); + } + + List<Group> result = new ArrayList<>(); + for (int i = 0; i < jsonArray.size(); i++) { + JsonNode groupNode = jsonArray.get(i); + Group mappedGroup = RpcMapper.mapGroup(groupNode); + result.add(mappedGroup); + } + + return result; + } + + /** + * Maps JsonNode to Member model. + * + * @param json Member in JSON format from Perun to be mapped. + * @return Mapped Member object. + */ + public static Member mapMember(JsonNode json) { + if (json == null || json.isNull()) { + return null; + } + + Long id = getRequiredFieldAsLong(json, ID); + Long userId = getRequiredFieldAsLong(json, USER_ID); + Long voId = getRequiredFieldAsLong(json, VO_ID); + MemberStatus status = MemberStatus.fromString(getRequiredFieldAsString(json, STATUS)); + + return new Member(id, userId, voId, status); + } + + /** + * Maps JsonNode to List of Members. + * + * @param jsonArray JSON array of members in JSON format from Perun to be mapped. + * @return List of members. + */ + public static List<Member> mapMembers(JsonNode jsonArray) { + if (jsonArray.isNull()) { + return new ArrayList<>(); + } + + List<Member> members = new ArrayList<>(); + for (int i = 0; i < jsonArray.size(); i++) { + JsonNode memberNode = jsonArray.get(i); + Member mappedMember = RpcMapper.mapMember(memberNode); + members.add(mappedMember); + } + + return members; + } + + /** + * Maps JsonNode to ExtSource model. + * + * @param json ExtSource in JSON format from Perun to be mapped. + * @return Mapped ExtSource object. + */ + public static ExtSource mapExtSource(JsonNode json) { + if (json == null || json.isNull()) { + return null; + } + + Long id = getRequiredFieldAsLong(json, ID); + String name = getRequiredFieldAsString(json, NAME); + String type = getRequiredFieldAsString(json, TYPE); + + return new ExtSource(id, name, type); + } + + /** + * Maps JsonNode to UserExtSource model. + * + * @param json UserExtSource in JSON format from Perun to be mapped. + * @return Mapped UserExtSource object. + */ + public static UserExtSource mapUserExtSource(JsonNode json) { + if (json == null || json.isNull()) { + return null; + } + + Long id = getRequiredFieldAsLong(json, ID); + String login = getRequiredFieldAsString(json, LOGIN); + ExtSource extSource = RpcMapper.mapExtSource(getRequiredFieldAsJsonNode(json, EXT_SOURCE)); + int loa = getRequiredFieldAsInt(json, LOA); + boolean persistent = getRequiredFieldAsBoolean(json, PERSISTENT); + Timestamp lastAccess = Timestamp.valueOf(getRequiredFieldAsString(json, LAST_ACCESS)); + + return new UserExtSource(id, extSource, login, loa, persistent, lastAccess); + } + + /** + * Maps JsonNode to List of UserExtSources. + * + * @param jsonArray JSON array of userExtSources in JSON format from Perun to be mapped. + * @return List of userExtSources. + */ + public static List<UserExtSource> mapUserExtSources(JsonNode jsonArray) { + if (jsonArray.isNull()) { + return new ArrayList<>(); + } + + List<UserExtSource> userExtSources = new ArrayList<>(); + + for (int i = 0; i < jsonArray.size(); i++) { + JsonNode userExtSource = jsonArray.get(i); + UserExtSource mappedUes = RpcMapper.mapUserExtSource(userExtSource); + userExtSources.add(mappedUes); + } + + return userExtSources; + } + + public static PerunAttributeValue mapAttributeValue(JsonNode json) { + if (json == null || json.isNull()) { + return null; + } + + String friendlyName = getRequiredFieldAsString(json, FRIENDLY_NAME); + String namespace = getRequiredFieldAsString(json, NAMESPACE); + String type = getRequiredFieldAsString(json, TYPE); + JsonNode value = getFieldAsJsonNode(json, VALUE); + + return new PerunAttributeValue(namespace + ':' + friendlyName, type, value); + } + + public static Map<String, PerunAttributeValue> mapAttributes(JsonNode jsonNode, Map<String, AttributeMapping> attrMappings) { + Map<String, PerunAttributeValue> attributesAsMap = new HashMap<>(); + + for (int i = 0; i < jsonNode.size(); i++) { + JsonNode attribute = jsonNode.get(i); + PerunAttributeValue mappedAttributeValue = mapAttributeValue(attribute); + + for (Map.Entry<String, AttributeMapping> entry : attrMappings.entrySet()) { + if (entry.getValue().getRpcName().equals(mappedAttributeValue.getAttrName())) { + attributesAsMap.put(entry.getKey(), mappedAttributeValue); + } + } + } + + return attributesAsMap; + } + + private static Long getRequiredFieldAsLong(JsonNode json, String name) { + if (!json.hasNonNull(name)) { + throw new MissingFieldException(); + } + return json.get(name).asLong(); + } + + private static Long getFieldAsLong(JsonNode json, String name) { + if (!json.hasNonNull(name)) { + return 0L; + } + return json.get(name).asLong(); + } + + private static int getRequiredFieldAsInt(JsonNode json, String name) { + if (!json.hasNonNull(name)) { + throw new MissingFieldException(); + } + return json.get(name).asInt(); + } + + private static int getFieldAsInt(JsonNode json, String name) { + if (!json.hasNonNull(name)) { + return 0; + } + return json.get(name).asInt(); + } + + private static boolean getRequiredFieldAsBoolean(JsonNode json, String name) { + if (!json.hasNonNull(name)) { + throw new MissingFieldException(); + } + return json.get(name).asBoolean(); + } + + private static String getRequiredFieldAsString(JsonNode json, String name) { + if (!json.hasNonNull(name)) { + throw new MissingFieldException(); + } + return json.get(name).asText(); + } + + private static String getFieldAsString(JsonNode json, String name) { + if (!json.hasNonNull(name)) { + return ""; + } + return json.get(name).asText(); + } + + private static JsonNode getRequiredFieldAsJsonNode(JsonNode json, String name) { + if (!json.hasNonNull(name)) { + throw new MissingFieldException(); + } + return json.get(name); + } + + private static JsonNode getFieldAsJsonNode(JsonNode json, String name) { + return json.get(name); + } +} diff --git a/src/main/java/cz/muni/ics/ga4gh/model/Affiliation.java b/src/main/java/cz/muni/ics/ga4gh/model/Affiliation.java new file mode 100644 index 0000000000000000000000000000000000000000..767a472b67a44e032fef3e05290d95d68aa0eb55 --- /dev/null +++ b/src/main/java/cz/muni/ics/ga4gh/model/Affiliation.java @@ -0,0 +1,19 @@ +package cz.muni.ics.ga4gh.model; + +import lombok.AllArgsConstructor; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.ToString; + +@Getter +@ToString +@EqualsAndHashCode +@AllArgsConstructor +public class Affiliation { + + private final String source; + + private final String value; + + private final long asserted; +} diff --git a/src/main/java/cz/muni/ics/ga4gh/model/AttributeMapping.java b/src/main/java/cz/muni/ics/ga4gh/model/AttributeMapping.java new file mode 100644 index 0000000000000000000000000000000000000000..c90f81f488cf07a97bdb208ccd6096c37d364e31 --- /dev/null +++ b/src/main/java/cz/muni/ics/ga4gh/model/AttributeMapping.java @@ -0,0 +1,23 @@ +package cz.muni.ics.ga4gh.model; + +import lombok.AllArgsConstructor; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; + +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@ToString +@EqualsAndHashCode +public class AttributeMapping { + + private String internalName; + + private String rpcName; + + private String ldapName; +} diff --git a/src/main/java/cz/muni/ics/ga4gh/model/ExtSource.java b/src/main/java/cz/muni/ics/ga4gh/model/ExtSource.java new file mode 100644 index 0000000000000000000000000000000000000000..eeb1cd965235d7d5b2f84e804bf6765258f4747a --- /dev/null +++ b/src/main/java/cz/muni/ics/ga4gh/model/ExtSource.java @@ -0,0 +1,40 @@ +package cz.muni.ics.ga4gh.model; + +import lombok.AllArgsConstructor; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; +import org.springframework.util.StringUtils; + +@Getter +@NoArgsConstructor +@AllArgsConstructor +@EqualsAndHashCode +@ToString +public class ExtSource { + + @Setter + private Long id; + + private String name; + + private String type; + + public void setName(String name) { + if (StringUtils.hasText(name)) { + throw new IllegalArgumentException("name cannot be null nor empty"); + } + + this.name = name; + } + + public void setType(String type) { + if (!StringUtils.hasText(type)) { + throw new IllegalArgumentException("type cannot be null nor empty"); + } + + this.type = type; + } +} diff --git a/src/main/java/cz/muni/ics/ga4gh/model/Ga4ghClaimRepository.java b/src/main/java/cz/muni/ics/ga4gh/model/Ga4ghClaimRepository.java new file mode 100644 index 0000000000000000000000000000000000000000..f9c7bd93c244bc0aa3fb8a35cb65b773009b9fe9 --- /dev/null +++ b/src/main/java/cz/muni/ics/ga4gh/model/Ga4ghClaimRepository.java @@ -0,0 +1,20 @@ +package cz.muni.ics.ga4gh.model; + +import lombok.AllArgsConstructor; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.ToString; +import org.springframework.web.client.RestTemplate; + +@Getter +@ToString +@EqualsAndHashCode +@AllArgsConstructor +public class Ga4ghClaimRepository { + + private final String name; + + private final String actionURL; + + private final RestTemplate restTemplate; +} diff --git a/src/main/java/cz/muni/ics/ga4gh/model/Ga4ghPassportVisa.java b/src/main/java/cz/muni/ics/ga4gh/model/Ga4ghPassportVisa.java new file mode 100644 index 0000000000000000000000000000000000000000..69175746fd93fd1585c0aaff526c701bd45e2007 --- /dev/null +++ b/src/main/java/cz/muni/ics/ga4gh/model/Ga4ghPassportVisa.java @@ -0,0 +1,59 @@ +package cz.muni.ics.ga4gh.model; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; + +@Getter +@Setter +@ToString +@EqualsAndHashCode +public class Ga4ghPassportVisa { + + public static final String GA4GH_VISA_V1 = "ga4gh_visa_v1"; + + public static final String TYPE_AFFILIATION_AND_ROLE = "AffiliationAndRole"; + public static final String TYPE_ACCEPTED_TERMS_AND_POLICIES = "AcceptedTermsAndPolicies"; + public static final String TYPE_RESEARCHER_STATUS = "ResearcherStatus"; + public static final String TYPE_LINKED_IDENTITIES = "LinkedIdentities"; + + public static final String BY_SYSTEM = "system"; + public static final String BY_SO = "so"; + public static final String BY_PEER = "peer"; + public static final String BY_SELF = "self"; + + public static final String SUB = "sub"; + public static final String EXP = "exp"; + public static final String ISS = "iss"; + public static final String TYPE = "type"; + public static final String ASSERTED = "asserted"; + public static final String VALUE = "value"; + public static final String SOURCE = "source"; + public static final String BY = "by"; + public static final String CONDITION = "condition"; + + private boolean verified = false; + private String linkedIdentity; + private String sub; + private String iss; + private String type; + private String value; + + @ToString.Exclude + private String signer; + + @ToString.Exclude + private String jwt; + + @ToString.Exclude + private String prettyPayload; + + public Ga4ghPassportVisa(String jwt) { + this.jwt = jwt; + } + + public String getPrettyString() { + return prettyPayload + ", signed by " + signer; + } +} diff --git a/src/main/java/cz/muni/ics/ga4gh/model/Group.java b/src/main/java/cz/muni/ics/ga4gh/model/Group.java new file mode 100644 index 0000000000000000000000000000000000000000..49aea56d2a3e9bbcc58f39611303d5137a6f7185 --- /dev/null +++ b/src/main/java/cz/muni/ics/ga4gh/model/Group.java @@ -0,0 +1,45 @@ +package cz.muni.ics.ga4gh.model; + +import com.fasterxml.jackson.databind.JsonNode; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; + +import java.util.LinkedHashMap; +import java.util.Map; + +@Getter +@Setter +@ToString +@EqualsAndHashCode +@NoArgsConstructor +public class Group { + + private Long id; + + private Long parentGroupId; + + private String name; + + private String description; + + private String uniqueGroupName; + + private String uuid; + + private Long voId; + + private Map<String, JsonNode> attributes = new LinkedHashMap<>(); + + public Group(Long id, Long parentGroupId, String name, String description, String uniqueGroupName, String uuid, Long voId) { + this.id = id; + this.parentGroupId = parentGroupId; + this.name = name; + this.description = description; + this.uniqueGroupName = uniqueGroupName; + this.uuid = uuid; + this.voId = voId; + } +} diff --git a/src/main/java/cz/muni/ics/ga4gh/model/Member.java b/src/main/java/cz/muni/ics/ga4gh/model/Member.java new file mode 100644 index 0000000000000000000000000000000000000000..ca73483eae0b9354cae75a9b9ad64c12c8fba914 --- /dev/null +++ b/src/main/java/cz/muni/ics/ga4gh/model/Member.java @@ -0,0 +1,26 @@ +package cz.muni.ics.ga4gh.model; + +import cz.muni.ics.ga4gh.enums.MemberStatus; +import lombok.AllArgsConstructor; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; + +@Getter +@Setter +@ToString +@EqualsAndHashCode +@NoArgsConstructor +@AllArgsConstructor +public class Member { + + private Long id; + + private Long userId; + + private Long voId; + + private MemberStatus status; +} diff --git a/src/main/java/cz/muni/ics/ga4gh/model/PerunAttributeValue.java b/src/main/java/cz/muni/ics/ga4gh/model/PerunAttributeValue.java new file mode 100644 index 0000000000000000000000000000000000000000..541b63aa7d3340025e40b08c9791b528d150f61f --- /dev/null +++ b/src/main/java/cz/muni/ics/ga4gh/model/PerunAttributeValue.java @@ -0,0 +1,173 @@ +package cz.muni.ics.ga4gh.model; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.BooleanNode; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.databind.node.NullNode; +import com.fasterxml.jackson.databind.node.NumericNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.fasterxml.jackson.databind.node.TextNode; +import cz.muni.ics.ga4gh.exceptions.InconvertibleValueException; +import lombok.AllArgsConstructor; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.ToString; +import org.springframework.util.StringUtils; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +@Getter +@NoArgsConstructor +@AllArgsConstructor +@EqualsAndHashCode +@ToString +public class PerunAttributeValue { + + public final static String STRING_TYPE = "java.lang.String"; + public final static String INTEGER_TYPE = "java.lang.Integer"; + public final static String BOOLEAN_TYPE = "java.lang.Boolean"; + public final static String ARRAY_TYPE = "java.util.ArrayList"; + public final static String MAP_TYPE = "java.util.LinkedHashMap"; + public final static String LARGE_STRING_TYPE = "java.lang.LargeString"; + public final static String LARGE_ARRAY_LIST_TYPE = "java.util.LargeArrayList"; + + private String attrName; + private String type; + private JsonNode value; + + public void setAttrName(String attrName) { + if (!StringUtils.hasText(attrName)) { + throw new IllegalArgumentException("type can't be null or empty"); + } + + this.attrName = attrName; + } + + public void setType(String type) { + if (!StringUtils.hasText(type)) { + throw new IllegalArgumentException("type can't be null or empty"); + } + + this.type = type; + } + + public void setValue(String type, JsonNode value) { + if (isNullValue(value)) { + if (BOOLEAN_TYPE.equals(type)) { + value = JsonNodeFactory.instance.booleanNode(false); + } else if (ARRAY_TYPE.equals(type)) { + value = JsonNodeFactory.instance.arrayNode(); + } else if (MAP_TYPE.equals(type)) { + value = JsonNodeFactory.instance.objectNode(); + } else { + value = JsonNodeFactory.instance.nullNode(); + } + } + + this.value = value; + } + + public String valueAsString() { + if ((STRING_TYPE.equals(type) || LARGE_STRING_TYPE.equals(type))) { + if (isNullValue(value)) { + return null; + } else if (value instanceof TextNode) { + return value.textValue(); + } + } + + return value.asText(); + } + + public Integer valueAsInteger() { + if (INTEGER_TYPE.equals(type)) { + if (isNullValue(value)) { + return null; + } else if (value instanceof NumericNode) { + return value.intValue(); + } + } + + throw this.inconvertible(Long.class.getName()); + } + + public boolean valueAsBoolean() { + if (BOOLEAN_TYPE.equals(type)) { + if (value == null || value instanceof NullNode) { + return false; + } else if (value instanceof BooleanNode) { + return value.asBoolean(); + } + } + + throw this.inconvertible(Boolean.class.getName()); + } + + public List<String> valueAsList() { + List<String> arr = new ArrayList<>(); + + if ((ARRAY_TYPE.equals(type) || LARGE_ARRAY_LIST_TYPE.equals(type))) { + if (isNullValue(value)) { + return null; + } else if (value instanceof ArrayNode) { + ArrayNode arrJson = (ArrayNode) value; + arrJson.forEach(item -> arr.add(item.asText())); + } + } else { + arr.add(this.valueAsString()); + } + + return arr; + } + + public Map<String, String> valueAsMap() { + if (MAP_TYPE.equals(type)) { + if (isNullValue(value)) { + return new HashMap<>(); + } else if (value instanceof ObjectNode) { + ObjectNode objJson = (ObjectNode) value; + + Map<String, String> res = new HashMap<>(); + Iterator<String> it = objJson.fieldNames(); + + while (it.hasNext()) { + String key = it.next(); + res.put(key, objJson.get(key).asText()); + } + + return res; + } + } + + throw this.inconvertible(Map.class.getName()); + } + + public JsonNode valueAsJson() { + return this.value; + } + + public boolean isNullValue() { + return value == null || + value instanceof NullNode || + value.isNull() || + "null".equalsIgnoreCase(value.asText()); + } + + public static boolean isNullValue(JsonNode value) { + return value == null || + value instanceof NullNode || + value.isNull() || + "null".equalsIgnoreCase(value.asText()); + } + + private InconvertibleValueException inconvertible(String clazzName) { + return new InconvertibleValueException("Cannot convert value of attribute to " + clazzName + + " for object: " + this); + } +} diff --git a/src/main/java/cz/muni/ics/ga4gh/model/Repo.java b/src/main/java/cz/muni/ics/ga4gh/model/Repo.java new file mode 100644 index 0000000000000000000000000000000000000000..23016acfe561437f7a6fe6b1d8c60c88fd474c93 --- /dev/null +++ b/src/main/java/cz/muni/ics/ga4gh/model/Repo.java @@ -0,0 +1,27 @@ +package cz.muni.ics.ga4gh.model; + +import lombok.AllArgsConstructor; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; + +import java.util.List; + +@Getter +@Setter +@ToString +@EqualsAndHashCode +@NoArgsConstructor +@AllArgsConstructor +public class Repo { + + private String name; + + private String url; + + private String jwks; + + private List<RepoHeader> headers; +} diff --git a/src/main/java/cz/muni/ics/ga4gh/model/RepoHeader.java b/src/main/java/cz/muni/ics/ga4gh/model/RepoHeader.java new file mode 100644 index 0000000000000000000000000000000000000000..d8932c1d87784df467f57c57f51220d9e08136b6 --- /dev/null +++ b/src/main/java/cz/muni/ics/ga4gh/model/RepoHeader.java @@ -0,0 +1,30 @@ +package cz.muni.ics.ga4gh.model; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.springframework.http.HttpRequest; +import org.springframework.http.client.ClientHttpRequestExecution; +import org.springframework.http.client.ClientHttpRequestInterceptor; +import org.springframework.http.client.ClientHttpResponse; + +import java.io.IOException; + +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor +public class RepoHeader implements ClientHttpRequestInterceptor { + + private String header; + + private String value; + + @Override + public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException { + request.getHeaders().add(header, value); + + return execution.execute(request, body); + } +} diff --git a/src/main/java/cz/muni/ics/ga4gh/model/UserExtSource.java b/src/main/java/cz/muni/ics/ga4gh/model/UserExtSource.java new file mode 100644 index 0000000000000000000000000000000000000000..19fcda59ce09e7dec1e8f016a1b2625815eaa67b --- /dev/null +++ b/src/main/java/cz/muni/ics/ga4gh/model/UserExtSource.java @@ -0,0 +1,56 @@ +package cz.muni.ics.ga4gh.model; + +import lombok.AllArgsConstructor; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; +import org.springframework.util.StringUtils; + +import java.sql.Timestamp; + +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@EqualsAndHashCode +@ToString +public class UserExtSource { + + private Long id; + + private ExtSource extSource; + + private String login; + + private int loa = 0; + + private boolean persistent; + + private Timestamp lastAccess; + + public void setExtSource(ExtSource extSource) { + if (extSource == null) { + throw new IllegalArgumentException("extSource can't be null"); + } + + this.extSource = extSource; + } + + public void setLogin(String login) { + if (!StringUtils.hasText(login)) { + throw new IllegalArgumentException("login can't be null or empty"); + } + + this.login = login; + } + + public void setLoa(int loa) { + if (loa < 0) { + throw new IllegalArgumentException("loa has to be 0 or higher"); + } + + this.loa = loa; + } +} diff --git a/src/main/java/cz/muni/ics/ga4gh/security/WebSecurityConfigurer.java b/src/main/java/cz/muni/ics/ga4gh/security/WebSecurityConfigurer.java new file mode 100644 index 0000000000000000000000000000000000000000..20d62d10cca5a87c99ae91ead6ccfcb3ab311249 --- /dev/null +++ b/src/main/java/cz/muni/ics/ga4gh/security/WebSecurityConfigurer.java @@ -0,0 +1,50 @@ +package cz.muni.ics.ga4gh.security; + +import cz.muni.ics.ga4gh.config.BasicAuthConfig; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.web.SecurityFilterChain; + +@Configuration +public class WebSecurityConfigurer { + + private static final String ROLE_USER = "ROLE_USER"; + private static final String PUBLIC_ENDPOINTS_PREFIX = "/public/**"; + + private final String username; + private final String password; + + private final PasswordEncoder passwordEncoder; + + @Autowired + public WebSecurityConfigurer(BasicAuthConfig config, PasswordEncoder passwordEncoder) { + this.username = config.getUsername(); + this.password = config.getPassword(); + this.passwordEncoder = passwordEncoder; + } + + @Autowired + public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { + auth.inMemoryAuthentication() + .withUser(username).password(passwordEncoder.encode(password)) + .authorities(ROLE_USER); + } + + @Bean + public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + + http.authorizeRequests() + .antMatchers(PUBLIC_ENDPOINTS_PREFIX).permitAll() + .anyRequest().authenticated() + .and() + .httpBasic(); + + http.headers().frameOptions().sameOrigin(); + + return http.build(); + } +} diff --git a/src/main/java/cz/muni/ics/ga4gh/service/Ga4ghBrokerService.java b/src/main/java/cz/muni/ics/ga4gh/service/Ga4ghBrokerService.java new file mode 100644 index 0000000000000000000000000000000000000000..824d9faecd779ee199d6abd57ddc7c35d99ff439 --- /dev/null +++ b/src/main/java/cz/muni/ics/ga4gh/service/Ga4ghBrokerService.java @@ -0,0 +1,8 @@ +package cz.muni.ics.ga4gh.service; + +import com.fasterxml.jackson.databind.node.ArrayNode; + +public interface Ga4ghBrokerService { + + ArrayNode getGa4ghPassport(String eppn); +} diff --git a/src/main/java/cz/muni/ics/ga4gh/service/JWTSigningAndValidationService.java b/src/main/java/cz/muni/ics/ga4gh/service/JWTSigningAndValidationService.java new file mode 100644 index 0000000000000000000000000000000000000000..97486622c5367bac38eb237e0cfec5164b3bb862 --- /dev/null +++ b/src/main/java/cz/muni/ics/ga4gh/service/JWTSigningAndValidationService.java @@ -0,0 +1,37 @@ +package cz.muni.ics.ga4gh.service; + +import com.nimbusds.jose.JWSAlgorithm; +import com.nimbusds.jose.jwk.JWK; +import com.nimbusds.jwt.SignedJWT; + +import java.util.Collection; +import java.util.Map; + +public interface JWTSigningAndValidationService { + + Map<String, JWK> getAllPublicKeys(); + + JWSAlgorithm getDefaultSigningAlgorithm(); + + Collection<JWSAlgorithm> getAllSigningAlgsSupported(); + + /** + * Called to sign a jwt in place for a client that hasn't registered a preferred signing algorithm. + * Use the default algorithm to sign. + * + * @param jwt the jwt to sign + * @throws IllegalStateException when calling default signing with no default signer ID set + */ + void signJwt(SignedJWT jwt); + + /** + * Sign a jwt using the selected algorithm. The algorithm is selected using the String parameter values specified + * in the JWT spec, section 6. I.E., "HS256" means HMAC with SHA-256 and corresponds to our HmacSigner class. + * + * @param jwt the jwt to sign + * @param alg the name of the algorithm to use, as specified in JWS s.6 + */ + void signJwt(SignedJWT jwt, JWSAlgorithm alg); + + String getDefaultSignerKeyId(); +} diff --git a/src/main/java/cz/muni/ics/ga4gh/service/impl/Ga4GhBrokerBrokerServiceImpl.java b/src/main/java/cz/muni/ics/ga4gh/service/impl/Ga4GhBrokerBrokerServiceImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..9999334676bfd4b0cbc27243b00f53e622ddfbe5 --- /dev/null +++ b/src/main/java/cz/muni/ics/ga4gh/service/impl/Ga4GhBrokerBrokerServiceImpl.java @@ -0,0 +1,61 @@ +package cz.muni.ics.ga4gh.service.impl; + +import com.fasterxml.jackson.databind.node.ArrayNode; +import cz.muni.ics.ga4gh.adapters.PerunAdapter; +import cz.muni.ics.ga4gh.config.AttributesConfig; +import cz.muni.ics.ga4gh.config.BrokerConfig; +import cz.muni.ics.ga4gh.exceptions.UserNotUniqueException; +import cz.muni.ics.ga4gh.model.AttributeMapping; +import cz.muni.ics.ga4gh.service.impl.brokers.Ga4ghBroker; +import cz.muni.ics.ga4gh.service.Ga4ghBrokerService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +@Service +@Slf4j +public class Ga4GhBrokerBrokerServiceImpl implements Ga4ghBrokerService { + + private final Ga4ghBroker broker; + + private final PerunAdapter adapter; + + private final List<String> attributesToSearch; + + private final Map<String, AttributeMapping> attributes; + + @Autowired + public Ga4GhBrokerBrokerServiceImpl(Ga4ghBroker broker, PerunAdapter adapter, BrokerConfig brokerConfig, AttributesConfig attributesConfig) { + this.broker = broker; + this.adapter = adapter; + this.attributesToSearch = brokerConfig.getAttributesToSearch(); + this.attributes = attributesConfig.getAttributeMappings(); + } + + @Override + public ArrayNode getGa4ghPassport(String eppn) { + Set<Long> userIds = new HashSet<>(); + + for (String attrName : attributesToSearch) { + if (attributes.get(attrName) != null) { + userIds.addAll(adapter.getAdapterPrimary().getUserIdsByAttributeValue(attributes.get(attrName), eppn)); + } + } + + if (userIds.isEmpty()) { + log.debug("User {} not found", eppn); + return null; + } + + if (userIds.size() > 1) { + throw new UserNotUniqueException("There are more users found by " + eppn + " - " + String.join(", ", userIds.toString())); + } + + return broker.constructGa4ghPassportVisa(userIds.iterator().next(), eppn); + } +} diff --git a/src/main/java/cz/muni/ics/ga4gh/service/impl/JWTSigningAndValidationServiceImpl.java b/src/main/java/cz/muni/ics/ga4gh/service/impl/JWTSigningAndValidationServiceImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..0fc7b3b780ab4aac31406c5ece4a25850057e7b3 --- /dev/null +++ b/src/main/java/cz/muni/ics/ga4gh/service/impl/JWTSigningAndValidationServiceImpl.java @@ -0,0 +1,227 @@ +package cz.muni.ics.ga4gh.service.impl; + +import com.nimbusds.jose.JOSEException; +import com.nimbusds.jose.JWSAlgorithm; +import com.nimbusds.jose.JWSProvider; +import com.nimbusds.jose.JWSSigner; +import com.nimbusds.jose.JWSVerifier; +import com.nimbusds.jose.crypto.ECDSASigner; +import com.nimbusds.jose.crypto.ECDSAVerifier; +import com.nimbusds.jose.crypto.MACSigner; +import com.nimbusds.jose.crypto.MACVerifier; +import com.nimbusds.jose.crypto.RSASSASigner; +import com.nimbusds.jose.crypto.RSASSAVerifier; +import com.nimbusds.jose.jwk.ECKey; +import com.nimbusds.jose.jwk.JWK; +import com.nimbusds.jose.jwk.OctetSequenceKey; +import com.nimbusds.jose.jwk.RSAKey; +import com.nimbusds.jwt.SignedJWT; +import cz.muni.ics.ga4gh.jose.keystore.JWKSetKeyStore; +import cz.muni.ics.ga4gh.service.JWTSigningAndValidationService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; + +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.UUID; + +@Slf4j +@Service +public class JWTSigningAndValidationServiceImpl implements JWTSigningAndValidationService { + + private final Map<String, JWSSigner> signers = new HashMap<>(); + private final Map<String, JWSVerifier> verifiers = new HashMap<>(); + + private String defaultSignerKeyId; + private JWSAlgorithm defaultAlgorithm; + private Map<String, JWK> keys = new HashMap<>(); + + /** + * Build this service based on the keys given. All public keys will be used + * to make verifiers, all private keys will be used to make signers. + * + * @param keys A map of key identifier to key. + */ + public JWTSigningAndValidationServiceImpl(Map<String, JWK> keys) { + this.keys = keys; + buildSignersAndVerifiers(); + } + + /** + * Build this service based on the given keystore. All keys must have a key + * id ({@code kid}) field in order to be used. + * + * @param keyStore The keystore to load all keys from. + */ + @Autowired + public JWTSigningAndValidationServiceImpl(JWKSetKeyStore keyStore) { + if (keyStore!= null && keyStore.getJwkSet() != null) { + for (JWK key : keyStore.getKeys()) { + if (!StringUtils.isEmpty(key.getKeyID())) { + this.keys.put(key.getKeyID(), key); + } else { + String fakeKid = UUID.randomUUID().toString(); + this.keys.put(fakeKid, key); + } + } + } + + buildSignersAndVerifiers(); + + this.defaultSignerKeyId = keyStore.getKeys().get(0).getKeyID(); + setDefaultSigningAlgorithmName(keyStore.getKeys().get(0).getAlgorithm().getName()); + } + + @Override + public String getDefaultSignerKeyId() { + return defaultSignerKeyId; + } + + public void setDefaultSignerKeyId(String defaultSignerId) { + this.defaultSignerKeyId = defaultSignerId; + } + + @Override + public JWSAlgorithm getDefaultSigningAlgorithm() { + return defaultAlgorithm; + } + + public void setDefaultSigningAlgorithmName(String algName) { + defaultAlgorithm = JWSAlgorithm.parse(algName); + } + + public String getDefaultSigningAlgorithmName() { + if (defaultAlgorithm != null) { + return defaultAlgorithm.getName(); + } else { + return null; + } + } + + @Override + public void signJwt(SignedJWT jwt) { + if (getDefaultSignerKeyId() == null) { + throw new IllegalStateException("Tried to call default signing with no default signer ID set"); + } + + JWSSigner signer = signers.get(getDefaultSignerKeyId()); + + try { + jwt.sign(signer); + } catch (JOSEException e) { + log.error("Failed to sign JWT, error was: ", e); + } + } + + @Override + public void signJwt(SignedJWT jwt, JWSAlgorithm alg) { + JWSSigner signer = null; + + for (JWSSigner s : signers.values()) { + if (s.supportedJWSAlgorithms().contains(alg)) { + signer = s; + break; + } + } + + if (signer == null) { + log.error("No matching algorithm found for alg={}", alg); + } else { + try { + jwt.sign(signer); + } catch (JOSEException e) { + log.error("Failed to sign JWT, error was: ", e); + } + } + } + + @Override + public Map<String, JWK> getAllPublicKeys() { + Map<String, JWK> pubKeys = new HashMap<>(); + + keys.keySet().forEach(keyId -> { + JWK key = keys.get(keyId); + JWK pub = key.toPublicJWK(); + if (pub != null) { + pubKeys.put(keyId, pub); + } + }); + + return pubKeys; + } + + @Override + public Collection<JWSAlgorithm> getAllSigningAlgsSupported() { + Set<JWSAlgorithm> algs = new HashSet<>(); + signers.values().stream().map(JWSProvider::supportedJWSAlgorithms).forEach(algs::addAll); + verifiers.values().stream().map(JWSProvider::supportedJWSAlgorithms).forEach(algs::addAll); + + return algs; + } + + private void buildSignersAndVerifiers() { + for (Map.Entry<String, JWK> jwkEntry : keys.entrySet()) { + String id = jwkEntry.getKey(); + JWK jwk = jwkEntry.getValue(); + + try { + if (jwk instanceof RSAKey) { + processRSAKey(signers, verifiers, jwk, id); + } else if (jwk instanceof ECKey) { + processECKey(signers, verifiers, jwk, id); + } else if (jwk instanceof OctetSequenceKey) { + processOctetKey(signers, verifiers, jwk, id); + } else { + log.warn("Unknown key type: {}", jwk); + } + } catch (JOSEException e) { + log.warn("Exception loading signer/verifier", e); + } + } + + if (defaultSignerKeyId == null && keys.size() == 1) { + setDefaultSignerKeyId(keys.keySet().iterator().next()); + } + } + + private void processOctetKey(Map<String, JWSSigner> signers, Map<String, JWSVerifier> verifiers, JWK jwk, String id) + throws JOSEException + { + if (jwk.isPrivate()) { + MACSigner signer = new MACSigner((OctetSequenceKey) jwk); + signers.put(id, signer); + } + + MACVerifier verifier = new MACVerifier((OctetSequenceKey) jwk); + verifiers.put(id, verifier); + } + + private void processECKey(Map<String, JWSSigner> signers, Map<String, JWSVerifier> verifiers, JWK jwk, String id) + throws JOSEException + { + if (jwk.isPrivate()) { + ECDSASigner signer = new ECDSASigner((ECKey) jwk); + signers.put(id, signer); + } + + ECDSAVerifier verifier = new ECDSAVerifier((ECKey) jwk); + verifiers.put(id, verifier); + } + + private void processRSAKey(Map<String, JWSSigner> signers, Map<String, JWSVerifier> verifiers, JWK jwk, String id) + throws JOSEException + { + if (jwk.isPrivate()) { + RSASSASigner signer = new RSASSASigner((RSAKey) jwk); + signers.put(id, signer); + } + + RSASSAVerifier verifier = new RSASSAVerifier((RSAKey) jwk); + verifiers.put(id, verifier); + } +} diff --git a/src/main/java/cz/muni/ics/ga4gh/service/impl/brokers/BbmriGa4ghBroker.java b/src/main/java/cz/muni/ics/ga4gh/service/impl/brokers/BbmriGa4ghBroker.java new file mode 100644 index 0000000000000000000000000000000000000000..f9a89b7deccda587f80665b50715a8fc262856b2 --- /dev/null +++ b/src/main/java/cz/muni/ics/ga4gh/service/impl/brokers/BbmriGa4ghBroker.java @@ -0,0 +1,204 @@ +package cz.muni.ics.ga4gh.service.impl.brokers; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ArrayNode; +import cz.muni.ics.ga4gh.adapters.PerunAdapter; +import cz.muni.ics.ga4gh.config.BrokerConfig; +import cz.muni.ics.ga4gh.config.Ga4ghConfig; +import cz.muni.ics.ga4gh.model.Affiliation; +import cz.muni.ics.ga4gh.model.Ga4ghClaimRepository; +import cz.muni.ics.ga4gh.service.JWTSigningAndValidationService; +import cz.muni.ics.ga4gh.utils.Utils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Profile; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; + +import java.net.MalformedURLException; +import java.net.URISyntaxException; +import java.sql.Timestamp; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Objects; +import java.util.Set; + +import static cz.muni.ics.ga4gh.model.Ga4ghPassportVisa.BY_PEER; +import static cz.muni.ics.ga4gh.model.Ga4ghPassportVisa.BY_SELF; +import static cz.muni.ics.ga4gh.model.Ga4ghPassportVisa.BY_SO; +import static cz.muni.ics.ga4gh.model.Ga4ghPassportVisa.BY_SYSTEM; +import static cz.muni.ics.ga4gh.model.Ga4ghPassportVisa.TYPE_ACCEPTED_TERMS_AND_POLICIES; +import static cz.muni.ics.ga4gh.model.Ga4ghPassportVisa.TYPE_AFFILIATION_AND_ROLE; +import static cz.muni.ics.ga4gh.model.Ga4ghPassportVisa.TYPE_LINKED_IDENTITIES; +import static cz.muni.ics.ga4gh.model.Ga4ghPassportVisa.TYPE_RESEARCHER_STATUS; + +@Service +@Profile("bbmri") +public class BbmriGa4ghBroker extends Ga4ghBroker { + + private static final String BONA_FIDE_URL = "https://doi.org/10.1038/s41431-018-0219-y"; + private static final String BBMRI_ERIC_ORG_URL = "https://www.bbmri-eric.eu/"; + private static final String BBMRI_ID = "bbmri_id"; + private static final String FACULTY_AT = "faculty@"; + + private final String bonaFideStatusAttr; + private final String groupAffiliationsAttr; + private final Long termsAndPoliciesGroupId; + + @Autowired + public BbmriGa4ghBroker(BrokerConfig brokerConfig, PerunAdapter adapter, JWTSigningAndValidationService jwtService, Ga4ghConfig ga4ghConfig) throws URISyntaxException, MalformedURLException { + super(adapter, jwtService, ga4ghConfig, brokerConfig); + + bonaFideStatusAttr = brokerConfig.getBonaFideStatusAttr(); + groupAffiliationsAttr = brokerConfig.getGroupAffiliationsAttr(); + termsAndPoliciesGroupId = Objects.requireNonNullElse(brokerConfig.getTermsAndPoliciesGroupId(), 10432L); + } + + @Override + protected void addAffiliationAndRoles(long now, ArrayNode passport, List<Affiliation> affiliations, String sub, Long userId) + { + //by=system for users with affiliation asserted by their IdP (set in UserExtSource attribute "affiliation") + if (affiliations == null) { + return; + } + + for (Affiliation affiliation: affiliations) { + //expires 1 year after the last login from the IdP asserting the affiliation + long expires = Utils.getOneYearExpires(affiliation.getAsserted()); + if (expires < now) { + continue; + } + + JsonNode visa = createPassportVisa(TYPE_AFFILIATION_AND_ROLE, sub, userId, affiliation.getValue(), affiliation.getSource(), BY_SYSTEM, affiliation.getAsserted(), expires, null); + if (visa != null) { + passport.add(visa); + } + } + } + + @Override + protected void addAcceptedTermsAndPolicies(long now, ArrayNode passport, Long userId, String sub) { + //by=self for members of the group 10432 "Bona Fide Researchers" + boolean userInGroup = adapter.isUserInGroup(userId, termsAndPoliciesGroupId); + if (!userInGroup) { + return; + } + + long asserted = now; + if (bonaFideStatusAttr != null) { + String bonaFideStatusCreatedAt = adapter.getAdapterRpc().getUserAttributeCreatedAt(userId, bonaFideStatusAttr); + if (bonaFideStatusCreatedAt != null) { + asserted = Timestamp.valueOf(bonaFideStatusCreatedAt).getTime() / 1000L; + } + } + + long expires = Utils.getExpires(asserted, 100L); + if (expires < now) { + return; + } + + JsonNode visa = createPassportVisa(TYPE_ACCEPTED_TERMS_AND_POLICIES, sub, userId, BONA_FIDE_URL, BBMRI_ERIC_ORG_URL, BY_SELF, asserted, expires, null); + if (visa != null) { + passport.add(visa); + } + } + + @Override + protected void addResearcherStatuses(long now, ArrayNode passport, List<Affiliation> affiliations, String sub, Long userId) + { + addResearcherStatusFromBonaFideAttribute(now, passport, userId, sub); + addResearcherStatusFromAffiliation(affiliations, now, passport, sub, userId); + addResearcherStatusGroupAffiliations(now, passport, sub, userId); + } + + @Override + protected void addControlledAccessGrants(long now, ArrayNode passport, String sub, Long userId) { + if (claimRepositories.isEmpty()) { + return; + } + + Set<String> linkedIdentities = new HashSet<>(); + for (Ga4ghClaimRepository repo: claimRepositories) { + callPermissionsJwtAPI(repo, Collections.singletonMap(BBMRI_ID, sub), passport, linkedIdentities); + } + + if (linkedIdentities.isEmpty()) { + return; + } + + for (String linkedIdentity : linkedIdentities) { + long expires = Utils.getOneYearExpires(now); + + JsonNode visa = createPassportVisa(TYPE_LINKED_IDENTITIES, sub, userId, linkedIdentity, BBMRI_ERIC_ORG_URL, BY_SYSTEM, now, expires, null); + if (visa != null) { + passport.add(visa); + } + } + } + + private void addResearcherStatusFromBonaFideAttribute(long now, ArrayNode passport, Long userId, String sub) + { + //by=peer for users with attribute elixirBonaFideStatusREMS + String bbmriBonaFideStatusCreatedAt = adapter.getAdapterRpc().getUserAttributeCreatedAt(userId, bonaFideStatusAttr); + + if (bbmriBonaFideStatusCreatedAt == null) { + return; + } + + long asserted = Timestamp.valueOf(bbmriBonaFideStatusCreatedAt).getTime() / 1000L; + long expires = Utils.getOneYearExpires(asserted); + + if (expires > now) { + JsonNode visa = createPassportVisa(TYPE_RESEARCHER_STATUS, sub, userId, BONA_FIDE_URL, BBMRI_ERIC_ORG_URL, BY_PEER, asserted, expires, null); + + if (visa != null) { + passport.add(visa); + } + } + } + + private void addResearcherStatusFromAffiliation(List<Affiliation> affiliations, long now, ArrayNode passport, String sub, Long userId) + { + //by=system for users with faculty affiliation asserted by their IdP (set in UserExtSource attribute "affiliation") + if (affiliations == null) { + return; + } + + for (Affiliation affiliation: affiliations) { + if (!StringUtils.startsWithIgnoreCase(affiliation.getValue(), FACULTY_AT)) { + continue; + } + + long expires = Utils.getOneYearExpires(affiliation.getAsserted()); + if (expires < now) { + continue; + } + + JsonNode visa = createPassportVisa(TYPE_RESEARCHER_STATUS, sub, userId, BONA_FIDE_URL, affiliation.getSource(), BY_SYSTEM, affiliation.getAsserted(), expires, null); + if (visa != null) { + passport.add(visa); + } + } + } + + private void addResearcherStatusGroupAffiliations(long now, ArrayNode passport, String sub, Long userId) { + //by=so for users with faculty affiliation asserted by membership in a group with groupAffiliations attribute + List<Affiliation> groupAffiliations = adapter.getGroupAffiliations(userId, groupAffiliationsAttr); + if (groupAffiliations == null) { + return; + } + + for (Affiliation affiliation: groupAffiliations) { + if (!StringUtils.startsWithIgnoreCase(affiliation.getValue(), FACULTY_AT)) { + continue; + } + + long expires = Utils.getOneYearExpires(now); + + JsonNode visa = createPassportVisa(TYPE_RESEARCHER_STATUS, sub, userId, BONA_FIDE_URL, BBMRI_ERIC_ORG_URL, BY_SO, affiliation.getAsserted(), expires, null); + if (visa != null) { + passport.add(visa); + } + } + } +} diff --git a/src/main/java/cz/muni/ics/ga4gh/service/impl/brokers/ElixirGa4ghBroker.java b/src/main/java/cz/muni/ics/ga4gh/service/impl/brokers/ElixirGa4ghBroker.java new file mode 100644 index 0000000000000000000000000000000000000000..cdef905347f247662b3753c38aaa5c28865d3e8b --- /dev/null +++ b/src/main/java/cz/muni/ics/ga4gh/service/impl/brokers/ElixirGa4ghBroker.java @@ -0,0 +1,201 @@ +package cz.muni.ics.ga4gh.service.impl.brokers; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ArrayNode; +import cz.muni.ics.ga4gh.adapters.PerunAdapter; +import cz.muni.ics.ga4gh.config.BrokerConfig; +import cz.muni.ics.ga4gh.config.Ga4ghConfig; +import cz.muni.ics.ga4gh.model.Affiliation; +import cz.muni.ics.ga4gh.model.Ga4ghClaimRepository; +import cz.muni.ics.ga4gh.service.JWTSigningAndValidationService; +import cz.muni.ics.ga4gh.utils.Utils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Profile; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; + +import java.net.MalformedURLException; +import java.net.URISyntaxException; +import java.sql.Timestamp; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Objects; +import java.util.Set; + +import static cz.muni.ics.ga4gh.model.Ga4ghPassportVisa.BY_PEER; +import static cz.muni.ics.ga4gh.model.Ga4ghPassportVisa.BY_SELF; +import static cz.muni.ics.ga4gh.model.Ga4ghPassportVisa.BY_SO; +import static cz.muni.ics.ga4gh.model.Ga4ghPassportVisa.BY_SYSTEM; +import static cz.muni.ics.ga4gh.model.Ga4ghPassportVisa.TYPE_ACCEPTED_TERMS_AND_POLICIES; +import static cz.muni.ics.ga4gh.model.Ga4ghPassportVisa.TYPE_AFFILIATION_AND_ROLE; +import static cz.muni.ics.ga4gh.model.Ga4ghPassportVisa.TYPE_LINKED_IDENTITIES; +import static cz.muni.ics.ga4gh.model.Ga4ghPassportVisa.TYPE_RESEARCHER_STATUS; + +@Service +@Profile("elixir") +public class ElixirGa4ghBroker extends Ga4ghBroker { + + private static final String BONA_FIDE_URL = "https://doi.org/10.1038/s41431-018-0219-y"; + private static final String ELIXIR_ORG_URL = "https://elixir-europe.org/"; + private static final String ELIXIR_ID = "elixir_id"; + private static final String FACULTY_AT = "faculty@"; + + private final String bonaFideStatusAttr; + private final String bonaFideStatusREMSAttr; + private final String groupAffiliationsAttr; + private final Long termsAndPoliciesGroupId; + + @Autowired + public ElixirGa4ghBroker(BrokerConfig brokerConfig, Ga4ghConfig ga4ghConfig, PerunAdapter adapter, JWTSigningAndValidationService jwtService) throws URISyntaxException, MalformedURLException { + super(adapter, jwtService, ga4ghConfig, brokerConfig); + + bonaFideStatusAttr = brokerConfig.getBonaFideStatusAttr(); + bonaFideStatusREMSAttr = brokerConfig.getBonaFideStatusRemsAttr(); + groupAffiliationsAttr = brokerConfig.getGroupAffiliationsAttr(); + termsAndPoliciesGroupId = Objects.requireNonNullElse(brokerConfig.getTermsAndPoliciesGroupId(), 10432L); + } + + @Override + protected void addAffiliationAndRoles(long now, ArrayNode passport, List<Affiliation> affiliations, String sub, Long userId) { + if (affiliations == null) { + return; + } + + for (Affiliation affiliation: affiliations) { + long expires = Utils.getOneYearExpires(affiliation.getAsserted()); + + if (expires < now) { + continue; + } + + JsonNode visa = createPassportVisa(TYPE_AFFILIATION_AND_ROLE, sub, userId, affiliation.getValue(), + affiliation.getSource(), BY_SYSTEM, affiliation.getAsserted(), expires, null); + + if (visa != null) { + passport.add(visa); + } + } + } + + @Override + protected void addAcceptedTermsAndPolicies(long now, ArrayNode passport, Long userId, String sub) { + boolean userInGroup = adapter.isUserInGroup(userId, termsAndPoliciesGroupId); + if (!userInGroup) { + return; + } + + long asserted = now; + if (bonaFideStatusAttr != null) { + String bonaFideStatusCreatedAt = adapter.getAdapterRpc().getUserAttributeCreatedAt(userId, bonaFideStatusAttr); + if (bonaFideStatusCreatedAt != null) { + asserted = Timestamp.valueOf(bonaFideStatusCreatedAt).getTime() / 1000L; + } + } + + long expires = Utils.getExpires(asserted, 100L); + if (expires < now) { + return; + } + + JsonNode visa = createPassportVisa(TYPE_ACCEPTED_TERMS_AND_POLICIES, sub, userId, BONA_FIDE_URL, ELIXIR_ORG_URL, BY_SELF, asserted, expires, null); + if (visa != null) { + passport.add(visa); + } + + } + + @Override + protected void addResearcherStatuses(long now, ArrayNode passport, List<Affiliation> affiliations, String sub, Long userId) + { + addResearcherStatusFromBonaFideAttribute(now, passport, sub, userId); + addResearcherStatusFromAffiliation(affiliations, now, passport, sub, userId); + addResearcherStatusGroupAffiliations(now, passport, userId, sub); + } + + @Override + protected void addControlledAccessGrants(long now, ArrayNode passport, String sub, Long userId) { + if (claimRepositories.isEmpty()) { + return; + } + Set<String> linkedIdentities = new HashSet<>(); + for (Ga4ghClaimRepository repo: claimRepositories) { + callPermissionsJwtAPI(repo, Collections.singletonMap(ELIXIR_ID, sub), passport, linkedIdentities); + } + if (linkedIdentities.isEmpty()) { + return; + } + for (String linkedIdentity : linkedIdentities) { + long expires = Utils.getOneYearExpires(now); + JsonNode visa = createPassportVisa(TYPE_LINKED_IDENTITIES, sub, userId, linkedIdentity, + ELIXIR_ORG_URL, BY_SYSTEM, now, expires, null); + if (visa != null) { + passport.add(visa); + } + } + } + + private void addResearcherStatusFromBonaFideAttribute(long now, ArrayNode passport, String sub, Long userId) + { + String elixirBonaFideStatusREMSCreatedAt = adapter.getAdapterRpc().getUserAttributeCreatedAt(userId, bonaFideStatusREMSAttr); + + if (elixirBonaFideStatusREMSCreatedAt == null) { + return; + } + + long asserted = Timestamp.valueOf(elixirBonaFideStatusREMSCreatedAt).getTime() / 1000L; + long expires = Utils.getOneYearExpires(asserted); + + if (expires < now) { + return; + } + + JsonNode visa = createPassportVisa(TYPE_RESEARCHER_STATUS, sub, userId, BONA_FIDE_URL, ELIXIR_ORG_URL, BY_PEER, asserted, expires, null); + if (visa != null) { + passport.add(visa); + } + } + + private void addResearcherStatusFromAffiliation(List<Affiliation> affiliations, long now, ArrayNode passport, String sub, Long userId) + { + if (affiliations == null) { + return; + } + + for (Affiliation affiliation: affiliations) { + if (!StringUtils.startsWithIgnoreCase(affiliation.getValue(), FACULTY_AT)) { + continue; + } + + long expires = Utils.getOneYearExpires(affiliation.getAsserted()); + if (expires < now) { + continue; + } + + JsonNode visa = createPassportVisa(TYPE_RESEARCHER_STATUS, sub, userId, BONA_FIDE_URL, affiliation.getSource(), BY_SYSTEM, affiliation.getAsserted(), expires, null); + if (visa != null) { + passport.add(visa); + } + } + } + + private void addResearcherStatusGroupAffiliations(long now, ArrayNode passport, Long userId, String sub) { + List<Affiliation> groupAffiliations = adapter.getGroupAffiliations(userId, groupAffiliationsAttr); + if (groupAffiliations == null) { + return; + } + + for (Affiliation affiliation: groupAffiliations) { + if (!StringUtils.startsWithIgnoreCase(affiliation.getValue(), FACULTY_AT)) { + continue; + } + + long expires = Utils.getOneYearExpires(now); + + JsonNode visa = createPassportVisa(TYPE_RESEARCHER_STATUS, sub, userId, BONA_FIDE_URL, ELIXIR_ORG_URL, BY_SO, affiliation.getAsserted(), expires, null); + if (visa != null) { + passport.add(visa); + } + } + } +} diff --git a/src/main/java/cz/muni/ics/ga4gh/service/impl/brokers/Ga4ghBroker.java b/src/main/java/cz/muni/ics/ga4gh/service/impl/brokers/Ga4ghBroker.java new file mode 100644 index 0000000000000000000000000000000000000000..51e8cc5e1ea806b2abb9c1eb0c908b1df7830126 --- /dev/null +++ b/src/main/java/cz/muni/ics/ga4gh/service/impl/brokers/Ga4ghBroker.java @@ -0,0 +1,223 @@ +package cz.muni.ics.ga4gh.service.impl.brokers; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.nimbusds.jose.JOSEObjectType; +import com.nimbusds.jose.JWSAlgorithm; +import com.nimbusds.jose.JWSHeader; +import com.nimbusds.jose.jwk.source.RemoteJWKSet; +import com.nimbusds.jose.proc.SecurityContext; +import com.nimbusds.jwt.JWTClaimsSet; +import com.nimbusds.jwt.SignedJWT; +import cz.muni.ics.ga4gh.adapters.PerunAdapter; +import cz.muni.ics.ga4gh.config.BrokerConfig; +import cz.muni.ics.ga4gh.config.Ga4ghConfig; +import cz.muni.ics.ga4gh.model.Affiliation; +import cz.muni.ics.ga4gh.model.Ga4ghClaimRepository; +import cz.muni.ics.ga4gh.model.Ga4ghPassportVisa; +import cz.muni.ics.ga4gh.service.JWTSigningAndValidationService; +import cz.muni.ics.ga4gh.utils.Utils; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.MediaType; +import org.springframework.web.client.HttpClientErrorException; + +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.time.Instant; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; + +@Slf4j +public abstract class Ga4ghBroker { + + public static final String GA4GH_CLAIM = "ga4gh_passport_v1"; + public static final String JSON = "json"; + + protected final List<Ga4ghClaimRepository> claimRepositories = new ArrayList<>(); + protected final Map<URI, RemoteJWKSet<SecurityContext>> remoteJwkSets = new HashMap<>(); + protected final Map<URI, String> signers = new HashMap<>(); + protected final ObjectMapper mapper = new ObjectMapper(); + + private final String issuer; + private final URI jku; + protected PerunAdapter adapter; + + protected JWTSigningAndValidationService jwtService; + + private final String affiliationsAttr; + + private final String orgUrlAttr; + + public Ga4ghBroker(PerunAdapter adapter, JWTSigningAndValidationService jwtService, Ga4ghConfig config, BrokerConfig brokerConfig) throws URISyntaxException, MalformedURLException { + this.adapter = adapter; + this.jwtService = jwtService; + this.affiliationsAttr = brokerConfig.getAffiliationsAttr(); + this.orgUrlAttr = brokerConfig.getOrgUrlAttr(); + this.issuer = brokerConfig.getIssuer(); + this.jku = new URL(brokerConfig.getJku()).toURI(); + + Utils.parseConfigFile(config, claimRepositories, remoteJwkSets, signers); + } + + public ArrayNode constructGa4ghPassportVisa(Long userId, String sub) { + List<Affiliation> affiliations = adapter.getAdapterRpc().getUserExtSourcesAffiliations(userId, affiliationsAttr, orgUrlAttr); + + ArrayNode ga4gh_passport_v1 = JsonNodeFactory.instance.arrayNode(); + long now = Instant.now().getEpochSecond(); + + addAffiliationAndRoles(now, ga4gh_passport_v1, affiliations, sub, userId); + addAcceptedTermsAndPolicies(now, ga4gh_passport_v1, userId, sub); + addResearcherStatuses(now, ga4gh_passport_v1, affiliations, sub, userId); + addControlledAccessGrants(now, ga4gh_passport_v1, sub, userId); + + return ga4gh_passport_v1; + } + + + protected abstract void addAffiliationAndRoles(long now, ArrayNode passport, List<Affiliation> affiliations, String sub, Long userId); + + protected abstract void addAcceptedTermsAndPolicies(long now, ArrayNode passport, Long userId, String sub); + + protected abstract void addResearcherStatuses(long now, ArrayNode passport, List<Affiliation> affiliations, String sub, Long userId); + + protected abstract void addControlledAccessGrants(long now, ArrayNode passport, String sub, Long userId); + + protected JsonNode createPassportVisa(String type, String sub, Long userId, String value, String source, + String by, long asserted, long expires, JsonNode condition) + { + long now = System.currentTimeMillis() / 1000L; + + if (asserted > now) { + log.warn("Visa asserted in future, it will be ignored!"); + log.debug("Visa information: perunUserId={}, sub={}, type={}, value={}, source={}, by={}, asserted={}", + userId, sub, type, value, source, by, Instant.ofEpochSecond(asserted)); + + return null; + } + + if (expires <= now) { + log.warn("Visa is expired, it will be ignored!"); + log.debug("Visa information: perunUserId={}, sub={}, type={}, value={}, source={}, by={}, expired={}", + userId, sub, type, value, source, by, Instant.ofEpochSecond(expires)); + + return null; + } + + Map<String, Object> passportVisaObject = new HashMap<>(); + passportVisaObject.put(Ga4ghPassportVisa.TYPE, type); + passportVisaObject.put(Ga4ghPassportVisa.ASSERTED, asserted); + passportVisaObject.put(Ga4ghPassportVisa.VALUE, value); + passportVisaObject.put(Ga4ghPassportVisa.SOURCE, source); + passportVisaObject.put(Ga4ghPassportVisa.BY, by); + + if (condition != null && !condition.isNull() && !condition.isMissingNode()) { + passportVisaObject.put(Ga4ghPassportVisa.CONDITION, condition); + } + + JWSHeader jwsHeader = new JWSHeader.Builder(JWSAlgorithm.parse(jwtService.getDefaultSigningAlgorithm().getName())) + .keyID(jwtService.getDefaultSignerKeyId()) + .type(JOSEObjectType.JWT) + .jwkURL(jku) + .build(); + + JWTClaimsSet jwtClaimsSet = new JWTClaimsSet.Builder() + .issuer(issuer) + .issueTime(new Date()) + .expirationTime(new Date(expires * 1000L)) + .subject(sub) + .jwtID(UUID.randomUUID().toString()) + .claim(Ga4ghPassportVisa.GA4GH_VISA_V1, passportVisaObject) + .build(); + + SignedJWT myToken = new SignedJWT(jwsHeader, jwtClaimsSet); + jwtService.signJwt(myToken); + + return JsonNodeFactory.instance.textNode(myToken.serialize()); + } + + protected void callPermissionsJwtAPI(Ga4ghClaimRepository repo, + Map<String, String> uriVariables, + ArrayNode passport, + Set<String> linkedIdentities) + { + log.debug("GA4GH: {}", uriVariables); + JsonNode response = callHttpJsonAPI(repo, uriVariables); + if (response != null) { + JsonNode visas = response.path(GA4GH_CLAIM); + if (visas.isArray()) { + for (JsonNode visaNode : visas) { + if (visaNode.isTextual()) { + Ga4ghPassportVisa visa = Utils.parseAndVerifyVisa(visaNode.asText(), signers, remoteJwkSets, mapper); + if (visa.isVerified()) { + log.debug("Adding a visa to passport: {}", visa); + passport.add(passport.textNode(visa.getJwt())); + linkedIdentities.add(visa.getLinkedIdentity()); + } else { + log.warn("Skipping visa: {}", visa); + } + } else { + log.warn("Element of {} is not a String: {}", GA4GH_CLAIM, visaNode); + } + } + } else { + log.warn("{} is not an array in {}", GA4GH_CLAIM, response); + } + } + } + + @SuppressWarnings("Duplicates") + private static JsonNode callHttpJsonAPI(Ga4ghClaimRepository repo, Map<String, String> uriVariables) { + //get permissions data + try { + JsonNode result; + try { + if (log.isDebugEnabled()) { + log.debug("Calling Permissions API at {}", repo.getRestTemplate().getUriTemplateHandler().expand(repo.getActionURL(), uriVariables)); + } + + result = repo.getRestTemplate().getForObject(repo.getActionURL(), JsonNode.class, uriVariables); + } catch (HttpClientErrorException ex) { + MediaType contentType = ex.getResponseHeaders().getContentType(); + String body = ex.getResponseBodyAsString(); + + log.error("HTTP ERROR: {}, URL: {}, Content-Type: {}", ex.getRawStatusCode(), repo.getActionURL(), contentType); + + if (ex.getRawStatusCode() == 404) { + log.warn("Got status 404 from Permissions endpoint {}, ELIXIR AAI user is not linked to user at Permissions API", + repo.getActionURL()); + + return null; + } + + if (JSON.equals(contentType.getSubtype())) { + try { + log.error(new ObjectMapper().readValue(body, JsonNode.class).path("message").asText()); + } catch (IOException e) { + log.error("cannot parse error message from JSON", e); + } + } else { + log.error("cannot make REST call, exception: {} message: {}", ex.getClass().getName(), ex.getMessage()); + } + + return null; + } + log.debug("Permissions API response: {}", result); + + return result; + } catch (Exception ex) { + log.error("Cannot get dataset permissions", ex); + } + + return null; + } +} diff --git a/src/main/java/cz/muni/ics/ga4gh/utils/Utils.java b/src/main/java/cz/muni/ics/ga4gh/utils/Utils.java new file mode 100644 index 0000000000000000000000000000000000000000..7037cb93b3eeb19733e802136441523ace3c841c --- /dev/null +++ b/src/main/java/cz/muni/ics/ga4gh/utils/Utils.java @@ -0,0 +1,218 @@ +package cz.muni.ics.ga4gh.utils; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.nimbusds.jose.Payload; +import com.nimbusds.jose.crypto.RSASSAVerifier; +import com.nimbusds.jose.jwk.JWK; +import com.nimbusds.jose.jwk.JWKMatcher; +import com.nimbusds.jose.jwk.JWKSelector; +import com.nimbusds.jose.jwk.RSAKey; +import com.nimbusds.jose.jwk.source.RemoteJWKSet; +import com.nimbusds.jose.proc.SecurityContext; +import com.nimbusds.jwt.JWTParser; +import com.nimbusds.jwt.SignedJWT; +import cz.muni.ics.ga4gh.config.Ga4ghConfig; +import cz.muni.ics.ga4gh.model.Ga4ghClaimRepository; +import cz.muni.ics.ga4gh.model.Ga4ghPassportVisa; +import cz.muni.ics.ga4gh.model.Repo; +import cz.muni.ics.ga4gh.model.RepoHeader; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.client.ClientHttpRequestInterceptor; +import org.springframework.http.client.InterceptingClientHttpRequestFactory; +import org.springframework.web.client.RestTemplate; + +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.time.Instant; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +@Slf4j +public class Utils { + + public static void parseConfigFile(Ga4ghConfig config, + List<Ga4ghClaimRepository> claimRepositories, + Map<URI, RemoteJWKSet<SecurityContext>> remoteJwkSets, + Map<URI, String> signers) + { + for (Repo repo : config.getRepos()) { + initializeRepo(repo, claimRepositories); + initializeSigner(signers, remoteJwkSets, repo.getName(), repo.getJwks()); + } + } + + public static Ga4ghPassportVisa parseAndVerifyVisa(String jwtString, + Map<URI, String> signers, + Map<URI, RemoteJWKSet<SecurityContext>> remoteJwkSets, + ObjectMapper mapper) + { + Ga4ghPassportVisa visa = new Ga4ghPassportVisa(jwtString); + + try { + SignedJWT signedJWT = (SignedJWT) JWTParser.parse(jwtString); + URI jku = signedJWT.getHeader().getJWKURL(); + + if (jku == null) { + log.error("JKU is missing in JWT header"); + return visa; + } + + visa.setSigner(signers.get(jku)); + RemoteJWKSet<SecurityContext> remoteJWKSet = remoteJwkSets.get(jku); + + if (remoteJWKSet == null) { + log.error("JKU '{}' is not among trusted key sets", jku); + return visa; + } + + List<JWK> keys = remoteJWKSet.get(new JWKSelector( + new JWKMatcher.Builder().keyID(signedJWT.getHeader().getKeyID()).build()), null); + + RSASSAVerifier verifier = new RSASSAVerifier(((RSAKey) keys.get(0)).toRSAPublicKey()); + visa.setVerified(signedJWT.verify(verifier)); + + if (visa.isVerified()) { + Utils.processPayload(mapper, visa, signedJWT.getPayload()); + } + } catch (Exception ex) { + log.error("Visa '{}' cannot be parsed and verified", jwtString, ex); + } + return visa; + } + + public static void processPayload(ObjectMapper mapper, Ga4ghPassportVisa visa, Payload payload) + throws IOException + { + JsonNode doc = mapper.readValue(payload.toString(), JsonNode.class); + checkVisaKey(visa, doc, Ga4ghPassportVisa.SUB); + checkVisaKey(visa, doc, Ga4ghPassportVisa.EXP); + checkVisaKey(visa, doc, Ga4ghPassportVisa.ISS); + + JsonNode visa_v1 = doc.path(Ga4ghPassportVisa.GA4GH_VISA_V1); + if (visa_v1.isMissingNode() || visa_v1.isNull() || visa_v1.isEmpty()) { + log.warn("Nothing available in '{}', considering visa as not verified", Ga4ghPassportVisa.GA4GH_VISA_V1); + visa.setVerified(false); + return; + } + + checkVisaKey(visa, visa_v1, Ga4ghPassportVisa.TYPE); + checkVisaKey(visa, visa_v1, Ga4ghPassportVisa.ASSERTED); + checkVisaKey(visa, visa_v1, Ga4ghPassportVisa.VALUE); + checkVisaKey(visa, visa_v1, Ga4ghPassportVisa.SOURCE); + checkVisaKey(visa, visa_v1, Ga4ghPassportVisa.BY); + + if (!visa.isVerified()) { + return; + } + + long exp = doc.get(Ga4ghPassportVisa.EXP).asLong(); + if (exp < Instant.now().getEpochSecond()) { + log.warn("visa expired on {}", isoDateTime(exp)); + visa.setVerified(false); + return; + } + + visa.setLinkedIdentity(URLEncoder.encode(doc.get(Ga4ghPassportVisa.SUB).asText(), StandardCharsets.UTF_8) + + ',' + URLEncoder.encode(doc.get(Ga4ghPassportVisa.ISS).asText(), StandardCharsets.UTF_8)); + + visa.setPrettyPayload( + visa_v1.get(Ga4ghPassportVisa.TYPE).asText() + ": '" + + visa_v1.get(Ga4ghPassportVisa.VALUE).asText() + "' asserted at '" + + isoDate(visa_v1.get(Ga4ghPassportVisa.ASSERTED).asLong()) + '\'' + ); + } + + public static long getOneYearExpires(long asserted) { + return getExpires(asserted, 1L); + } + + public static long getExpires(long asserted, long addYears) { + return Instant.ofEpochSecond(asserted).atZone(ZoneId.systemDefault()).plusYears(addYears).toEpochSecond(); + } + + private static void initializeSigner(Map<URI, String> signers, + Map<URI, RemoteJWKSet<SecurityContext>> remoteJwkSets, + String name, + String jwks) + { + try { + URL jku = new URL(jwks); + remoteJwkSets.put(jku.toURI(), new RemoteJWKSet<>(jku)); + signers.put(jku.toURI(), name); + + log.info("JWKS Signer '{}' added with keys '{}'", name, jwks); + } catch (MalformedURLException | URISyntaxException e) { + log.error("cannot add to RemoteJWKSet map: '{}' -> '{}'", name, jwks, e); + } + } + + private static void initializeRepo(Repo repo, List<Ga4ghClaimRepository> claimRepositories) { + String name = repo.getName(); + String actionURL = repo.getUrl(); + List<RepoHeader> headers = repo.getHeaders(); + + if (actionURL == null || headers.isEmpty()) { + log.error("claim repository '{}' not defined with url|auth_header|auth_value", repo); + return; + } + + RestTemplate restTemplate = new RestTemplate(); + restTemplate.setRequestFactory( + new InterceptingClientHttpRequestFactory(restTemplate.getRequestFactory(), getClientHttpRequestInterceptors(headers)) + ); + + claimRepositories.add(new Ga4ghClaimRepository(name, actionURL, restTemplate)); + log.info("GA4GH Claims Repository '{}' configured at '{}'", name, actionURL); + } + + private static void checkVisaKey(Ga4ghPassportVisa visa, JsonNode jsonNode, String key) { + if (jsonNode.path(key).isMissingNode()) { + log.warn("Key '{}' is missing in the Visa, therefore cannot be verified", key); + visa.setVerified(false); + } else { + switch (key) { + case Ga4ghPassportVisa.SUB: + visa.setSub(jsonNode.path(key).asText()); + break; + case Ga4ghPassportVisa.ISS: + visa.setIss(jsonNode.path(key).asText()); + break; + case Ga4ghPassportVisa.TYPE: + visa.setType(jsonNode.path(key).asText()); + break; + case Ga4ghPassportVisa.VALUE: + visa.setValue(jsonNode.path(key).asText()); + break; + default: + log.warn("Unknown visa key: {}", key); + } + } + } + + private static String isoDate(long linuxTime) { + return isoFormat(linuxTime, DateTimeFormatter.ISO_LOCAL_DATE); + } + + private static String isoDateTime(long linuxTime) { + return isoFormat(linuxTime, DateTimeFormatter.ISO_DATE_TIME); + } + + private static String isoFormat(long linuxTime, DateTimeFormatter formatter) { + ZonedDateTime zdt = ZonedDateTime.ofInstant(Instant.ofEpochSecond(linuxTime), ZoneId.systemDefault()); + return formatter.format(zdt); + } + + private static List<ClientHttpRequestInterceptor> getClientHttpRequestInterceptors(List<RepoHeader> headers) { + return new ArrayList<>(headers); + } +} diff --git a/src/main/resources/application-bbmri.yml b/src/main/resources/application-bbmri.yml new file mode 100644 index 0000000000000000000000000000000000000000..39cd3d19dbea8d22c75a7274d67a7e05cb546fec --- /dev/null +++ b/src/main/resources/application-bbmri.yml @@ -0,0 +1,77 @@ +--- + +spring: + profiles: bbmri + +broker: + bona-fide-status-attr: bona_fide_status + bona-fide-status-rems-attr: bona_fide_status_rems + group-affiliations-attr: groupAffiliations + terms-and-policies-group-id: 1 + affiliations-attr: affiliations + org-url-attr: orgUrl + ext-source-name: ext-source-name + issuer: issuer + path-to-jwk-file: path +adapter: + adapter-primary: rpc + call-fallback: false +ldap: + host: host + user: user + password: password + base-dn: dn + use-tls: false + use-ssl: true + allow-untrusted-ssl: false + timeout-secs: 10 + port: 636 +rpc: + enabled: true + url: url + username: username + password: password + serializer: json +attributes: + attributeMappings: + bona_fide_status: + internal-name: bona_fide_status + rpc-name: urn:perun:user:attribute-def:def:bonaFideStatus + ldap-name: bonaFideStatus + bona_fide_status_rems: + internal-name: bona_fide_status_rems + rpc-name: urn:perun:user:attribute-def:def:elixirBonaFideStatusREMS + ldap-name: bonaFideStatusREMS + groupAffiliations: + internal-name: groupAffiliations + rpc-name: urn:perun:group:attribute-def:def:groupAffiliations + ldap-name: groupAffiliations + affiliations: + internal-name: affiliations + rpc-name: urn:perun:ues:attribute-def:def:affiliation + ldap-name: + orgUrl: + internal-name: orgUrl + rpc-name: urn:perun:ues:attribute-def:def:organizationURL + ldap-name: +ga4gh: + repos: + - + name: repo1 + url: url1 + jwks: jwks1 + headers: + - + header: header + value: value + - + name: repo2 + url: url2 + jwks: jwks2 + headers: + - + header: header + value: value +basic-auth: + username: Honza + password: Lojza diff --git a/src/main/resources/application-elixir.yml b/src/main/resources/application-elixir.yml new file mode 100644 index 0000000000000000000000000000000000000000..881f452dd9c553e07db9b31a0703ae215cc394f9 --- /dev/null +++ b/src/main/resources/application-elixir.yml @@ -0,0 +1,92 @@ +--- + +spring: + config: + activate: + on-profile: elixir + mvc: + pathmatch: + matching-strategy: ant_path_matcher + +broker: + bona-fide-status-attr: bona_fide_status + bona-fide-status-rems-attr: bona_fide_status_rems + group-affiliations-attr: groupAffiliations + terms-and-policies-group-id: 1 + affiliations-attr: affiliations + org-url-attr: orgUrl + attributes-to-search: + - "elixir-persistent-shadow" + issuer: issuer + jku: jku-url + path-to-jwk-file: path +adapter: + adapter-primary: ldap + call-fallback: false +ldap: + host: host + user: user + password: password + base-dn: dn + use-tls: false + use-ssl: true + allow-untrusted-ssl: false + timeout-secs: 10 + port: 636 +rpc: + enabled: true + url: url + username: username + password: password + serializer: json +attributes: + attributeMappings: + bona_fide_status: + internal-name: bona_fide_status + rpc-name: name + ldap-name: name + bona_fide_status_rems: + internal-name: bona_fide_status_rems + rpc-name: name + ldap-name: name + groupAffiliations: + internal-name: groupAffiliations + rpc-name: name + ldap-name: name + affiliations: + internal-name: affiliations + rpc-name: name + ldap-name: name + orgUrl: + internal-name: orgUrl + rpc-name: name + ldap-name: name + elixir-persistent-shadow: + internal-name: elixir-persistent-shadow + rpc-name: name + ldap-name: name + preferred-mail: + internal-name: preferred-mail + rpc-name: name + ldap-name: name +ga4gh: + repos: + - + name: repo1 + url: url1 + jwks: jwks1 + headers: + - + header: header + value: value + - + name: repo2 + url: url2 + jwks: jwks2 + headers: + - + header: header + value: value +basic-auth: + username: username + password: password diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml new file mode 100644 index 0000000000000000000000000000000000000000..63d98d736ad5e47da768db98de5d9c5477c6e277 --- /dev/null +++ b/src/main/resources/application.yml @@ -0,0 +1,13 @@ +--- + +spring: + main: + allow-bean-definition-overriding: true + profiles: + active: elixir + +logging: + file: + path: /var/lib/tomcat9/logs/broker.log + level: + root: debug \ No newline at end of file diff --git a/update-versions.sh b/update-versions.sh new file mode 100755 index 0000000000000000000000000000000000000000..d89eb68a8cd4ec14448509c3fc12f9474e60fd5c --- /dev/null +++ b/update-versions.sh @@ -0,0 +1,2 @@ +#!/bin/bash +./mvnw versions:set -DnewVersion=$1 \ No newline at end of file