diff --git a/frontend/src/components/results/ResultTitle.vue b/frontend/src/components/results/ResultTitle.vue index 54a5a6450..dcb99be1e 100644 --- a/frontend/src/components/results/ResultTitle.vue +++ b/frontend/src/components/results/ResultTitle.vue @@ -11,11 +11,6 @@ -
diff --git a/frontend/src/components/results/modal/GitInfoPrompt.vue b/frontend/src/components/results/modal/GitInfoPrompt.vue index 4f3c25947..339ca1f0b 100644 --- a/frontend/src/components/results/modal/GitInfoPrompt.vue +++ b/frontend/src/components/results/modal/GitInfoPrompt.vue @@ -80,7 +80,7 @@ export default { } }, resetModal: function () { - this.gitLink = model.codeOrigin.scanUrl; + this.gitLink = model.codeOrigin.gitUrl; this.gitBranch = model.codeOrigin.revision; this.commitID = model.codeOrigin.commitID; diff --git a/frontend/src/helpers/cbom.js b/frontend/src/helpers/cbom.js index 668d33a9f..2fe3bb916 100644 --- a/frontend/src/helpers/cbom.js +++ b/frontend/src/helpers/cbom.js @@ -262,7 +262,6 @@ export function setCbom(cbom) { if (Object.hasOwn(cbom, "metadata")) { if (Object.hasOwn(cbom.metadata, "properties") && Array.isArray(cbom.metadata.properties)) { - model.codeOrigin.purls = [] cbom.metadata.properties.forEach(function (prop) { if (Object.hasOwn(prop, "name") && Object.hasOwn(prop, "value")) { switch (prop.name) { @@ -275,9 +274,6 @@ export function setCbom(cbom) { case "subfolder": model.codeOrigin.subfolder = prop.value break; - case "purl": - model.codeOrigin.purls.push(prop.value) - break; case "commit": model.codeOrigin.commitID = prop.value } diff --git a/frontend/src/helpers/general.js b/frontend/src/helpers/general.js index 989c1c49d..5775f4300 100644 --- a/frontend/src/helpers/general.js +++ b/frontend/src/helpers/general.js @@ -51,7 +51,7 @@ export function openGitRepo(gitUrl) { } export function canOpenOnline() { - let gitUrl = model.codeOrigin.scanUrl; + let gitUrl = model.codeOrigin.gitUrl; let branch = model.codeOrigin.revision; let commitID = model.codeOrigin.commitID; diff --git a/frontend/src/helpers/scan.js b/frontend/src/helpers/scan.js index 12dd2b07c..85066abb6 100644 --- a/frontend/src/helpers/scan.js +++ b/frontend/src/helpers/scan.js @@ -146,8 +146,12 @@ function handleMessage(messageJson) { let cbomString = obj["message"]; setCbom(JSON.parse(cbomString)); console.log("Received CBOM from scanning:", model.cbom); + } else if (obj["type"] === "GITURL") { + model.codeOrigin.gitUrl = obj["message"]; } else if (obj["type"] === "BRANCH") { model.codeOrigin.revision = obj["message"]; + } else if (obj["type"] === "FOLDER") { + model.codeOrigin.subfolder = obj["message"]; } else if (obj["type"] === "SCANNED_FILE_COUNT") { model.scanning.numberOfFiles = obj["message"]; } else if (obj["type"] === "SCANNED_NUMBER_OF_LINES") { diff --git a/frontend/src/model.js b/frontend/src/model.js index 6e3dbc00d..09d4571b8 100644 --- a/frontend/src/model.js +++ b/frontend/src/model.js @@ -27,7 +27,6 @@ export const model = reactive({ revision: null, subfolder: null, commitID: null, - purls: [], uploadedFileName: null, }, credentials: { @@ -69,7 +68,6 @@ export const model = reactive({ model.codeOrigin.revision = null; model.codeOrigin.subfolder = null; model.codeOrigin.commitID = null; - model.codeOrigin.purls = []; model.codeOrigin.uploadedFileName = null; }, resetCredentials() { diff --git a/pom.xml b/pom.xml index 8b410ac9f..d7eea18cb 100644 --- a/pom.xml +++ b/pom.xml @@ -151,6 +151,17 @@ google-java-format ${google-java-format.version} + + + org.apache.maven + maven-model + 3.9.9 + + + org.tomlj + tomlj + 1.1.1 + diff --git a/src/main/java/com/ibm/domain/scanning/ScanAggregate.java b/src/main/java/com/ibm/domain/scanning/ScanAggregate.java index 9eaf09789..b000a7a22 100644 --- a/src/main/java/com/ibm/domain/scanning/ScanAggregate.java +++ b/src/main/java/com/ibm/domain/scanning/ScanAggregate.java @@ -26,15 +26,18 @@ import com.ibm.domain.scanning.errors.CommitHashAlreadyExists; import com.ibm.domain.scanning.errors.GitUrlAlreadyResolved; import com.ibm.domain.scanning.errors.InvalidScanUrl; +import com.ibm.domain.scanning.errors.PackageFolderAlreadyExists; import com.ibm.domain.scanning.errors.ScanResultForLanguageAlreadyExists; import com.ibm.domain.scanning.events.CommitHashIdentifiedEvent; import com.ibm.domain.scanning.events.GitUrlResolvedEvent; import com.ibm.domain.scanning.events.LanguageScanDoneEvent; +import com.ibm.domain.scanning.events.PackageFolderResolvedEvent; import com.ibm.domain.scanning.events.PurlScanRequestedEvent; import com.ibm.domain.scanning.events.ScanFinishedEvent; import com.ibm.domain.scanning.events.ScanRequestedEvent; import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; +import java.nio.file.Path; import java.util.ArrayList; import java.util.EnumMap; import java.util.List; @@ -46,6 +49,7 @@ public final class ScanAggregate extends AggregateRoot { @Nullable private GitUrl gitUrl; @Nullable private PackageURL purl; @Nonnull private Revision revision; + @Nullable private Path packageFolder; @Nullable private Commit commit; @Nullable private Map languageScans; @@ -66,11 +70,13 @@ private ScanAggregate( @Nonnull ScanRequest scanRequest, @Nullable GitUrl gitUrl, @Nullable PackageURL purl, + @Nullable Path packageFolder, @Nullable Commit commit, @Nullable Map languageScans) { this(id, scanRequest); this.gitUrl = gitUrl; this.purl = purl; + this.packageFolder = packageFolder; this.commit = commit; this.languageScans = languageScans; } @@ -111,6 +117,14 @@ public void setCommitHash(@Nonnull Commit commit) throws CommitHashAlreadyExists this.apply(new CommitHashIdentifiedEvent(this.getId())); } + public void setPackageFolder(@Nonnull Path packageFolder) throws PackageFolderAlreadyExists { + if (this.packageFolder != null) { + throw new PackageFolderAlreadyExists(this.getId()); + } + this.packageFolder = packageFolder; + this.apply(new PackageFolderResolvedEvent(this.getId())); + } + public void reportScanResults(@Nonnull LanguageScan scan) throws ScanResultForLanguageAlreadyExists { if (languageScans == null) { @@ -153,6 +167,10 @@ public Revision getRevision() { return revision; } + @Nullable public Path getPackageFolder() { + return packageFolder; + } + @Nonnull public Optional> getLanguageScans() { return Optional.ofNullable(languageScans).map(Map::values).map(ArrayList::new); @@ -194,8 +212,10 @@ public static ScanAggregate reconstruct( @Nonnull ScanRequest scanRequest, @Nullable GitUrl gitUrl, @Nullable PackageURL purl, + @Nullable Path packageFolder, @Nullable Commit commit, @Nullable Map languageScans) { - return new ScanAggregate(id, scanRequest, gitUrl, purl, commit, languageScans); + return new ScanAggregate( + id, scanRequest, gitUrl, purl, packageFolder, commit, languageScans); } } diff --git a/src/main/java/com/ibm/domain/scanning/errors/PackageFolderAlreadyExists.java b/src/main/java/com/ibm/domain/scanning/errors/PackageFolderAlreadyExists.java new file mode 100644 index 000000000..a24ac90ad --- /dev/null +++ b/src/main/java/com/ibm/domain/scanning/errors/PackageFolderAlreadyExists.java @@ -0,0 +1,30 @@ +/* + * 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.errors; + +import app.bootstrap.core.ddd.DomainException; +import com.ibm.domain.scanning.ScanId; +import jakarta.annotation.Nonnull; + +public class PackageFolderAlreadyExists extends DomainException { + public PackageFolderAlreadyExists(@Nonnull ScanId scanId) { + super("Subfolder already exists in scan " + scanId); + } +} diff --git a/src/main/java/com/ibm/domain/scanning/events/PackageFolderResolvedEvent.java b/src/main/java/com/ibm/domain/scanning/events/PackageFolderResolvedEvent.java new file mode 100644 index 000000000..2435f012d --- /dev/null +++ b/src/main/java/com/ibm/domain/scanning/events/PackageFolderResolvedEvent.java @@ -0,0 +1,43 @@ +/* + * 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.events; + +import app.bootstrap.core.ddd.DomainEvent; +import com.ibm.domain.scanning.ScanId; +import jakarta.annotation.Nonnull; + +public final class PackageFolderResolvedEvent extends DomainEvent { + @Nonnull private final ScanId scanId; + + public PackageFolderResolvedEvent(@Nonnull ScanId scanId) { + this.scanId = scanId; + } + + @Nonnull + public ScanId getScanId() { + return scanId; + } + + @Nonnull + @Override + public String toString() { + return this.getClass().getSimpleName() + "[id=" + scanId + "]"; + } +} 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 0f3db4880..8a40e842d 100644 --- a/src/main/java/com/ibm/infrastructure/database/readmodels/CBOMReadModel.java +++ b/src/main/java/com/ibm/infrastructure/database/readmodels/CBOMReadModel.java @@ -39,7 +39,15 @@ @Entity @Cacheable @JsonInclude(JsonInclude.Include.NON_NULL) -@JsonPropertyOrder({"projectIdentifier", "gitUrl", "branch", "commit", "createdAt", "bom"}) +@JsonPropertyOrder({ + "projectIdentifier", + "gitUrl", + "branch", + "folder", + "commit", + "createdAt", + "bom" +}) public class CBOMReadModel extends PanacheEntityBase implements IReadModel { @JsonIgnore @Id @Nonnull public UUID id; @@ -52,6 +60,9 @@ public class CBOMReadModel extends PanacheEntityBase implements IReadModel @JsonProperty("branch") @Nullable protected String revision; + @JsonProperty("folder") + @Nullable protected String packageFolder; + @Nullable protected String commit; @Nonnull protected Timestamp createdAt; @@ -64,6 +75,7 @@ public CBOMReadModel( @Nonnull String projectIdentifier, @Nonnull String repository, @Nullable String revision, + @Nullable String packageFolder, @Nullable String commit, @Nonnull Timestamp createdAt, @Nonnull JsonNode bom) { @@ -71,6 +83,7 @@ public CBOMReadModel( this.projectIdentifier = projectIdentifier; this.repository = repository; this.revision = revision; + this.packageFolder = packageFolder; this.commit = commit; this.createdAt = createdAt; this.bom = bom; @@ -97,6 +110,10 @@ public String getRepository() { return revision; } + @Nullable public String getPackageFolder() { + return packageFolder; + } + @Nullable public String getCommit() { return commit; } 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 bdd5b8b53..a1c085094 100644 --- a/src/main/java/com/ibm/infrastructure/database/readmodels/CBOMReadRepository.java +++ b/src/main/java/com/ibm/infrastructure/database/readmodels/CBOMReadRepository.java @@ -32,6 +32,7 @@ import jakarta.inject.Singleton; import jakarta.persistence.EntityManager; import jakarta.persistence.TypedQuery; +import java.nio.file.Path; import java.util.Collection; import java.util.List; import java.util.Optional; @@ -49,26 +50,17 @@ public CBOMReadRepository(@Nonnull IDomainEventBus domainEventBus) { } @Override - public @Nonnull Optional findBy(@Nonnull GitUrl gitUrl, @Nonnull Commit commit) { - return findByRepository(gitUrl.value(), commit); - } - - @Override - public @Nonnull Optional findBy(@Nonnull GitUrl gitUrl) { - return findByRepository(gitUrl.value(), null); + public @Nonnull Optional findBy( + @Nonnull GitUrl gitUrl, @Nullable Commit commit, @Nullable Path packageFolder) { + return findByRepository(gitUrl.value(), commit, packageFolder); } @Override public @Nonnull Optional findBy( - @Nonnull PackageURL purl, @Nonnull Commit commit) { + @Nonnull PackageURL purl, @Nullable Commit commit) { return findByProjectIdentifier(purl.canonicalize(), commit); } - @Override - public @Nonnull Optional findBy(@Nonnull PackageURL purl) { - return findByProjectIdentifier(purl.canonicalize(), null); - } - @Override public @Nonnull Optional findBy(@Nonnull String projectIdentifier) { return findByProjectIdentifier(projectIdentifier, null); @@ -170,16 +162,21 @@ public void delete(@Nonnull UUID uuid) { } private @Nonnull Optional findByRepository( - @Nonnull String repository, @Nullable Commit commit) { + @Nonnull String repository, @Nullable Commit commit, @Nullable Path packageFolder) { final EntityManager entityManager = CBOMReadModel.getEntityManager(); final ArcContainer container = Arc.container(); container.requestContext().activate(); try { QuarkusTransaction.begin(); String qString = - commit != null - ? "SELECT read FROM CBOMReadModel read WHERE read.commit = :commit AND read.repository = :repository" - : "SELECT read FROM CBOMReadModel read WHERE read.repository = :repository"; + "SELECT read FROM CBOMReadModel read WHERE read.repository = :repository"; + + if (commit != null) { + qString += " AND read.commit = :commit"; + } + if (packageFolder != null) { + qString += " AND read.packageFolder = :packageFolder"; + } qString += " ORDER BY createdAt desc"; TypedQuery query = @@ -190,6 +187,9 @@ public void delete(@Nonnull UUID uuid) { if (commit != null) { query.setParameter("commit", commit.hash()); } + if (packageFolder != null) { + query.setParameter("packageFolder", packageFolder.toString()); + } Optional match = query.getResultStream().findFirst(); QuarkusTransaction.commit(); return match; diff --git a/src/main/java/com/ibm/infrastructure/database/readmodels/ICBOMReadRepository.java b/src/main/java/com/ibm/infrastructure/database/readmodels/ICBOMReadRepository.java index 2f67ec829..30406cf7b 100644 --- a/src/main/java/com/ibm/infrastructure/database/readmodels/ICBOMReadRepository.java +++ b/src/main/java/com/ibm/infrastructure/database/readmodels/ICBOMReadRepository.java @@ -24,29 +24,28 @@ import com.ibm.domain.scanning.Commit; import com.ibm.domain.scanning.GitUrl; import jakarta.annotation.Nonnull; +import jakarta.annotation.Nullable; +import java.nio.file.Path; import java.util.Collection; import java.util.Optional; import java.util.UUID; public interface ICBOMReadRepository extends IReadRepository { - @Nonnull - Optional findBy(@Nonnull GitUrl gitUrl, @Nonnull Commit commit); - /** - * Returns latest CBOM related to the git url. + * Returns latest CBOM related to the git url, commit and packageFolder. * * @param gitUrl the git url that identifies the CBOM. + * @param commit the commit id (optional) + * @param packageFolder the packageFolder (optional) * @return CBOM read model. */ @Nonnull - Optional findBy(@Nonnull GitUrl gitUrl); - - @Nonnull - Optional findBy(@Nonnull PackageURL purl, @Nonnull Commit commit); + Optional findBy( + @Nonnull GitUrl gitUrl, @Nullable Commit commit, @Nullable Path packageFolder); @Nonnull - Optional findBy(@Nonnull PackageURL purl); + Optional findBy(@Nonnull PackageURL purl, @Nullable Commit commit); @Nonnull Optional findBy(@Nonnull String projectIdentifier); diff --git a/src/main/java/com/ibm/infrastructure/progress/ProgressMessageType.java b/src/main/java/com/ibm/infrastructure/progress/ProgressMessageType.java index 0f5b4f95a..dee1be5e9 100644 --- a/src/main/java/com/ibm/infrastructure/progress/ProgressMessageType.java +++ b/src/main/java/com/ibm/infrastructure/progress/ProgressMessageType.java @@ -24,7 +24,9 @@ public enum ProgressMessageType { DETECTION, ERROR, CBOM, + GITURL, BRANCH, + FOLDER, REVISION_HASH, SCANNED_FILE_COUNT, SCANNED_NUMBER_OF_LINES, diff --git a/src/main/java/com/ibm/infrastructure/scanning/repositories/Scan.java b/src/main/java/com/ibm/infrastructure/scanning/repositories/Scan.java index 3dcb46708..51be34bdb 100644 --- a/src/main/java/com/ibm/infrastructure/scanning/repositories/Scan.java +++ b/src/main/java/com/ibm/infrastructure/scanning/repositories/Scan.java @@ -43,6 +43,7 @@ import jakarta.persistence.FetchType; import jakarta.persistence.Id; import jakarta.persistence.OneToMany; +import java.nio.file.Path; import java.util.ArrayList; import java.util.Collection; import java.util.EnumMap; @@ -65,6 +66,7 @@ class Scan extends PanacheEntityBase { @Nonnull public String revision; @Nullable public String commitHash; @Nullable public String subFolder; + @Nullable public String packageFolder; @Nullable public String purl; @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER) @@ -84,6 +86,9 @@ protected Scan() {} } else { this.revision = aggregate.getScanRequest().revision().value(); } + this.subFolder = aggregate.getScanRequest().subFolder(); + this.packageFolder = + Optional.ofNullable(aggregate.getPackageFolder()).map(Path::toString).orElse(null); this.commitHash = aggregate.getCommit().map(Commit::hash).orElse(null); final Optional> languageScans = aggregate.getLanguageScans(); @@ -139,6 +144,7 @@ protected ScanAggregate asAggregate() throws AggregateReconstructionFailed { new ScanUrl(this.scanUrl), new Revision(this.revision), this.subFolder), Optional.ofNullable(this.gitUrl).map(GitUrl::new).orElse(null), optionalPackageURL.orElse(null), + Optional.ofNullable(this.packageFolder).map(Path::of).orElse(null), Optional.ofNullable(this.commitHash).map(Commit::new).orElse(null), languageScans); } catch (MalformedPackageURLException | CBOMSerializationFailed e) { diff --git a/src/main/java/com/ibm/presentation/api/v1/compliance/ComplianceResource.java b/src/main/java/com/ibm/presentation/api/v1/compliance/ComplianceResource.java index 49f483fe4..7cb549c32 100644 --- a/src/main/java/com/ibm/presentation/api/v1/compliance/ComplianceResource.java +++ b/src/main/java/com/ibm/presentation/api/v1/compliance/ComplianceResource.java @@ -56,7 +56,8 @@ public ComplianceResource(@Nonnull IQueryBus queryBus) { public Response checkStored( @Nullable @RestQuery("policyIdentifier") String policyIdentifier, @Nullable @RestQuery("gitUrl") String gitUrl, - @Nullable @RestQuery("commit") String commit) + @Nullable @RestQuery("commit") String commit, + @Nullable @RestQuery("folder") String folder) throws ExecutionException, InterruptedException { if (policyIdentifier == null) { return Response.status(Response.Status.BAD_REQUEST).build(); @@ -69,7 +70,7 @@ public Response checkStored( return this.queryBus .send( new RequestComplianceCheckForScannedGitRepositoryQuery( - policyIdentifier, gitUrl, commit)) + policyIdentifier, gitUrl, commit, folder)) .thenApply(res -> Response.ok(res).build()) .get(); } diff --git a/src/main/java/com/ibm/usecases/compliance/queries/RequestComplianceCheckForScannedGitRepositoryQuery.java b/src/main/java/com/ibm/usecases/compliance/queries/RequestComplianceCheckForScannedGitRepositoryQuery.java index 48ff0c061..cd348df06 100644 --- a/src/main/java/com/ibm/usecases/compliance/queries/RequestComplianceCheckForScannedGitRepositoryQuery.java +++ b/src/main/java/com/ibm/usecases/compliance/queries/RequestComplianceCheckForScannedGitRepositoryQuery.java @@ -25,5 +25,8 @@ import jakarta.annotation.Nullable; public record RequestComplianceCheckForScannedGitRepositoryQuery( - @Nonnull String policyIdentifier, @Nonnull String gitUrl, @Nullable String commit) + @Nonnull String policyIdentifier, + @Nonnull String gitUrl, + @Nullable String commit, + @Nullable String packageFolder) implements IQuery {} diff --git a/src/main/java/com/ibm/usecases/compliance/queries/RequestComplianceCheckForScannedGitRepositoryQueryHandler.java b/src/main/java/com/ibm/usecases/compliance/queries/RequestComplianceCheckForScannedGitRepositoryQueryHandler.java index 4ce870e77..6f9bf8554 100644 --- a/src/main/java/com/ibm/usecases/compliance/queries/RequestComplianceCheckForScannedGitRepositoryQueryHandler.java +++ b/src/main/java/com/ibm/usecases/compliance/queries/RequestComplianceCheckForScannedGitRepositoryQueryHandler.java @@ -36,6 +36,7 @@ import jakarta.annotation.Nonnull; import jakarta.enterprise.event.Observes; import jakarta.inject.Singleton; +import java.nio.file.Path; import java.util.Collection; import java.util.Optional; @@ -73,6 +74,11 @@ public RequestComplianceCheckForScannedGitRepositoryQueryHandler( Optional.ofNullable( requestComplianceCheckForScannedGitRepositoryQuery.commit()) .map(Commit::new) + .orElse(null), + Optional.ofNullable( + requestComplianceCheckForScannedGitRepositoryQuery + .packageFolder()) + .map(Path::of) .orElse(null)); final PolicyIdentifier policyIdentifier = diff --git a/src/main/java/com/ibm/usecases/compliance/service/CompliancePreparationService.java b/src/main/java/com/ibm/usecases/compliance/service/CompliancePreparationService.java index 84d17fcf5..36f7368c2 100644 --- a/src/main/java/com/ibm/usecases/compliance/service/CompliancePreparationService.java +++ b/src/main/java/com/ibm/usecases/compliance/service/CompliancePreparationService.java @@ -32,6 +32,7 @@ import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; import java.nio.charset.StandardCharsets; +import java.nio.file.Path; import java.util.Collection; import org.cyclonedx.exception.ParseException; import org.cyclonedx.model.Bom; @@ -43,25 +44,17 @@ public final class CompliancePreparationService { public Collection receiveCryptographicAssets( @Nonnull ICBOMReadRepository readRepository, @Nonnull GitUrl gitUrl, - @Nullable Commit commit) + @Nullable Commit commit, + @Nullable Path packageFolder) throws CBOMSerializationFailed, CouldNotFindCBOMForGitRepository, InvalidScanUrl { // validate gitUrl.validate(); - final CBOMReadModel cbomReadModel; - if (commit != null) { - cbomReadModel = - readRepository - .findBy(gitUrl, commit) - .orElseThrow( - () -> new CouldNotFindCBOMForGitRepository(gitUrl.value())); - } else { - cbomReadModel = - readRepository - .findBy(gitUrl) - .orElseThrow( - () -> new CouldNotFindCBOMForGitRepository(gitUrl.value())); - } + final CBOMReadModel cbomReadModel = + readRepository + .findBy(gitUrl, commit, packageFolder) + .orElseThrow(() -> new CouldNotFindCBOMForGitRepository(gitUrl.value())); + final CBOM cbom = CBOM.formJSON(cbomReadModel.getBom()); return cbom.cycloneDXbom().getComponents().stream() .map(component -> new CryptographicAsset(component.getBomRef(), component)) diff --git a/src/main/java/com/ibm/usecases/scanning/commands/SetPackageFolderCommand.java b/src/main/java/com/ibm/usecases/scanning/commands/SetPackageFolderCommand.java new file mode 100644 index 000000000..5b996cb4b --- /dev/null +++ b/src/main/java/com/ibm/usecases/scanning/commands/SetPackageFolderCommand.java @@ -0,0 +1,26 @@ +/* + * 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.usecases.scanning.commands; + +import app.bootstrap.core.cqrs.ICommand; +import com.ibm.domain.scanning.ScanId; +import jakarta.annotation.Nonnull; + +public record SetPackageFolderCommand(@Nonnull ScanId id) implements ICommand {} 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 83b17b2b8..a94e0b3df 100644 --- a/src/main/java/com/ibm/usecases/scanning/processmanager/ScanProcessManager.java +++ b/src/main/java/com/ibm/usecases/scanning/processmanager/ScanProcessManager.java @@ -32,10 +32,10 @@ import com.ibm.domain.scanning.ScanAggregate; import com.ibm.domain.scanning.ScanId; import com.ibm.domain.scanning.ScanMetadata; -import com.ibm.domain.scanning.ScanRequest; import com.ibm.domain.scanning.errors.CBOMSerializationFailed; import com.ibm.domain.scanning.errors.CommitHashAlreadyExists; import com.ibm.domain.scanning.errors.GitUrlAlreadyResolved; +import com.ibm.domain.scanning.errors.PackageFolderAlreadyExists; import com.ibm.domain.scanning.errors.ScanResultForLanguageAlreadyExists; import com.ibm.infrastructure.errors.ClientDisconnected; import com.ibm.infrastructure.errors.EntityNotFoundById; @@ -48,6 +48,7 @@ import com.ibm.usecases.scanning.commands.IndexModulesCommand; import com.ibm.usecases.scanning.commands.RequestScanCommand; import com.ibm.usecases.scanning.commands.ScanCommand; +import com.ibm.usecases.scanning.commands.SetPackageFolderCommand; import com.ibm.usecases.scanning.errors.GitCloneFailed; import com.ibm.usecases.scanning.errors.GitCloneResultNotAvailable; import com.ibm.usecases.scanning.errors.NoCommitProvided; @@ -62,12 +63,16 @@ import com.ibm.usecases.scanning.services.indexing.JavaIndexService; import com.ibm.usecases.scanning.services.indexing.ProjectModule; import com.ibm.usecases.scanning.services.indexing.PythonIndexService; +import com.ibm.usecases.scanning.services.pkg.JavaPackageFinderService; +import com.ibm.usecases.scanning.services.pkg.PythonPackageFinderService; import com.ibm.usecases.scanning.services.scan.ScanResultDTO; import com.ibm.usecases.scanning.services.scan.java.JavaScannerService; import com.ibm.usecases.scanning.services.scan.python.PythonScannerService; import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; import java.io.File; +import java.io.IOException; +import java.nio.file.Path; import java.util.EnumMap; import java.util.List; import java.util.Map; @@ -103,6 +108,8 @@ public void handle(@Nonnull ICommand command) throws Exception { this.handleFetchDataFromDepsDevCommand(fetchDataFromDepsDevCommand); case CloneGitRepositoryCommand cloneGitRepositoryCommand -> this.handleCloneGitRepositoryCommand(cloneGitRepositoryCommand); + case SetPackageFolderCommand setPackageFolderCommmand -> + this.handleSetPackageFolderCommand(setPackageFolderCommmand); case IndexModulesCommand indexModulesCommand -> this.handleIndexModulesCommand(indexModulesCommand); case ScanCommand scanCommand -> this.handleScanCommand(scanCommand); @@ -116,7 +123,8 @@ private void handleFetchDataFromDepsDevCommand(@Nonnull FetchDataFromDepsDevComm throws EntityNotFoundById, NoDataAvailableInDepsDevForPurl, GitUrlAlreadyResolved, - NoPurlSpecifiedForScan { + NoPurlSpecifiedForScan, + ClientDisconnected { if (this.scanId != command.id()) { return; } @@ -127,13 +135,17 @@ private void handleFetchDataFromDepsDevCommand(@Nonnull FetchDataFromDepsDevComm // Fetch source repo from deps.dev final DepsDevService depsDev = new DepsDevService(); final String gitUrl = - depsDev.fetch( + depsDev.getSourceRepo( scanAggregate .getPurl() .map(PackageURL::canonicalize) .orElseThrow(() -> new NoPurlSpecifiedForScan(scanId))); // update aggregate scanAggregate.setResolvedGitUrl(gitUrl); + if (gitUrl != null) { + this.progressDispatcher.send( + new ProgressMessage(ProgressMessageType.GITURL, gitUrl)); + } this.repository.save(scanAggregate); this.commandBus.send( @@ -176,8 +188,8 @@ private void handleCloneGitRepositoryCommand(@Nonnull CloneGitRepositoryCommand // update aggregate scanAggregate.setCommitHash(cloneResultDTO.commit()); this.repository.save(scanAggregate); - // start indexing - this.commandBus.send(new IndexModulesCommand(command.id())); + // set subfolder + this.commandBus.send(new SetPackageFolderCommand(command.id())); } catch (GitCloneFailed gitCloneFailed) { // if previous attempted failed with `main`, try `master` if (scanAggregate.getScanRequest().revision().value().equalsIgnoreCase("main")) { @@ -206,6 +218,54 @@ private void handleCloneGitRepositoryCommand(@Nonnull CloneGitRepositoryCommand } } + private void handleSetPackageFolderCommand(@Nonnull SetPackageFolderCommand command) + throws EntityNotFoundById, IOException, PackageFolderAlreadyExists, ClientDisconnected { + if (this.scanId != command.id()) { + return; + } + + try { + final Optional possibleScanAggregate = + this.repository.read(command.id()); + final ScanAggregate scanAggregate = + possibleScanAggregate.orElseThrow(() -> new EntityNotFoundById(command.id())); + + // Initialize packagePath from subFolder + Optional packagePath = + Optional.ofNullable(scanAggregate.getScanRequest().subFolder()).map(Path::of); + + // Extract packagePath from purl and overwrite + if (scanAggregate.getPurl().isPresent()) { + PackageURL purl = scanAggregate.getPurl().get(); + if (purl.getType().equals("maven")) { + JavaPackageFinderService jpfs = + new JavaPackageFinderService(this.projectDirectory.toPath()); + packagePath = jpfs.findPackage(purl); + // TODO: find gradle package + } else if (purl.getType().equals("pypi")) { + PythonPackageFinderService ppfs = + new PythonPackageFinderService(this.projectDirectory.toPath()); + packagePath = ppfs.findPackage(purl); + } + } + if (packagePath.isPresent()) { + scanAggregate.setPackageFolder(packagePath.get()); + this.progressDispatcher.send( + new ProgressMessage( + ProgressMessageType.FOLDER, packagePath.get().toString())); + } + + this.repository.save(scanAggregate); + // start indexing + this.commandBus.send(new IndexModulesCommand(command.id())); + } catch (Exception e) { + this.progressDispatcher.send( + new ProgressMessage(ProgressMessageType.ERROR, e.getMessage())); + this.compensate(command.id()); + throw e; + } + } + private void handleIndexModulesCommand(@Nonnull IndexModulesCommand command) throws EntityNotFoundById, GitCloneResultNotAvailable, ClientDisconnected { if (this.scanId != command.id()) { @@ -217,7 +277,6 @@ private void handleIndexModulesCommand(@Nonnull IndexModulesCommand command) this.repository.read(command.id()); final ScanAggregate scanAggregate = possibleScanAggregate.orElseThrow(() -> new EntityNotFoundById(command.id())); - final ScanRequest scanRequest = scanAggregate.getScanRequest(); this.index = new EnumMap<>(Language.class); // java final JavaIndexService javaIndexService = new JavaIndexService(this.progressDispatcher); @@ -225,13 +284,13 @@ private void handleIndexModulesCommand(@Nonnull IndexModulesCommand command) Optional.ofNullable(this.projectDirectory) .orElseThrow(GitCloneResultNotAvailable::new); final List javaIndex = - javaIndexService.index(dir, scanRequest.subFolder()); + javaIndexService.index(dir, scanAggregate.getPackageFolder()); this.index.put(Language.JAVA, javaIndex); // python final PythonIndexService pythonIndexService = new PythonIndexService(this.progressDispatcher); final List pythonIndex = - pythonIndexService.index(dir, scanRequest.subFolder()); + pythonIndexService.index(dir, scanAggregate.getPackageFolder()); this.index.put(Language.PYTHON, pythonIndex); // continue with scan this.commandBus.send(new ScanCommand(command.id())); @@ -261,7 +320,6 @@ private void handleScanCommand(@Nonnull ScanCommand command) this.repository.read(command.id()); final ScanAggregate scanAggregate = possibleScanAggregate.orElseThrow(() -> new EntityNotFoundById(command.id())); - final ScanRequest scanRequest = scanAggregate.getScanRequest(); final Commit commit = scanAggregate.getCommit().orElseThrow(NoCommitProvided::new); // progress scan statistics @@ -284,7 +342,7 @@ private void handleScanCommand(@Nonnull ScanCommand command) .orElseThrow(() -> new NoGitUrlSpecifiedForScan(scanId)), scanAggregate.getRevision(), commit, - scanRequest.subFolder(), + scanAggregate.getPackageFolder(), Optional.ofNullable(this.index) .map(i -> i.get(Language.JAVA)) .orElseThrow(NoIndexForProject::new)); @@ -320,7 +378,7 @@ private void handleScanCommand(@Nonnull ScanCommand command) .orElseThrow(() -> new NoGitUrlSpecifiedForScan(scanId)), scanAggregate.getRevision(), commit, - scanRequest.subFolder(), + scanAggregate.getPackageFolder(), Optional.ofNullable(this.index) .map(i -> i.get(Language.PYTHON)) .orElseThrow(NoIndexForProject::new)); 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 02b9ae04c..e6201a88a 100644 --- a/src/main/java/com/ibm/usecases/scanning/projector/CBOMProjector.java +++ b/src/main/java/com/ibm/usecases/scanning/projector/CBOMProjector.java @@ -29,7 +29,6 @@ import com.ibm.domain.scanning.LanguageScan; import com.ibm.domain.scanning.ScanAggregate; import com.ibm.domain.scanning.ScanId; -import com.ibm.domain.scanning.ScanRequest; import com.ibm.domain.scanning.errors.CBOMSerializationFailed; import com.ibm.domain.scanning.events.ScanFinishedEvent; import com.ibm.infrastructure.database.readmodels.CBOMReadModel; @@ -41,6 +40,7 @@ import jakarta.annotation.Nonnull; import jakarta.enterprise.event.Observes; import jakarta.inject.Singleton; +import java.nio.file.Path; import java.util.List; import java.util.Optional; import java.util.UUID; @@ -81,7 +81,6 @@ private void handleScanFinishedEvent(@Nonnull ScanFinishedEvent scanFinishedEven final Optional possibleScanAggregate = this.sourceRepository.read(scanId); final ScanAggregate scanAggregate = possibleScanAggregate.orElseThrow(() -> new EntityNotFoundById(scanId)); - final ScanRequest scanRequest = scanAggregate.getScanRequest(); // check for existing read model if (this.repository instanceof ICBOMReadRepository cbomReadRepository) { final Optional possibleCBOMReadModel = @@ -89,7 +88,8 @@ private void handleScanFinishedEvent(@Nonnull ScanFinishedEvent scanFinishedEven scanAggregate .getGitUrl() .orElseThrow(() -> new NoGitUrlSpecifiedForScan(scanId)), - scanAggregate.getCommit().orElseThrow(NoCBOMForScan::new)); + scanAggregate.getCommit().orElseThrow(NoCBOMForScan::new), + scanAggregate.getPackageFolder()); if (possibleCBOMReadModel.isPresent()) { LOGGER.info( "No need to update CBOM read model, since scan request didn't change for {}", @@ -120,12 +120,15 @@ private void handleScanFinishedEvent(@Nonnull ScanFinishedEvent scanFinishedEven final CBOMReadModel cbomReadModel = new CBOMReadModel( scanAggregate.getId().getUuid(), - scanRequest.scanUrl().getIdentifier(), + scanAggregate.getScanRequest().scanUrl().getIdentifier(), scanAggregate .getGitUrl() .map(GitUrl::value) .orElseThrow(() -> new NoGitUrlSpecifiedForScan(scanId)), scanAggregate.getRevision().value(), + Optional.ofNullable(scanAggregate.getPackageFolder()) + .map(Path::toString) + .orElse(null), scanAggregate.getCommit().map(Commit::hash).orElse(null), scanFinishedEvent.getTimestamp(), mergedCBOM.toJSON()); diff --git a/src/main/java/com/ibm/usecases/scanning/services/depsdev/DepsDevService.java b/src/main/java/com/ibm/usecases/scanning/services/depsdev/DepsDevService.java index 7f0745475..c5bf6f017 100644 --- a/src/main/java/com/ibm/usecases/scanning/services/depsdev/DepsDevService.java +++ b/src/main/java/com/ibm/usecases/scanning/services/depsdev/DepsDevService.java @@ -28,10 +28,13 @@ import java.io.InputStream; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; +import org.apache.hc.client5.http.ClientProtocolException; import org.apache.hc.client5.http.classic.methods.HttpGet; import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; -import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; import org.apache.hc.client5.http.impl.classic.HttpClientBuilder; +import org.apache.hc.core5.http.ClassicHttpResponse; +import org.apache.hc.core5.http.HttpStatus; +import org.apache.hc.core5.http.io.HttpClientResponseHandler; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -40,40 +43,47 @@ public class DepsDevService { private static final String DEPS_DEV_URI = "https://api.deps.dev/v3alpha/purl/"; private static final String SOURCE_REPO = "SOURCE_REPO"; + private final class DepsDevResponseHandler implements HttpClientResponseHandler { + @Override + public String handleResponse(ClassicHttpResponse httpResponse) + throws ClientProtocolException, IOException { + if (httpResponse.getCode() != HttpStatus.SC_OK) { + return null; + } + return extractSourceRepo(httpResponse.getEntity().getContent()); + } + + @Nonnull + public String extractSourceRepo(InputStream in) throws IOException { + ObjectMapper objectMapper = new ObjectMapper(); + JsonNode rootNode = objectMapper.readTree(in); + JsonNode version = rootNode.get("version"); + if (version != null) { + ArrayNode links = (ArrayNode) version.get("links"); + if (links != null) { + for (JsonNode link : links) { + if (SOURCE_REPO.equals(link.get("label").textValue())) { + return link.get("url").textValue(); + } + } + } + } + throw new IOException("Invalid deps.dev response"); + } + } + @Nonnull - public String fetch(@Nonnull String purl) throws NoDataAvailableInDepsDevForPurl { + public String getSourceRepo(@Nonnull String purl) throws NoDataAvailableInDepsDevForPurl { LOGGER.info("Sending DepsDev request for " + purl); final HttpGet request = new HttpGet(DEPS_DEV_URI + URLEncoder.encode(purl, StandardCharsets.UTF_8)); - try (final CloseableHttpClient httpClient = HttpClientBuilder.create().build(); - final CloseableHttpResponse response = httpClient.execute(request); ) { - if (response.getCode() != 200) { - throw new NoDataAvailableInDepsDevForPurl( - purl, "bad status code: " + response.getCode()); - } - final InputStream in = response.getEntity().getContent(); - return extractSourceRepo(in); + try (final CloseableHttpClient httpClient = HttpClientBuilder.create().build()) { + final String srcRepo = httpClient.execute(request, new DepsDevResponseHandler()); + LOGGER.info("Source code repository: {}", srcRepo); + return srcRepo; } catch (IOException ioe) { throw new NoDataAvailableInDepsDevForPurl(purl, ioe.getMessage()); } } - - @Nonnull - public String extractSourceRepo(InputStream in) throws IOException { - ObjectMapper objectMapper = new ObjectMapper(); - JsonNode rootNode = objectMapper.readTree(in); - JsonNode version = rootNode.get("version"); - if (version != null) { - ArrayNode links = (ArrayNode) version.get("links"); - if (links != null) { - for (JsonNode link : links) { - if (SOURCE_REPO.equals(link.get("label").textValue())) { - return link.get("url").textValue(); - } - } - } - } - throw new IOException(); - } } diff --git a/src/main/java/com/ibm/usecases/scanning/services/indexing/IndexingService.java b/src/main/java/com/ibm/usecases/scanning/services/indexing/IndexingService.java index 7eb8f6740..c7154e68f 100644 --- a/src/main/java/com/ibm/usecases/scanning/services/indexing/IndexingService.java +++ b/src/main/java/com/ibm/usecases/scanning/services/indexing/IndexingService.java @@ -29,6 +29,7 @@ import java.io.File; import java.io.IOException; import java.nio.file.Files; +import java.nio.file.Path; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -55,10 +56,10 @@ protected IndexingService( } @Nonnull - public List index(@Nonnull File projectDirectory, @Nullable String subFolder) + public List index(@Nonnull File projectDirectory, @Nullable Path packageFolder) throws ClientDisconnected { - if (subFolder != null) { - projectDirectory = new File(projectDirectory.getPath() + File.separator + subFolder); + if (packageFolder != null) { + projectDirectory = projectDirectory.toPath().resolve(packageFolder).toFile(); } this.progressDispatcher.send( new ProgressMessage(ProgressMessageType.LABEL, "Indexing projects ...")); diff --git a/src/main/java/com/ibm/usecases/scanning/services/pkg/JavaPackageFinderService.java b/src/main/java/com/ibm/usecases/scanning/services/pkg/JavaPackageFinderService.java new file mode 100644 index 000000000..2c200258e --- /dev/null +++ b/src/main/java/com/ibm/usecases/scanning/services/pkg/JavaPackageFinderService.java @@ -0,0 +1,47 @@ +/* + * CBOMkit + * Copyright (C) 2025 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.usecases.scanning.services.pkg; + +import jakarta.annotation.Nonnull; +import java.io.FileReader; +import java.nio.file.Path; +import org.apache.maven.model.Model; +import org.apache.maven.model.io.xpp3.MavenXpp3Reader; + +public class JavaPackageFinderService extends PackageFinderService { + private MavenXpp3Reader reader = new MavenXpp3Reader(); + + public JavaPackageFinderService(@Nonnull Path root) throws IllegalArgumentException { + super(root); + + this.reader = new MavenXpp3Reader(); + } + + @Override + public boolean isBuildFile(@Nonnull Path file) { + return file.endsWith("pom.xml"); + } + + @Override + public String getPackageName(@Nonnull Path buildFile) throws Exception { + Model model = reader.read(new FileReader(buildFile.toFile())); + return model.getArtifactId(); + } +} diff --git a/src/main/java/com/ibm/usecases/scanning/services/pkg/PackageFinderService.java b/src/main/java/com/ibm/usecases/scanning/services/pkg/PackageFinderService.java new file mode 100644 index 000000000..b363baf62 --- /dev/null +++ b/src/main/java/com/ibm/usecases/scanning/services/pkg/PackageFinderService.java @@ -0,0 +1,82 @@ +/* + * CBOMkit + * Copyright (C) 2025 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.usecases.scanning.services.pkg; + +import com.github.packageurl.PackageURL; +import jakarta.annotation.Nonnull; +import jakarta.annotation.Nullable; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public abstract class PackageFinderService { + private static final Logger LOGGER = LoggerFactory.getLogger(PackageFinderService.class); + + protected Path root; + + public PackageFinderService(@Nonnull Path root) throws IllegalArgumentException { + if (!Files.isDirectory(root)) { + throw new IllegalArgumentException("Path must be a directory!"); + } + this.root = root; + } + + @Nullable public Optional findPackage(@Nonnull PackageURL purl) throws IOException { + List poms; + + LOGGER.info("Trying to find package path for purl {}", purl); + + try (Stream walk = Files.walk(this.root)) { + poms = + walk.filter(p -> !Files.isDirectory(p)) + // .map(p -> p.toString().toLowerCase()) + // .filter(p -> p.endsWith(getBuildFileName())) + .filter(p -> isBuildFile(p)) + .collect(Collectors.toList()); + } + + for (Path pom : poms) { + try { + if (purl.getName().equals(getPackageName(pom))) { + Path pkgPath = this.root.relativize(pom.getParent()); + LOGGER.info( + "Package path: {}", pkgPath.equals(Paths.get("")) ? "" : pkgPath); + return Optional.of(pkgPath); + } + } catch (Exception e) { + continue; + } + } + + LOGGER.warn("Package path not found"); + return Optional.empty(); + } + + public abstract boolean isBuildFile(@Nonnull Path file); + + public abstract String getPackageName(@Nonnull Path buildFile) throws Exception; +} diff --git a/src/main/java/com/ibm/usecases/scanning/services/pkg/PythonPackageFinderService.java b/src/main/java/com/ibm/usecases/scanning/services/pkg/PythonPackageFinderService.java new file mode 100644 index 000000000..e8218ce98 --- /dev/null +++ b/src/main/java/com/ibm/usecases/scanning/services/pkg/PythonPackageFinderService.java @@ -0,0 +1,43 @@ +/* + * CBOMkit + * Copyright (C) 2025 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.usecases.scanning.services.pkg; + +import jakarta.annotation.Nonnull; +import java.nio.file.Path; +import org.tomlj.Toml; +import org.tomlj.TomlParseResult; + +public class PythonPackageFinderService extends PackageFinderService { + + public PythonPackageFinderService(@Nonnull Path root) throws IllegalArgumentException { + super(root); + } + + @Override + public boolean isBuildFile(Path file) { + return file.endsWith("pyproject.toml"); + } + + @Override + public String getPackageName(Path buildFile) throws Exception { + TomlParseResult result = Toml.parse(buildFile); + return result.getString(("project.name")); + } +} diff --git a/src/main/java/com/ibm/usecases/scanning/services/scan/IScannerService.java b/src/main/java/com/ibm/usecases/scanning/services/scan/IScannerService.java index 0f3566e58..70e47f6f7 100644 --- a/src/main/java/com/ibm/usecases/scanning/services/scan/IScannerService.java +++ b/src/main/java/com/ibm/usecases/scanning/services/scan/IScannerService.java @@ -26,6 +26,7 @@ import com.ibm.usecases.scanning.services.indexing.ProjectModule; import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; +import java.nio.file.Path; import java.util.List; import java.util.function.Consumer; @@ -36,7 +37,7 @@ ScanResultDTO scan( @Nonnull GitUrl gitUrl, @Nonnull Revision revision, @Nonnull Commit commit, - @Nullable String subFolder, + @Nullable Path subFolder, @Nonnull List index) throws Exception; } diff --git a/src/main/java/com/ibm/usecases/scanning/services/scan/ScannerService.java b/src/main/java/com/ibm/usecases/scanning/services/scan/ScannerService.java index e06187349..edbdf1e9d 100644 --- a/src/main/java/com/ibm/usecases/scanning/services/scan/ScannerService.java +++ b/src/main/java/com/ibm/usecases/scanning/services/scan/ScannerService.java @@ -35,6 +35,7 @@ import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; import java.io.File; +import java.nio.file.Path; import java.util.Collections; import java.util.List; import java.util.Optional; @@ -92,7 +93,7 @@ protected synchronized Optional receiveBom( @Nonnull GitUrl gitUrl, @Nonnull Revision revision, @Nonnull Commit commit, - @Nullable String subFolder) { + @Nullable Path subFolder) { final Bom bom = this.cbomOutputFile.getBom(); // sanitizeOccurrence @@ -118,7 +119,7 @@ protected synchronized Optional receiveBom( if (subFolder != null) { final Property subFolderProperty = new Property(); subFolderProperty.setName("subfolder"); - subFolderProperty.setValue(subFolder); + subFolderProperty.setValue(subFolder.toString()); metadata.addProperty(subFolderProperty); } diff --git a/src/main/java/com/ibm/usecases/scanning/services/scan/java/JavaScannerService.java b/src/main/java/com/ibm/usecases/scanning/services/scan/java/JavaScannerService.java index 0cf8ef979..4753bfd6d 100644 --- a/src/main/java/com/ibm/usecases/scanning/services/scan/java/JavaScannerService.java +++ b/src/main/java/com/ibm/usecases/scanning/services/scan/java/JavaScannerService.java @@ -33,6 +33,7 @@ import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; import java.io.File; +import java.nio.file.Path; import java.util.List; import org.sonar.api.batch.fs.InputFile; import org.sonar.api.batch.fs.internal.DefaultFileSystem; @@ -66,7 +67,7 @@ public synchronized ScanResultDTO scan( @Nonnull GitUrl gitUrl, @Nonnull Revision revision, @Nonnull Commit commit, - @Nullable String subFolder, + @Nullable Path subFolder, @Nonnull List index) throws ClientDisconnected { final List visitors = List.of(new JavaDetectionCollectionRule(this)); diff --git a/src/main/java/com/ibm/usecases/scanning/services/scan/python/PythonScannerService.java b/src/main/java/com/ibm/usecases/scanning/services/scan/python/PythonScannerService.java index 664fc02c0..b4ee1d0a5 100644 --- a/src/main/java/com/ibm/usecases/scanning/services/scan/python/PythonScannerService.java +++ b/src/main/java/com/ibm/usecases/scanning/services/scan/python/PythonScannerService.java @@ -33,6 +33,7 @@ import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; import java.io.File; +import java.nio.file.Path; import java.util.List; import org.sonar.api.batch.fs.InputFile; import org.sonar.plugins.python.api.PythonCheck; @@ -51,7 +52,7 @@ public PythonScannerService( @Nonnull GitUrl gitUrl, @Nonnull Revision revision, @Nonnull Commit commit, - @Nullable String subFolder, + @Nullable Path subFolder, @Nonnull List index) throws ClientDisconnected { final PythonCheck visitor = new PythonDetectionCollectionRule(this);