Skip to content

Commit 1a43f98

Browse files
committed
add CycloneDxAggregateEnforceMojo
Signed-off-by: XenoAmess <[email protected]>
1 parent a2754bf commit 1a43f98

File tree

4 files changed

+244
-4
lines changed

4 files changed

+244
-4
lines changed

pom.xml

+6
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,12 @@
203203
<artifactId>junit-vintage-engine</artifactId>
204204
<scope>test</scope>
205205
</dependency>
206+
<dependency>
207+
<groupId>org.jetbrains</groupId>
208+
<artifactId>annotations</artifactId>
209+
<scope>provided</scope>
210+
<version>23.0.0</version>
211+
</dependency>
206212
</dependencies>
207213

208214
<dependencyManagement>

src/main/java/org/cyclonedx/maven/BaseCycloneDxMojo.java

+59-2
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,10 @@
4949
import java.util.List;
5050
import java.util.Set;
5151
import java.util.UUID;
52+
import java.util.jar.JarEntry;
53+
import java.util.jar.JarFile;
54+
import org.apache.commons.io.input.BOMInputStream;
55+
import org.jetbrains.annotations.NotNull;
5256

5357
public abstract class BaseCycloneDxMojo extends AbstractMojo {
5458

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

206+
@Parameter(property = "enforceExcludeArtifactId", required = false)
207+
protected String[] enforceExcludeArtifactId;
208+
209+
@Parameter(property = "enforceComponentsSameVersion", defaultValue = "true", required = false)
210+
protected boolean enforceComponentsSameVersion = true;
211+
212+
@Parameter(property = "enforceLicensesBlackList", required = false)
213+
protected String[] enforceLicensesBlackList;
214+
215+
@Parameter(property = "enforceLicensesWhiteList", required = false)
216+
protected String[] enforceLicensesWhiteList;
217+
218+
@Parameter(property = "mergeBomFile", required = false)
219+
protected File mergeBomFile;
220+
202221
/**
203222
* Various messages sent to console.
204223
*/
@@ -267,7 +286,7 @@ public void execute() throws MojoExecutionException {
267286
private void generateBom(String analysis, Metadata metadata, Set<Component> components, Set<Dependency> dependencies) throws MojoExecutionException {
268287
try {
269288
getLog().info(String.format(MESSAGE_CREATING_BOM, schemaVersion, components.size()));
270-
final Bom bom = new Bom();
289+
Bom bom = new Bom();
271290
bom.setComponents(new ArrayList<>(components));
272291

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

323+
@NotNull
324+
protected Bom postProcessingBom(@NotNull Bom bom) throws MojoExecutionException {
325+
if (mergeBomFile != null) {
326+
Bom mergeBom;
327+
try {
328+
try {
329+
mergeBom = new JsonParser().parse(mergeBomFile);
330+
} catch (Exception e) {
331+
mergeBom = new XmlParser().parse(mergeBomFile);
332+
}
333+
} catch (Exception e) {
334+
throw new MojoExecutionException("parse failed", e);
335+
}
336+
{
337+
LinkedHashSet<Component> components = new LinkedHashSet<>();
338+
if (mergeBom.getComponents() != null) {
339+
components.addAll(mergeBom.getComponents());
340+
}
341+
if (bom.getComponents() != null) {
342+
components.addAll(bom.getComponents());
343+
}
344+
bom.setComponents(new ArrayList<>(components));
345+
}
346+
{
347+
LinkedHashSet<Dependency> dependencies = new LinkedHashSet<>();
348+
if (mergeBom.getDependencies() != null) {
349+
dependencies.addAll(mergeBom.getDependencies());
350+
}
351+
if (bom.getDependencies() != null) {
352+
dependencies.addAll(bom.getDependencies());
353+
}
354+
bom.setDependencies(new ArrayList<>(dependencies));
355+
}
356+
}
357+
return bom;
358+
}
359+
303360
private void saveBom(Bom bom) throws ParserConfigurationException, IOException, GeneratorException,
304361
MojoExecutionException {
305362
if ("all".equalsIgnoreCase(outputFormat) || "xml".equalsIgnoreCase(outputFormat)) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
/*
2+
* This file is part of CycloneDX Maven Plugin.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*
16+
* SPDX-License-Identifier: Apache-2.0
17+
* Copyright (c) OWASP Foundation. All Rights Reserved.
18+
*/
19+
package org.cyclonedx.maven;
20+
21+
import java.util.Arrays;
22+
import java.util.HashMap;
23+
import java.util.HashSet;
24+
import java.util.List;
25+
import java.util.Map;
26+
import java.util.Set;
27+
28+
import org.apache.commons.lang3.ArrayUtils;
29+
import org.apache.commons.lang3.StringUtils;
30+
import org.apache.commons.lang3.tuple.Pair;
31+
import org.apache.maven.plugin.MojoExecutionException;
32+
import org.apache.maven.plugins.annotations.LifecyclePhase;
33+
import org.apache.maven.plugins.annotations.Mojo;
34+
import org.apache.maven.plugins.annotations.ResolutionScope;
35+
import org.apache.maven.project.MavenProject;
36+
import org.cyclonedx.model.Bom;
37+
import org.cyclonedx.model.Component;
38+
import org.cyclonedx.model.License;
39+
import org.cyclonedx.model.LicenseChoice;
40+
import org.jetbrains.annotations.NotNull;
41+
42+
@Mojo(
43+
name = "enforceAggregateBom",
44+
defaultPhase = LifecyclePhase.PACKAGE,
45+
aggregator = true,
46+
requiresOnline = true,
47+
requiresDependencyCollection = ResolutionScope.TEST,
48+
requiresDependencyResolution = ResolutionScope.TEST
49+
)
50+
public class CycloneDxAggregateEnforceMojo extends CycloneDxAggregateMojo {
51+
52+
@Override
53+
protected boolean shouldExclude(@NotNull MavenProject mavenProject) {
54+
if (super.shouldExclude(mavenProject)) {
55+
return true;
56+
}
57+
if (enforceExcludeArtifactId != null && enforceExcludeArtifactId.length > 0) {
58+
if (Arrays.asList(enforceExcludeArtifactId).contains(mavenProject.getArtifactId())) {
59+
return true;
60+
}
61+
}
62+
return false;
63+
}
64+
65+
@Override
66+
@NotNull
67+
protected Bom postProcessingBom(@NotNull Bom bom) throws MojoExecutionException {
68+
bom = super.postProcessingBom(bom);
69+
doEnforceComponentsSameVersion(bom);
70+
doEnforceLicensesBlackListAndWhiteList(bom);
71+
return bom;
72+
}
73+
74+
private void doEnforceComponentsSameVersion(@NotNull Bom bom) throws MojoExecutionException {
75+
if (this.enforceComponentsSameVersion) {
76+
List<Component> components = bom.getComponents();
77+
if (components != null) {
78+
Map<Pair<String, String>, Set<String>> componentMap =
79+
new HashMap<>((int) Math.ceil(components.size() / 0.75));
80+
for (Component component : components) {
81+
if (component == null) {
82+
continue;
83+
}
84+
String group = component.getGroup();
85+
String name = component.getName();
86+
String version = component.getVersion();
87+
Pair<String, String> key = Pair.of(group, name);
88+
Set<String> versions = componentMap.computeIfAbsent(
89+
key,
90+
stringStringPair -> new HashSet<>()
91+
);
92+
versions.add(version);
93+
}
94+
StringBuilder stringBuilder = new StringBuilder();
95+
for (Map.Entry<Pair<String, String>, Set<String>> entry : componentMap.entrySet()) {
96+
Pair<String, String> key = entry.getKey();
97+
Set<String> versions = entry.getValue();
98+
if (versions.size() > 1) {
99+
stringBuilder
100+
.append("[ERROR]Duplicated versions for ")
101+
.append(key.getLeft())
102+
.append(":")
103+
.append(key.getRight())
104+
.append(" , versions : ")
105+
.append(StringUtils.join(versions.iterator(), ","))
106+
.append("\n");
107+
}
108+
}
109+
if (stringBuilder.length() > 0) {
110+
throw new MojoExecutionException(stringBuilder.toString());
111+
}
112+
}
113+
}
114+
}
115+
116+
private void doEnforceLicensesBlackListAndWhiteList(@NotNull Bom bom) throws MojoExecutionException {
117+
List<Component> components = bom.getComponents();
118+
if (components != null) {
119+
StringBuilder stringBuilder = new StringBuilder();
120+
for (Component component : components) {
121+
if (component == null) {
122+
continue;
123+
}
124+
String group = component.getGroup();
125+
String name = component.getName();
126+
LicenseChoice licenseChoice = component.getLicenseChoice();
127+
if (licenseChoice == null) {
128+
continue;
129+
}
130+
if (StringUtils.isNotBlank(licenseChoice.getExpression())) {
131+
getLog().error("[ERROR]Cannot handle spdx license expression for " + group + ":" + name + " , use license id instead");
132+
}
133+
if (licenseChoice.getLicenses() != null) {
134+
for (License license : licenseChoice.getLicenses()) {
135+
if (!ArrayUtils.isEmpty(this.enforceLicensesBlackList)) {
136+
if (
137+
ArrayUtils.contains(this.enforceLicensesBlackList, license.getId())
138+
|| ArrayUtils.contains(this.enforceLicensesBlackList, license.getName())
139+
) {
140+
stringBuilder
141+
.append("[ERROR]License in blackList for ")
142+
.append(group)
143+
.append(":")
144+
.append(name)
145+
.append(" , license : ")
146+
.append(license.getId())
147+
.append("\n");
148+
}
149+
}
150+
if (!ArrayUtils.isEmpty(this.enforceLicensesWhiteList)) {
151+
if (
152+
!(
153+
ArrayUtils.contains(this.enforceLicensesBlackList, license.getId())
154+
|| ArrayUtils.contains(this.enforceLicensesBlackList, license.getName())
155+
)
156+
) {
157+
stringBuilder
158+
.append("[ERROR]License not in whiteList for ")
159+
.append(group)
160+
.append(":")
161+
.append(name)
162+
.append(" , license : ")
163+
.append(license.getId())
164+
.append("\n");
165+
}
166+
}
167+
}
168+
}
169+
}
170+
if (stringBuilder.length() > 0) {
171+
throw new MojoExecutionException(stringBuilder.toString());
172+
}
173+
}
174+
}
175+
176+
}

src/main/java/org/cyclonedx/maven/CycloneDxAggregateMojo.java

+3-2
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import org.apache.maven.shared.dependency.analyzer.ProjectDependencyAnalysis;
2929
import org.cyclonedx.model.Component;
3030
import org.cyclonedx.model.Dependency;
31+
import org.jetbrains.annotations.NotNull;
3132

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

90-
protected boolean shouldExclude(MavenProject mavenProject) {
91+
protected boolean shouldExclude(@NotNull MavenProject mavenProject) {
9192
boolean shouldExclude = false;
9293
if (excludeArtifactId != null && excludeArtifactId.length > 0) {
9394
shouldExclude = Arrays.asList(excludeArtifactId).contains(mavenProject.getArtifactId());
@@ -98,7 +99,7 @@ protected boolean shouldExclude(MavenProject mavenProject) {
9899
if (excludeTestProject && mavenProject.getArtifactId().contains("test")) {
99100
shouldExclude = true;
100101
}
101-
return shouldExclude;
102+
return excludeTestProject && mavenProject.getArtifactId().contains("test");
102103
}
103104

104105
@Override

0 commit comments

Comments
 (0)