Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add CycloneDxAggregateEnforceMojo #183

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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