forked from eclipse-sw360/sw360
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'main' into feat/api/splitComponents
- Loading branch information
Showing
79 changed files
with
3,988 additions
and
374 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
294 changes: 294 additions & 0 deletions
294
backend/src-common/src/main/java/org/eclipse/sw360/cyclonedx/CycloneDxBOMExporter.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,294 @@ | ||
/* | ||
* Copyright Siemens Healthineers GmBH, 2023. Part of the SW360 Portal Project. | ||
* | ||
* This program and the accompanying materials are made | ||
* available under the terms of the Eclipse Public License 2.0 | ||
* which is available at https://www.eclipse.org/legal/epl-2.0/ | ||
* | ||
* SPDX-License-Identifier: EPL-2.0 | ||
*/ | ||
package org.eclipse.sw360.cyclonedx; | ||
|
||
import java.io.IOException; | ||
import java.util.AbstractMap; | ||
import java.util.Date; | ||
import java.util.List; | ||
import java.util.Map; | ||
import java.util.Objects; | ||
import java.util.Set; | ||
import java.util.TreeSet; | ||
import java.util.stream.Collectors; | ||
|
||
import org.apache.jena.ext.com.google.common.collect.Sets; | ||
import org.apache.logging.log4j.LogManager; | ||
import org.apache.logging.log4j.Logger; | ||
import org.cyclonedx.exception.GeneratorException; | ||
import org.cyclonedx.generators.json.BomJsonGenerator14; | ||
import org.cyclonedx.generators.xml.BomXmlGenerator14; | ||
import org.cyclonedx.model.Bom; | ||
import org.cyclonedx.model.Component.Type; | ||
import org.cyclonedx.model.ExternalReference; | ||
import org.cyclonedx.model.License; | ||
import org.cyclonedx.model.LicenseChoice; | ||
import org.cyclonedx.model.Metadata; | ||
import org.cyclonedx.model.Tool; | ||
import org.eclipse.sw360.datahandler.common.CommonUtils; | ||
import org.eclipse.sw360.datahandler.common.SW360Constants; | ||
import org.eclipse.sw360.datahandler.common.SW360Utils; | ||
import org.eclipse.sw360.datahandler.db.ComponentDatabaseHandler; | ||
import org.eclipse.sw360.datahandler.db.ProjectDatabaseHandler; | ||
import org.eclipse.sw360.datahandler.thrift.CycloneDxComponentType; | ||
import org.eclipse.sw360.datahandler.thrift.RequestStatus; | ||
import org.eclipse.sw360.datahandler.thrift.RequestSummary; | ||
import org.eclipse.sw360.datahandler.thrift.SW360Exception; | ||
import org.eclipse.sw360.datahandler.thrift.ThriftClients; | ||
import org.eclipse.sw360.datahandler.thrift.components.Component; | ||
import org.eclipse.sw360.datahandler.thrift.components.Release; | ||
import org.eclipse.sw360.datahandler.thrift.projects.Project; | ||
import org.eclipse.sw360.datahandler.thrift.projects.ProjectService; | ||
import org.eclipse.sw360.datahandler.thrift.users.User; | ||
|
||
import com.fasterxml.jackson.databind.ObjectMapper; | ||
import com.google.common.collect.Lists; | ||
|
||
/** | ||
* CycloneDX BOM export implementation. | ||
* Supports both XML and JSON format of CycloneDX SBOM | ||
* | ||
* @author [email protected] | ||
*/ | ||
public class CycloneDxBOMExporter { | ||
|
||
private static final Logger log = LogManager.getLogger(CycloneDxBOMExporter.class); | ||
private final ProjectDatabaseHandler projectDatabaseHandler; | ||
private final ComponentDatabaseHandler componentDatabaseHandler; | ||
private final User user; | ||
|
||
public CycloneDxBOMExporter(ProjectDatabaseHandler projectDatabaseHandler, ComponentDatabaseHandler componentDatabaseHandler, User user) { | ||
this.projectDatabaseHandler = projectDatabaseHandler; | ||
this.componentDatabaseHandler = componentDatabaseHandler; | ||
this.user = user; | ||
} | ||
|
||
public RequestSummary exportSbom(String projectId, String bomType, Boolean includeSubProjReleases, User user) { | ||
final RequestSummary summary = new RequestSummary(RequestStatus.SUCCESS); | ||
try { | ||
Project project = projectDatabaseHandler.getProjectById(projectId, user); | ||
Bom bom = new Bom(); | ||
Set<String> linkedReleaseIds = Sets.newHashSet(CommonUtils.getNullToEmptyKeyset(project.getReleaseIdToUsage())); | ||
|
||
if (!SW360Utils.isUserAtleastDesiredRoleInPrimaryOrSecondaryGroup(user, SW360Constants.SBOM_IMPORT_EXPORT_ACCESS_USER_ROLE)) { | ||
log.warn("User does not have permission to export the SBOM: " + user.getEmail()); | ||
summary.setRequestStatus(RequestStatus.ACCESS_DENIED); | ||
return summary; | ||
} | ||
|
||
if (includeSubProjReleases && project.getLinkedProjectsSize() > 0) { | ||
ProjectService.Iface client = new ThriftClients().makeProjectClient(); | ||
linkedReleaseIds.addAll(SW360Utils.getLinkedReleaseIdsOfAllSubProjectsAsFlatList(project, Sets.newHashSet(), Sets.newHashSet(), client, user)); | ||
} | ||
|
||
if (CommonUtils.isNotEmpty(linkedReleaseIds)) { | ||
List<Release> linkedReleases = componentDatabaseHandler.getReleasesByIds(linkedReleaseIds); | ||
Set<String> componentIds = linkedReleases.stream().map(Release::getComponentId).filter(Objects::nonNull).collect(Collectors.toSet()); | ||
List<Component> components = componentDatabaseHandler.getComponentsByIds(componentIds); | ||
List<org.cyclonedx.model.Component> sbomComponents = getCycloneDxComponentsFromSw360Releases(linkedReleases, components); | ||
bom.setComponents(sbomComponents); | ||
} else { | ||
log.warn("Cannot export SBOM for project without linked releases: " + projectId); | ||
summary.setRequestStatus(RequestStatus.FAILED_SANITY_CHECK); | ||
return summary; | ||
} | ||
|
||
org.cyclonedx.model.Component metadataComp = getMetadataComponent(project); | ||
Tool tool = getTool(); | ||
Metadata metadata = new Metadata(); | ||
metadata.setComponent(metadataComp); | ||
metadata.setTimestamp(new Date()); | ||
metadata.setTools(Lists.newArrayList(tool)); | ||
bom.setMetadata(metadata); | ||
|
||
if (SW360Constants.JSON_FILE_EXTENSION.equalsIgnoreCase(bomType)) { | ||
BomJsonGenerator14 jsonBom = new BomJsonGenerator14(bom); | ||
summary.setMessage(jsonBom.toJsonString()); | ||
} else { | ||
BomXmlGenerator14 xmlBom = new BomXmlGenerator14(bom); | ||
summary.setMessage(xmlBom.toXmlString()); | ||
} | ||
return summary; | ||
} catch (SW360Exception e) { | ||
log.error(String.format("An error occured while fetching project: %s from db, for SBOM export!", projectId), e); | ||
summary.setMessage("An error occured while fetching project from db, for SBOM export: " + e.getMessage()); | ||
} catch (GeneratorException e) { | ||
log.error(String.format("An error occured while exporting xml SBOM for project with id: %s", projectId), e); | ||
summary.setMessage("An error occured while exporting xml SBOM for project: " + e.getMessage()); | ||
} catch (Exception e) { | ||
log.error("An error occured while exporting SBOm for project: " + projectId, e); | ||
summary.setMessage("An error occured while exporting SBOM for project: " + e.getMessage()); | ||
} | ||
summary.setRequestStatus(RequestStatus.FAILURE); | ||
return summary; | ||
} | ||
|
||
private static Tool getTool() { | ||
Tool tool = new Tool(); | ||
tool.setName(SW360Constants.TOOL_NAME); | ||
tool.setVendor(SW360Constants.TOOL_VENDOR); | ||
tool.setVersion(SW360Utils.getSW360Version()); | ||
return tool; | ||
} | ||
|
||
private org.cyclonedx.model.Component getMetadataComponent(Project project) { | ||
org.cyclonedx.model.Component component = new org.cyclonedx.model.Component(); | ||
component.setAuthor(user.getEmail()); | ||
component.setDescription(CommonUtils.nullToEmptyString(project.getDescription())); | ||
component.setName(project.getName()); | ||
component.setVersion(CommonUtils.nullToEmptyString(project.getVersion())); | ||
component.setType(Type.APPLICATION); | ||
component.setGroup(CommonUtils.nullToEmptyString(project.getBusinessUnit())); | ||
return component; | ||
} | ||
|
||
private List<org.cyclonedx.model.Component> getCycloneDxComponentsFromSw360Releases(List<Release> releases, List<Component> components) { | ||
List<org.cyclonedx.model.Component> comps = Lists.newArrayList(); | ||
Map<String, Component> compIdToComponentMap = components.stream() | ||
.map(comp -> new AbstractMap.SimpleEntry<>(comp.getId(), comp)) | ||
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (oldVal, newVal) -> newVal)); | ||
for (Release release : releases) { | ||
Component sw360Comp = compIdToComponentMap.get(release.getComponentId()); | ||
org.cyclonedx.model.Component comp = new org.cyclonedx.model.Component(); | ||
comp.setName(release.getName()); | ||
comp.setVersion(release.getVersion()); | ||
comp.setDescription(CommonUtils.nullToEmptyString(sw360Comp.getDescription())); | ||
|
||
// set package URL | ||
Set<String> purlSet = new TreeSet<>(); | ||
if (!CommonUtils.isNullOrEmptyMap(release.getExternalIds())) { | ||
if (release.getExternalIds().containsKey(SW360Constants.PACKAGE_URL)) { | ||
purlSet.addAll(getPurlFromSw360Document(release.getExternalIds(), SW360Constants.PACKAGE_URL)); | ||
} | ||
if ( release.getExternalIds().containsKey(SW360Constants.PURL_ID)) { | ||
purlSet.addAll(getPurlFromSw360Document(release.getExternalIds(), SW360Constants.PURL_ID)); | ||
} | ||
} else if (!CommonUtils.isNullOrEmptyMap(sw360Comp.getExternalIds())) { | ||
if (sw360Comp.getExternalIds().containsKey(SW360Constants.PACKAGE_URL)) { | ||
purlSet.addAll(getPurlFromSw360Document(sw360Comp.getExternalIds(), SW360Constants.PACKAGE_URL)); | ||
} | ||
if ( sw360Comp.getExternalIds().containsKey(SW360Constants.PURL_ID)) { | ||
purlSet.addAll(getPurlFromSw360Document(sw360Comp.getExternalIds(), SW360Constants.PURL_ID)); | ||
} | ||
} | ||
if (CommonUtils.isNotEmpty(purlSet)) { | ||
comp.setPurl(String.join(", ", purlSet)); | ||
} | ||
|
||
// set CycloneDx component type | ||
if (sw360Comp.isSetCdxComponentType()) { | ||
comp.setType(getCdxComponentType(sw360Comp.getCdxComponentType())); | ||
} | ||
|
||
// set vcs, website and mailing list | ||
List<ExternalReference> extRefs = Lists.newArrayList(); | ||
if (null != release.getRepository() && null != release.getRepository().getUrl()) { | ||
ExternalReference extRef = new ExternalReference(); | ||
extRef.setType(org.cyclonedx.model.ExternalReference.Type.VCS); | ||
extRef.setUrl(release.getRepository().getUrl()); | ||
extRefs.add(extRef); | ||
} | ||
if (CommonUtils.isNotNullEmptyOrWhitespace(sw360Comp.getHomepage())) { | ||
ExternalReference extRef = new ExternalReference(); | ||
extRef.setType(org.cyclonedx.model.ExternalReference.Type.WEBSITE); | ||
extRef.setUrl(sw360Comp.getHomepage()); | ||
extRefs.add(extRef); | ||
} | ||
if (CommonUtils.isNotNullEmptyOrWhitespace(sw360Comp.getMailinglist())) { | ||
ExternalReference extRef = new ExternalReference(); | ||
extRef.setType(org.cyclonedx.model.ExternalReference.Type.MAILING_LIST); | ||
extRef.setUrl(sw360Comp.getMailinglist()); | ||
extRefs.add(extRef); | ||
} | ||
if (CommonUtils.isNotNullEmptyOrWhitespace(sw360Comp.getWiki())) { | ||
ExternalReference extRef = new ExternalReference(); | ||
extRef.setType(org.cyclonedx.model.ExternalReference.Type.SUPPORT); | ||
extRef.setUrl(sw360Comp.getWiki()); | ||
extRefs.add(extRef); | ||
} | ||
if (CommonUtils.isNotEmpty(extRefs)) { | ||
comp.setExternalReferences(extRefs); | ||
} | ||
|
||
// set licenses | ||
Set<String> licenses = Sets.newHashSet(); | ||
if (CommonUtils.isNotEmpty(release.getMainLicenseIds())) { | ||
licenses.addAll(release.getMainLicenseIds()); | ||
} | ||
if (CommonUtils.isNotEmpty(release.getOtherLicenseIds())) { | ||
licenses.addAll(release.getOtherLicenseIds()); | ||
} | ||
if (CommonUtils.isNotEmpty(sw360Comp.getMainLicenseIds())) { | ||
licenses.addAll(sw360Comp.getMainLicenseIds()); | ||
} | ||
if (CommonUtils.isNotEmpty(licenses)) { | ||
comp.setLicenseChoice(getLicenseFromSw360Document(licenses)); | ||
} | ||
|
||
comps.add(comp); | ||
} | ||
return comps; | ||
} | ||
|
||
|
||
private LicenseChoice getLicenseFromSw360Document(Set<String> sw360Licenses) { | ||
LicenseChoice licenseChoice = new LicenseChoice(); | ||
List<License> licenses = Lists.newArrayList(); | ||
for (String lic : sw360Licenses) { | ||
License license = new License(); | ||
license.setId(lic); | ||
licenses.add(license); | ||
} | ||
licenseChoice.setLicenses(licenses); | ||
return licenseChoice; | ||
} | ||
|
||
private org.cyclonedx.model.Component.Type getCdxComponentType(CycloneDxComponentType compType) { | ||
switch (compType) { | ||
case APPLICATION: | ||
return org.cyclonedx.model.Component.Type.APPLICATION; | ||
case CONTAINER: | ||
return org.cyclonedx.model.Component.Type.CONTAINER; | ||
case DEVICE: | ||
return org.cyclonedx.model.Component.Type.DEVICE; | ||
case FILE: | ||
return org.cyclonedx.model.Component.Type.FILE; | ||
case FIRMWARE: | ||
return org.cyclonedx.model.Component.Type.FIRMWARE; | ||
case FRAMEWORK: | ||
return org.cyclonedx.model.Component.Type.FRAMEWORK; | ||
case LIBRARY: | ||
return org.cyclonedx.model.Component.Type.LIBRARY; | ||
case OPERATING_SYSTEM: | ||
return org.cyclonedx.model.Component.Type.OPERATING_SYSTEM; | ||
default: | ||
return null; | ||
} | ||
} | ||
|
||
@SuppressWarnings("unchecked") | ||
private Set<String> getPurlFromSw360Document(Map<String, String> externalIds, String key) { | ||
String existingPurl = CommonUtils.nullToEmptyMap(externalIds).getOrDefault(key, ""); | ||
Set<String> purlSet = Sets.newHashSet(); | ||
if (CommonUtils.isNotNullEmptyOrWhitespace(existingPurl)) { | ||
ObjectMapper mapper = new ObjectMapper(); | ||
try { | ||
if (existingPurl.equals(SW360Constants.NULL_STRING)) { | ||
purlSet.add(SW360Constants.NULL_STRING); | ||
} else { | ||
purlSet = mapper.readValue(existingPurl, Set.class); | ||
} | ||
} catch (IOException e) { | ||
purlSet.add(existingPurl); | ||
} | ||
} | ||
return purlSet; | ||
} | ||
} |
Oops, something went wrong.