diff --git a/sechub-pds-solutions/checkmarx/env b/sechub-pds-solutions/checkmarx/env
index 967a1ccb8..253d8fbaf 100644
--- a/sechub-pds-solutions/checkmarx/env
+++ b/sechub-pds-solutions/checkmarx/env
@@ -12,3 +12,5 @@ BUILD_TYPE=download
# The Checkmarx Wrapper version to use
# See: https://github.com/mercedes-benz/sechub/releases
CHECKMARX_WRAPPER_VERSION="1.3.0"
+PDS_ENCRYPTION_ALGORITHM="NONE"
+PDS_ENCRYPTION_SECRET_KEY=""
\ No newline at end of file
diff --git a/sechub-pds-solutions/findsecuritybugs/env b/sechub-pds-solutions/findsecuritybugs/env
index cb9dc5e97..e37fa266f 100644
--- a/sechub-pds-solutions/findsecuritybugs/env
+++ b/sechub-pds-solutions/findsecuritybugs/env
@@ -2,3 +2,5 @@
FINDSECURITYBUGS_VERSION="1.13.0"
# The Spotbugs version to use. See https://github.com/spotbugs/spotbugs/releases
SPOTBUGS_VERSION="4.8.3"
+PDS_ENCRYPTION_ALGORITHM="NONE"
+PDS_ENCRYPTION_SECRET_KEY=""
\ No newline at end of file
diff --git a/sechub-pds-solutions/gitleaks/env b/sechub-pds-solutions/gitleaks/env
index 7645b8551..b0387fe34 100644
--- a/sechub-pds-solutions/gitleaks/env
+++ b/sechub-pds-solutions/gitleaks/env
@@ -16,3 +16,5 @@ BUILD_TYPE=download
# The Secret-Validation Wrapper version to use
# See: https://github.com/mercedes-benz/sechub/releases
SECRETVALIDATION_WRAPPER_VERSION="1.1.0"
+PDS_ENCRYPTION_ALGORITHM="NONE"
+PDS_ENCRYPTION_SECRET_KEY=""
\ No newline at end of file
diff --git a/sechub-pds-solutions/gosec/env b/sechub-pds-solutions/gosec/env
index 12ba91a52..5d736aaa6 100644
--- a/sechub-pds-solutions/gosec/env
+++ b/sechub-pds-solutions/gosec/env
@@ -1,2 +1,4 @@
# See: https://github.com/securego/gosec/releases
GOSEC_VERSION="2.16.0"
+PDS_ENCRYPTION_ALGORITHM="NONE"
+PDS_ENCRYPTION_SECRET_KEY=""
\ No newline at end of file
diff --git a/sechub-pds-solutions/iac/env b/sechub-pds-solutions/iac/env
index b4c1cacc0..4e1d017a2 100644
--- a/sechub-pds-solutions/iac/env
+++ b/sechub-pds-solutions/iac/env
@@ -2,3 +2,5 @@
# uncomment to use local image
# BASE_IMAGE="pds-base_pds"
KICS_VERSION="2.1.3"
+PDS_ENCRYPTION_ALGORITHM="NONE"
+PDS_ENCRYPTION_SECRET_KEY=""
\ No newline at end of file
diff --git a/sechub-pds-solutions/owaspzap/env b/sechub-pds-solutions/owaspzap/env
index a569e932d..e1965fe02 100644
--- a/sechub-pds-solutions/owaspzap/env
+++ b/sechub-pds-solutions/owaspzap/env
@@ -22,3 +22,5 @@ PDS_CONFIG_EXECUTE_WORKER_THREAD_COUNT=1
#ZAP_PROXY_HOST=127.0.0.1
#ZAP_PROXY_PORT=9999
ZAP_PROXY_FOR_PDS_TARGET_TYPE=INTERNET
+PDS_ENCRYPTION_ALGORITHM="NONE"
+PDS_ENCRYPTION_SECRET_KEY=""
\ No newline at end of file
diff --git a/sechub-pds-solutions/prepare/env b/sechub-pds-solutions/prepare/env
index 19f0cd716..bb3f657e2 100644
--- a/sechub-pds-solutions/prepare/env
+++ b/sechub-pds-solutions/prepare/env
@@ -37,4 +37,6 @@ PDS_PREPARE_MODULE_GIT_REMOVE_GIT_FILES_BEFORE_UPLOAD="true"
PDS_PREPARE_MODULE_GIT_CLONE_WITHOUT_GIT_HISTORY="true"
# SKOPEO
# Enable/ Disable skopeo module
-PDS_PREPARE_MODULE_SKOPEO_ENABLED="true"
\ No newline at end of file
+PDS_PREPARE_MODULE_SKOPEO_ENABLED="true"
+PDS_ENCRYPTION_ALGORITHM="NONE"
+PDS_ENCRYPTION_SECRET_KEY=""
\ No newline at end of file
diff --git a/sechub-pds-solutions/scancode/env b/sechub-pds-solutions/scancode/env
index fd3739528..b0f52bdc5 100644
--- a/sechub-pds-solutions/scancode/env
+++ b/sechub-pds-solutions/scancode/env
@@ -5,3 +5,5 @@ SCANCODE_VERSION="32.0.4"
SPDX_TOOL_VERSION="1.1.7"
PDS_MAX_FILE_UPLOAD_BYTES=26214400000
+PDS_ENCRYPTION_ALGORITHM="NONE"
+PDS_ENCRYPTION_SECRET_KEY=""
\ No newline at end of file
diff --git a/sechub-pds-solutions/tern/env b/sechub-pds-solutions/tern/env
index 0b3aa6304..0c39a3c59 100644
--- a/sechub-pds-solutions/tern/env
+++ b/sechub-pds-solutions/tern/env
@@ -5,3 +5,5 @@ TERN_VERSION="2.12.1"
SCANCODE_VERSION="32.0.4"
PDS_MAX_FILE_UPLOAD_BYTES=26214400000
+PDS_ENCRYPTION_ALGORITHM="NONE"
+PDS_ENCRYPTION_SECRET_KEY=""
\ No newline at end of file
diff --git a/sechub-pds-solutions/xray/env b/sechub-pds-solutions/xray/env
index ec7da32d2..3fd4adb3b 100644
--- a/sechub-pds-solutions/xray/env
+++ b/sechub-pds-solutions/xray/env
@@ -15,3 +15,5 @@ BUILD_TYPE=download
# The Xray Wrapper version to use
# See: https://github.com/mercedes-benz/sechub/releases
XRAY_WRAPPER_VERSION="1.0.0"
+PDS_ENCRYPTION_ALGORITHM="NONE"
+PDS_ENCRYPTION_SECRET_KEY=""
\ No newline at end of file
diff --git a/sechub-solution/docker/SecHub-Debian.dockerfile b/sechub-solution/docker/SecHub-Debian.dockerfile
index 0de68d2d6..3d73e2bb2 100644
--- a/sechub-solution/docker/SecHub-Debian.dockerfile
+++ b/sechub-solution/docker/SecHub-Debian.dockerfile
@@ -13,7 +13,7 @@ ARG BASE_IMAGE
ARG TARGETARCH
# Build args
-ARG BUILD_TYPE="download"
+ARG BUILD_TYPE
ARG SECHUB_VERSION
ARG TAG=""
diff --git a/sechub-web-ui/.env b/sechub-web-ui/.env
index 6ca560f87..016b70e58 100644
--- a/sechub-web-ui/.env
+++ b/sechub-web-ui/.env
@@ -5,6 +5,7 @@
VITE_API_HOST=http://localhost:3000
# Optional variables needed for testing with SecHub Server Basic Auth
+# Note: if your password includes special characters e.g. $ you must write /$
VITE_API_LOCAL_DEV=false
VITE_API_USER='example-test-user'
VITE_API_PASSWORD='example-api-token'
\ No newline at end of file
diff --git a/sechub-web-ui/README.md b/sechub-web-ui/README.md
index d5ee0f159..84d9f7c04 100644
--- a/sechub-web-ui/README.md
+++ b/sechub-web-ui/README.md
@@ -40,22 +40,44 @@ To start the development server with hot-reload, run the following command. The
npm run dev
```
-> If you receive an empty page, do a reload (sometimes it needs a little bit of time until everything is setup correctly)
+> If you receive an empty page or buttons do not work, do a reload (sometimes it needs a little bit of time until everything is setup correctly)
> Add NODE_OPTIONS='--no-warnings' to suppress the JSON import warnings that happen as part of the Vuetify import mapping. If you are on Node [v21.3.0](https://nodejs.org/en/blog/release/v21.3.0) or higher, you can change this to NODE_OPTIONS='--disable-warning=5401'. If you don't mind the warning, you can remove this from your package.json dev script.
-#### Running in Development mode with sechub for testing
+#### Running in Development mode with SecHub Integrationtest Server for testing
-1. Start SecHub Integration Test Server (or Docker Server)
+1. Start SecHub Integration Test Server (or Docker Server)
+(for the correct run configuration follow the [developer guide](https://mercedes-benz.github.io/sechub/latest/sechub-developer-quickstart-guide.html#run-integration-tests-from-ide))
2. Configure your `.env.local` file by copying `.env` to `.env.local` and adjusting the variables as needed.
Set `VITE_API_LOCAL_DEV=true`
Set `VITE_API_USER` to your SecHub user
Set `VITE_API_PASSWORD` to your SecHub Api Token
3. Set `VITE_API_HOST` to the URL of your application http://localhost:3000 - this is because of the proxy defined in the Vite dev server to avoid CORS Issues
4. Start the SPA in Development mode (npm run dev)
+5. (Optional) See the /test-setups/setup-integration-test-server.sh script for setups (please note: the executor is only needed when PDS is used)
Happy Testing!
+#### Running in Development mode with SecHub Integrationtest Server and PDS Integrationtest Server (using Mocked scan products)
+> Only useful If you want to get mocked scan results
+1. Follow the steps above
+2. Start the integration test PDS
+(for the correct run configuration follow the [developer guide](https://mercedes-benz.github.io/sechub/latest/sechub-developer-quickstart-guide.html#run-integration-tests-from-ide))
+3. (Optional) Initial setup: execute /test-setups/setup-integration-test-server.sh
+
+#### Running in Development mode with SecHub Server and PDS as Docker Container
+> Only useful If you want to get real scan results
+1. Start the SecHub Server as Docker Container (see sechub-solution/01-...)
+2. Start the required PDS as Docker (e.g. sechub-pds-solutions/gosec/05-...)
+3. Set up PDS in sechub-solution/setups/ e.g. setup-gosec.sh
+4. Configure your `.env.local` file by copying `.env` to `.env.local` and adjusting the variables as needed.
+Set `VITE_API_LOCAL_DEV=true`
+Set `VITE_API_USER` to your SecHub user
+Set `VITE_API_PASSWORD` to your SecHub Api Token
+5. Make sure your user is assigned to the project you want to scan
+
+Now you can test your web-ui with sechub and real scans!
+
### Building for Production
Set Environment Variables:
diff --git a/sechub-web-ui/package-lock.json b/sechub-web-ui/package-lock.json
index 5f7e2c0eb..6a52b299a 100644
--- a/sechub-web-ui/package-lock.json
+++ b/sechub-web-ui/package-lock.json
@@ -11,8 +11,10 @@
"@mdi/font": "7.4.47",
"@openapitools/openapi-generator-cli": "^2.15.3",
"core-js": "^3.37.1",
+ "crypto-js": "^4.2.0",
"pinia": "^2.3.0",
"roboto-fontface": "*",
+ "uuid": "^11.0.3",
"vue": "^3.4.31",
"vue-i18n": "^10.0.5",
"vuetify": "^3.6.11"
@@ -20,6 +22,7 @@
"devDependencies": {
"@babel/types": "^7.24.7",
"@intlify/unplugin-vue-i18n": "^6.0.0",
+ "@types/crypto-js": "^4.2.2",
"@types/node": "^20.14.10",
"@vitejs/plugin-vue": "^5.0.5",
"@vue/eslint-config-typescript": "^13.0.0",
@@ -1523,6 +1526,13 @@
"integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==",
"license": "MIT"
},
+ "node_modules/@types/crypto-js": {
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/@types/crypto-js/-/crypto-js-4.2.2.tgz",
+ "integrity": "sha512-sDOLlVbHhXpAUAL0YHDUUwDZf3iN4Bwi4W6a0W0b+QcAezUbRtH4FVb+9J4h+XFPW7l/gQ9F8qC7P+Ec4k8QVQ==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@types/estree": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz",
@@ -2816,6 +2826,12 @@
"node": ">= 8"
}
},
+ "node_modules/crypto-js": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz",
+ "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==",
+ "license": "MIT"
+ },
"node_modules/cssesc": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
@@ -7171,6 +7187,19 @@
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
"license": "MIT"
},
+ "node_modules/uuid": {
+ "version": "11.0.3",
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.0.3.tgz",
+ "integrity": "sha512-d0z310fCWv5dJwnX1Y/MncBAqGMKEzlBb1AOf7z9K8ALnd0utBX/msg/fA0+sbyN1ihbMsLhrBlnl1ak7Wa0rg==",
+ "funding": [
+ "https://github.com/sponsors/broofa",
+ "https://github.com/sponsors/ctavan"
+ ],
+ "license": "MIT",
+ "bin": {
+ "uuid": "dist/esm/bin/uuid"
+ }
+ },
"node_modules/vite": {
"version": "5.4.11",
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.11.tgz",
diff --git a/sechub-web-ui/package.json b/sechub-web-ui/package.json
index ffe48f1ef..b54c86290 100644
--- a/sechub-web-ui/package.json
+++ b/sechub-web-ui/package.json
@@ -12,8 +12,10 @@
"@mdi/font": "7.4.47",
"@openapitools/openapi-generator-cli": "^2.15.3",
"core-js": "^3.37.1",
+ "crypto-js": "^4.2.0",
"pinia": "^2.3.0",
"roboto-fontface": "*",
+ "uuid": "^11.0.3",
"vue": "^3.4.31",
"vue-i18n": "^10.0.5",
"vuetify": "^3.6.11"
@@ -21,6 +23,7 @@
"devDependencies": {
"@babel/types": "^7.24.7",
"@intlify/unplugin-vue-i18n": "^6.0.0",
+ "@types/crypto-js": "^4.2.2",
"@types/node": "^20.14.10",
"@vitejs/plugin-vue": "^5.0.5",
"@vue/eslint-config-typescript": "^13.0.0",
diff --git a/sechub-web-ui/src/components.d.ts b/sechub-web-ui/src/components.d.ts
index 005e46954..bce1077da 100644
--- a/sechub-web-ui/src/components.d.ts
+++ b/sechub-web-ui/src/components.d.ts
@@ -13,8 +13,13 @@ declare module 'vue' {
Project: typeof import('./components/Project.vue')['default']
ProjectDetails: typeof import('./components/ProjectDetails.vue')['default']
ProjectDetailsFab: typeof import('./components/ProjectDetailsFab.vue')['default']
+ ProjectJobList: typeof import('./components/ProjectJobList.vue')['default']
ProjectsList: typeof import('./components/ProjectsList.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
+ ScanCreate: typeof import('./components/ScanCreate.vue')['default']
+ ScanFileUpload: typeof import('./components/ScanFileUpload.vue')['default']
+ ScanFileUploadTest: typeof import('./components/ScanFileUploadTest.vue')['default']
+ ScanTypeSelect: typeof import('./components/ScanTypeSelect.vue')['default']
}
}
diff --git a/sechub-web-ui/src/components/AppHeader.vue b/sechub-web-ui/src/components/AppHeader.vue
index 541fd47e7..ab9798f51 100644
--- a/sechub-web-ui/src/components/AppHeader.vue
+++ b/sechub-web-ui/src/components/AppHeader.vue
@@ -54,7 +54,7 @@
diff --git a/sechub-web-ui/src/components/ProjectsList.vue b/sechub-web-ui/src/components/ProjectsList.vue
index c5d569d93..136e8e9c0 100644
--- a/sechub-web-ui/src/components/ProjectsList.vue
+++ b/sechub-web-ui/src/components/ProjectsList.vue
@@ -48,26 +48,27 @@
-
+
diff --git a/sechub-web-ui/src/components/ScanFileUpload.vue b/sechub-web-ui/src/components/ScanFileUpload.vue
new file mode 100644
index 000000000..2b61f7d49
--- /dev/null
+++ b/sechub-web-ui/src/components/ScanFileUpload.vue
@@ -0,0 +1,135 @@
+
+
+
+
+ {{ error }}
+
+
+
+
+
+
+
+ {{ $t('SCAN_CREATE_FILE_UPLOAD_NOTE_ONE') }}
+ {{ $t('SCAN_CREATE_FILE_UPLOAD_NOTE_TWO') }}
+ {{ $t('SCAN_CREATE_FILE_UPLOAD_NOTE_THREE') }}
+
+
+
+
+ {{ file?.name }}
+
+
+
+
+
+
diff --git a/sechub-web-ui/src/components/ScanTypeSelect.vue b/sechub-web-ui/src/components/ScanTypeSelect.vue
new file mode 100644
index 000000000..08492db59
--- /dev/null
+++ b/sechub-web-ui/src/components/ScanTypeSelect.vue
@@ -0,0 +1,49 @@
+
+
+
+
+ {{ item }}
+
+
+
+
diff --git a/sechub-web-ui/src/i18n/locales/en.json b/sechub-web-ui/src/i18n/locales/en.json
index ae7995eda..8e066dda5 100644
--- a/sechub-web-ui/src/i18n/locales/en.json
+++ b/sechub-web-ui/src/i18n/locales/en.json
@@ -4,6 +4,7 @@
"OWNED": "owned",
"MEMBER": "member",
"NO_PROJECTS_ASSIGNED": "You are not assigned to any projects.",
+ "ERROR_MESSAGE_FETCHING_PROJECTS": "ProjectAPI error fetching assigned projects.",
"NO_JOBS_RUNNED": "No jobs were started for this project.",
"ERROR_FETCHING_DATA": "Could not load data from Server.",
"HEADER_JOB_TABLE_STATUS": "State",
@@ -18,5 +19,30 @@
"PROJECT_DETAILS_MEMBERS": "Members",
"PROJECT_DETAILS_NOT_OWNED": "You are not the owner or an admin and not allowed to view project members.",
"PROJECT_DETAILS_TOOLTIP_OPEN": "Open project details",
- "PROJECT_DETAILS_TOOLTIP_CLOSE": "Close project details"
+ "PROJECT_DETAILS_TOOLTIP_CLOSE": "Close project details",
+ "SCAN_CREATE_TITLE": "Create new Scan",
+ "SCAN_CREATE_SELECT_SCAN_TYPE": "Select Scan Type",
+ "SCAN_CREATE_FILE_UPLOAD": "File Upload",
+ "SCAN_CREATE_FILE_UPLOAD_NOTE_TITLE": "Important for secretScan",
+ "SCAN_CREATE_FILE_UPLOAD_NOTE_ONE": "1. Put your code in the structure __data__/web-ui-upload/your-code",
+ "SCAN_CREATE_FILE_UPLOAD_NOTE_TWO": "2. Create __data__.zip",
+ "SCAN_CREATE_FILE_UPLOAD_NOTE_THREE": "3. Upload __data__.zip",
+ "SCAN_CREATE_FILE_UPLOAD_INPUT": "Upload File",
+ "SCAN_CREATE_FILE_UPLOAD_PROGRESS": "Uploading your data...",
+ "SCAN_CREATE_FILE_UPLOAD_INPUT_ERROR_TITLE": "Upload Error. Please select a valid file",
+ "SCAN_CREATE_FILE_UPLOAD_INPUT_ERROR_ZIP": "File must be a valid .zip containing the code you wish to scan.",
+ "SCAN_CREATE_FILE_UPLOAD_INPUT_ERROR_TAR": "File must be a valid .tar containing the binary you wish to scan.",
+ "SCAN_CREATE_BINARIES": "Binaires",
+ "SCAN_CREATE_SOURCE_CODE": "Source Code",
+ "SCAN_CREATE_CODE_SCAN": "codeScan",
+ "SCAN_CREATE_SECRET_SCAN": "secretScan",
+ "SCAN_CREATE_SCAN_START": "Scan",
+ "SCAN_CREATE_SCAN_CONFIGURATION": "Scan Configuration",
+ "SCAN_ERROR_ALERT_TITLE": "Ooops, your scan failed",
+ "SCAN_ERROR_ALERT_JOB_NOT_CREATED": "Could not create a new Job.",
+ "SCAN_ERROR_ALERT_SOURCE_UPLOAD_FAILED": "Could not upload sources. Your Zip file might be too big or is not correctly packed. We recommend using the cli client for large files.",
+ "SCAN_ERROR_ALERT_BINARY_UPLOAD_FAILED": "Could not upload binaries. Your Tar file might be too big or is not correctly packed. We recommend using the cli client for large files.",
+ "SCAN_ERROR_ALERT_JOB_NOT_APPROVED": "Could not approve Job.",
+ "SCAN_ERROR_ALERT_NO_DATA_SECTION": "Could not get data section from configuration. Internal Error.",
+ "SCAN_ERROR_ALERT_GENERIC": "An internal error ocurred."
}
\ No newline at end of file
diff --git a/sechub-web-ui/src/pages/[id]/index.vue b/sechub-web-ui/src/pages/[id]/index.vue
new file mode 100644
index 000000000..5b27b5e80
--- /dev/null
+++ b/sechub-web-ui/src/pages/[id]/index.vue
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/sechub-web-ui/src/pages/[id].vue b/sechub-web-ui/src/pages/[id]/scan.vue
similarity index 78%
rename from sechub-web-ui/src/pages/[id].vue
rename to sechub-web-ui/src/pages/[id]/scan.vue
index 9dfada12c..72b66b445 100644
--- a/sechub-web-ui/src/pages/[id].vue
+++ b/sechub-web-ui/src/pages/[id]/scan.vue
@@ -1,4 +1,4 @@
-
+
diff --git a/sechub-web-ui/src/services/defaultClient.ts b/sechub-web-ui/src/services/defaultClient.ts
index 822b52a6e..ca5bb7b18 100644
--- a/sechub-web-ui/src/services/defaultClient.ts
+++ b/sechub-web-ui/src/services/defaultClient.ts
@@ -3,12 +3,16 @@ import configurationApi from './configurationService'
import projectApi from './productAdministrationService'
import systemApi from './systemApiService'
import otherApi from './otherService'
+import executionApi from './executionService/executionService'
+import scanService from './executionService/ScanService'
const defaultClient = {
withProjectApi: projectApi,
withSystemApi: systemApi,
withConfigurationApi: configurationApi,
withOtherApi: otherApi,
+ withExecutionApi: executionApi,
+ withScanService: scanService,
}
export default defaultClient
diff --git a/sechub-web-ui/src/services/executionService/FormDataBodyBuilder.ts b/sechub-web-ui/src/services/executionService/FormDataBodyBuilder.ts
new file mode 100644
index 000000000..21554be49
--- /dev/null
+++ b/sechub-web-ui/src/services/executionService/FormDataBodyBuilder.ts
@@ -0,0 +1,93 @@
+// SPDX-License-Identifier: MIT
+// This is implemented according to the sechub default client in Java
+// Instead of Multipart (Java) we use FromData (TypeScript)
+type FormDataContentType = 'STRING' | 'FILE' | 'STREAM' | 'BOUNDARY';
+
+interface FormDataContent {
+ name: string;
+ type: FormDataContentType;
+ value?: string;
+ file?: File;
+ stream?: () => Promise;
+ filename?: string;
+ contentType?: string;
+}
+
+export class FormDataBodyBuilder {
+ private formDataContent: FormDataContent[] = []
+ private boundary: string
+
+ constructor (boundary: string) {
+ this.boundary = boundary
+ }
+
+ public async build (): Promise {
+ if (this.formDataContent.length === 0) {
+ throw new Error('Must have at least one part to build a formData message.')
+ }
+
+ this.formDataContent.push(this.boundaryPart())
+
+ const parts: (string | Uint8Array)[] = []
+
+ for (const part of this.formDataContent) {
+ parts.push(this.constructPartHeader(part))
+
+ if (part.type === 'FILE' && part.file) {
+ await this.appendFileInChunks(part.file, parts)
+ } else if (part.type === 'STREAM' && part.stream) {
+ parts.push(new Uint8Array(await part.stream()))
+ }
+
+ if (part.type !== 'BOUNDARY') {
+ parts.push(new TextEncoder().encode('\r\n'))
+ }
+ }
+ return new Blob(parts)
+ }
+
+ public addString (name: string, value: string): this {
+ this.formDataContent.push({ name, type: 'STRING', value })
+ return this
+ }
+
+ public addFile (name: string, file: File): this {
+ this.formDataContent.push({ name, type: 'FILE', file, contentType: file.type })
+ return this
+ }
+
+ public addStream (name: string, stream: () => Promise, filename: string, contentType: string): this {
+ this.formDataContent.push({ name, type: 'STREAM', stream, filename, contentType })
+ return this
+ }
+
+ private boundaryPart (): FormDataContent {
+ return { name: '', type: 'BOUNDARY', value: `--${this.boundary}--` }
+ }
+
+ private constructPartHeader (part: FormDataContent): Uint8Array {
+ if (part.type === 'STRING') {
+ return new TextEncoder().encode(
+ `--${this.boundary}\r\nContent-Disposition: form-data; name="${part.name}"\r\nContent-Type: text/plain; charset=UTF-8\r\n\r\n${part.value}`
+ )
+ } else if (part.type === 'BOUNDARY') {
+ return new TextEncoder().encode(`--${this.boundary}--\r\n`)
+ } else {
+ const filename = part.file ? part.file.name : part.filename
+ const contentType = part.contentType || 'application/octet-stream'
+ return new TextEncoder().encode(
+ `--${this.boundary}\r\nContent-Disposition: form-data; name="${part.name}"; filename="${filename}"\r\nContent-Type: ${contentType}\r\n\r\n`
+ )
+ }
+ }
+
+ private async appendFileInChunks (file: File, parts: (string | Uint8Array)[]): Promise {
+ // const chunkSize = 64 * 1024 * 1024 // 64MB chunks
+ const chunkSize = 8192 // 8KB chunks
+ for (let offset = 0; offset < file.size; offset += chunkSize) {
+ const chunk = file.slice(offset, offset + chunkSize)
+ const arrayBuffer = await chunk.arrayBuffer()
+ parts.push(new Uint8Array(arrayBuffer))
+ }
+ }
+}
diff --git a/sechub-web-ui/src/services/executionService/ScanService.ts b/sechub-web-ui/src/services/executionService/ScanService.ts
new file mode 100644
index 000000000..874a6d129
--- /dev/null
+++ b/sechub-web-ui/src/services/executionService/ScanService.ts
@@ -0,0 +1,100 @@
+// SPDX-License-Identifier: MIT
+import {
+ SchedulerResult,
+ SecHubConfiguration,
+ UserApproveJobRequest,
+ UserCreateNewJobRequest,
+} from '@/generated-sources/openapi'
+import executionApi from './executionService'
+import { createSha256Checksum } from '../../utils/cryptoUtils'
+import { UserUploadsBinariesWorkaroundRequest, UserUploadSourceCodeWorkaroundRequest } from '@/services/executionService/executionService'
+import i18n from '@/i18n'
+
+// Implements the scan of a file in three steps: creating a Job, uploading the data and approve the job
+class ScanService {
+ async scan (configuration: SecHubConfiguration, projectId: string, file: File): Promise {
+ const errorMessages: string[] = []
+ try {
+ const jobId = await this.createJob(configuration)
+ if (jobId) {
+ await this.uploadData(configuration, jobId, file, errorMessages)
+ await this.approveJob(projectId, jobId, errorMessages)
+ } else {
+ errorMessages.push(i18n.global.t('SCAN_ERROR_ALERT_JOB_NOT_CREATED'))
+ }
+ } catch (error) {
+ console.error('Scan failed:', error)
+ errorMessages.push(i18n.global.t('SCAN_ERROR_ALERT_GENERIC'))
+ }
+ return errorMessages
+ }
+
+ private async createJob (configuration: SecHubConfiguration): Promise {
+ const requestParameters: UserCreateNewJobRequest = {
+ projectId: configuration.projectId,
+ secHubConfiguration: configuration,
+ }
+
+ try {
+ const result: SchedulerResult = await executionApi.userCreateNewJob(requestParameters)
+ return result.jobId
+ } catch (error) {
+ console.error('Job creation failed:', error)
+ return undefined
+ }
+ }
+
+ private async uploadData (configuration: SecHubConfiguration, jobId: string, file: File, errorMessages: string[]) {
+ const checksum: string = await createSha256Checksum(file)
+
+ if (configuration.data?.sources) {
+ const requestParameters: UserUploadSourceCodeWorkaroundRequest = {
+ projectId: configuration.projectId,
+ jobUUID: jobId,
+ checkSum: checksum,
+ file,
+ }
+
+ try {
+ await executionApi.userUploadSourceCode(requestParameters)
+ } catch (error) {
+ console.error('Source code upload failed:', error)
+ errorMessages.push(i18n.global.t('SCAN_ERROR_ALERT_SOURCE_UPLOAD_FAILED'))
+ }
+ } else if (configuration.data?.binaries) {
+ const size: string = file.size.toString()
+ const requestParameters: UserUploadsBinariesWorkaroundRequest = {
+ projectId: configuration.projectId,
+ jobUUID: jobId,
+ checkSum: checksum,
+ xFileSize: size,
+ file,
+ }
+
+ try {
+ await executionApi.userUploadsBinaries(requestParameters)
+ } catch (error) {
+ console.error('Binary upload failed:', error)
+ errorMessages.push(i18n.global.t('SCAN_ERROR_ALERT_BINARY_UPLOAD_FAILED'))
+ }
+ } else {
+ errorMessages.push(i18n.global.t('SCAN_ERROR_ALERT_NO_DATA_SECTION'))
+ }
+ }
+
+ private async approveJob (projectId: string, jobId: string, errorMessages: string[]) {
+ const requestParameters: UserApproveJobRequest = {
+ projectId,
+ jobUUID: jobId,
+ }
+
+ try {
+ await executionApi.userApproveJob(requestParameters)
+ } catch (error) {
+ console.error('Job approval failed:', error)
+ errorMessages.push(i18n.global.t('SCAN_ERROR_ALERT_JOB_NOT_APPROVED'))
+ }
+ }
+}
+
+export default new ScanService()
diff --git a/sechub-web-ui/src/services/executionService/executionService.ts b/sechub-web-ui/src/services/executionService/executionService.ts
new file mode 100644
index 000000000..c5e9be6e9
--- /dev/null
+++ b/sechub-web-ui/src/services/executionService/executionService.ts
@@ -0,0 +1,153 @@
+// SPDX-License-Identifier: MIT
+import { SecHubExecutionApi } from '@/generated-sources/openapi'
+import * as runtime from '@/generated-sources/openapi/runtime'
+import apiConfig from '../configuration'
+import { v4 as uuidv4 } from 'uuid'
+import { FormDataBodyBuilder } from './FormDataBodyBuilder'
+
+export interface UserUploadSourceCodeWorkaroundRequest {
+ projectId: string;
+ jobUUID: string;
+ checkSum: string;
+ file: File;
+}
+
+export interface UserUploadsBinariesWorkaroundRequest {
+ projectId: string;
+ jobUUID: string;
+ file: File;
+ xFileSize: string;
+ checkSum: string;
+}
+
+// We use this class to override the upload functions generated by the openapi client
+class SecHubExecutionApiWorkaround extends SecHubExecutionApi {
+ async userUploadSourceCode (requestParameters: UserUploadSourceCodeWorkaroundRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise {
+ await this.userUploadSourceCodeRaw(requestParameters, initOverrides)
+ }
+
+ async userUploadSourceCodeRaw (requestParameters: UserUploadSourceCodeWorkaroundRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> {
+ if (requestParameters.projectId == null) {
+ throw new runtime.RequiredError(
+ 'projectId',
+ 'Required parameter "projectId" was null or undefined when calling userUploadSourceCode().'
+ )
+ }
+
+ if (requestParameters.jobUUID == null) {
+ throw new runtime.RequiredError(
+ 'jobUUID',
+ 'Required parameter "jobUUID" was null or undefined when calling userUplosadSourceCode().'
+ )
+ }
+
+ if (requestParameters.checkSum == null) {
+ throw new runtime.RequiredError(
+ 'checkSum',
+ 'Required parameter "checkSum" was null or undefined when calling userUploadSourceCode().'
+ )
+ }
+
+ if (requestParameters.file == null) {
+ throw new runtime.RequiredError(
+ 'file',
+ 'Required parameter "file" was null or undefined when calling userUploadSourceCode().'
+ )
+ }
+
+ // Create boundary (not generated by openapi)
+ const boundary = uuidv4()
+
+ // building header with boundary
+ const headerParameters: runtime.HTTPHeaders = {}
+ headerParameters['Content-Type'] = `multipart/form-data; boundary=${boundary}`
+ if (this.configuration && (this.configuration.username !== undefined || this.configuration.password !== undefined)) {
+ headerParameters.Authorization = 'Basic ' + btoa(this.configuration.username + ':' + this.configuration.password)
+ }
+
+ // building body
+ const builder = new FormDataBodyBuilder(boundary)
+ builder.addString('checkSum', requestParameters.checkSum)
+ builder.addFile('file', requestParameters.file)
+ const formData = await builder.build()
+
+ const response = await this.request({
+ path: `/api/project/{projectId}/job/{jobUUID}/sourcecode`.replace(`{${'projectId'}}`, encodeURIComponent(String(requestParameters.projectId))).replace(`{${'jobUUID'}}`, encodeURIComponent(String(requestParameters.jobUUID))),
+ method: 'POST',
+ headers: headerParameters,
+ body: formData,
+ }, initOverrides)
+
+ return new runtime.VoidApiResponse(response)
+ }
+
+ async userUploadsBinaries (requestParameters: UserUploadsBinariesWorkaroundRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise {
+ await this.userUploadsBinariesRaw(requestParameters, initOverrides)
+ }
+
+ async userUploadsBinariesRaw (requestParameters: UserUploadsBinariesWorkaroundRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> {
+ if (requestParameters.projectId == null) {
+ throw new runtime.RequiredError(
+ 'projectId',
+ 'Required parameter "projectId" was null or undefined when calling userUploadsBinaries().'
+ )
+ }
+
+ if (requestParameters.jobUUID == null) {
+ throw new runtime.RequiredError(
+ 'jobUUID',
+ 'Required parameter "jobUUID" was null or undefined when calling userUploadsBinaries().'
+ )
+ }
+
+ if (requestParameters.file == null) {
+ throw new runtime.RequiredError(
+ 'file',
+ 'Required parameter "file" was null or undefined when calling userUploadsBinaries().'
+ )
+ }
+
+ if (requestParameters.xFileSize == null) {
+ throw new runtime.RequiredError(
+ 'xFileSize',
+ 'Required parameter "xFileSize" was null or undefined when calling userUploadsBinaries().'
+ )
+ }
+
+ if (requestParameters.checkSum == null) {
+ throw new runtime.RequiredError(
+ 'checkSum',
+ 'Required parameter "checkSum" was null or undefined when calling userUploadsBinaries().'
+ )
+ }
+
+ // Adding content type and boundary (not generated)
+ const boundary = uuidv4()
+
+ // building header
+ const headerParameters: runtime.HTTPHeaders = {}
+ headerParameters['Content-Type'] = `multipart/form-data; boundary=${boundary}`
+ headerParameters['x-file-size'] = requestParameters.xFileSize
+ if (this.configuration && (this.configuration.username !== undefined || this.configuration.password !== undefined)) {
+ headerParameters.Authorization = 'Basic ' + btoa(this.configuration.username + ':' + this.configuration.password)
+ }
+
+ // building body
+ const builder = new FormDataBodyBuilder(boundary)
+ builder.addString('checkSum', requestParameters.checkSum)
+ builder.addFile('file', requestParameters.file)
+ const formData = await builder.build()
+
+ const response = await this.request({
+ path: `/api/project/{projectId}/job/{jobUUID}/binaries`.replace(`{${'projectId'}}`, encodeURIComponent(String(requestParameters.projectId))).replace(`{${'jobUUID'}}`, encodeURIComponent(String(requestParameters.jobUUID))),
+ method: 'POST',
+ headers: headerParameters,
+ body: formData,
+ }, initOverrides)
+
+ return new runtime.VoidApiResponse(response)
+ }
+}
+
+const executionApi = new SecHubExecutionApiWorkaround(apiConfig)
+export default executionApi
diff --git a/sechub-web-ui/src/typed-router.d.ts b/sechub-web-ui/src/typed-router.d.ts
index 9cfb62a16..a75be2f18 100644
--- a/sechub-web-ui/src/typed-router.d.ts
+++ b/sechub-web-ui/src/typed-router.d.ts
@@ -20,6 +20,7 @@ declare module 'vue-router/auto-routes' {
*/
export interface RouteNamedMap {
'/': RouteRecordInfo<'/', '/', Record, Record>,
- '/[id]': RouteRecordInfo<'/[id]', '/:id', { id: ParamValue }, { id: ParamValue }>,
+ '/[id]/': RouteRecordInfo<'/[id]/', '/:id', { id: ParamValue }, { id: ParamValue }>,
+ '/[id]/scan': RouteRecordInfo<'/[id]/scan', '/:id/scan', { id: ParamValue }, { id: ParamValue }>,
}
}
diff --git a/sechub-web-ui/src/utils/cryptoUtils.ts b/sechub-web-ui/src/utils/cryptoUtils.ts
new file mode 100644
index 000000000..168ea012d
--- /dev/null
+++ b/sechub-web-ui/src/utils/cryptoUtils.ts
@@ -0,0 +1,41 @@
+// SPDX-License-Identifier: MIT
+import CryptoJS from 'crypto-js'
+
+export async function createSha256Checksum (file: File): Promise {
+ const chunkSize = 64 * 1024 * 1024 // 64MB chunks
+ const fileReader = new FileReader()
+ let offset = 0
+ const sha256 = CryptoJS.algo.SHA256.create()
+
+ return new Promise((resolve, reject) => {
+ fileReader.onload = (event: ProgressEvent) => {
+ if (!event.target) {
+ reject(new Error('FileReader event target is null'))
+ return
+ }
+
+ const data = event.target.result as ArrayBuffer
+ const wordArray = CryptoJS.lib.WordArray.create(data)
+ sha256.update(wordArray)
+
+ offset += chunkSize
+ if (offset < file.size) {
+ readNextChunk()
+ } else {
+ const hash = sha256.finalize()
+ resolve(hash.toString(CryptoJS.enc.Hex))
+ }
+ }
+
+ fileReader.onerror = error => {
+ reject(error)
+ }
+
+ function readNextChunk () {
+ const slice = file.slice(offset, offset + chunkSize)
+ fileReader.readAsArrayBuffer(slice)
+ }
+
+ readNextChunk()
+ })
+}
diff --git a/sechub-web-ui/src/utils/projectUtils.ts b/sechub-web-ui/src/utils/projectUtils.ts
index b4d073986..cf1ee1440 100644
--- a/sechub-web-ui/src/utils/projectUtils.ts
+++ b/sechub-web-ui/src/utils/projectUtils.ts
@@ -1,5 +1,8 @@
// SPDX-License-Identifier: MIT
export function formatDate (dateString: string) {
+ if (dateString === '') {
+ return
+ }
const date = new Date(dateString)
const day = String(date.getDate()).padStart(2, '0')
const month = String(date.getMonth() + 1).padStart(2, '0')
diff --git a/sechub-web-ui/src/utils/scanConfigUtils.ts b/sechub-web-ui/src/utils/scanConfigUtils.ts
new file mode 100644
index 000000000..6f66f3b5e
--- /dev/null
+++ b/sechub-web-ui/src/utils/scanConfigUtils.ts
@@ -0,0 +1,49 @@
+// SPDX-License-Identifier: MIT
+import {
+ SecHubCodeScanConfiguration,
+ SecHubConfiguration,
+ SecHubDataConfiguration,
+ SecHubFileSystemConfiguration,
+ SecHubSecretScanConfiguration,
+} from '@/generated-sources/openapi'
+
+export function buildSecHubConfiguration (scanTypes: string[], uploadFile: File, fileType: string, projectId: string): SecHubConfiguration {
+ const UNIQUE_NAME = 'web-ui-upload'
+
+ const fileSystemConfig: SecHubFileSystemConfiguration = {
+ files: [uploadFile.name],
+ }
+
+ const dataConfiguration: SecHubDataConfiguration = {
+ sources: fileType === 'sources' ? [{ name: UNIQUE_NAME, fileSystem: fileSystemConfig }] : undefined,
+ binaries: fileType === 'binaries' ? [{ name: UNIQUE_NAME, fileSystem: fileSystemConfig }] : undefined,
+ }
+
+ const codeScanConfiguration: SecHubCodeScanConfiguration = {}
+ const secretScanConfiguration: SecHubSecretScanConfiguration = {}
+
+ if (scanTypes.includes('codeScan')) {
+ codeScanConfiguration.use = [UNIQUE_NAME]
+ }
+
+ if (scanTypes.includes('secretScan')) {
+ secretScanConfiguration.use = [UNIQUE_NAME]
+ }
+
+ const config: SecHubConfiguration = {
+ apiVersion: '1.0',
+ projectId,
+ data: dataConfiguration,
+ }
+
+ // adding scan types to configuration
+ if (codeScanConfiguration.use) {
+ config.codeScan = codeScanConfiguration
+ }
+
+ if (secretScanConfiguration.use) {
+ config.secretScan = secretScanConfiguration
+ }
+
+ return config
+}
diff --git a/sechub-web-ui/test-setups/gosec-executor.json b/sechub-web-ui/test-setups/gosec-executor.json
new file mode 100644
index 000000000..3fbbff38d
--- /dev/null
+++ b/sechub-web-ui/test-setups/gosec-executor.json
@@ -0,0 +1,39 @@
+{
+ "name": "pds-gosec",
+ "productIdentifier": "PDS_CODESCAN",
+ "executorVersion": 1,
+ "enabled": true,
+ "setup": {
+ "baseURL": "https://localhost:8444",
+ "credentials": {
+ "user": "pds-inttest-techuser",
+ "password": "pds-inttest-apitoken"
+ },
+ "jobParameters": [
+ {
+ "key": "pds.config.productidentifier",
+ "value": "PDS_SOLUTION_GOSEC_MOCKED"
+ },
+ {
+ "key": "pds.config.use.sechub.storage",
+ "value": false
+ },
+ {
+ "key": "pds.mocking.disabled",
+ "value": true
+ },
+ {
+ "key": "sechub.productexecutor.pds.timeout.minutes",
+ "value": 60
+ },
+ {
+ "key": "sechub.productexecutor.pds.timetowait.nextcheck.milliseconds",
+ "value": 500
+ },
+ {
+ "key": "sechub.productexecutor.pds.trustall.certificates",
+ "value": true
+ }
+ ]
+ }
+}
diff --git a/sechub-web-ui/test-setups/setup-integration-test-server.sh b/sechub-web-ui/test-setups/setup-integration-test-server.sh
new file mode 100644
index 000000000..101c671b8
--- /dev/null
+++ b/sechub-web-ui/test-setups/setup-integration-test-server.sh
@@ -0,0 +1,14 @@
+#!/bin/bash
+# SPDX-License-Identifier: MIT
+export SECHUB_APITOKEN='';
+export SECHUB_USERID=;
+export SECHUB_SERVER=https://localhost:8443;
+export TEST_PROJECT_NAME="test-gosec"
+
+../sechub-developertools/scripts/sechub-api.sh project_create $TEST_PROJECT_NAME $SECHUB_USERID
+../sechub-developertools/scripts/sechub-api.sh project_assign_user $TEST_PROJECT_NAME $SECHUB_USERID
+
+# creates and assigns a mocked executor, the result will always be RED
+../sechub-developertools/scripts/sechub-api.sh executor_create gosec-executor.json
+../sechub-developertools/scripts/sechub-api.sh profile_create gosec-profile pds-gosec
+../sechub-developertools/scripts/sechub-api.sh project_assign_profile $TEST_PROJECT_NAME gosec-profile