Skip to content

Commit

Permalink
add CycloneDxAggregateEnforceMojo
Browse files Browse the repository at this point in the history
Signed-off-by: XenoAmess <xenoamess@gmail.com>
  • Loading branch information
XenoAmess committed Mar 9, 2023
1 parent a2754bf commit 1a43f98
Show file tree
Hide file tree
Showing 4 changed files with 244 additions and 4 deletions.
6 changes: 6 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,12 @@
<artifactId>junit-vintage-engine</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jetbrains</groupId>
<artifactId>annotations</artifactId>
<scope>provided</scope>
<version>23.0.0</version>
</dependency>
</dependencies>

<dependencyManagement>
Expand Down
61 changes: 59 additions & 2 deletions src/main/java/org/cyclonedx/maven/BaseCycloneDxMojo.java
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import org.apache.commons.io.input.BOMInputStream;
import org.jetbrains.annotations.NotNull;

public abstract class BaseCycloneDxMojo extends AbstractMojo {

Expand All @@ -61,7 +65,7 @@ public abstract class BaseCycloneDxMojo extends AbstractMojo {
/**
* The component type associated to the SBOM metadata. See
* <a href="https://cyclonedx.org/docs/1.4/json/#metadata_component_type">CycloneDX reference</a> for supported
* values.
* values.
*/
@Parameter(property = "projectType", defaultValue = "library", required = false)
private String projectType;
Expand Down Expand Up @@ -199,6 +203,21 @@ public abstract class BaseCycloneDxMojo extends AbstractMojo {
@org.apache.maven.plugins.annotations.Component
private ProjectDependenciesConverter projectDependenciesConverter;

@Parameter(property = "enforceExcludeArtifactId", required = false)
protected String[] enforceExcludeArtifactId;

@Parameter(property = "enforceComponentsSameVersion", defaultValue = "true", required = false)
protected boolean enforceComponentsSameVersion = true;

@Parameter(property = "enforceLicensesBlackList", required = false)
protected String[] enforceLicensesBlackList;

@Parameter(property = "enforceLicensesWhiteList", required = false)
protected String[] enforceLicensesWhiteList;

@Parameter(property = "mergeBomFile", required = false)
protected File mergeBomFile;

/**
* Various messages sent to console.
*/
Expand Down Expand Up @@ -267,7 +286,7 @@ public void execute() throws MojoExecutionException {
private void generateBom(String analysis, Metadata metadata, Set<Component> components, Set<Dependency> dependencies) throws MojoExecutionException {
try {
getLog().info(String.format(MESSAGE_CREATING_BOM, schemaVersion, components.size()));
final Bom bom = new Bom();
Bom bom = new Bom();
bom.setComponents(new ArrayList<>(components));

if (schemaVersion().getVersion() >= 1.1 && includeBomSerialNumber) {
Expand All @@ -291,6 +310,7 @@ private void generateBom(String analysis, Metadata metadata, Set<Component> comp
if ("all".equalsIgnoreCase(outputFormat)
|| "xml".equalsIgnoreCase(outputFormat)
|| "json".equalsIgnoreCase(outputFormat)) {
bom = postProcessingBom(bom);
saveBom(bom);
} else {
getLog().error("Unsupported output format. Valid options are XML and JSON");
Expand All @@ -300,6 +320,43 @@ private void generateBom(String analysis, Metadata metadata, Set<Component> comp
}
}

@NotNull
protected Bom postProcessingBom(@NotNull Bom bom) throws MojoExecutionException {
if (mergeBomFile != null) {
Bom mergeBom;
try {
try {
mergeBom = new JsonParser().parse(mergeBomFile);
} catch (Exception e) {
mergeBom = new XmlParser().parse(mergeBomFile);
}
} catch (Exception e) {
throw new MojoExecutionException("parse failed", e);
}
{
LinkedHashSet<Component> components = new LinkedHashSet<>();
if (mergeBom.getComponents() != null) {
components.addAll(mergeBom.getComponents());
}
if (bom.getComponents() != null) {
components.addAll(bom.getComponents());
}
bom.setComponents(new ArrayList<>(components));
}
{
LinkedHashSet<Dependency> dependencies = new LinkedHashSet<>();
if (mergeBom.getDependencies() != null) {
dependencies.addAll(mergeBom.getDependencies());
}
if (bom.getDependencies() != null) {
dependencies.addAll(bom.getDependencies());
}
bom.setDependencies(new ArrayList<>(dependencies));
}
}
return bom;
}

private void saveBom(Bom bom) throws ParserConfigurationException, IOException, GeneratorException,
MojoExecutionException {
if ("all".equalsIgnoreCase(outputFormat) || "xml".equalsIgnoreCase(outputFormat)) {
Expand Down
176 changes: 176 additions & 0 deletions src/main/java/org/cyclonedx/maven/CycloneDxAggregateEnforceMojo.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
/*
* This file is part of CycloneDX Maven Plugin.
*
* Licensed 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
*
* http://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.
*
* SPDX-License-Identifier: Apache-2.0
* Copyright (c) OWASP Foundation. All Rights Reserved.
*/
package org.cyclonedx.maven;

import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.ResolutionScope;
import org.apache.maven.project.MavenProject;
import org.cyclonedx.model.Bom;
import org.cyclonedx.model.Component;
import org.cyclonedx.model.License;
import org.cyclonedx.model.LicenseChoice;
import org.jetbrains.annotations.NotNull;

@Mojo(
name = "enforceAggregateBom",
defaultPhase = LifecyclePhase.PACKAGE,
aggregator = true,
requiresOnline = true,
requiresDependencyCollection = ResolutionScope.TEST,
requiresDependencyResolution = ResolutionScope.TEST
)
public class CycloneDxAggregateEnforceMojo extends CycloneDxAggregateMojo {

@Override
protected boolean shouldExclude(@NotNull MavenProject mavenProject) {
if (super.shouldExclude(mavenProject)) {
return true;
}
if (enforceExcludeArtifactId != null && enforceExcludeArtifactId.length > 0) {
if (Arrays.asList(enforceExcludeArtifactId).contains(mavenProject.getArtifactId())) {
return true;
}
}
return false;
}

@Override
@NotNull
protected Bom postProcessingBom(@NotNull Bom bom) throws MojoExecutionException {
bom = super.postProcessingBom(bom);
doEnforceComponentsSameVersion(bom);
doEnforceLicensesBlackListAndWhiteList(bom);
return bom;
}

private void doEnforceComponentsSameVersion(@NotNull Bom bom) throws MojoExecutionException {
if (this.enforceComponentsSameVersion) {
List<Component> components = bom.getComponents();
if (components != null) {
Map<Pair<String, String>, Set<String>> componentMap =
new HashMap<>((int) Math.ceil(components.size() / 0.75));
for (Component component : components) {
if (component == null) {
continue;
}
String group = component.getGroup();
String name = component.getName();
String version = component.getVersion();
Pair<String, String> key = Pair.of(group, name);
Set<String> versions = componentMap.computeIfAbsent(
key,
stringStringPair -> new HashSet<>()
);
versions.add(version);
}
StringBuilder stringBuilder = new StringBuilder();
for (Map.Entry<Pair<String, String>, Set<String>> entry : componentMap.entrySet()) {
Pair<String, String> key = entry.getKey();
Set<String> versions = entry.getValue();
if (versions.size() > 1) {
stringBuilder
.append("[ERROR]Duplicated versions for ")
.append(key.getLeft())
.append(":")
.append(key.getRight())
.append(" , versions : ")
.append(StringUtils.join(versions.iterator(), ","))
.append("\n");
}
}
if (stringBuilder.length() > 0) {
throw new MojoExecutionException(stringBuilder.toString());
}
}
}
}

private void doEnforceLicensesBlackListAndWhiteList(@NotNull Bom bom) throws MojoExecutionException {
List<Component> components = bom.getComponents();
if (components != null) {
StringBuilder stringBuilder = new StringBuilder();
for (Component component : components) {
if (component == null) {
continue;
}
String group = component.getGroup();
String name = component.getName();
LicenseChoice licenseChoice = component.getLicenseChoice();
if (licenseChoice == null) {
continue;
}
if (StringUtils.isNotBlank(licenseChoice.getExpression())) {
getLog().error("[ERROR]Cannot handle spdx license expression for " + group + ":" + name + " , use license id instead");
}
if (licenseChoice.getLicenses() != null) {
for (License license : licenseChoice.getLicenses()) {
if (!ArrayUtils.isEmpty(this.enforceLicensesBlackList)) {
if (
ArrayUtils.contains(this.enforceLicensesBlackList, license.getId())
|| ArrayUtils.contains(this.enforceLicensesBlackList, license.getName())
) {
stringBuilder
.append("[ERROR]License in blackList for ")
.append(group)
.append(":")
.append(name)
.append(" , license : ")
.append(license.getId())
.append("\n");
}
}
if (!ArrayUtils.isEmpty(this.enforceLicensesWhiteList)) {
if (
!(
ArrayUtils.contains(this.enforceLicensesBlackList, license.getId())
|| ArrayUtils.contains(this.enforceLicensesBlackList, license.getName())
)
) {
stringBuilder
.append("[ERROR]License not in whiteList for ")
.append(group)
.append(":")
.append(name)
.append(" , license : ")
.append(license.getId())
.append("\n");
}
}
}
}
}
if (stringBuilder.length() > 0) {
throw new MojoExecutionException(stringBuilder.toString());
}
}
}

}
5 changes: 3 additions & 2 deletions src/main/java/org/cyclonedx/maven/CycloneDxAggregateMojo.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import org.apache.maven.shared.dependency.analyzer.ProjectDependencyAnalysis;
import org.cyclonedx.model.Component;
import org.cyclonedx.model.Dependency;
import org.jetbrains.annotations.NotNull;

import java.util.ArrayList;
import java.util.Arrays;
Expand Down Expand Up @@ -87,7 +88,7 @@ public class CycloneDxAggregateMojo extends CycloneDxMojo {
@Parameter(property = "excludeTestProject", defaultValue = "false", required = false)
protected Boolean excludeTestProject;

protected boolean shouldExclude(MavenProject mavenProject) {
protected boolean shouldExclude(@NotNull MavenProject mavenProject) {
boolean shouldExclude = false;
if (excludeArtifactId != null && excludeArtifactId.length > 0) {
shouldExclude = Arrays.asList(excludeArtifactId).contains(mavenProject.getArtifactId());
Expand All @@ -98,7 +99,7 @@ protected boolean shouldExclude(MavenProject mavenProject) {
if (excludeTestProject && mavenProject.getArtifactId().contains("test")) {
shouldExclude = true;
}
return shouldExclude;
return excludeTestProject && mavenProject.getArtifactId().contains("test");
}

@Override
Expand Down

0 comments on commit 1a43f98

Please sign in to comment.