From 6de864219408847d79d5069a090f09ae49f011ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicklas=20K=C3=B6rtge?= Date: Wed, 20 Nov 2024 15:39:27 +0100 Subject: [PATCH 1/3] allow scanning private repos, update error handling in frontend MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Nicklas Körtge --- .../components/global/NotificationsView.vue | 27 ++++----- frontend/src/components/home/SearchBar.vue | 59 +++++++++++++------ frontend/src/helpers/scan.js | 51 ++++++++++++---- frontend/src/model.js | 16 ++++- pom.xml | 2 +- .../ibm/domain/scanning/ScanAggregate.java | 13 ++-- .../scanning/authentication/ICredentials.java | 22 +++++++ .../authentication/PersonalAccessToken.java | 24 ++++++++ .../UsernameAndPasswordCredentials.java | 25 ++++++++ .../scanning/events/ScanRequestedEvent.java | 20 ++++++- .../database/readmodels/CBOMReadModel.java | 3 +- .../readmodels/CBOMReadRepository.java | 3 +- .../api/v1/scanning/Credentials.java | 28 +++++++++ .../api/v1/scanning/ScanRequest.java | 9 ++- .../api/v1/scanning/ScanningResource.java | 29 ++++++++- ...estComplianceCheckForCBOMQueryHandler.java | 5 +- .../commands/CloneGitRepositoryCommand.java | 20 ++++++- .../scanning/commands/RequestScanCommand.java | 28 ++++++++- .../commands/RequestScanCommandHandler.java | 7 ++- .../eventhandler/ScanEventHandler.java | 3 +- .../processmanager/ScanProcessManager.java | 16 ++++- .../scanning/projector/CBOMProjector.java | 3 +- .../scanning/services/git/GitService.java | 27 ++++++++- .../java/JavaDetectionCollectionRule.java | 3 +- .../python/PythonDetectionCollectionRule.java | 3 +- 25 files changed, 359 insertions(+), 87 deletions(-) create mode 100644 src/main/java/com/ibm/domain/scanning/authentication/ICredentials.java create mode 100644 src/main/java/com/ibm/domain/scanning/authentication/PersonalAccessToken.java create mode 100644 src/main/java/com/ibm/domain/scanning/authentication/UsernameAndPasswordCredentials.java create mode 100644 src/main/java/com/ibm/presentation/api/v1/scanning/Credentials.java diff --git a/frontend/src/components/global/NotificationsView.vue b/frontend/src/components/global/NotificationsView.vue index 79a6c2532..d62386e68 100644 --- a/frontend/src/components/global/NotificationsView.vue +++ b/frontend/src/components/global/NotificationsView.vue @@ -31,47 +31,42 @@ export default { var kind = "error"; var title = "Unknown error"; var description = "An unknown error has occured."; - if (error == ErrorStatus.NoConnection) { + if (error.status === ErrorStatus.NoConnection) { kind = "error"; title = "No connection"; description = "Connection to the server has failed. Please try again later."; - } else if (error == ErrorStatus.InvalidRepo) { + } else if (error.status === ErrorStatus.InvalidRepo) { kind = "error"; title = "Invalid repository"; description = "The provided address does not lead to a readable repository."; - } else if (error == ErrorStatus.JsonParsing) { + } else if (error.status === ErrorStatus.JsonParsing) { kind = "error"; title = "Parsing error"; description = "An incorrect JSON file cannot be parsed."; - } else if (error == ErrorStatus.B64Decoding) { + } else if (error.status === ErrorStatus.ScanError) { kind = "error"; - title = "Decoding error"; - description = "An incorrect base 64 file cannot be decoded."; - } else if (error == ErrorStatus.BranchNotSpecified) { - kind = "error"; - title = "Clone fail"; - description = - "Could not clone git repo branch 'main' or 'master'. Try to specify the branch."; - } else if (error == ErrorStatus.InvalidCbom) { + title = "Error while scanning"; + description = error.message; + } else if (error.status === ErrorStatus.InvalidCbom) { kind = "error"; title = "Invalid CBOM"; description = "The provided CBOM does not respect the expected format."; - } else if (error == ErrorStatus.IgnoredComponent) { + } else if (error.status === ErrorStatus.IgnoredComponent) { kind = "info"; title = "Some components are not shown"; description = "The provided CBOM contains one or several components that are not cryptographic assets. They are not displayed here."; - } else if (error == ErrorStatus.MultiUpload) { + } else if (error.status === ErrorStatus.MultiUpload) { kind = "error"; title = "Multiple upload"; description = "Please only upload a single CBOM file."; - } else if (error == ErrorStatus.EmptyDatabase) { + } else if (error.status === ErrorStatus.EmptyDatabase) { kind = "warning"; title = "Empty database"; description = "Connection to the server was successful, but the CBOM database is empty."; - } else if (error == ErrorStatus.FallBackLocalComplianceReport) { + } else if (error.status === ErrorStatus.FallBackLocalComplianceReport) { kind = "warning"; title = "Limited compliance results"; description = diff --git a/frontend/src/components/home/SearchBar.vue b/frontend/src/components/home/SearchBar.vue index 2ee37fa19..6db7083fb 100644 --- a/frontend/src/components/home/SearchBar.vue +++ b/frontend/src/components/home/SearchBar.vue @@ -6,12 +6,12 @@ class="search-bar" placeholder="Enter the Git URL to scan" v-model="model.codeOrigin.gitLink" - @keyup.enter="connectAndScan(gitInfo()[0], gitInfo()[1])" + @keyup.enter="connectAndScan(advancedOptions()[0], advancedOptions()[1], advancedOptions()[2])" /> Scan @@ -26,18 +26,37 @@
- - + + + + + + + + + +
@@ -58,14 +77,16 @@ export default { filterOpen: false, gitBranch: null, gitSubfolder: null, + username: null, + passwordOrPAT: null, }; }, methods: { - gitInfo: function () { + advancedOptions: function () { if (this.filterOpen) { - return [this.gitBranch, this.gitSubfolder]; + return [this.gitBranch, this.gitSubfolder, { username: this.username, passwordOrPAT: this.passwordOrPAT }]; } else { - return [null, null]; + return [null, null, null]; } }, }, @@ -92,11 +113,11 @@ export default { .filters-leave-active { transition: all 0.4s; /* max-height should be larger than the tallest element: https://stackoverflow.com/questions/42591331/animate-height-on-v-if-in-vuejs-using-transition */ - max-height: 150px; + max-height: 250px; } .filters-enter, .filters-leave-to { opacity: 0; - max-height: 0px; + max-height: 0; } diff --git a/frontend/src/helpers/scan.js b/frontend/src/helpers/scan.js index af484c5f5..911063927 100644 --- a/frontend/src/helpers/scan.js +++ b/frontend/src/helpers/scan.js @@ -44,7 +44,7 @@ function startWebSocket(socketURL) { // In safari, manually closing the connection creates an error: // Do not display an error when the connection is manually closed by the user console.warn( - "The connection was closed by the client. An connection error occured, but has NOT been notified in the UI." + "The connection was closed by the client. An connection error occurred, but has NOT been notified in the UI." ); } else { console.error("WebSocket error:", error); @@ -68,9 +68,10 @@ export function stopWebSocket() { // } } -export function connectAndScan(gitBranch, gitSubfolder) { +export function connectAndScan(gitBranch, gitSubfolder, credentials) { model.resetScanningInfo(); - setAndCleanCodeOrigin(gitBranch, gitSubfolder); + setCodeOrigin(gitBranch, gitSubfolder); + setCredentials(credentials) let clientId = uuid4(); let socketURL = `${API_SCAN_URL}/${clientId}`; startWebSocket(socketURL); @@ -81,19 +82,30 @@ function scan() { model.addError(ErrorStatus.NoConnection); console.log("No socket in model"); } else if (!model.codeOrigin.gitLink) { - // TODO: Should I validate the look of the Git link in the frontend? model.addError(ErrorStatus.InvalidRepo); console.log("Git URL not valid"); } else { - var request = {}; - request["gitUrl"] = model.codeOrigin.gitLink; + // build scan request + const scanRequest = {}; + // set scan options + scanRequest["gitUrl"] = model.codeOrigin.gitLink; if (model.codeOrigin.gitBranch) { - request["branch"] = model.codeOrigin.gitBranch; + scanRequest["branch"] = model.codeOrigin.gitBranch; } if (model.codeOrigin.gitSubfolder) { - request["subfolder"] = model.codeOrigin.gitSubfolder; + scanRequest["subfolder"] = model.codeOrigin.gitSubfolder; } - model.scanning.socket.send(JSON.stringify(request)); + // set credentials + if (model.credentials.pat) { + scanRequest["credentials"] = {} + scanRequest["credentials"]["pat"] = model.credentials.pat; + } else if (model.credentials.username && model.credentials.password) { + scanRequest["credentials"] = {} + scanRequest["credentials"]["username"] = model.credentials.username; + scanRequest["credentials"]["password"] = model.credentials.password; + } + + model.scanning.socket.send(JSON.stringify(scanRequest)); // this.filterOpen = false model.scanning.isScanning = true; model.scanning.scanningStatus = STATES.LOADING; @@ -118,11 +130,13 @@ function handleMessage(messageJson) { ); // Time in seconds } } else if (obj["type"] === "ERROR") { - model.addError(ErrorStatus.BranchNotSpecified); // TODO: When several different error messages will exist, this will have to be changed + model.addError(ErrorStatus.ScanError, model.scanning.scanningStatusMessage = obj["message"]); // + // update state model.scanning.scanningStatusMessage = obj["message"]; - console.error("Error from backend:", model.scanning.scanningStatusMessage); model.scanning.scanningStatus = STATES.ERROR; model.scanning.isScanning = false; + // log + console.error("Error from backend:", model.scanning.scanningStatusMessage); } else if (obj["type"] === "PURL") { model.codeOrigin.gitPurls = obj["purls"]; // This is not strictly necessary anymore now that I read PURLs from the CBOM, but it arrives before the CBOM so I leave it @@ -150,7 +164,7 @@ function handleMessage(messageJson) { } } -function setAndCleanCodeOrigin(gitBranch, gitSubfolder) { +function setCodeOrigin(gitBranch, gitSubfolder) { if (model.codeOrigin.gitLink) { model.codeOrigin.gitLink = model.codeOrigin.gitLink.trim(); } @@ -161,3 +175,16 @@ function setAndCleanCodeOrigin(gitBranch, gitSubfolder) { model.codeOrigin.gitSubfolder = gitSubfolder.trim(); } } + +function setCredentials(credentials) { + if (credentials === null) { + return + } + + if (credentials.username && credentials.passwordOrPAT) { + model.credentials.username = credentials.username; + model.credentials.password = credentials.passwordOrPAT; + } else if (credentials.passwordOrPAT) { + model.credentials.pat = credentials.passwordOrPAT; + } +} diff --git a/frontend/src/model.js b/frontend/src/model.js index f5ba138cf..13a703971 100644 --- a/frontend/src/model.js +++ b/frontend/src/model.js @@ -28,6 +28,11 @@ export const model = reactive({ gitPurls: [], uploadedFileName: null, }, + credentials: { + username: null, + password: null, + pat: null, + }, policyCheckResult: null, errors: [], lastCboms: [], @@ -62,8 +67,13 @@ export const model = reactive({ model.codeOrigin.gitPurls = []; model.codeOrigin.uploadedFileName = null; }, - addError(error) { - this.errors.push(error); + resetCredentials() { + model.credentials.username = null; + model.credentials.password = null; + model.credentials.pat = null; + }, + addError(errorStatus, message) { + this.errors.push({status: errorStatus, message: message}); }, closeError(index) { this.errors.splice(index, 1); @@ -73,7 +83,7 @@ export const model = reactive({ export const ErrorStatus = { NoConnection: "NoConnection", InvalidRepo: "InvalidRepo", - BranchNotSpecified: "BranchNotSpecified", + ScanError: "ScanError", JsonParsing: "JsonParsing", InvalidCbom: "InvalidCbom", IgnoredComponent: "IgnoredComponent", diff --git a/pom.xml b/pom.xml index ab29ad355..c7347e6bc 100644 --- a/pom.xml +++ b/pom.xml @@ -27,7 +27,7 @@ io.quarkus.platform 3.16.3 - 1.3.4 + 1.3.5 10.14.0.2599 10.7.0.96327 diff --git a/src/main/java/com/ibm/domain/scanning/ScanAggregate.java b/src/main/java/com/ibm/domain/scanning/ScanAggregate.java index c3c6923d9..4066263af 100644 --- a/src/main/java/com/ibm/domain/scanning/ScanAggregate.java +++ b/src/main/java/com/ibm/domain/scanning/ScanAggregate.java @@ -20,6 +20,7 @@ package com.ibm.domain.scanning; import app.bootstrap.core.ddd.AggregateRoot; +import com.ibm.domain.scanning.authentication.ICredentials; import com.ibm.domain.scanning.errors.CommitHashAlreadyExists; import com.ibm.domain.scanning.errors.InvalidGitUrl; import com.ibm.domain.scanning.errors.ScanResultForLanguageAlreadyExists; @@ -34,7 +35,6 @@ import java.util.List; import java.util.Map; import java.util.Optional; -import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -51,7 +51,7 @@ private ScanAggregate(@Nonnull final ScanId id, @Nonnull final ScanRequest scanR } private ScanAggregate( - @NotNull ScanId id, + @Nonnull ScanId id, @Nonnull ScanRequest scanRequest, @Nullable Commit commit, @Nullable Map languageScans) { @@ -63,14 +63,17 @@ private ScanAggregate( @Nonnull public static ScanAggregate requestScan( - @Nonnull ScanId scanId, @Nonnull final ScanRequest scanRequest) throws InvalidGitUrl { + @Nonnull ScanId scanId, + @Nonnull final ScanRequest scanRequest, + @Nullable ICredentials credentials) + throws InvalidGitUrl { // validate value object scanRequest.validate(); // create aggregate final ScanAggregate aggregate = new ScanAggregate(scanId, scanRequest); // change state: start a scan // add domain event, uncommited! - aggregate.apply(new ScanRequestedEvent(aggregate.getId())); + aggregate.apply(new ScanRequestedEvent(aggregate.getId(), credentials)); return aggregate; } @@ -138,7 +141,7 @@ public int hashCode() { */ @Nonnull public static ScanAggregate reconstruct( - @NotNull ScanId id, + @Nonnull ScanId id, @Nonnull ScanRequest scanRequest, @Nullable Commit commit, @Nullable Map languageScans) { diff --git a/src/main/java/com/ibm/domain/scanning/authentication/ICredentials.java b/src/main/java/com/ibm/domain/scanning/authentication/ICredentials.java new file mode 100644 index 000000000..cdbcc1c31 --- /dev/null +++ b/src/main/java/com/ibm/domain/scanning/authentication/ICredentials.java @@ -0,0 +1,22 @@ +/* + * CBOMkit + * Copyright (C) 2024 IBM + * + * 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. + */ +package com.ibm.domain.scanning.authentication; + +public interface ICredentials {} diff --git a/src/main/java/com/ibm/domain/scanning/authentication/PersonalAccessToken.java b/src/main/java/com/ibm/domain/scanning/authentication/PersonalAccessToken.java new file mode 100644 index 000000000..54ca73942 --- /dev/null +++ b/src/main/java/com/ibm/domain/scanning/authentication/PersonalAccessToken.java @@ -0,0 +1,24 @@ +/* + * CBOMkit + * Copyright (C) 2024 IBM + * + * 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. + */ +package com.ibm.domain.scanning.authentication; + +import jakarta.annotation.Nonnull; + +public record PersonalAccessToken(@Nonnull String token) implements ICredentials {} diff --git a/src/main/java/com/ibm/domain/scanning/authentication/UsernameAndPasswordCredentials.java b/src/main/java/com/ibm/domain/scanning/authentication/UsernameAndPasswordCredentials.java new file mode 100644 index 000000000..1dbad0a0b --- /dev/null +++ b/src/main/java/com/ibm/domain/scanning/authentication/UsernameAndPasswordCredentials.java @@ -0,0 +1,25 @@ +/* + * CBOMkit + * Copyright (C) 2024 IBM + * + * 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. + */ +package com.ibm.domain.scanning.authentication; + +import jakarta.annotation.Nonnull; + +public record UsernameAndPasswordCredentials(@Nonnull String username, @Nonnull String password) + implements ICredentials {} diff --git a/src/main/java/com/ibm/domain/scanning/events/ScanRequestedEvent.java b/src/main/java/com/ibm/domain/scanning/events/ScanRequestedEvent.java index 4a0ea93cb..c7b1824d2 100644 --- a/src/main/java/com/ibm/domain/scanning/events/ScanRequestedEvent.java +++ b/src/main/java/com/ibm/domain/scanning/events/ScanRequestedEvent.java @@ -21,13 +21,18 @@ import app.bootstrap.core.ddd.DomainEvent; import com.ibm.domain.scanning.ScanId; +import com.ibm.domain.scanning.authentication.ICredentials; import jakarta.annotation.Nonnull; +import jakarta.annotation.Nullable; +import java.util.Optional; public final class ScanRequestedEvent extends DomainEvent { @Nonnull private final ScanId scanId; + @Nullable private final ICredentials credentials; - public ScanRequestedEvent(@Nonnull ScanId scanId) { + public ScanRequestedEvent(@Nonnull ScanId scanId, @Nullable ICredentials credentials) { this.scanId = scanId; + this.credentials = credentials; } @Nonnull @@ -35,9 +40,20 @@ public ScanId getScanId() { return scanId; } + @Nullable public ICredentials getCredentials() { + return credentials; + } + @Nonnull @Override public String toString() { - return this.getClass().getSimpleName() + "[scanId=" + scanId + "]"; + return this.getClass().getSimpleName() + + "[scanId=" + + scanId + + ", credentials=" + + Optional.ofNullable(credentials) + .map(c -> c.getClass().getSimpleName()) + .orElse("none") + + "]"; } } diff --git a/src/main/java/com/ibm/infrastructure/database/readmodels/CBOMReadModel.java b/src/main/java/com/ibm/infrastructure/database/readmodels/CBOMReadModel.java index f08bfeaaa..0f3db4880 100644 --- a/src/main/java/com/ibm/infrastructure/database/readmodels/CBOMReadModel.java +++ b/src/main/java/com/ibm/infrastructure/database/readmodels/CBOMReadModel.java @@ -35,7 +35,6 @@ import java.util.UUID; import org.hibernate.annotations.JdbcTypeCode; import org.hibernate.type.SqlTypes; -import org.jetbrains.annotations.NotNull; @Entity @Cacheable @@ -80,7 +79,7 @@ public CBOMReadModel( protected CBOMReadModel() {} @Override - public @NotNull UUID getId() { + public @Nonnull UUID getId() { return this.id; } diff --git a/src/main/java/com/ibm/infrastructure/database/readmodels/CBOMReadRepository.java b/src/main/java/com/ibm/infrastructure/database/readmodels/CBOMReadRepository.java index 2d716a75d..1afbe254d 100644 --- a/src/main/java/com/ibm/infrastructure/database/readmodels/CBOMReadRepository.java +++ b/src/main/java/com/ibm/infrastructure/database/readmodels/CBOMReadRepository.java @@ -33,7 +33,6 @@ import java.util.List; import java.util.Optional; import java.util.UUID; -import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -76,7 +75,7 @@ public CBOMReadRepository(@Nonnull IDomainEventBus domainEventBus) { } @Override - public @NotNull Optional findBy(@NotNull GitUrl gitUrl) { + public @Nonnull Optional findBy(@Nonnull GitUrl gitUrl) { final EntityManager entityManager = CBOMReadModel.getEntityManager(); final ArcContainer container = Arc.container(); container.requestContext().activate(); diff --git a/src/main/java/com/ibm/presentation/api/v1/scanning/Credentials.java b/src/main/java/com/ibm/presentation/api/v1/scanning/Credentials.java new file mode 100644 index 000000000..97678a517 --- /dev/null +++ b/src/main/java/com/ibm/presentation/api/v1/scanning/Credentials.java @@ -0,0 +1,28 @@ +/* + * CBOMkit + * Copyright (C) 2024 IBM + * + * 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. + */ +package com.ibm.presentation.api.v1.scanning; + +import com.fasterxml.jackson.annotation.JsonProperty; +import jakarta.annotation.Nullable; + +public record Credentials( + @Nullable @JsonProperty("username") String username, + @Nullable @JsonProperty("password") String password, + @Nullable @JsonProperty("pat") String pat) {} diff --git a/src/main/java/com/ibm/presentation/api/v1/scanning/ScanRequest.java b/src/main/java/com/ibm/presentation/api/v1/scanning/ScanRequest.java index ffe81086e..6d4b14194 100644 --- a/src/main/java/com/ibm/presentation/api/v1/scanning/ScanRequest.java +++ b/src/main/java/com/ibm/presentation/api/v1/scanning/ScanRequest.java @@ -27,16 +27,19 @@ public class ScanRequest { private String gitUrl; private @Nullable String branch; private @Nullable String subfolder; + private @Nullable Credentials credentials; protected ScanRequest() {} public ScanRequest( @Nonnull @JsonProperty("gitUrl") String gitUrl, @Nullable @JsonProperty("branch") String branch, - @Nullable @JsonProperty("subfolder") String subfolder) { + @Nullable @JsonProperty("subfolder") String subfolder, + @Nullable @JsonProperty("credentials") Credentials credentials) { this.gitUrl = gitUrl; this.branch = branch; this.subfolder = subfolder; + this.credentials = credentials; } public String getGitUrl() { @@ -50,4 +53,8 @@ public String getGitUrl() { @Nullable public String getSubfolder() { return subfolder; } + + @Nullable public Credentials getCredentials() { + return credentials; + } } diff --git a/src/main/java/com/ibm/presentation/api/v1/scanning/ScanningResource.java b/src/main/java/com/ibm/presentation/api/v1/scanning/ScanningResource.java index ab6ea7e16..764503622 100644 --- a/src/main/java/com/ibm/presentation/api/v1/scanning/ScanningResource.java +++ b/src/main/java/com/ibm/presentation/api/v1/scanning/ScanningResource.java @@ -23,6 +23,9 @@ import app.bootstrap.core.ddd.IDomainEventBus; import com.fasterxml.jackson.databind.ObjectMapper; import com.ibm.domain.scanning.ScanId; +import com.ibm.domain.scanning.authentication.ICredentials; +import com.ibm.domain.scanning.authentication.PersonalAccessToken; +import com.ibm.domain.scanning.authentication.UsernameAndPasswordCredentials; import com.ibm.infrastructure.progress.ProgressMessage; import com.ibm.infrastructure.progress.ProgressMessageType; import com.ibm.infrastructure.progress.WebSocketProgressDispatcher; @@ -31,6 +34,7 @@ import com.ibm.usecases.scanning.commands.RequestScanCommand; import com.ibm.usecases.scanning.processmanager.ScanProcessManager; import jakarta.annotation.Nonnull; +import jakarta.annotation.Nullable; import jakarta.enterprise.context.ApplicationScoped; import jakarta.websocket.OnClose; import jakarta.websocket.OnError; @@ -84,9 +88,10 @@ public void onError( } @OnMessage - public void onMessage(String requestJSONString, @PathParam("clientId") String clientId) { + public void onMessage( + @Nullable String requestJSONString, @Nullable @PathParam("clientId") String clientId) { try { - LOGGER.info("Received {} from {}", requestJSONString, clientId); + LOGGER.info("Received from {}", clientId); final Session session = Optional.ofNullable(sessions.get(clientId)).orElseThrow(); final WebSocketProgressDispatcher webSocketProgressDispatcher = new WebSocketProgressDispatcher(session); @@ -105,6 +110,8 @@ public void onMessage(String requestJSONString, @PathParam("clientId") String cl this.configuration); this.commandBus.register(scanProcessManager); + final ICredentials authCredentials = getCredentials(scanRequest); + webSocketProgressDispatcher.send( new ProgressMessage(ProgressMessageType.LABEL, "Starting...")); commandBus.send( @@ -112,10 +119,26 @@ public void onMessage(String requestJSONString, @PathParam("clientId") String cl scanId, scanRequest.getGitUrl(), scanRequest.getBranch(), - scanRequest.getSubfolder())); + scanRequest.getSubfolder(), + authCredentials)); } catch (Exception e) { LOGGER.error("Error processing request", e); } } + + @Nullable private static ICredentials getCredentials(@Nonnull ScanRequest scanRequest) { + @Nullable ICredentials authCredentials = null; + final Credentials credentials = scanRequest.getCredentials(); + if (credentials != null) { + if (credentials.username() != null && credentials.password() != null) { + authCredentials = + new UsernameAndPasswordCredentials( + credentials.username(), credentials.password()); + } else if (credentials.pat() != null) { + authCredentials = new PersonalAccessToken(credentials.pat()); + } + } + return authCredentials; + } } diff --git a/src/main/java/com/ibm/usecases/compliance/queries/RequestComplianceCheckForCBOMQueryHandler.java b/src/main/java/com/ibm/usecases/compliance/queries/RequestComplianceCheckForCBOMQueryHandler.java index 67be419dc..b9ffe7a26 100644 --- a/src/main/java/com/ibm/usecases/compliance/queries/RequestComplianceCheckForCBOMQueryHandler.java +++ b/src/main/java/com/ibm/usecases/compliance/queries/RequestComplianceCheckForCBOMQueryHandler.java @@ -34,7 +34,6 @@ import jakarta.enterprise.event.Observes; import jakarta.inject.Singleton; import java.util.Collection; -import org.jetbrains.annotations.NotNull; @Singleton public final class RequestComplianceCheckForCBOMQueryHandler @@ -53,8 +52,8 @@ public RequestComplianceCheckForCBOMQueryHandler( } @Override - public @NotNull ComplianceResult handle( - @NotNull RequestComplianceCheckForCBOMQuery requestComplianceCheckForCBOMQuery) + public @Nonnull ComplianceResult handle( + @Nonnull RequestComplianceCheckForCBOMQuery requestComplianceCheckForCBOMQuery) throws Exception { final CompliancePreparationService compliancePreparationService = new CompliancePreparationService(); diff --git a/src/main/java/com/ibm/usecases/scanning/commands/CloneGitRepositoryCommand.java b/src/main/java/com/ibm/usecases/scanning/commands/CloneGitRepositoryCommand.java index e34f15bd0..cfb0206b5 100644 --- a/src/main/java/com/ibm/usecases/scanning/commands/CloneGitRepositoryCommand.java +++ b/src/main/java/com/ibm/usecases/scanning/commands/CloneGitRepositoryCommand.java @@ -21,6 +21,24 @@ import app.bootstrap.core.cqrs.ICommand; import com.ibm.domain.scanning.ScanId; +import com.ibm.domain.scanning.authentication.ICredentials; import jakarta.annotation.Nonnull; +import jakarta.annotation.Nullable; +import java.util.Optional; -public record CloneGitRepositoryCommand(@Nonnull ScanId id) implements ICommand {} +public record CloneGitRepositoryCommand(@Nonnull ScanId id, @Nullable ICredentials credentials) + implements ICommand { + + @Nonnull + @Override + public String toString() { + return this.getClass().getSimpleName() + + "[id=" + + id + + ", credentials=" + + Optional.ofNullable(credentials) + .map(c -> c.getClass().getSimpleName()) + .orElse("none") + + "]"; + } +} diff --git a/src/main/java/com/ibm/usecases/scanning/commands/RequestScanCommand.java b/src/main/java/com/ibm/usecases/scanning/commands/RequestScanCommand.java index a5349cdc7..4f1572d1e 100644 --- a/src/main/java/com/ibm/usecases/scanning/commands/RequestScanCommand.java +++ b/src/main/java/com/ibm/usecases/scanning/commands/RequestScanCommand.java @@ -21,12 +21,36 @@ import app.bootstrap.core.cqrs.ICommand; import com.ibm.domain.scanning.ScanId; +import com.ibm.domain.scanning.authentication.ICredentials; import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; +import java.util.Optional; public record RequestScanCommand( @Nonnull ScanId scanId, @Nonnull String gitUrl, @Nullable String branch, - @Nullable String subfolder) - implements ICommand {} + @Nullable String subfolder, + // authentication + @Nullable ICredentials credentials) + implements ICommand { + + @Nonnull + @Override + public String toString() { + return this.getClass().getSimpleName() + + "[scanId=" + + scanId + + ", gitUrl=" + + gitUrl + + ", branch=" + + branch + + ", subfolder=" + + subfolder + + ", credentials=" + + Optional.ofNullable(credentials) + .map(c -> c.getClass().getSimpleName()) + .orElse("none") + + "]"; + } +} diff --git a/src/main/java/com/ibm/usecases/scanning/commands/RequestScanCommandHandler.java b/src/main/java/com/ibm/usecases/scanning/commands/RequestScanCommandHandler.java index 01b2747cf..a88f9b755 100644 --- a/src/main/java/com/ibm/usecases/scanning/commands/RequestScanCommandHandler.java +++ b/src/main/java/com/ibm/usecases/scanning/commands/RequestScanCommandHandler.java @@ -28,6 +28,7 @@ import com.ibm.domain.scanning.ScanAggregate; import com.ibm.domain.scanning.ScanId; import com.ibm.domain.scanning.ScanRequest; +import com.ibm.domain.scanning.authentication.ICredentials; import com.ibm.domain.scanning.errors.InvalidGitUrl; import io.quarkus.runtime.StartupEvent; import jakarta.annotation.Nonnull; @@ -57,7 +58,8 @@ public void handle(@Nonnull ICommand command) throws InvalidGitUrl { @Nonnull ScanId scanId, @Nonnull String gitUrl, @Nullable String branch, - @Nullable String subfolder)) { + @Nullable String subfolder, + @Nullable ICredentials credentials)) { final ScanRequest scanRequest = new ScanRequest( new GitUrl(gitUrl), @@ -65,7 +67,8 @@ public void handle(@Nonnull ICommand command) throws InvalidGitUrl { subfolder); // create Aggregate and start scan // it will emit a domain event that the scan is requested - final ScanAggregate scanAggregate = ScanAggregate.requestScan(scanId, scanRequest); + final ScanAggregate scanAggregate = + ScanAggregate.requestScan(scanId, scanRequest, credentials); this.repository.save(scanAggregate); } } diff --git a/src/main/java/com/ibm/usecases/scanning/eventhandler/ScanEventHandler.java b/src/main/java/com/ibm/usecases/scanning/eventhandler/ScanEventHandler.java index c3899cc93..5588d1259 100644 --- a/src/main/java/com/ibm/usecases/scanning/eventhandler/ScanEventHandler.java +++ b/src/main/java/com/ibm/usecases/scanning/eventhandler/ScanEventHandler.java @@ -52,6 +52,7 @@ public void handleEvent(@Nonnull IDomainEvent event) throws Exception { private void handleScanRequested(@Nonnull ScanRequestedEvent scanRequestedEvent) { final ScanId scanId = scanRequestedEvent.getScanId(); - this.commandBus.send(new CloneGitRepositoryCommand(scanId)); + this.commandBus.send( + new CloneGitRepositoryCommand(scanId, scanRequestedEvent.getCredentials())); } } diff --git a/src/main/java/com/ibm/usecases/scanning/processmanager/ScanProcessManager.java b/src/main/java/com/ibm/usecases/scanning/processmanager/ScanProcessManager.java index f5d000f36..dd8047b72 100644 --- a/src/main/java/com/ibm/usecases/scanning/processmanager/ScanProcessManager.java +++ b/src/main/java/com/ibm/usecases/scanning/processmanager/ScanProcessManager.java @@ -114,7 +114,7 @@ private void handleCloneGitRepositoryCommand(@Nonnull CloneGitRepositoryCommand final GitService gitService = new GitService(this.progressDispatcher, this.baseCloneDirPath); final CloneResultDTO cloneResultDTO = - gitService.clone(this.scanId, scanAggregate.getScanRequest()); + gitService.clone(scanAggregate.getScanRequest(), command.credentials()); this.projectDirectory = cloneResultDTO.directory(); // update aggregate scanAggregate.setCommitHash(cloneResultDTO.commit()); @@ -134,12 +134,18 @@ private void handleCloneGitRepositoryCommand(@Nonnull CloneGitRepositoryCommand this.scanId, scanAggregate.getScanRequest().gitUrl().value(), "master", - scanAggregate.getScanRequest().subFolder())); + scanAggregate.getScanRequest().subFolder(), + command.credentials())); } else { + this.progressDispatcher.send( + new ProgressMessage( + ProgressMessageType.ERROR, gitCloneFailed.getMessage())); this.compensate(command.id()); throw gitCloneFailed; } - } catch (ClientDisconnected | CommitHashAlreadyExists e) { + } catch (Exception e) { + this.progressDispatcher.send( + new ProgressMessage(ProgressMessageType.ERROR, e.getMessage())); this.compensate(command.id()); throw e; } @@ -174,6 +180,8 @@ private void handleIndexModulesCommand(@Nonnull IndexModulesCommand command) // continue with scan this.commandBus.send(new ScanCommand(command.id())); } catch (Exception e) { + this.progressDispatcher.send( + new ProgressMessage(ProgressMessageType.ERROR, e.getMessage())); this.compensate(command.id()); throw e; } @@ -303,6 +311,8 @@ private void handleScanCommand(@Nonnull ScanCommand command) this.progressDispatcher.send( new ProgressMessage(ProgressMessageType.LABEL, "Finished")); } catch (Exception e) { + this.progressDispatcher.send( + new ProgressMessage(ProgressMessageType.ERROR, e.getMessage())); this.compensate(command.id()); throw e; } finally { diff --git a/src/main/java/com/ibm/usecases/scanning/projector/CBOMProjector.java b/src/main/java/com/ibm/usecases/scanning/projector/CBOMProjector.java index 398956bd9..69c7f7747 100644 --- a/src/main/java/com/ibm/usecases/scanning/projector/CBOMProjector.java +++ b/src/main/java/com/ibm/usecases/scanning/projector/CBOMProjector.java @@ -41,7 +41,6 @@ import java.util.List; import java.util.Optional; import java.util.UUID; -import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -63,7 +62,7 @@ public CBOMProjector( } @Override - public void handleEvent(@NotNull IDomainEvent event) throws Exception { + public void handleEvent(@Nonnull IDomainEvent event) throws Exception { if (event instanceof ScanFinishedEvent scanFinishedEvent) { this.handleLanguageScanDoneEvent(scanFinishedEvent); } diff --git a/src/main/java/com/ibm/usecases/scanning/services/git/GitService.java b/src/main/java/com/ibm/usecases/scanning/services/git/GitService.java index 357d0f0d7..4dd0b5a1f 100644 --- a/src/main/java/com/ibm/usecases/scanning/services/git/GitService.java +++ b/src/main/java/com/ibm/usecases/scanning/services/git/GitService.java @@ -20,14 +20,17 @@ package com.ibm.usecases.scanning.services.git; import com.ibm.domain.scanning.Commit; -import com.ibm.domain.scanning.ScanId; import com.ibm.domain.scanning.ScanRequest; +import com.ibm.domain.scanning.authentication.ICredentials; +import com.ibm.domain.scanning.authentication.PersonalAccessToken; +import com.ibm.domain.scanning.authentication.UsernameAndPasswordCredentials; import com.ibm.infrastructure.errors.ClientDisconnected; import com.ibm.infrastructure.progress.IProgressDispatcher; import com.ibm.infrastructure.progress.ProgressMessage; import com.ibm.infrastructure.progress.ProgressMessageType; import com.ibm.usecases.scanning.errors.GitCloneFailed; import jakarta.annotation.Nonnull; +import jakarta.annotation.Nullable; import java.io.File; import java.io.IOException; import java.util.UUID; @@ -35,6 +38,8 @@ import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.transport.CredentialsProvider; +import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider; public final class GitService { @Nonnull private final IProgressDispatcher progressDispatcher; @@ -47,11 +52,12 @@ public GitService( } @Nonnull - public CloneResultDTO clone(@Nonnull ScanId scanId, @Nonnull ScanRequest scanRequest) + public CloneResultDTO clone( + @Nonnull ScanRequest scanRequest, @Nullable ICredentials credentials) throws GitCloneFailed, ClientDisconnected { try { // create directory - final String folderId = UUID.randomUUID().toString().replaceAll("-", ""); + final String folderId = UUID.randomUUID().toString().replace("-", ""); final String scanClonePath = this.baseCloneDirPath + File.separator + folderId; final File scanCloneFile = new File(scanClonePath); if (scanCloneFile.exists()) { @@ -72,12 +78,14 @@ public CloneResultDTO clone(@Nonnull ScanId scanId, @Nonnull ScanRequest scanReq // nothing } }); + final Git clonedRepo = Git.cloneRepository() .setProgressMonitor(gitProgressMonitor) .setURI(scanRequest.gitUrl().value()) .setBranch(scanRequest.revision().value()) .setDirectory(scanCloneFile) + .setCredentialsProvider(getCredentialsProvider(credentials)) .call(); Ref revisionRef = clonedRepo.getRepository().findRef(scanRequest.revision().value()); if (revisionRef == null) { @@ -101,4 +109,17 @@ public CloneResultDTO clone(@Nonnull ScanId scanId, @Nonnull ScanRequest scanReq throw new GitCloneFailed("Git clone failed: " + e.getMessage()); } } + + @Nullable private CredentialsProvider getCredentialsProvider(@Nullable ICredentials credentials) { + if (credentials + instanceof + UsernameAndPasswordCredentials( + @Nonnull String username, + @Nonnull String password)) { + return new UsernamePasswordCredentialsProvider(username, password); + } else if (credentials instanceof PersonalAccessToken(@Nonnull String token)) { + return new UsernamePasswordCredentialsProvider(token, ""); + } + return null; + } } diff --git a/src/main/java/com/ibm/usecases/scanning/services/scan/java/JavaDetectionCollectionRule.java b/src/main/java/com/ibm/usecases/scanning/services/scan/java/JavaDetectionCollectionRule.java index eec62738f..bd0c4ec4f 100644 --- a/src/main/java/com/ibm/usecases/scanning/services/scan/java/JavaDetectionCollectionRule.java +++ b/src/main/java/com/ibm/usecases/scanning/services/scan/java/JavaDetectionCollectionRule.java @@ -25,7 +25,6 @@ import jakarta.annotation.Nonnull; import java.util.List; import java.util.function.Consumer; -import org.jetbrains.annotations.NotNull; import org.sonar.plugins.java.api.JavaCheck; import org.sonar.plugins.java.api.JavaFileScannerContext; import org.sonar.plugins.java.api.semantic.Symbol; @@ -39,7 +38,7 @@ public JavaDetectionCollectionRule(@Nonnull Consumer> findingConsume } @Override - public void update(@NotNull Finding finding) { + public void update(@Nonnull Finding finding) { super.update(finding); final List nodes = javaTranslationProcess.initiate(finding.detectionStore()); handler.accept(nodes); diff --git a/src/main/java/com/ibm/usecases/scanning/services/scan/python/PythonDetectionCollectionRule.java b/src/main/java/com/ibm/usecases/scanning/services/scan/python/PythonDetectionCollectionRule.java index 1dbeb9db7..1d398047c 100644 --- a/src/main/java/com/ibm/usecases/scanning/services/scan/python/PythonDetectionCollectionRule.java +++ b/src/main/java/com/ibm/usecases/scanning/services/scan/python/PythonDetectionCollectionRule.java @@ -25,7 +25,6 @@ import jakarta.annotation.Nonnull; import java.util.List; import java.util.function.Consumer; -import org.jetbrains.annotations.NotNull; import org.sonar.plugins.python.api.PythonCheck; import org.sonar.plugins.python.api.PythonVisitorContext; import org.sonar.plugins.python.api.symbols.Symbol; @@ -39,7 +38,7 @@ public PythonDetectionCollectionRule(@Nonnull Consumer> findingConsu } @Override - public void update(@NotNull Finding finding) { + public void update(@Nonnull Finding finding) { super.update(finding); final List nodes = pythonTranslationProcess.initiate(finding.detectionStore()); handler.accept(nodes); From d4ab4057c732b6a201bffd9157781386d81b43d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicklas=20K=C3=B6rtge?= Date: Wed, 20 Nov 2024 15:48:01 +0100 Subject: [PATCH 2/3] update description MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Nicklas Körtge --- frontend/src/components/home/SearchBar.vue | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/components/home/SearchBar.vue b/frontend/src/components/home/SearchBar.vue index 6db7083fb..da328be8b 100644 --- a/frontend/src/components/home/SearchBar.vue +++ b/frontend/src/components/home/SearchBar.vue @@ -45,14 +45,14 @@ From 0feb1717d80c6d958d369f73cb89f42ab8b22655 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicklas=20K=C3=B6rtge?= Date: Wed, 20 Nov 2024 15:59:00 +0100 Subject: [PATCH 3/3] update readme, add openapi spec again MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Nicklas Körtge --- README.md | 9 +++-- openapi.yaml | 95 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 99 insertions(+), 5 deletions(-) create mode 100644 openapi.yaml diff --git a/README.md b/README.md index 3c3440e24..15654843b 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ [![Current Release](https://img.shields.io/github/release/IBM/cbomkit.svg?logo=IBM)](https://github.com/IBM/cbomkit/releases) CBOMkit is a toolset for dealing with Cryptography Bill of Materials (CBOM). CBOMkit includes a -- **CBOM Generation** ([CBOMkit-hyperion](https://github.com/IBM/sonar-cryptography), [CBOMkit-theia](https://github.com/IBM/cbomkit-theia)): Generate CBOMs from source code by scanning git repositories to find the used cryptography. +- **CBOM Generation** ([CBOMkit-hyperion](https://github.com/IBM/sonar-cryptography), [CBOMkit-theia](https://github.com/IBM/cbomkit-theia)): Generate CBOMs from source code by scanning private and public git repositories to find the used cryptography. - **CBOM Viewer ([CBOMkit-coeus](https://github.com/IBM/cbomkit?tab=readme-ov-file#cbomkit-coeus))**: Visualize a generated or uploaded CBOM and access comprehensive statistics. - **CBOM Compliance Check**: Evaluate CBOMs created or uploaded against specified compliance policies and receive detailed compliance status reports. - **CBOM Database**: Collect and store CBOMs into the database and expose this data through a RESTful API. @@ -80,15 +80,14 @@ The API server functions as the central component of the CBOMkit, offering a com #### Features - Retrieve the most recent generated CBOMs -- Access stored CBOMs from the database using Package URLs (PURL) -- Execute targeted searches for scanned Git repositories utilizing specific algorithms (by name and Oid) +- Access stored CBOMs from the database - Perform compliance checks for user-provided CBOMs against specified policies - Conduct compliance assessments for stored or generated CBOMs against defined policies -*Sample Query to Retrieve CBOM by Package URL* +*Sample Query to Retrieve CBOM project identifier* ```shell curl --request GET \ - --url 'http://localhost:8081/api/v1/cbom?purls=pkg:github/OddSource/java-license-manager' + --url 'http://localhost:8081/api/v1/cbom/github.com%2Fkeycloak%2Fkeycloak' ``` In addition to the RESTful API, the server incorporates WebSocket integration, enabling: diff --git a/openapi.yaml b/openapi.yaml new file mode 100644 index 000000000..06e798c64 --- /dev/null +++ b/openapi.yaml @@ -0,0 +1,95 @@ +--- +openapi: 3.0.3 +info: + title: CBOMkit API + description: Service for generating and retrieving CBOMs + version: 1.2.0 +paths: + /api: + get: + tags: + - Status + summary: Health test + description: Health test endpoint + responses: + "200": + description: OK + content: + application/json: + schema: + example: + status: ok + /api/v1/cbom/last/{limit}: + get: + tags: + - CBOM Resource + summary: Return recently generated CBOMs from the repository + description: Returns a list of the most recently generated CBOMs. The length + of the list can by specified via the optional 'limit' parameter. + parameters: + - name: limit + in: path + required: true + schema: + format: int32 + type: integer + responses: + "200": + description: OK + /api/v1/cbom/{projectIdentifier}: + get: + tags: + - CBOM Resource + description: Get CBOM + parameters: + - name: projectIdentifier + in: path + required: true + schema: + type: string + responses: + "200": + description: OK + /api/v1/compliance/check: + get: + tags: + - Compliance Resource + summary: Verify the compliance of a stored CBOM identified by it's PURL against + a policy + description: "Returns the JSON sent by the Regulator API, containing various\ + \ information about the compliance of the CBOM for a set policy." + parameters: + - name: commit + in: query + schema: + type: string + - name: gitUrl + in: query + schema: + type: string + - name: policyIdentifier + in: query + schema: + type: string + responses: + "200": + description: OK + post: + tags: + - Compliance Resource + summary: Verify the compliance of a provided CBOM against a policy + description: "Returns the JSON sent by the Regulator API, containing various\ + \ information about the compliance of the CBOM for a set policy." + parameters: + - name: policyIdentifier + in: query + schema: + type: string + requestBody: + content: + application/json: + schema: + type: string + responses: + "200": + description: OK