From 3225ad5a43b00533aebd1bc3b3ac365426fc73ba Mon Sep 17 00:00:00 2001 From: Mamoudou DIALLO Date: Fri, 23 Aug 2024 13:11:12 -0400 Subject: [PATCH] Initial checking --- .DS_Store | Bin 0 -> 6148 bytes .github/workflows/main.yml | 88 ++++++ .gitignore | 33 +++ .mvn/wrapper/maven-wrapper.properties | 19 ++ Dockerfile | 40 +++ mvnw | 259 ++++++++++++++++++ mvnw.cmd | 149 ++++++++++ pom.xml | 130 +++++++++ readme.md | 70 +++++ scripts/install.sh | 28 ++ .../moudjames23/scan2dojo/Application.java | 36 +++ .../scan2dojo/CustomShellPrompt.java | 13 + .../scan2dojo/command/S2DCommand.java | 192 +++++++++++++ .../scan2dojo/dto/Configuration.java | 92 +++++++ .../dto/requests/EngagementRequest.java | 15 + .../scan2dojo/dto/requests/ImportRequest.java | 16 ++ .../dto/requests/ProductRequest.java | 4 + .../dto/responses/EngagementResponse.java | 24 ++ .../dto/responses/ImportResponse.java | 24 ++ .../dto/responses/ProductResponse.java | 22 ++ .../dto/responses/ResponseMessage.java | 6 + .../scan2dojo/enums/EnumWithValue.java | 6 + .../scan2dojo/enums/HttpMethod.java | 18 ++ .../moudjames23/scan2dojo/enums/ScanType.java | 209 ++++++++++++++ .../moudjames23/scan2dojo/enums/Severity.java | 22 ++ .../exception/CommandNotFoundException.java | 20 ++ .../scan2dojo/exception/GlobalException.java | 12 + .../scan2dojo/http/Engagement.java | 64 +++++ .../moudjames23/scan2dojo/http/Import.java | 56 ++++ .../moudjames23/scan2dojo/http/Product.java | 64 +++++ .../moudjames23/scan2dojo/http/Request.java | 42 +++ .../moudjames23/scan2dojo/http/Scan2Dojo.java | 95 +++++++ .../moudjames23/scan2dojo/util/DateUtil.java | 32 +++ .../moudjames23/scan2dojo/util/EnumUtil.java | 21 ++ .../scan2dojo/util/MessageUtil.java | 17 ++ .../scan2dojo/util/RequestUtil.java | 48 ++++ .../scan2dojo/util/URLValidator.java | 41 +++ .../META-INF/native-image/reflect-config.json | 29 ++ .../native-image/resource-config.json | 9 + src/main/resources/application.properties | 14 + .../scan2dojo/Scan2dojoApplicationTests.java | 11 + .../scan2dojo/http/Scan2DojoTest.java | 104 +++++++ .../scan2dojo/util/DateUtilTest.java | 39 +++ .../scan2dojo/util/EnumUtilTest.java | 61 +++++ .../scan2dojo/util/MessageUtilTest.java | 61 +++++ .../scan2dojo/util/URLValidatorTest.java | 117 ++++++++ 46 files changed, 2472 insertions(+) create mode 100644 .DS_Store create mode 100644 .github/workflows/main.yml create mode 100644 .gitignore create mode 100644 .mvn/wrapper/maven-wrapper.properties create mode 100644 Dockerfile create mode 100755 mvnw create mode 100644 mvnw.cmd create mode 100644 pom.xml create mode 100644 readme.md create mode 100644 scripts/install.sh create mode 100644 src/main/java/io/github/moudjames23/scan2dojo/Application.java create mode 100644 src/main/java/io/github/moudjames23/scan2dojo/CustomShellPrompt.java create mode 100644 src/main/java/io/github/moudjames23/scan2dojo/command/S2DCommand.java create mode 100644 src/main/java/io/github/moudjames23/scan2dojo/dto/Configuration.java create mode 100644 src/main/java/io/github/moudjames23/scan2dojo/dto/requests/EngagementRequest.java create mode 100644 src/main/java/io/github/moudjames23/scan2dojo/dto/requests/ImportRequest.java create mode 100644 src/main/java/io/github/moudjames23/scan2dojo/dto/requests/ProductRequest.java create mode 100644 src/main/java/io/github/moudjames23/scan2dojo/dto/responses/EngagementResponse.java create mode 100644 src/main/java/io/github/moudjames23/scan2dojo/dto/responses/ImportResponse.java create mode 100644 src/main/java/io/github/moudjames23/scan2dojo/dto/responses/ProductResponse.java create mode 100644 src/main/java/io/github/moudjames23/scan2dojo/dto/responses/ResponseMessage.java create mode 100644 src/main/java/io/github/moudjames23/scan2dojo/enums/EnumWithValue.java create mode 100644 src/main/java/io/github/moudjames23/scan2dojo/enums/HttpMethod.java create mode 100644 src/main/java/io/github/moudjames23/scan2dojo/enums/ScanType.java create mode 100644 src/main/java/io/github/moudjames23/scan2dojo/enums/Severity.java create mode 100644 src/main/java/io/github/moudjames23/scan2dojo/exception/CommandNotFoundException.java create mode 100644 src/main/java/io/github/moudjames23/scan2dojo/exception/GlobalException.java create mode 100644 src/main/java/io/github/moudjames23/scan2dojo/http/Engagement.java create mode 100644 src/main/java/io/github/moudjames23/scan2dojo/http/Import.java create mode 100644 src/main/java/io/github/moudjames23/scan2dojo/http/Product.java create mode 100644 src/main/java/io/github/moudjames23/scan2dojo/http/Request.java create mode 100644 src/main/java/io/github/moudjames23/scan2dojo/http/Scan2Dojo.java create mode 100644 src/main/java/io/github/moudjames23/scan2dojo/util/DateUtil.java create mode 100644 src/main/java/io/github/moudjames23/scan2dojo/util/EnumUtil.java create mode 100644 src/main/java/io/github/moudjames23/scan2dojo/util/MessageUtil.java create mode 100644 src/main/java/io/github/moudjames23/scan2dojo/util/RequestUtil.java create mode 100644 src/main/java/io/github/moudjames23/scan2dojo/util/URLValidator.java create mode 100644 src/main/resources/META-INF/native-image/reflect-config.json create mode 100644 src/main/resources/META-INF/native-image/resource-config.json create mode 100644 src/main/resources/application.properties create mode 100644 src/test/java/io/github/moudjames23/scan2dojo/Scan2dojoApplicationTests.java create mode 100644 src/test/java/io/github/moudjames23/scan2dojo/http/Scan2DojoTest.java create mode 100644 src/test/java/io/github/moudjames23/scan2dojo/util/DateUtilTest.java create mode 100644 src/test/java/io/github/moudjames23/scan2dojo/util/EnumUtilTest.java create mode 100644 src/test/java/io/github/moudjames23/scan2dojo/util/MessageUtilTest.java create mode 100644 src/test/java/io/github/moudjames23/scan2dojo/util/URLValidatorTest.java diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..462b6b4270655c941b10c3d4e947e978b4a44d38 GIT binary patch literal 6148 zcmeHK%}T>S5Z-NTn^J@v6nb3nTCh>;4_-p7FJMFuDm5Xc24l9gsX3HF&iX<=iO=KA z?&c6IcoVTRu=~x<&u->}>ykqpJeW5LAIEC!R!u4*HR{7 zsR!XrJkCe<&V@{~AWp|Kl@Lc`2)VzF(@5r?T%^%l<@(xTwXOEZ?k<;uvz|CObyhvG zbe!X(o){dStX6GncW?jvYV@4Er1DJ@$$@hv+XhQ`2c=Tet3OK>;Qk<>A-V<&jcV(F4zJJXZy=(8j&BJ>VbC>LXao-k z*QtOym76C9*XdvvCeAfjXw>P9tCe9MvvT=(;c9iT3l+||tC4zQfEcJUP}f5n&;LvK zW!66Ot0^=h28e-w#sF^&{h> $GITHUB_ENV + + - name: Build and push Docker image + uses: docker/build-push-action@v5 + with: + context: . + push: true + tags: moudjames23/scan2dojo:${{ env.VERSION }} + file: Dockerfile diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..549e00a --- /dev/null +++ b/.gitignore @@ -0,0 +1,33 @@ +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000..8f96f52 --- /dev/null +++ b/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,19 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# 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. +wrapperVersion=3.3.2 +distributionType=only-script +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.7/apache-maven-3.9.7-bin.zip diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..a24d4c0 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,40 @@ +FROM eclipse-temurin:22-alpine AS jre-builder + +# Install binutils, required by jlink +RUN apk update && \ + apk add binutils + +# Build small JRE image +RUN $JAVA_HOME/bin/jlink \ + --verbose \ + --add-modules ALL-MODULE-PATH \ + --strip-debug \ + --no-man-pages \ + --no-header-files \ + --compress=2 \ + --output /optimized-jdk-17 + +# Second stage, Use the custom JRE and build the app image +FROM alpine:latest +ENV JAVA_HOME=/opt/jdk/jdk-17 +ENV PATH="${JAVA_HOME}/bin:${PATH}" + +# copy JRE from the base image +COPY --from=jre-builder /optimized-jdk-17 $JAVA_HOME + +# Add app user +ARG APPLICATION_USER=spring + +# Create a user to run the application, don't run as root +RUN addgroup --system $APPLICATION_USER && adduser --system $APPLICATION_USER --ingroup $APPLICATION_USER + +# Create the application directory +RUN mkdir /app && chown -R $APPLICATION_USER /app + +COPY --chown=$APPLICATION_USER:$APPLICATION_USER target/*.jar /app/app.jar + +WORKDIR /app + +USER $APPLICATION_USER + +ENTRYPOINT [ "java", "-jar", "/app/app.jar" ] \ No newline at end of file diff --git a/mvnw b/mvnw new file mode 100755 index 0000000..d7c358e --- /dev/null +++ b/mvnw @@ -0,0 +1,259 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# 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. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Apache Maven Wrapper startup batch script, version 3.3.2 +# +# Optional ENV vars +# ----------------- +# JAVA_HOME - location of a JDK home dir, required when download maven via java source +# MVNW_REPOURL - repo url base for downloading maven distribution +# MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +# MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output +# ---------------------------------------------------------------------------- + +set -euf +[ "${MVNW_VERBOSE-}" != debug ] || set -x + +# OS specific support. +native_path() { printf %s\\n "$1"; } +case "$(uname)" in +CYGWIN* | MINGW*) + [ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")" + native_path() { cygpath --path --windows "$1"; } + ;; +esac + +# set JAVACMD and JAVACCMD +set_java_home() { + # For Cygwin and MinGW, ensure paths are in Unix format before anything is touched + if [ -n "${JAVA_HOME-}" ]; then + if [ -x "$JAVA_HOME/jre/sh/java" ]; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACCMD="$JAVA_HOME/jre/sh/javac" + else + JAVACMD="$JAVA_HOME/bin/java" + JAVACCMD="$JAVA_HOME/bin/javac" + + if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then + echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2 + echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2 + return 1 + fi + fi + else + JAVACMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v java + )" || : + JAVACCMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v javac + )" || : + + if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then + echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2 + return 1 + fi + fi +} + +# hash string like Java String::hashCode +hash_string() { + str="${1:-}" h=0 + while [ -n "$str" ]; do + char="${str%"${str#?}"}" + h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296)) + str="${str#?}" + done + printf %x\\n $h +} + +verbose() { :; } +[ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; } + +die() { + printf %s\\n "$1" >&2 + exit 1 +} + +trim() { + # MWRAPPER-139: + # Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds. + # Needed for removing poorly interpreted newline sequences when running in more + # exotic environments such as mingw bash on Windows. + printf "%s" "${1}" | tr -d '[:space:]' +} + +# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties +while IFS="=" read -r key value; do + case "${key-}" in + distributionUrl) distributionUrl=$(trim "${value-}") ;; + distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;; + esac +done <"${0%/*}/.mvn/wrapper/maven-wrapper.properties" +[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in ${0%/*}/.mvn/wrapper/maven-wrapper.properties" + +case "${distributionUrl##*/}" in +maven-mvnd-*bin.*) + MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ + case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in + *AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;; + :Darwin*x86_64) distributionPlatform=darwin-amd64 ;; + :Darwin*arm64) distributionPlatform=darwin-aarch64 ;; + :Linux*x86_64*) distributionPlatform=linux-amd64 ;; + *) + echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2 + distributionPlatform=linux-amd64 + ;; + esac + distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip" + ;; +maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;; +*) MVN_CMD="mvn${0##*/mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;; +esac + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +[ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}" +distributionUrlName="${distributionUrl##*/}" +distributionUrlNameMain="${distributionUrlName%.*}" +distributionUrlNameMain="${distributionUrlNameMain%-bin}" +MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}" +MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")" + +exec_maven() { + unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || : + exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD" +} + +if [ -d "$MAVEN_HOME" ]; then + verbose "found existing MAVEN_HOME at $MAVEN_HOME" + exec_maven "$@" +fi + +case "${distributionUrl-}" in +*?-bin.zip | *?maven-mvnd-?*-?*.zip) ;; +*) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;; +esac + +# prepare tmp dir +if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then + clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; } + trap clean HUP INT TERM EXIT +else + die "cannot create temp dir" +fi + +mkdir -p -- "${MAVEN_HOME%/*}" + +# Download and Install Apache Maven +verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +verbose "Downloading from: $distributionUrl" +verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" + +# select .zip or .tar.gz +if ! command -v unzip >/dev/null; then + distributionUrl="${distributionUrl%.zip}.tar.gz" + distributionUrlName="${distributionUrl##*/}" +fi + +# verbose opt +__MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR='' +[ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v + +# normalize http auth +case "${MVNW_PASSWORD:+has-password}" in +'') MVNW_USERNAME='' MVNW_PASSWORD='' ;; +has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;; +esac + +if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then + verbose "Found wget ... using wget" + wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl" +elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then + verbose "Found curl ... using curl" + curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl" +elif set_java_home; then + verbose "Falling back to use Java to download" + javaSource="$TMP_DOWNLOAD_DIR/Downloader.java" + targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName" + cat >"$javaSource" <<-END + public class Downloader extends java.net.Authenticator + { + protected java.net.PasswordAuthentication getPasswordAuthentication() + { + return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() ); + } + public static void main( String[] args ) throws Exception + { + setDefault( new Downloader() ); + java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() ); + } + } + END + # For Cygwin/MinGW, switch paths to Windows format before running javac and java + verbose " - Compiling Downloader.java ..." + "$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java" + verbose " - Running Downloader.java ..." + "$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")" +fi + +# If specified, validate the SHA-256 sum of the Maven distribution zip file +if [ -n "${distributionSha256Sum-}" ]; then + distributionSha256Result=false + if [ "$MVN_CMD" = mvnd.sh ]; then + echo "Checksum validation is not supported for maven-mvnd." >&2 + echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + elif command -v sha256sum >/dev/null; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c >/dev/null 2>&1; then + distributionSha256Result=true + fi + elif command -v shasum >/dev/null; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then + distributionSha256Result=true + fi + else + echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2 + echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + fi + if [ $distributionSha256Result = false ]; then + echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2 + echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2 + exit 1 + fi +fi + +# unzip and move +if command -v unzip >/dev/null; then + unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip" +else + tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar" +fi +printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/mvnw.url" +mv -- "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME" + +clean || : +exec_maven "$@" diff --git a/mvnw.cmd b/mvnw.cmd new file mode 100644 index 0000000..6f779cf --- /dev/null +++ b/mvnw.cmd @@ -0,0 +1,149 @@ +<# : batch portion +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM https://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Apache Maven Wrapper startup batch script, version 3.3.2 +@REM +@REM Optional ENV vars +@REM MVNW_REPOURL - repo url base for downloading maven distribution +@REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +@REM MVNW_VERBOSE - true: enable verbose log; others: silence the output +@REM ---------------------------------------------------------------------------- + +@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0) +@SET __MVNW_CMD__= +@SET __MVNW_ERROR__= +@SET __MVNW_PSMODULEP_SAVE=%PSModulePath% +@SET PSModulePath= +@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @( + IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B) +) +@SET PSModulePath=%__MVNW_PSMODULEP_SAVE% +@SET __MVNW_PSMODULEP_SAVE= +@SET __MVNW_ARG0_NAME__= +@SET MVNW_USERNAME= +@SET MVNW_PASSWORD= +@IF NOT "%__MVNW_CMD__%"=="" (%__MVNW_CMD__% %*) +@echo Cannot start maven from wrapper >&2 && exit /b 1 +@GOTO :EOF +: end batch / begin powershell #> + +$ErrorActionPreference = "Stop" +if ($env:MVNW_VERBOSE -eq "true") { + $VerbosePreference = "Continue" +} + +# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties +$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl +if (!$distributionUrl) { + Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" +} + +switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) { + "maven-mvnd-*" { + $USE_MVND = $true + $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip" + $MVN_CMD = "mvnd.cmd" + break + } + default { + $USE_MVND = $false + $MVN_CMD = $script -replace '^mvnw','mvn' + break + } +} + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +if ($env:MVNW_REPOURL) { + $MVNW_REPO_PATTERN = if ($USE_MVND) { "/org/apache/maven/" } else { "/maven/mvnd/" } + $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace '^.*'+$MVNW_REPO_PATTERN,'')" +} +$distributionUrlName = $distributionUrl -replace '^.*/','' +$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$','' +$MAVEN_HOME_PARENT = "$HOME/.m2/wrapper/dists/$distributionUrlNameMain" +if ($env:MAVEN_USER_HOME) { + $MAVEN_HOME_PARENT = "$env:MAVEN_USER_HOME/wrapper/dists/$distributionUrlNameMain" +} +$MAVEN_HOME_NAME = ([System.Security.Cryptography.MD5]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join '' +$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME" + +if (Test-Path -Path "$MAVEN_HOME" -PathType Container) { + Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME" + Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" + exit $? +} + +if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) { + Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl" +} + +# prepare tmp dir +$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile +$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir" +$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null +trap { + if ($TMP_DOWNLOAD_DIR.Exists) { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } + } +} + +New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null + +# Download and Install Apache Maven +Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +Write-Verbose "Downloading from: $distributionUrl" +Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" + +$webclient = New-Object System.Net.WebClient +if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) { + $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD) +} +[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 +$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null + +# If specified, validate the SHA-256 sum of the Maven distribution zip file +$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum +if ($distributionSha256Sum) { + if ($USE_MVND) { + Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." + } + Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash + if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) { + Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property." + } +} + +# unzip and move +Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null +Rename-Item -Path "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" -NewName $MAVEN_HOME_NAME | Out-Null +try { + Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null +} catch { + if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) { + Write-Error "fail to move MAVEN_HOME" + } +} finally { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } +} + +Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..2444e43 --- /dev/null +++ b/pom.xml @@ -0,0 +1,130 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 3.3.1 + + + io.github.moudjames23 + scan2dojo + 1.0.0 + Scan2dojo + Demo project for Spring Boot + + + + + + + + + + + + + + + + 17 + 3.3.0 + 2.10.1 + 4.10.0 + 2.14.2 + 1.18.30 + + + + + org.springframework.shell + spring-shell-starter + + + + org.projectlombok + lombok + ${lombok.version} + provided + + + + org.springframework.boot + spring-boot-configuration-processor + true + + + org.springframework.boot + spring-boot-starter-test + test + + + org.springframework + spring-test + + + io.projectreactor + reactor-test + test + + + org.springframework.shell + spring-shell-starter-test + test + + + com.h2database + h2 + runtime + + + org.springframework.boot + spring-boot-starter-data-jpa + + + com.google.code.gson + gson + ${gson.version} + + + com.squareup.okhttp3 + okhttp + 4.10.0 + + + com.fasterxml.jackson.datatype + jackson-datatype-hppc + ${jackson.verion} + + + + + + org.springframework.shell + spring-shell-dependencies + ${spring-shell.version} + pom + import + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + org.graalvm.buildtools + native-maven-plugin + + + + + + scan2dojo + + + diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..f8f531d --- /dev/null +++ b/readme.md @@ -0,0 +1,70 @@ + +## Scan2dojo +**scan2dojo** is a CLI (Command Line Interface) allowing you to easily upload scan reports to [DefectDojo](https://github.com/DefectDojo/django-DefectDojo). It can be used both on the command line and integrated into a CI/CD pipeline to automate the import of your security reports. + +## Features + +- **Configuration**: Configure the endpoint and API key for integration with DefectDojo. +- **Product Creation**: Create new products in DefectDojo. +- **Creating commitments**: Create new commitments associated with products. +- **Scan Import**: Import scan results into DefectDojo with advanced configuration options. + +## Facility + +### Via Docker + +The recommended way to use `scan2dojo` is through Docker. Make sure you have Docker installed on your machine. + + docker pull moudjames23/scan2dojo:v1 + +### MacOs or Linux + + curl -L https://github.com/moudjames23/scan2dojo/releases/download/v1.0.0/install.sh | bash + +### Windows + +## Usage + +### Show version + +To view the current version of `scan2dojo`: + + scan2dojo version + +### Show help + +To display the custom help message, use: + + scan2dojo help + +### Configure endpoint and API key + +Before using `scan2dojo`, you need to configure it to connect to your DefectDojo instance: + + scan2dojo configure --endpoint https://api.example.com --apiKey your-api-key + +### Create a product + +To create a new product in DefectDojo: + + scan2dojo create:product --name "Product Name" --description "Product Description" --typeId 1 --slaConfiguration 1 + +### Create an engagement + +To create a new engagement in DefectDojo: + + scan2dojo create:engagement --name "Engagement name" --description "Engagement description" --start "2024-01-01" --end "2024-12-31" --productId 123 + +### Import a scan result + +To import a scan result into DefectDojo: + + scan2dojo import --scanType "Trivy Scan" --file /path/to/scan_result.json --productName "Product Name" --engagementName "Engagement Name" --minimumSeverity High + +## CI/CD integration + +### GitHub Actions + +### GitLab CI + +###Jenkins diff --git a/scripts/install.sh b/scripts/install.sh new file mode 100644 index 0000000..d51c21f --- /dev/null +++ b/scripts/install.sh @@ -0,0 +1,28 @@ +#!/bin/bash + +# Define variables +CLI_NAME="scan2dojo" +VERSION="v1.0.0" +OS=$(uname | tr '[:upper:]' '[:lower:]') + +# Determine the download URL based on OS +if [ "$OS" == "darwin" ]; then + DOWNLOAD_URL="https://github.com/moudjames23/scan2dojo/releases/download/$VERSION/$CLI_NAME-macos" +elif [ "$OS" == "linux" ]; then + DOWNLOAD_URL="https://github.com/moudjames23/scan2dojo/releases/download/$VERSION/$CLI_NAME-linux" +else + echo "Unsupported OS: $OS" + exit 1 +fi + +# Download the binary +echo "Downloading $CLI_NAME from $DOWNLOAD_URL..." +curl -L "$DOWNLOAD_URL" -o "$CLI_NAME" + +# Make the binary executable +chmod +x "$CLI_NAME" + +# Move the binary to /usr/local/bin (or another directory in PATH) +sudo mv "$CLI_NAME" /usr/local/bin/ + +echo "$CLI_NAME installed successfully!" diff --git a/src/main/java/io/github/moudjames23/scan2dojo/Application.java b/src/main/java/io/github/moudjames23/scan2dojo/Application.java new file mode 100644 index 0000000..a575efa --- /dev/null +++ b/src/main/java/io/github/moudjames23/scan2dojo/Application.java @@ -0,0 +1,36 @@ +package io.github.moudjames23.scan2dojo; + +import io.github.moudjames23.scan2dojo.exception.CommandNotFoundException; +import okhttp3.OkHttpClient; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; +import org.springframework.shell.result.CommandNotFoundMessageProvider; + +import java.util.concurrent.TimeUnit; + +@SpringBootApplication +public class Application { + + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + + } + + + @Bean + public OkHttpClient okHttpClient() + { + return new OkHttpClient.Builder() + .connectTimeout(60, TimeUnit.SECONDS) + .readTimeout(75, TimeUnit.SECONDS) + .writeTimeout(120, TimeUnit.SECONDS) + .build(); + } + + @Bean + public CommandNotFoundMessageProvider provider() + { + return new CommandNotFoundException(); + } +} diff --git a/src/main/java/io/github/moudjames23/scan2dojo/CustomShellPrompt.java b/src/main/java/io/github/moudjames23/scan2dojo/CustomShellPrompt.java new file mode 100644 index 0000000..92d0853 --- /dev/null +++ b/src/main/java/io/github/moudjames23/scan2dojo/CustomShellPrompt.java @@ -0,0 +1,13 @@ +package io.github.moudjames23.scan2dojo; + +import org.jline.utils.AttributedString; +import org.springframework.context.annotation.Configuration; +import org.springframework.shell.jline.PromptProvider; + +@Configuration +public class CustomShellPrompt implements PromptProvider { + @Override + public AttributedString getPrompt() { + return new AttributedString("scan2dojo:>"); + } +} diff --git a/src/main/java/io/github/moudjames23/scan2dojo/command/S2DCommand.java b/src/main/java/io/github/moudjames23/scan2dojo/command/S2DCommand.java new file mode 100644 index 0000000..d2e0101 --- /dev/null +++ b/src/main/java/io/github/moudjames23/scan2dojo/command/S2DCommand.java @@ -0,0 +1,192 @@ +package io.github.moudjames23.scan2dojo.command; + +import io.github.moudjames23.scan2dojo.dto.requests.EngagementRequest; +import io.github.moudjames23.scan2dojo.dto.requests.ImportRequest; +import io.github.moudjames23.scan2dojo.dto.requests.ProductRequest; +import io.github.moudjames23.scan2dojo.http.Scan2Dojo; +import io.github.moudjames23.scan2dojo.util.DateUtil; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.shell.standard.ShellComponent; +import org.springframework.shell.standard.ShellMethod; +import org.springframework.shell.standard.ShellOption; + +import java.io.IOException; +import java.net.URISyntaxException; + + +@ShellComponent +public class S2DCommand { + + private final Scan2Dojo scan2Dojo; + + @Value("${APP_VERSION}") + private String version; + + + public S2DCommand(Scan2Dojo importService) { + this.scan2Dojo = importService; + } + + /** + * Displays the current version of the Scan2Dojo application. + * + */ + @ShellMethod(key = "version", value = "show the current version of scan2dojo") + public String showVersion() { + return "Version: " + version; + } + + @ShellMethod(value = "Display custom help", key = "help") + public String displayHelp() { + + return "Scanner for vulnerabilities in container images, file systems, and Git repositories,\n" + + "as well as for configuration issues and hard-coded secrets\n\n" + + "Usage:\n" + + " scan2dojo [global flags] command [flags] target\n" + + " scan2dojo [command]\n\n" + + "Examples:\n" + + " # Show the current version of scan2dojo\n" + + " $ scan2dojo version\n\n" + + " # Configure endpoint and API key\n" + + " $ scan2dojo configure --endpoint https://api.example.com --apiKey your-api-key\n\n" + + " # Create a new product in DefectDojo\n" + + " $ scan2dojo create:product --name 'My Product' --description 'Product description' --typeId 1 --slaConfiguration 1\n\n" + + " # Import a scan result to DefectDojo\n" + + " $ scan2dojo import --scanType 'Trivy Scan' --file /path/to/scan_result.json --productName 'My Product' --engagementName 'Release Engagement' --minimumSeverity High\n\n" + + "Scanning Commands:\n" + + " configure Configure endpoint and API key\n" + + " create:product Create new product\n" + + " create:engagement Create new engagement\n" + + " import Import scan result to DefectDojo\n\n" + + "Utility Commands:\n" + + " version Show the current version of scan2dojo\n" + + " help Display this custom help message\n\n"; + } + + + /** + * Shell command to configure the endpoint and API key for Scan2Dojo. + * + *

+ * This method sets the endpoint and API key that will be used by the application + * to communicate with the Scan2Dojo service. If no endpoint or API key is specified, + * default values (empty strings) will be used. + *

+ * + * @param endpoint The URL of the Scan2Dojo service endpoint. Defaults to an empty string. + * @param apiKey The API key used to authenticate requests to the endpoint. Defaults to an empty string. + * @throws IOException If an I/O error occurs during configuration. + */ + @ShellMethod(key = "configure", value = "Configure endpoint and apikey") + public void configure( + @ShellOption(defaultValue = "", help = "The URL of the Scan2Dojo service endpoint.") String endpoint, + @ShellOption(defaultValue = "", help = "The API key used to authenticate requests to the endpoint.") String apiKey + ) throws IOException, URISyntaxException { + + this.scan2Dojo.configure(endpoint, apiKey); + + } + + + /** + * Shell command to create a new product in the defectdojo. + * + *

+ * This method allows the user to create a new product by providing the necessary details + * such as the product name, description, type ID, and SLA configuration. If the SLA + * configuration is set to 0, it will be defaulted to 1. + *

+ * + * @param name The name of the product. This is a required field. + * @param description A brief description of the product. This is a required field. + * @param typeId The ID representing the type of the product. This is a required field. + * @param slaConfiguration The Service Level Agreement (SLA) configuration ID for the product. If set to 0, it defaults to 1. + * @throws IOException If an input/output error occurs during the creation process. + */ + @ShellMethod(key = "create:product", value = "create new product") + public void createProduct( + @ShellOption(help = "The name of the product.") String name, + @ShellOption(help = "A brief description of the product.") String description, + @ShellOption(help = "The ID representing the type of the product.") int typeId, + @ShellOption(help = "The Service Level Agreement (SLA) configuration ID for the product. Defaults to 1 if set to 0.") int slaConfiguration + ) throws IOException { + + + if (slaConfiguration == 0) slaConfiguration = 1; + + ProductRequest productRequest = new ProductRequest(name, description, typeId, slaConfiguration); + + this.scan2Dojo.createProduct(productRequest); + } + + /** + * Shell command to create a new engagement in the defectdojo. + * + *

+ * This method allows the user to create a new engagement by providing necessary details such as + * the engagement name, description, start and end dates, and the associated product ID. If the + * start and end dates are not provided, they default to today's date and one year from today, respectively. + *

+ * + * @param name The name of the engagement. This is a required field. + * @param description A brief description of the engagement. This is a required field. + * @param start The start date of the engagement in the format yyyy-MM-dd. Defaults to today's date if not provided. + * @param end The end date of the engagement in the format yyyy-MM-dd. Defaults to one year from today's date if not provided. + * @param productId The ID of the product associated with this engagement. This is a required field. + * @throws IOException If an input/output error occurs during the creation process. + */ + @ShellMethod(key = "create:engagement", value = "create new engagement") + public void createEngagement( + @ShellOption(help = "The name of the engagement.") String name, + @ShellOption(help = "A brief description of the engagement.") String description, + @ShellOption(help = "The start date of the engagement in the format yyyy-MM-dd. Defaults to today's date if not provided.", defaultValue = "") String start, + @ShellOption(help = "The end date of the engagement in the format yyyy-MM-dd. Defaults to one year from today's date if not provided.", defaultValue = "") String end, + @ShellOption(help = "The ID of the product associated with this engagement.") int productId + ) throws IOException { + + start = start.isEmpty() ? DateUtil.today() : start; + end = end.isEmpty() ? DateUtil.nextYear() : end; + + EngagementRequest engagementRequest = new EngagementRequest(name, description, productId, start, end); + + this.scan2Dojo.createEngagement(engagementRequest); + } + + + /** + * Imports a scan result to DefectDojo. + * + * @param active Indicates if the scan result should be marked as active. Default is true. + * Example: --active true + * @param verified Indicates if the scan result should be marked as verified. Default is true. + * Example: --verified true + * @param scanType The type of the scan. This should correspond to the scanner used. + * Example: --scanType "Trivy Scan" + * @param file The path to the scan result file to be imported. + * Example: --file /path/to/scan_result.json + * @param productName The name of the product in DefectDojo to which the scan result belongs. + * Example: --productName "My Product" + * @param engagementName The name of the engagement in DefectDojo under which the scan result will be recorded. + * Example: --engagementName "Release Engagement" + * @param minimumSeverity The minimum severity of findings to be imported. Possible values are Low, Medium, High, etc. + * Example: --minimumSeverity High + */ + @ShellMethod(key = "import", value = "import scan result to defectdojo") + public void importer( + @ShellOption(defaultValue = "true", help = "Indicates if the scan result should be marked as active. Default is true.") Boolean active, + @ShellOption(defaultValue = "true", help = "Indicates if the scan result should be marked as verified. Default is true.") Boolean verified, + @ShellOption(help = "The type of the scan. This should correspond to the scanner used.") String scanType, + @ShellOption(help = "The path to the scan result file to be imported.") String file, + @ShellOption(help = "The name of the product in DefectDojo to which the scan result belongs.") String productName, + @ShellOption(help = "The name of the engagement in DefectDojo under which the scan result will be recorded.") String engagementName, + @ShellOption(help = "The minimum severity of findings to be imported. Possible values are Low, Medium, High, etc.") String minimumSeverity + ) throws IOException { + + ImportRequest request = new ImportRequest(active, verified, scanType, file, productName, engagementName, minimumSeverity); + + this.scan2Dojo.importScan(request); + + } + + +} diff --git a/src/main/java/io/github/moudjames23/scan2dojo/dto/Configuration.java b/src/main/java/io/github/moudjames23/scan2dojo/dto/Configuration.java new file mode 100644 index 0000000..7323774 --- /dev/null +++ b/src/main/java/io/github/moudjames23/scan2dojo/dto/Configuration.java @@ -0,0 +1,92 @@ +package io.github.moudjames23.scan2dojo.dto; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import lombok.*; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URISyntaxException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.HashMap; +import java.util.Map; + +import static io.github.moudjames23.scan2dojo.util.MessageUtil.*; +import static io.github.moudjames23.scan2dojo.util.URLValidator.checkURL; + + +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@Builder +@ToString +public class Configuration { + + private static final String CONFIG_FILE_NAME = "config.json"; + private static final ObjectMapper objectMapper = new ObjectMapper().enable(SerializationFeature.INDENT_OUTPUT); + + private String endpoint; + + + private String apiKey; + + private static final Path configPath = Paths.get(System.getProperty("user.home"), ".config", "scan2dojo", CONFIG_FILE_NAME); + + + public void load() throws IOException { + + + + if (Files.exists(configPath)) { + Map data = objectMapper.readValue(Files.newInputStream(configPath), Map.class); + + if (data.get("endpoint") == null || data.get("endpoint").isEmpty()) { + printlnWithBorder(RED, "Endpoint is not defined, please provide a valid endpoint."); + return; + } + + if (data.get("apikey") == null || data.get("apikey").isEmpty()) { + printlnWithBorder(RED, "API key is not defined, please provide a valid API key."); + return; + } + + this.endpoint = data.get("endpoint"); + this.apiKey = data.get("apikey"); + } else { + printlnWithBorder(RED, "Configuration file not found. Please run 'scan2dojo configure' to set up the configuration."); + } + } + + + public void save() throws IOException { + + if (this.endpoint == null || this.endpoint.isEmpty()) { + printlnWithBorder(RED, "Endpoint is not defined, please provide a valid endpoint."); + return; + } + + checkURL(endpoint); + + if (this.apiKey == null || this.apiKey.isEmpty()) { + printlnWithBorder(RED, "API key is not defined, please provide a valid API key."); + return; + } + + Files.createDirectories(configPath.getParent()); + + Map data = new HashMap<>(); + data.put("endpoint", this.endpoint); + data.put("apikey", this.apiKey); + + objectMapper.writeValue(configPath.toFile(), data); + + } + +} diff --git a/src/main/java/io/github/moudjames23/scan2dojo/dto/requests/EngagementRequest.java b/src/main/java/io/github/moudjames23/scan2dojo/dto/requests/EngagementRequest.java new file mode 100644 index 0000000..382ff91 --- /dev/null +++ b/src/main/java/io/github/moudjames23/scan2dojo/dto/requests/EngagementRequest.java @@ -0,0 +1,15 @@ +package io.github.moudjames23.scan2dojo.dto.requests; + + +public record EngagementRequest ( + String name, + + String description, + + int productId, + + String start, + + String end +) +{ } diff --git a/src/main/java/io/github/moudjames23/scan2dojo/dto/requests/ImportRequest.java b/src/main/java/io/github/moudjames23/scan2dojo/dto/requests/ImportRequest.java new file mode 100644 index 0000000..ad795c5 --- /dev/null +++ b/src/main/java/io/github/moudjames23/scan2dojo/dto/requests/ImportRequest.java @@ -0,0 +1,16 @@ +package io.github.moudjames23.scan2dojo.dto.requests; + +public record ImportRequest( + Boolean active, + Boolean verified, + + String scanType, + + String file, + + String productName, + + String engagementName, + + String minimumSeverity +) { } diff --git a/src/main/java/io/github/moudjames23/scan2dojo/dto/requests/ProductRequest.java b/src/main/java/io/github/moudjames23/scan2dojo/dto/requests/ProductRequest.java new file mode 100644 index 0000000..75450d1 --- /dev/null +++ b/src/main/java/io/github/moudjames23/scan2dojo/dto/requests/ProductRequest.java @@ -0,0 +1,4 @@ +package io.github.moudjames23.scan2dojo.dto.requests; + +public record ProductRequest(String name, String description, int prod_type, int sla_configuration) { +} diff --git a/src/main/java/io/github/moudjames23/scan2dojo/dto/responses/EngagementResponse.java b/src/main/java/io/github/moudjames23/scan2dojo/dto/responses/EngagementResponse.java new file mode 100644 index 0000000..e9fc2f9 --- /dev/null +++ b/src/main/java/io/github/moudjames23/scan2dojo/dto/responses/EngagementResponse.java @@ -0,0 +1,24 @@ +package io.github.moudjames23.scan2dojo.dto.responses; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; + +@JsonIgnoreProperties(ignoreUnknown = true) +public record EngagementResponse( + int id, + String name, + String description, + @JsonProperty("product") + int productId, + @JsonProperty("target_start") + String start, + @JsonProperty("target_end") + String end +) implements ResponseMessage, Serializable { + + @Override + public String success() { + return "Engagement created \uD83C\uDF89\uD83C\uDF89\uD83C\uDF89 \nEngagementId: " + id; + } +} diff --git a/src/main/java/io/github/moudjames23/scan2dojo/dto/responses/ImportResponse.java b/src/main/java/io/github/moudjames23/scan2dojo/dto/responses/ImportResponse.java new file mode 100644 index 0000000..d4a6818 --- /dev/null +++ b/src/main/java/io/github/moudjames23/scan2dojo/dto/responses/ImportResponse.java @@ -0,0 +1,24 @@ +package io.github.moudjames23.scan2dojo.dto.responses; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; + +@JsonIgnoreProperties(ignoreUnknown = true) +public record ImportResponse( + @JsonProperty("scan_date") + String scanDate, + @JsonProperty("minimum_severity") + String minimumSeverity, + boolean active, + boolean verified, + @JsonProperty("scan_type") + String scanType, + String file +) implements ResponseMessage, Serializable { + + @Override + public String success() { + return "Scan imported \uD83C\uDF89\uD83C\uDF89\uD83C\uDF89"; + } +} diff --git a/src/main/java/io/github/moudjames23/scan2dojo/dto/responses/ProductResponse.java b/src/main/java/io/github/moudjames23/scan2dojo/dto/responses/ProductResponse.java new file mode 100644 index 0000000..c5ab85d --- /dev/null +++ b/src/main/java/io/github/moudjames23/scan2dojo/dto/responses/ProductResponse.java @@ -0,0 +1,22 @@ +package io.github.moudjames23.scan2dojo.dto.responses; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; + +@JsonIgnoreProperties(ignoreUnknown = true) +public record ProductResponse( + int id, + String name, + String description, + @JsonProperty("prod_type") + int prodType, + @JsonProperty("sla_configuration") + int slaConfiguration +) implements ResponseMessage, Serializable { + + @Override + public String success() { + return "Product created \uD83C\uDF89\uD83C\uDF89\uD83C\uDF89 \nProductId: " + id; + } +} diff --git a/src/main/java/io/github/moudjames23/scan2dojo/dto/responses/ResponseMessage.java b/src/main/java/io/github/moudjames23/scan2dojo/dto/responses/ResponseMessage.java new file mode 100644 index 0000000..0a899ee --- /dev/null +++ b/src/main/java/io/github/moudjames23/scan2dojo/dto/responses/ResponseMessage.java @@ -0,0 +1,6 @@ +package io.github.moudjames23.scan2dojo.dto.responses; + +public interface ResponseMessage { + + String success(); +} diff --git a/src/main/java/io/github/moudjames23/scan2dojo/enums/EnumWithValue.java b/src/main/java/io/github/moudjames23/scan2dojo/enums/EnumWithValue.java new file mode 100644 index 0000000..81369e4 --- /dev/null +++ b/src/main/java/io/github/moudjames23/scan2dojo/enums/EnumWithValue.java @@ -0,0 +1,6 @@ +package io.github.moudjames23.scan2dojo.enums; + +public interface EnumWithValue { + + String getValue(); +} diff --git a/src/main/java/io/github/moudjames23/scan2dojo/enums/HttpMethod.java b/src/main/java/io/github/moudjames23/scan2dojo/enums/HttpMethod.java new file mode 100644 index 0000000..84f92cb --- /dev/null +++ b/src/main/java/io/github/moudjames23/scan2dojo/enums/HttpMethod.java @@ -0,0 +1,18 @@ +package io.github.moudjames23.scan2dojo.enums; + + + +public enum HttpMethod { + + POST(1), GET(2); + + private final int code; + + HttpMethod(int code) { + this.code = code; + } + + public int getCode() { + return code; + } +} diff --git a/src/main/java/io/github/moudjames23/scan2dojo/enums/ScanType.java b/src/main/java/io/github/moudjames23/scan2dojo/enums/ScanType.java new file mode 100644 index 0000000..b49004b --- /dev/null +++ b/src/main/java/io/github/moudjames23/scan2dojo/enums/ScanType.java @@ -0,0 +1,209 @@ +package io.github.moudjames23.scan2dojo.enums; + +public enum ScanType implements EnumWithValue{ + + + ACUNETIX_SCAN("Acunetix Scan"), + ACUNETIX360_SCAN("Acunetix360 Scan"), + ANCHORE_ENGINE_SCAN("Anchore Engine Scan"), + ANCHORE_ENTERPRISE_POLICY_CHECK("Anchore Enterprise Policy Check"), + ANCHORE_GRYPE("anchore_grype"), + ANCHORECTL_POLICIES_REPORT("AnchoreCTL Policies Report"), + ANCHORECTL_VULN_REPORT("AnchoreCTL Vuln Report"), + API_TEST("API Test"), + APP_SPIDER_SCAN("AppSpider Scan"), + AQUA_SCAN("Aqua Scan"), + ARACHNI_SCAN("Arachni Scan"), + AUDIT_JS_SCAN("AuditJS Scan"), + AWS_PROWLER_SCAN("AWS Prowler Scan"), + AWS_PROWLER_SCANNER("AWS Prowler Scanner"), + AWS_PROWLER_V3("AWS Prowler V3"), + AWS_SCOUT2_SCAN("AWS Scout2 Scan"), + AWS_SCOUT2_SCANNER("AWS Scout2 Scanner"), + AWS_SECURITY_FINDING_FORMAT_ASFF_SCAN("AWS Security Finding Format (ASFF) Scan"), + AWS_SECURITY_HUB_SCAN("AWS Security Hub Scan"), + AZURE_SECURITY_CENTER_RECOMMENDATIONS_SCAN("Azure Security Center Recommendations Scan"), + BANDIT_SCAN("Bandit Scan"), + BLACKDUCK_API("BlackDuck API"), + BLACKDUCK_COMPONENT_RISK("Blackduck Component Risk"), + BLACKDUCK_HUB_SCAN("Blackduck Hub Scan"), + BRAKEMAN_SCAN("Brakeman Scan"), + BUG_CROWD_SCAN("BugCrowd Scan"), + BUG_CROWD("BugCrowd "), + BUGCROWD_API_IMPORT("Bugcrowd API Import"), + BUNDLER_AUDIT_SCAN("Bundler-Audit Scan"), + BURP_ENTERPRISE_SCAN("Burp Enterprise Scan"), + BURP_GRAPHQL_API("Burp GraphQL API"), + BURP_REST_API("Burp REST API"), + BURP_SCAN("Burp Scan"), + BURP_SUITE_ENTERPRISE("Burp Suite Enterprise"), + CARGO_AUDIT_SCAN("CargoAudit Scan"), + CCVS_REPORT("CCVS Report"), + CHECKMARX_OSA("Checkmarx OSA"), + CHECKMARX_SCAN_DETAILED("Checkmarx Scan detailed"), + CHECKMARX_SCAN("Checkmarx Scan"), + CHECKOV_SCAN("Checkov Scan"), + CHOCTAW_HOG_SCAN("Choctaw Hog Scan"), + CLAIR_KLAR_SCAN("Clair Klar Scan"), + CLAIR_SCAN("Clair Scan"), + CLOUDSPLOIT_SCAN("Cloudsploit Scan"), + COBALT_IO_SCAN("Cobalt.io Scan"), + CODECHECKER_REPORT_NATIVE("Codechecker Report native"), + CONTRAST_SCAN("Contrast Scan"), + COVERITY_API("Coverity API"), + CRASHTEST_SECURITY_JSON_FILE("Crashtest Security JSON File"), + CRASHTEST_SECURITY_SCAN("Crashtest Security Scan"), + CRASHTEST_SECURITY_XML_FILE("Crashtest Security XML File"), + CRED_SCAN_SCAN("CredScan Scan"), + CYCLONE_DX_SCAN("CycloneDX Scan"), + DAWN_SCANNER_SCAN("DawnScanner Scan"), + DEPENDENCY_CHECK_SCAN("Dependency Check Scan"), + DEPENDENCY_TRACK_FINDING_PACKAGING_FORMAT_FPF_EXPORT("Dependency Track Finding Packaging Format (FPF) Export"), + DETECT_SECRETS_SCAN("Detect-secrets Scan"), + DOCKER_BENCH_SECURITY_SCAN("docker-bench-security Scan"), + DOCKLE_SCAN("Dockle Scan"), + DR_HEADER_JSON_IMPORTER("DrHeader JSON Importer"), + DSOP_SCAN("DSOP Scan"), + EDGESCAN_SCAN("Edgescan Scan"), + ES_LINT_SCAN("ESLint Scan"), + FORTIFY_SCAN("Fortify Scan"), + FORTIFY("Fortify"), + GENERIC_FINDINGS_IMPORT("Generic Findings Import"), + GGSHIELD_SCAN("Ggshield Scan"), + GIT_LAB_API_FUZZING_REPORT_SCAN("GitLab API Fuzzing Report Scan"), + GIT_LAB_CONTAINER_SCAN("GitLab Container Scan"), + GIT_LAB_DAST_REPORT("GitLab DAST Report"), + GIT_LAB_DEPENDENCY_SCANNING_REPORT("GitLab Dependency Scanning Report"), + GIT_LAB_SAST_REPORT("GitLab SAST Report"), + GIT_LAB_SECRET_DETECTION_REPORT("GitLab Secret Detection Report"), + GITHUB_VULNERABILITY_SCAN("Github Vulnerability Scan"), + GITLEAKS_SCAN("Gitleaks Scan"), + GOSEC_SCANNER("Gosec Scanner"), + GOVULNCHECK_SCANNER("Govulncheck Scanner"), + HACKER_ONE_CASES("HackerOne Cases"), + HADOLINT_DOCKERFILE_CHECK("Hadolint Dockerfile check"), + HARBOR_VULNERABILITY_SCAN("Harbor Vulnerability Scan"), + HCLAPPSCAN_XML("HCLAppScan XML"), + HORUSEC_SCAN("Horusec Scan"), + HUMBLE_JSON_IMPORTER("Humble Json Importer"), + HUSKY_CI_REPORT("HuskyCI Report"), + HYDRA_SCAN("Hydra Scan"), + IBM_APP_SCAN_DAST("IBM AppScan DAST"), + IMMUNIWEB_SCAN("Immuniweb Scan"), + INT_SIGHTS_REPORT("IntSights Report"), + J_FROG_XRAY_SCAN("JFrog Xray Scan"), + J_FROG_XRAY_UNIFIED_SCAN("JFrog Xray Unified Scan"), + JFROG_XRAY_API_SUMMARY_ARTIFACT_SCAN("JFrog Xray API Summary Artifact Scan"), + JFROG_XRAY_ON_DEMAND_BINARY_SCAN("JFrog Xray On Demand Binary Scan"), + KICS_SCAN("KICS Scan"), + KIUWAN_SCAN("Kiuwan Scan"), + KUBE_BENCH_SCAN("kube-bench Scan"), + KUBEHUNTER_SCAN("KubeHunter Scan"), + MANUAL_CODE_REVIEW("Manual Code Review"), + METERIAN_SCAN("Meterian Scan"), + MICROFOCUS_WEBINSPECT_SCAN("Microfocus Webinspect Scan"), + MOB_SF_SCAN("MobSF Scan"), + MOB_SF_SCANNER("MobSF Scanner"), + MOBSFSCAN_SCAN("Mobsfscan Scan"), + MOZILLA_OBSERVATORY_SCAN("Mozilla Observatory Scan"), + NESSUS_SCAN("Nessus Scan"), + NESSUS_WAS_SCAN("Nessus WAS Scan"), + NETSPARKER_SCAN("Netsparker Scan"), + NEUVECTOR_COMPLIANCE("NeuVector (compliance)"), + NEUVECTOR_REST("NeuVector (REST)"), + NEXPOSE_SCAN("Nexpose Scan"), + NIKTO_SCAN("Nikto Scan"), + NMAP_XML_SCAN("Nmap Scan"), + NODE_SECURITY_PLATFORM_SCAN("Node Security Platform Scan"), + NPM_AUDIT_SCAN("NPM Audit Scan"), + NUCLEI_SCAN("Nuclei Scan"), + OPEN_VAS_CSV("OpenVAS CSV"), + OPENSCAP_VULNERABILITY_SCAN("Openscap Vulnerability Scan"), + OPENVAS_XML("OpenVAS XML"), + ORT_EVALUATED_MODEL_IMPORTER("ORT evaluated model Importer"), + OSS_INDEX_DEVAUDIT_SCA_SCAN_IMPORTER("OssIndex Devaudit SCA Scan Importer"), + OUTPOST24_SCAN("Outpost24 Scan"), + PEN_TEST("Pen Test"), + PHP_SECURITY_AUDIT_V2("PHP Security Audit v2"), + PHP_SYMFONY_SECURITY_CHECK("PHP Symfony Security Check"), + PHP_SYMFONY_SECURITY_CHECKER("PHP Symfony Security Checker"), + PIP_AUDIT_SCAN("pip-audit Scan"), + PMD_SCAN("PMD Scan"), + POPEYE_SCAN("Popeye Scan"), + PWN_SAST("PWN SAST"), + QUALYS_INFRASTRUCTURE_SCAN_WEB_GUI_XML("Qualys Infrastructure Scan (WebGUI XML)"), + QUALYS_SCAN("Qualys Scan"), + QUALYS_WEBAPP_SCAN("Qualys Webapp Scan"), + RETIRE_JS_SCAN("Retire.js Scan"), + RISK_RECON_API_IMPORTER("Risk Recon API Importer"), + RUBOCOP_SCAN("Rubocop Scan"), + RUSTY_HOG_SCAN("Rusty Hog Scan"), + SAFETY_SCAN("Safety Scan"), + SARIF("SARIF"), + SCANTIST_SCAN("Scantist Scan"), + SCOUT_SUITE_MULTI_CLOUD_SECURITY_AUDITING_TOOL("ScoutSuite Multi-Cloud Security Auditing Tool"), + SCOUT_SUITE_SCAN("Scout Suite Scan"), + SECURITY_RESEARCH("Security Research"), + SEMGREP_JSON_REPORT("Semgrep JSON Report"), + SKF_SCAN("SKF Scan"), + SNYK_SCAN("Snyk Scan"), + SOLAR_APPSCREENER_SCAN("Solar Appscreener Scan"), + SONAR_QUBE_API_IMPORT("SonarQube API Import"), + SONAR_QUBE_SCAN_DETAILED("SonarQube Scan detailed"), + SONAR_QUBE_SCAN("SonarQube Scan"), + SONAR_QUBE("SonarQube"), + SONATYPE_APPLICATION_SCAN("Sonatype Application Scan"), + SPOT_BUGS_SCAN("SpotBugs Scan"), + SPOT_BUGS("SpotBugs"), + SSH_AUDIT_IMPORTER("SSH Audit Importer"), + SSL_LABS_SCAN("SSL Labs Scan"), + SSLSCAN("Sslscan"), + SSLYZE_JSON_SCAN("SSLyze Scan (JSON)"), + SSLYZE_SCAN("Sslyze Scan"), + STACKHAWK_HAWKSCAN("StackHawk HawkScan"), + STATIC_CHECK("Static Check"), + SYSDIG_VULNERABILITY_REPORT_PIPELINE_REGISTRY_RUNTIME_CSV("Sysdig Vulnerability Report - Pipeline, Registry and Runtime (CSV)"), + TALISMAN_SCAN("Talisman Scan"), + TENABLE_SCAN("Tenable Scan"), + TERRASCAN_SCAN("Terrascan Scan"), + TESTSSL_SCAN("Testssl Scan"), + TF_SEC_SCAN("TFSec Scan"), + THREAGILE_RISKS_REPORT("Threagile risks report"), + THREAT_MODELING("Threat Modeling"), + TRIVY_OPERATOR_SCAN("Trivy Operator Scan"), + TRIVY_SCAN("Trivy Scan"), + TRUFFLEHOG_SCAN("Trufflehog Scan"), + TRUFFLEHOG("Trufflehog"), + TRUFFLEHOG3_SCAN("Trufflehog3 Scan"), + TRUSTWAVE_FUSION_API_SCAN("Trustwave Fusion API Scan"), + TRUSTWAVE_SCAN_CSV("Trustwave Scan (CSV)"), + TRUSTWAVE("Trustwave"), + TWISTLOCK_IMAGE_SCAN("Twistlock Image Scan"), + VCG_SCAN("VCG Scan"), + VERACODE_SCAN("Veracode Scan"), + VERACODE_SOURCECLEAR_SCAN("Veracode SourceClear Scan"), + VULNERS("Vulners"), + W_FUZZ_JSON_REPORT("WFuzz JSON report"), + WAPITI_SCAN("Wapiti Scan"), + WAZUH("Wazuh"), + WEB_APPLICATION_TEST("Web Application Test"), + WHISPERS_SCAN("Whispers Scan"), + WHITE_HAT_SENTINEL("WhiteHat Sentinel"), + WHITESOURCE_SCAN("Whitesource Scan"), + WPSCAN("Wpscan"), + XANITIZER_SCAN("Xanitizer Scan"), + YARN_AUDIT_SCAN("Yarn Audit Scan"), + ZAP_SCAN("ZAP Scan"); + + private final String value; + + ScanType(String value) { + this.value = value; + } + + + @Override + public String getValue() { + return this.value; + } +} diff --git a/src/main/java/io/github/moudjames23/scan2dojo/enums/Severity.java b/src/main/java/io/github/moudjames23/scan2dojo/enums/Severity.java new file mode 100644 index 0000000..578643e --- /dev/null +++ b/src/main/java/io/github/moudjames23/scan2dojo/enums/Severity.java @@ -0,0 +1,22 @@ +package io.github.moudjames23.scan2dojo.enums; + +public enum Severity implements EnumWithValue{ + + INFO("Info"), + LOW("Low"), + MEDIUM("Medium"), + HIGH("High"), + CRITICAL("Critical"); + + private final String value; + + Severity(String value) { + this.value = value; + } + + + @Override + public String getValue() { + return this.value; + } +} diff --git a/src/main/java/io/github/moudjames23/scan2dojo/exception/CommandNotFoundException.java b/src/main/java/io/github/moudjames23/scan2dojo/exception/CommandNotFoundException.java new file mode 100644 index 0000000..75ed3b4 --- /dev/null +++ b/src/main/java/io/github/moudjames23/scan2dojo/exception/CommandNotFoundException.java @@ -0,0 +1,20 @@ +package io.github.moudjames23.scan2dojo.exception; + + +import io.github.moudjames23.scan2dojo.util.MessageUtil; +import org.springframework.shell.result.CommandNotFoundMessageProvider; + +import static io.github.moudjames23.scan2dojo.util.MessageUtil.RED; + + +public class CommandNotFoundException implements CommandNotFoundMessageProvider { + + + @Override + public String apply(ProviderContext context) { + String message = "Command '" + context.text() + "' not found. Please use 'help' to see the list of available commands."; + MessageUtil.printlnWithBorder(RED, message); + + return ""; + } +} diff --git a/src/main/java/io/github/moudjames23/scan2dojo/exception/GlobalException.java b/src/main/java/io/github/moudjames23/scan2dojo/exception/GlobalException.java new file mode 100644 index 0000000..2c2195a --- /dev/null +++ b/src/main/java/io/github/moudjames23/scan2dojo/exception/GlobalException.java @@ -0,0 +1,12 @@ +package io.github.moudjames23.scan2dojo.exception; + +import static io.github.moudjames23.scan2dojo.util.MessageUtil.RED; +import static io.github.moudjames23.scan2dojo.util.MessageUtil.printlnWithBorder; + +public class GlobalException extends RuntimeException { + public GlobalException(String message) { + super(message); + + printlnWithBorder(RED, message); + } +} diff --git a/src/main/java/io/github/moudjames23/scan2dojo/http/Engagement.java b/src/main/java/io/github/moudjames23/scan2dojo/http/Engagement.java new file mode 100644 index 0000000..e518056 --- /dev/null +++ b/src/main/java/io/github/moudjames23/scan2dojo/http/Engagement.java @@ -0,0 +1,64 @@ +package io.github.moudjames23.scan2dojo.http; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.github.moudjames23.scan2dojo.dto.requests.EngagementRequest; +import io.github.moudjames23.scan2dojo.dto.Configuration; +import io.github.moudjames23.scan2dojo.enums.HttpMethod; +import okhttp3.Headers; +import okhttp3.MediaType; +import okhttp3.RequestBody; + +import java.util.HashMap; +import java.util.Map; + +public class Engagement implements Request { + + private final Configuration config; + private final EngagementRequest engagementRequest; + + public Engagement(Configuration config, EngagementRequest engagementRequest) { + this.config = config; + this.engagementRequest = engagementRequest; + } + + @Override + public HttpMethod getMethod() { + return HttpMethod.POST; + } + + @Override + public Headers getHeaders() { + return new Headers.Builder() + .add(getAuthorization(), BEARER.concat(this.config.getApiKey())) + .add("Content-Type", APPLICATION_JSON) + .build(); + } + + @Override + public String getUri() { + return config.getEndpoint().concat("/api/v2/engagements/"); + } + + @Override + public RequestBody getBody() { + RequestBody requestBody; + + Map data = new HashMap<>(); + + data.put("name", engagementRequest.name()); + data.put("description", engagementRequest.description()); + data.put("target_start", engagementRequest.start()); + data.put("target_end", engagementRequest.end()); + data.put("product", engagementRequest.productId()); + + + try { + String json = new ObjectMapper().writeValueAsString(data); + requestBody = RequestBody.create(json, MediaType.parse(APPLICATION_JSON)); + } catch (JsonProcessingException e) { + throw new RuntimeException("Les données fournies sont incorrectes"); + } + return requestBody; + } +} diff --git a/src/main/java/io/github/moudjames23/scan2dojo/http/Import.java b/src/main/java/io/github/moudjames23/scan2dojo/http/Import.java new file mode 100644 index 0000000..aaacce9 --- /dev/null +++ b/src/main/java/io/github/moudjames23/scan2dojo/http/Import.java @@ -0,0 +1,56 @@ +package io.github.moudjames23.scan2dojo.http; + +import io.github.moudjames23.scan2dojo.dto.requests.ImportRequest; +import io.github.moudjames23.scan2dojo.dto.Configuration; +import io.github.moudjames23.scan2dojo.enums.HttpMethod; +import okhttp3.Headers; +import okhttp3.MediaType; +import okhttp3.MultipartBody; +import okhttp3.RequestBody; + +import java.io.File; + +public class Import implements Request { + + + private final Configuration config; + private final ImportRequest importRequest; + + public Import(Configuration config, ImportRequest importRequest) { + this.config = config; + this.importRequest = importRequest; + } + + @Override + public HttpMethod getMethod() { + return HttpMethod.POST; + } + + @Override + public Headers getHeaders() { + return new Headers.Builder() + .add(getAuthorization(), BEARER.concat(this.config.getApiKey())) + .add("Content-Type", APPLICATION_JSON) + .build(); + } + + public String getUri() { + return config.getEndpoint().concat("/api/v2/import-scan/"); + } + + @Override + public RequestBody getBody() { + File file = new File(importRequest.file()); + + return new MultipartBody.Builder() + .setType(MultipartBody.FORM) + .addFormDataPart("actived", importRequest.active().toString()) + .addFormDataPart("verified", importRequest.verified().toString()) + .addFormDataPart("file", file.getName(), RequestBody.create(file, MediaType.parse("application/json"))) + .addFormDataPart("product_name", importRequest.productName()) + .addFormDataPart("scan_type", importRequest.scanType()) + .addFormDataPart("engagement_name", importRequest.engagementName()) + .addFormDataPart("minimum_severity", importRequest.minimumSeverity()) + .build(); + } +} diff --git a/src/main/java/io/github/moudjames23/scan2dojo/http/Product.java b/src/main/java/io/github/moudjames23/scan2dojo/http/Product.java new file mode 100644 index 0000000..6ab4769 --- /dev/null +++ b/src/main/java/io/github/moudjames23/scan2dojo/http/Product.java @@ -0,0 +1,64 @@ +package io.github.moudjames23.scan2dojo.http; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.github.moudjames23.scan2dojo.dto.requests.ProductRequest; +import io.github.moudjames23.scan2dojo.dto.Configuration; +import io.github.moudjames23.scan2dojo.enums.HttpMethod; +import okhttp3.Headers; +import okhttp3.MediaType; +import okhttp3.RequestBody; + +import java.util.HashMap; +import java.util.Map; + +public class Product implements Request { + + private final Configuration config; + private final ProductRequest productRequest; + + public Product(Configuration config, ProductRequest productRequest) { + this.config = config; + this.productRequest = productRequest; + } + + + @Override + public HttpMethod getMethod() { + return HttpMethod.POST; + } + + @Override + public Headers getHeaders() { + return new Headers.Builder() + .add(getAuthorization(), BEARER.concat(this.config.getApiKey())) + .add("Content-Type", APPLICATION_JSON) + .build(); + } + + @Override + public String getUri() { + return config.getEndpoint().concat("/api/v2/products/"); + } + + @Override + public RequestBody getBody() { + RequestBody requestBody; + + Map data = new HashMap<>(); + + data.put("name", productRequest.name()); + data.put("description", productRequest.description()); + data.put("prod_type", productRequest.prod_type()); + data.put("sla_configuration", productRequest.sla_configuration()); + + + try { + String json = new ObjectMapper().writeValueAsString(data); + requestBody = RequestBody.create(json, MediaType.parse(APPLICATION_JSON)); + } catch (JsonProcessingException e) { + throw new RuntimeException("Les données fournies sont incorrectes"); + } + return requestBody; + } +} diff --git a/src/main/java/io/github/moudjames23/scan2dojo/http/Request.java b/src/main/java/io/github/moudjames23/scan2dojo/http/Request.java new file mode 100644 index 0000000..a25e597 --- /dev/null +++ b/src/main/java/io/github/moudjames23/scan2dojo/http/Request.java @@ -0,0 +1,42 @@ +package io.github.moudjames23.scan2dojo.http; + +import io.github.moudjames23.scan2dojo.enums.HttpMethod; +import okhttp3.Headers; +import okhttp3.RequestBody; + +public interface Request { + + + String APPLICATION_JSON = "application/json"; + + String BEARER = "Token "; + + default String getBaseUrl() { + return ""; + } + + default String getAuthorization() { + return "Authorization"; + } + + default String contentType() + { + return "application/json"; + } + + default Headers getHeaders() { + return new Headers.Builder().build(); // Provide default empty headers + } + + default RequestBody getBody() { + return null; + } + + default HttpMethod getMethod() { + return HttpMethod.GET; // Provide a default HTTP method (e.g., GET) + } + + default String getUri() { + return ""; // Provide a default empty URI + } +} diff --git a/src/main/java/io/github/moudjames23/scan2dojo/http/Scan2Dojo.java b/src/main/java/io/github/moudjames23/scan2dojo/http/Scan2Dojo.java new file mode 100644 index 0000000..f354771 --- /dev/null +++ b/src/main/java/io/github/moudjames23/scan2dojo/http/Scan2Dojo.java @@ -0,0 +1,95 @@ +package io.github.moudjames23.scan2dojo.http; + +import io.github.moudjames23.scan2dojo.dto.requests.EngagementRequest; +import io.github.moudjames23.scan2dojo.dto.requests.ImportRequest; +import io.github.moudjames23.scan2dojo.dto.requests.ProductRequest; +import io.github.moudjames23.scan2dojo.dto.responses.EngagementResponse; +import io.github.moudjames23.scan2dojo.dto.responses.ImportResponse; +import io.github.moudjames23.scan2dojo.dto.responses.ProductResponse; +import io.github.moudjames23.scan2dojo.dto.Configuration; +import io.github.moudjames23.scan2dojo.enums.ScanType; +import io.github.moudjames23.scan2dojo.enums.Severity; +import io.github.moudjames23.scan2dojo.util.EnumUtil; +import io.github.moudjames23.scan2dojo.util.RequestUtil; +import org.springframework.stereotype.Service; + +import java.io.File; +import java.io.IOException; +import java.net.URISyntaxException; + +import static io.github.moudjames23.scan2dojo.util.MessageUtil.*; + +@Service +public class Scan2Dojo { + + private Configuration configuration; + + public Scan2Dojo() { + + this.configuration = new Configuration(); + } + + public void version() { + printlnWithBorder(RED, "Version"); + } + + public void configure(String endpoint, String apiKey) throws IOException, URISyntaxException { + + configuration.setEndpoint(endpoint); + configuration.setApiKey(apiKey); + + configuration.save(); + + printlnWithBorder(GREEN, "Configuration updated successfully 🎉🎉🎉"); + + } + + public void createProduct(ProductRequest productRequest) throws IOException { + + configuration.load(); + + Product product = new Product(configuration, productRequest); + RequestUtil.executeRequest(product, ProductResponse.class); + } + + public void createEngagement(EngagementRequest engagementRequest) throws IOException { + + configuration.load(); + + Engagement engagement = new Engagement(configuration, engagementRequest); + RequestUtil.executeRequest(engagement, EngagementResponse.class); + } + + public void importScan(ImportRequest importRequest) throws IOException { + + configuration.load(); + + validateScanType(importRequest.scanType()); + validateSeverity(importRequest.minimumSeverity()); + + File file = new File(importRequest.file()); + if (!file.exists()) { + printlnWithBorder(RED,"File " + importRequest.file() + " doesn't exist"); + return; + } + + Import importer = new Import(configuration, importRequest); + RequestUtil.executeRequest(importer, ImportResponse.class); + } + + + + private void validateScanType(String scanType) { + if (!EnumUtil.isStringInEnum(scanType, ScanType.class)) { + printlnWithBorder(RED,scanType + " is not supported. Please find the list of supported scan types."); + return; + } + } + + private void validateSeverity(String severity) { + if (!EnumUtil.isStringInEnum(severity, Severity.class)) { + printlnWithBorder(RED,severity + " is not a valid severity level. Supported levels are: Info, Low, Medium, High, Critical."); + return; + } + } +} diff --git a/src/main/java/io/github/moudjames23/scan2dojo/util/DateUtil.java b/src/main/java/io/github/moudjames23/scan2dojo/util/DateUtil.java new file mode 100644 index 0000000..3f121d5 --- /dev/null +++ b/src/main/java/io/github/moudjames23/scan2dojo/util/DateUtil.java @@ -0,0 +1,32 @@ +package io.github.moudjames23.scan2dojo.util; + +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; + +public class DateUtil { + + private DateUtil() { + } + + private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + + /** + * Returns the current date formatted as yyyy-MM-dd. + * + * @return A string representing today's date in the format yyyy-MM-dd. + */ + public static String today() { + LocalDate today = LocalDate.now(); + return today.format(FORMATTER); + } + + /** + * Returns the date one year from now formatted as yyyy-MM-dd. + * + * @return A string representing the date one year from today in the format yyyy-MM-dd. + */ + public static String nextYear() { + LocalDate nextYear = LocalDate.now().plusYears(1); + return nextYear.format(FORMATTER); + } +} diff --git a/src/main/java/io/github/moudjames23/scan2dojo/util/EnumUtil.java b/src/main/java/io/github/moudjames23/scan2dojo/util/EnumUtil.java new file mode 100644 index 0000000..675c320 --- /dev/null +++ b/src/main/java/io/github/moudjames23/scan2dojo/util/EnumUtil.java @@ -0,0 +1,21 @@ +package io.github.moudjames23.scan2dojo.util; + +import io.github.moudjames23.scan2dojo.enums.EnumWithValue; + +public class EnumUtil { + + private EnumUtil(){} + + public static & EnumWithValue> boolean isStringInEnum(String value, Class enumClass) { + if (value == null || enumClass == null) { + return false; + } + + for (E enumConstant : enumClass.getEnumConstants()) { + if (enumConstant.getValue().equalsIgnoreCase(value)) { + return true; + } + } + return false; + } +} diff --git a/src/main/java/io/github/moudjames23/scan2dojo/util/MessageUtil.java b/src/main/java/io/github/moudjames23/scan2dojo/util/MessageUtil.java new file mode 100644 index 0000000..8524e96 --- /dev/null +++ b/src/main/java/io/github/moudjames23/scan2dojo/util/MessageUtil.java @@ -0,0 +1,17 @@ +package io.github.moudjames23.scan2dojo.util; + +public class MessageUtil { + + // Codes de couleur ANSI + public static final String RESET = "\u001B[0m"; + + public static final String RED = "\u001B[31m"; + public static final String GREEN = "\u001B[32m"; + + public static void printlnWithBorder(String color, String message) { + String border = "--------------------------------------------------------------------------------"; + System.out.println(color + border); + System.out.println(message); + System.out.println(border + RESET); + } +} diff --git a/src/main/java/io/github/moudjames23/scan2dojo/util/RequestUtil.java b/src/main/java/io/github/moudjames23/scan2dojo/util/RequestUtil.java new file mode 100644 index 0000000..050e439 --- /dev/null +++ b/src/main/java/io/github/moudjames23/scan2dojo/util/RequestUtil.java @@ -0,0 +1,48 @@ +package io.github.moudjames23.scan2dojo.util; + +import com.fasterxml.jackson.databind.ObjectMapper; +import io.github.moudjames23.scan2dojo.dto.responses.ResponseMessage; +import io.github.moudjames23.scan2dojo.http.Request; +import okhttp3.OkHttpClient; +import okhttp3.Response; + +import java.io.IOException; + +import static io.github.moudjames23.scan2dojo.util.MessageUtil.*; + +public class RequestUtil { + + private static final OkHttpClient httpClient = new OkHttpClient(); + + private RequestUtil() { + } + + public static void executeRequest(Request entity, Class responseMessageClass) throws IOException { + + okhttp3.Request request = new okhttp3.Request.Builder() + .url(entity.getUri()) + .headers(entity.getHeaders()) + .post(entity.getBody()) + .build(); + + OkHttpClient okHttpClient = new OkHttpClient(); + + try (Response response = okHttpClient.newCall(request).execute()) { + assert response.body() != null; + String data = response.body().string(); + + if (!response.isSuccessful()) { + printlnWithBorder(RED, data); + return; + } + + ResponseMessage message = (ResponseMessage) new ObjectMapper().readValue(data, responseMessageClass); + + printlnWithBorder(GREEN, message.success()); + } + catch (Exception e) + { + printlnWithBorder(RED, e.getMessage()); + } + } +} diff --git a/src/main/java/io/github/moudjames23/scan2dojo/util/URLValidator.java b/src/main/java/io/github/moudjames23/scan2dojo/util/URLValidator.java new file mode 100644 index 0000000..06a9ba8 --- /dev/null +++ b/src/main/java/io/github/moudjames23/scan2dojo/util/URLValidator.java @@ -0,0 +1,41 @@ +package io.github.moudjames23.scan2dojo.util; + +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URISyntaxException; + +import static io.github.moudjames23.scan2dojo.util.MessageUtil.RED; +import static io.github.moudjames23.scan2dojo.util.MessageUtil.printlnWithBorder; + +public class URLValidator { + + private URLValidator() { + } + + public static String checkURL(String url) throws MalformedURLException { + + if (!isValidURL(url)) + printlnWithBorder(RED, "Endpoint is not an URL"); + + return removeTrailingSlash(url); + } + + public static boolean isValidURL(String urlString) { + try { + URI uri = new URI(urlString); + + String scheme = uri.getScheme(); + return "http".equalsIgnoreCase(scheme) || "https".equalsIgnoreCase(scheme); + } catch (URISyntaxException e) { + + return false; + } + } + + public static String removeTrailingSlash(String input) { + if (input != null && input.endsWith("/")) { + return input.substring(0, input.length() - 1); + } + return input; + } +} diff --git a/src/main/resources/META-INF/native-image/reflect-config.json b/src/main/resources/META-INF/native-image/reflect-config.json new file mode 100644 index 0000000..2dd19bd --- /dev/null +++ b/src/main/resources/META-INF/native-image/reflect-config.json @@ -0,0 +1,29 @@ +[ + { + "name": "io.github.moudjames23.scan2dojo.dto.responses.ProductResponse", + "allDeclaredConstructors" : true, + "allPublicConstructors" : true, + "allDeclaredMethods" : true, + "allPublicMethods" : true, + "allDeclaredClasses" : true, + "allPublicClasses" : true + }, + { + "name": "io.github.moudjames23.scan2dojo.dto.responses.EngagementResponse", + "allDeclaredConstructors" : true, + "allPublicConstructors" : true, + "allDeclaredMethods" : true, + "allPublicMethods" : true, + "allDeclaredClasses" : true, + "allPublicClasses" : true + }, + { + "name": "io.github.moudjames23.scan2dojo.dto.responses.ImportResponse", + "allDeclaredConstructors" : true, + "allPublicConstructors" : true, + "allDeclaredMethods" : true, + "allPublicMethods" : true, + "allDeclaredClasses" : true, + "allPublicClasses" : true + } +] diff --git a/src/main/resources/META-INF/native-image/resource-config.json b/src/main/resources/META-INF/native-image/resource-config.json new file mode 100644 index 0000000..72634bc --- /dev/null +++ b/src/main/resources/META-INF/native-image/resource-config.json @@ -0,0 +1,9 @@ +{ + "resources": { + "includes": [ + { + "pattern": "config.json" + } + ] + } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties new file mode 100644 index 0000000..67c6ca6 --- /dev/null +++ b/src/main/resources/application.properties @@ -0,0 +1,14 @@ +spring.application.name=Scan2dojo + +spring.shell.interactive.enabled=true +spring.shell.history.enabled=false +spring.shell.script.enabled=false +spring.shell.command.version.enabled=false +spring.shell.command.help.enabled=false +spring.shell.command.stacktrace.enabled=false + +logging.level.root=off + +spring.main.banner-mode=off + +APP_VERSION=1.0.0 diff --git a/src/test/java/io/github/moudjames23/scan2dojo/Scan2dojoApplicationTests.java b/src/test/java/io/github/moudjames23/scan2dojo/Scan2dojoApplicationTests.java new file mode 100644 index 0000000..7187a50 --- /dev/null +++ b/src/test/java/io/github/moudjames23/scan2dojo/Scan2dojoApplicationTests.java @@ -0,0 +1,11 @@ +package io.github.moudjames23.scan2dojo; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class Scan2dojoApplicationTests { + + + +} diff --git a/src/test/java/io/github/moudjames23/scan2dojo/http/Scan2DojoTest.java b/src/test/java/io/github/moudjames23/scan2dojo/http/Scan2DojoTest.java new file mode 100644 index 0000000..5dd2b16 --- /dev/null +++ b/src/test/java/io/github/moudjames23/scan2dojo/http/Scan2DojoTest.java @@ -0,0 +1,104 @@ +package io.github.moudjames23.scan2dojo.http; + +import io.github.moudjames23.scan2dojo.dto.Configuration; +import io.github.moudjames23.scan2dojo.dto.requests.ImportRequest; +import okhttp3.*; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.io.IOException; + +import static org.mockito.Mockito.*; + +class Scan2DojoTest { + + @Mock + private Configuration config; + + @Mock + private OkHttpClient httpClient; + + @InjectMocks + private Scan2Dojo importService; + + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); + } + + @Test + void shouldNotProceedWhenScanTypeIsUnsupported() throws IOException { + // Given: an import request with an unsupported scan type + ImportRequest importRequest = new ImportRequest(true, true, "UnsupportedType", "non_existent_file.json", "productName", "engagementName", "Low"); + + // When: importScan is called + importService.importScan(importRequest); + + // Then: config methods should not be called + verify(config, never()).getEndpoint(); + verify(config, never()).getApiKey(); + } + + @Test + void shouldNotProceedWhenMinimumSeverityIsInvalid() throws IOException { + // Given: an import request with an invalid minimum severity + ImportRequest importRequest = new ImportRequest(true, true, "DYNAMIC", "non_existent_file.json", "productName", "engagementName", "InvalidSeverity"); + + // When: importScan is called + importService.importScan(importRequest); + + // Then: config methods should not be called + verify(config, never()).getEndpoint(); + verify(config, never()).getApiKey(); + } + + @Test + void shouldNotProceedWhenFileDoesNotExist() throws IOException { + // Given: an import request with a non-existent file + ImportRequest importRequest = new ImportRequest(true, true, "DYNAMIC", "non_existent_file.json", "productName", "engagementName", "Low"); + + // When: importScan is called + importService.importScan(importRequest); + + // Then: config methods should not be called + verify(config, never()).getEndpoint(); + verify(config, never()).getApiKey(); + } + + /*@Test + void shouldSendRequestWhenImportScanIsSuccessful() throws IOException { + // Given: a valid import request and a mock successful response + ImportRequest importRequest = new ImportRequest(true, true, "Trivy Scan", "/Users/moud/Desktop/Libs/trivy.json", "Moud", "Release", "Low"); + + File file = mock(File.class); + when(file.exists()).thenReturn(true); + + when(config.getBaseUrl()).thenReturn("https://demo.defectdojo.org"); + when(config.getApiKey()).thenReturn("xxxxxxxxxxxxxxxxxxxxxxxxxxx"); + + Response response = mock(Response.class); + when(response.isSuccessful()).thenReturn(true); + when(response.body()).thenReturn(ResponseBody.create("{}", MediaType.parse("application/json"))); + when(response.code()).thenReturn(200); + + Call call = mock(Call.class); + when(httpClient.newCall(any(Request.class))).thenReturn(call); + when(call.execute()).thenReturn(response); + + // When: importScan is called + importService.importScan(importRequest); + + // Then: the HTTP request should be sent with the correct parameters + ArgumentCaptor requestCaptor = ArgumentCaptor.forClass(Request.class); + verify(httpClient).newCall(requestCaptor.capture()); + + Request capturedRequest = requestCaptor.getValue(); + assertNotNull(capturedRequest); + assertEquals("https://demo.defectdojo.org/api/v2/import-scan/", capturedRequest.url().toString()); + assertEquals("Token api_key", capturedRequest.header("Authorization")); + assertEquals("multipart/form-data", capturedRequest.header("Content-Type")); + }*/ +} diff --git a/src/test/java/io/github/moudjames23/scan2dojo/util/DateUtilTest.java b/src/test/java/io/github/moudjames23/scan2dojo/util/DateUtilTest.java new file mode 100644 index 0000000..f49bd94 --- /dev/null +++ b/src/test/java/io/github/moudjames23/scan2dojo/util/DateUtilTest.java @@ -0,0 +1,39 @@ +package io.github.moudjames23.scan2dojo.util; + +import org.junit.jupiter.api.Test; + +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; + +import static org.junit.jupiter.api.Assertions.*; + +class DateUtilTest { + + private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + + @Test + public void testToday() { + // Given: the expected formatted date for today + LocalDate expectedDate = LocalDate.now(); + String expectedFormattedDate = expectedDate.format(FORMATTER); + + // When: we call the today() method + String actualFormattedDate = DateUtil.today(); + + // Then: the result should match the expected formatted date + assertEquals(expectedFormattedDate, actualFormattedDate, "The formatted date should match today's date"); + } + + @Test + public void testNextYear() { + // Given: the expected formatted date for one year from today + LocalDate expectedDate = LocalDate.now().plusYears(1); + String expectedFormattedDate = expectedDate.format(FORMATTER); + + // When: we call the nextYear() method + String actualFormattedDate = DateUtil.nextYear(); + + // Then: the result should match the expected formatted date one year from today + assertEquals(expectedFormattedDate, actualFormattedDate, "The formatted date should match the date one year from today"); + } +} \ No newline at end of file diff --git a/src/test/java/io/github/moudjames23/scan2dojo/util/EnumUtilTest.java b/src/test/java/io/github/moudjames23/scan2dojo/util/EnumUtilTest.java new file mode 100644 index 0000000..4892874 --- /dev/null +++ b/src/test/java/io/github/moudjames23/scan2dojo/util/EnumUtilTest.java @@ -0,0 +1,61 @@ +package io.github.moudjames23.scan2dojo.util; + +import io.github.moudjames23.scan2dojo.enums.ScanType; +import io.github.moudjames23.scan2dojo.enums.Severity; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class EnumUtilTest { + + @Test + public void shouldReturnTrueForValidStringInEnum() { + // Given: valid strings that correspond to enum values + String validSeverityValue1 = "Low"; + String validSeverityValue2 = "low"; + String validSeverityValue3 = "HIGH"; + + String validScanTypeValue1 = "Trivy Scan"; + String validScanTypeValue2 = "Anchore Enterprise Policy Check"; + String validScanTypeValue3 = "CredSCAN scan"; + + // When & Then: the method should return true for valid strings + assertTrue(EnumUtil.isStringInEnum(validSeverityValue1, Severity.class)); + assertTrue(EnumUtil.isStringInEnum(validSeverityValue2, Severity.class)); + assertTrue(EnumUtil.isStringInEnum(validSeverityValue3, Severity.class)); + + assertTrue(EnumUtil.isStringInEnum(validScanTypeValue1, ScanType.class)); + assertTrue(EnumUtil.isStringInEnum(validScanTypeValue2, ScanType.class)); + assertTrue(EnumUtil.isStringInEnum(validScanTypeValue3, ScanType.class)); + } + + @Test + public void shouldReturnFalseForInvalidStringInEnum() { + // Given: invalid strings that do not correspond to any enum values + String invalidSeverityValue = "Unknown"; + String invalidScanTypeValue = "invalid"; + + // When & Then: the method should return false for invalid strings + assertFalse(EnumUtil.isStringInEnum(invalidSeverityValue, Severity.class)); + assertFalse(EnumUtil.isStringInEnum(invalidScanTypeValue, ScanType.class)); + } + + @Test + public void shouldReturnFalseForNullStringInEnum() { + // Given: a null string + String nullValue = null; + + // When & Then: the method should return false for a null string + assertFalse(EnumUtil.isStringInEnum(nullValue, ScanType.class)); + } + + @Test + public void shouldReturnFalseForNullEnumClass() { + // Given: a valid string but a null enum class + String validValue = "Info"; + + // When & Then: the method should return false for a null enum class + assertFalse(EnumUtil.isStringInEnum(validValue, null)); + } + +} \ No newline at end of file diff --git a/src/test/java/io/github/moudjames23/scan2dojo/util/MessageUtilTest.java b/src/test/java/io/github/moudjames23/scan2dojo/util/MessageUtilTest.java new file mode 100644 index 0000000..15ce5af --- /dev/null +++ b/src/test/java/io/github/moudjames23/scan2dojo/util/MessageUtilTest.java @@ -0,0 +1,61 @@ +package io.github.moudjames23.scan2dojo.util; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; + +import static org.junit.jupiter.api.Assertions.*; + +class MessageUtilTest { + + private final ByteArrayOutputStream outContent = new ByteArrayOutputStream(); + private final PrintStream originalOut = System.out; + + @BeforeEach + public void setUpStreams() { + System.setOut(new PrintStream(outContent)); + } + + @AfterEach + public void restoreStreams() { + System.setOut(originalOut); + } + + @Test + public void shouldPrintMessageWithRedBorder() { + // Given: a message and the expected output with red border + String message = "This is a red message"; + String expectedOutput = MessageUtil.RED + + "--------------------------------------------------------------------------------\n" + + message + "\n" + + "--------------------------------------------------------------------------------" + + MessageUtil.RESET + "\n"; + + // When: printlnWithBorder is called with RED color + MessageUtil.printlnWithBorder(MessageUtil.RED, message); + + // Then: the output should match the expected output + assertEquals(expectedOutput, outContent.toString()); + } + + @Test + public void shouldPrintMessageWithGreenBorder() { + // Given: a message and the expected output with green border + String message = "This is a green message"; + String expectedOutput = MessageUtil.GREEN + + "--------------------------------------------------------------------------------\n" + + message + "\n" + + "--------------------------------------------------------------------------------" + + MessageUtil.RESET + "\n"; + + // When: printlnWithBorder is called with GREEN color + MessageUtil.printlnWithBorder(MessageUtil.GREEN, message); + + // Then: the output should match the expected output + assertEquals(expectedOutput, outContent.toString()); + } + +} \ No newline at end of file diff --git a/src/test/java/io/github/moudjames23/scan2dojo/util/URLValidatorTest.java b/src/test/java/io/github/moudjames23/scan2dojo/util/URLValidatorTest.java new file mode 100644 index 0000000..fdf5a39 --- /dev/null +++ b/src/test/java/io/github/moudjames23/scan2dojo/util/URLValidatorTest.java @@ -0,0 +1,117 @@ +package io.github.moudjames23.scan2dojo.util; + +import org.junit.jupiter.api.Test; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.net.MalformedURLException; + +import static org.junit.jupiter.api.Assertions.*; + +class URLValidatorTest { + + @Test + public void shouldReturnUrlWithoutTrailingSlashWhenUrlIsValid() throws MalformedURLException { + // Given: a valid URL with a trailing slash + String url = "https://example.com/"; + + // When: checkURL is called + String result = URLValidator.checkURL(url); + + // Then: the result should be the URL without the trailing slash + assertEquals("https://example.com", result); + } + + @Test + public void shouldReturnUrlAsIsWhenUrlHasNoTrailingSlashAndIsValid() throws MalformedURLException { + // Given: a valid URL without a trailing slash + String url = "https://example.com"; + + // When: checkURL is called + String result = URLValidator.checkURL(url); + + // Then: the result should be the URL as is + assertEquals(url, result); + } + + @Test + public void shouldPrintErrorWhenUrlIsInvalid() throws MalformedURLException { + // Given: an invalid URL + String url = "invalid-url"; + String expectedMessage = "Endpoint is not an URL"; + + // Redirect the output stream to capture the printed message + ByteArrayOutputStream outContent = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outContent)); + + // When: checkURL is called with an invalid URL + URLValidator.checkURL(url); + + // Then: an error message should be printed + assertTrue(outContent.toString().contains(expectedMessage)); + + // Restore the original output stream + System.setOut(System.out); + } + + @Test + public void shouldReturnFalseForInvalidURL() { + // Given: an invalid URL string + String invalidUrl = "invalid-url"; + + // When: isValidURL is called + boolean result = URLValidator.isValidURL(invalidUrl); + + // Then: the result should be false + assertFalse(result); + } + + @Test + public void shouldReturnTrueForValidHttpURL() { + // Given: a valid HTTP URL + String validUrl = "http://example.com"; + + // When: isValidURL is called + boolean result = URLValidator.isValidURL(validUrl); + + // Then: the result should be true + assertTrue(result); + } + + @Test + public void shouldReturnTrueForValidHttpsURL() { + // Given: a valid HTTPS URL + String validUrl = "https://example.com"; + + // When: isValidURL is called + boolean result = URLValidator.isValidURL(validUrl); + + // Then: the result should be true + assertTrue(result); + } + + @Test + public void shouldRemoveTrailingSlash() { + // Given: a string with a trailing slash + String input = "https://example.com/"; + + // When: removeTrailingSlash is called + String result = URLValidator.removeTrailingSlash(input); + + // Then: the result should be the string without the trailing slash + assertEquals("https://example.com", result); + } + + @Test + public void shouldNotRemoveTrailingSlashWhenThereIsNone() { + // Given: a string without a trailing slash + String input = "https://example.com"; + + // When: removeTrailingSlash is called + String result = URLValidator.removeTrailingSlash(input); + + // Then: the result should be the same string + assertEquals(input, result); + } + +} \ No newline at end of file