Skip to content

Commit ec53236

Browse files
committed
add CycloneDxAggregateEnforceMojo
Signed-off-by: XenoAmess <xenoamess@gmail.com>
1 parent 8b3ae0c commit ec53236

5 files changed

Lines changed: 364 additions & 10 deletions

File tree

pom.xml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,17 @@
171171
<scope>test</scope>
172172
<version>3.0.0</version>
173173
</dependency>
174+
<dependency>
175+
<groupId>org.spdx</groupId>
176+
<artifactId>java-spdx-library</artifactId>
177+
<version>1.0.10</version>
178+
</dependency>
179+
<dependency>
180+
<groupId>org.jetbrains</groupId>
181+
<artifactId>annotations</artifactId>
182+
<scope>provided</scope>
183+
<version>23.0.0</version>
184+
</dependency>
174185
</dependencies>
175186

176187
<prerequisites>

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

Lines changed: 53 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@
9393
import java.util.jar.JarEntry;
9494
import java.util.jar.JarFile;
9595
import org.apache.commons.io.input.BOMInputStream;
96+
import org.jetbrains.annotations.NotNull;
9697

9798
import static org.apache.maven.artifact.Artifact.SCOPE_COMPILE;
9899

@@ -170,6 +171,21 @@ public abstract class BaseCycloneDxMojo extends AbstractMojo implements Contextu
170171
@Parameter(property = "cyclonedx.verbose", defaultValue = "true", required = false)
171172
private boolean verbose = true;
172173

174+
@Parameter(property = "enforceExcludeArtifactId", required = false)
175+
protected String[] enforceExcludeArtifactId;
176+
177+
@Parameter(property = "enforceComponentsSameVersion", defaultValue = "true", required = false)
178+
protected boolean enforceComponentsSameVersion = true;
179+
180+
@Parameter(property = "enforceLicensesBlackList", required = false)
181+
protected String[] enforceLicensesBlackList;
182+
183+
@Parameter(property = "enforceLicensesWhiteList", required = false)
184+
protected String[] enforceLicensesWhiteList;
185+
186+
@Parameter(property = "mergeBomFile", required = false)
187+
protected File mergeBomFile;
188+
173189
/**
174190
* Various messages sent to console.
175191
*/
@@ -764,7 +780,7 @@ private MavenProject readPom(InputStream in) {
764780
protected void execute(Set<Component> components, Set<Dependency> dependencies, MavenProject mavenProject) throws MojoExecutionException {
765781
try {
766782
getLog().info(MESSAGE_CREATING_BOM);
767-
final Bom bom = new Bom();
783+
Bom bom = new Bom();
768784
if (schemaVersion().getVersion() >= 1.1 && includeBomSerialNumber) {
769785
bom.setSerialNumber("urn:uuid:" + UUID.randomUUID());
770786
}
@@ -794,13 +810,48 @@ protected void execute(Set<Component> components, Set<Dependency> dependencies,
794810
return;
795811
}
796812

813+
bom = postProcessingBom(bom);
814+
797815
createBom(bom, mavenProject);
798816

799-
} catch (GeneratorException | ParserConfigurationException | IOException e) {
817+
} catch (Exception e) {
800818
throw new MojoExecutionException("An error occurred executing " + this.getClass().getName() + ": " + e.getMessage(), e);
801819
}
802820
}
803821

822+
@NotNull
823+
protected Bom postProcessingBom(@NotNull Bom bom) throws Exception {
824+
if (mergeBomFile != null) {
825+
Bom mergeBom;
826+
try {
827+
mergeBom = new JsonParser().parse(mergeBomFile);
828+
} catch (Exception e) {
829+
mergeBom = new XmlParser().parse(mergeBomFile);
830+
}
831+
{
832+
LinkedHashSet<Component> components = new LinkedHashSet<>();
833+
if (mergeBom.getComponents() != null) {
834+
components.addAll(mergeBom.getComponents());
835+
}
836+
if (bom.getComponents() != null) {
837+
components.addAll(bom.getComponents());
838+
}
839+
bom.setComponents(new ArrayList<>(components));
840+
}
841+
{
842+
LinkedHashSet<Dependency> dependencies = new LinkedHashSet<>();
843+
if (mergeBom.getDependencies() != null) {
844+
dependencies.addAll(mergeBom.getDependencies());
845+
}
846+
if (bom.getDependencies() != null) {
847+
dependencies.addAll(bom.getDependencies());
848+
}
849+
bom.setDependencies(new ArrayList<>(dependencies));
850+
}
851+
}
852+
return bom;
853+
}
854+
804855
private void createBom(Bom bom, MavenProject mavenProject) throws ParserConfigurationException, IOException, GeneratorException,
805856
MojoExecutionException {
806857
if (outputFormat.trim().equalsIgnoreCase("all") || outputFormat.trim().equalsIgnoreCase("xml")) {
Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
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.MojoFailureException;
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.cyclonedx.maven.utils.SpdxLicenseUtil;
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+
import org.spdx.library.InvalidSPDXAnalysisException;
42+
import org.spdx.library.model.license.AnyLicenseInfo;
43+
import org.spdx.library.model.license.LicenseInfoFactory;
44+
45+
@Mojo(
46+
name = "enforceAggregateBom",
47+
defaultPhase = LifecyclePhase.PACKAGE,
48+
aggregator = true,
49+
requiresOnline = true,
50+
requiresDependencyCollection = ResolutionScope.TEST,
51+
requiresDependencyResolution = ResolutionScope.TEST
52+
)
53+
public class CycloneDxAggregateEnforceMojo extends CycloneDxAggregateMojo {
54+
55+
@Override
56+
protected boolean shouldExclude(@NotNull String mavenProjectArtifactId) {
57+
if (super.shouldExclude(mavenProjectArtifactId)) {
58+
return true;
59+
}
60+
if (enforceExcludeArtifactId != null && enforceExcludeArtifactId.length > 0) {
61+
if (Arrays.asList(enforceExcludeArtifactId).contains(mavenProjectArtifactId)) {
62+
return true;
63+
}
64+
}
65+
return false;
66+
}
67+
68+
@Override
69+
@NotNull
70+
protected Bom postProcessingBom(@NotNull Bom bom) throws Exception {
71+
bom = super.postProcessingBom(bom);
72+
doEnforceComponentsSameVersion(bom);
73+
doEnforceLicensesBlackListAndWhiteList(bom);
74+
return bom;
75+
}
76+
77+
private void doEnforceComponentsSameVersion(@NotNull Bom bom) throws MojoFailureException {
78+
if (this.enforceComponentsSameVersion) {
79+
List<Component> components = bom.getComponents();
80+
if (components != null) {
81+
Map<Pair<String, String>, Set<String>> componentMap =
82+
new HashMap<>((int) Math.ceil(components.size() / 0.75));
83+
for (Component component : components) {
84+
if (component == null) {
85+
continue;
86+
}
87+
String group = component.getGroup();
88+
String name = component.getName();
89+
String version = component.getVersion();
90+
Pair<String, String> key = Pair.of(group, name);
91+
Set<String> versions = componentMap.computeIfAbsent(
92+
key,
93+
stringStringPair -> new HashSet<>()
94+
);
95+
versions.add(version);
96+
}
97+
StringBuilder stringBuilder = new StringBuilder();
98+
for (Map.Entry<Pair<String, String>, Set<String>> entry : componentMap.entrySet()) {
99+
Pair<String, String> key = entry.getKey();
100+
Set<String> versions = entry.getValue();
101+
if (versions.size() > 1) {
102+
stringBuilder
103+
.append("[ERROR]Duplicated versions for ")
104+
.append(key.getLeft())
105+
.append(":")
106+
.append(key.getRight())
107+
.append(" , versions : ")
108+
.append(StringUtils.join(versions.iterator(), ","))
109+
.append("\n");
110+
}
111+
}
112+
if (stringBuilder.length() > 0) {
113+
throw new MojoFailureException(stringBuilder.toString());
114+
}
115+
}
116+
}
117+
}
118+
119+
private void doEnforceLicensesBlackListAndWhiteList(@NotNull Bom bom) throws MojoFailureException {
120+
List<Component> components = bom.getComponents();
121+
if (components != null) {
122+
StringBuilder stringBuilder = new StringBuilder();
123+
for (Component component : components) {
124+
if (component == null) {
125+
continue;
126+
}
127+
String group = component.getGroup();
128+
String name = component.getName();
129+
LicenseChoice licenseChoice = component.getLicenseChoice();
130+
if (licenseChoice == null) {
131+
continue;
132+
}
133+
if (StringUtils.isNotBlank(licenseChoice.getExpression())) {
134+
try {
135+
AnyLicenseInfo anyLicenseInfo = LicenseInfoFactory.parseSPDXLicenseString(licenseChoice.getExpression());
136+
if (!ArrayUtils.isEmpty(this.enforceLicensesBlackList)) {
137+
if (!SpdxLicenseUtil.isLicensePassBlackList(anyLicenseInfo, this.enforceLicensesBlackList)) {
138+
stringBuilder
139+
.append("[ERROR]License in blackList for ")
140+
.append(group)
141+
.append(":")
142+
.append(name)
143+
.append(" , license : ")
144+
.append(licenseChoice.getExpression())
145+
.append("\n");
146+
}
147+
}
148+
if (!ArrayUtils.isEmpty(this.enforceLicensesWhiteList)) {
149+
if (!SpdxLicenseUtil.isLicensePassWhiteList(anyLicenseInfo, this.enforceLicensesWhiteList)) {
150+
stringBuilder
151+
.append("[ERROR]License not in whiteList for ")
152+
.append(group)
153+
.append(":")
154+
.append(name)
155+
.append(" , license : ")
156+
.append(licenseChoice.getExpression())
157+
.append("\n");
158+
}
159+
}
160+
} catch (InvalidSPDXAnalysisException e) {
161+
getLog().warn(e);
162+
}
163+
} else if (licenseChoice.getLicenses() != null) {
164+
for (License license : licenseChoice.getLicenses()) {
165+
if (!ArrayUtils.isEmpty(this.enforceLicensesBlackList)) {
166+
if (
167+
ArrayUtils.contains(this.enforceLicensesBlackList, license.getId())
168+
|| ArrayUtils.contains(this.enforceLicensesBlackList, license.getName())
169+
) {
170+
stringBuilder
171+
.append("[ERROR]License in blackList for ")
172+
.append(group)
173+
.append(":")
174+
.append(name)
175+
.append(" , license : ")
176+
.append(license.getId())
177+
.append("\n");
178+
}
179+
}
180+
if (!ArrayUtils.isEmpty(this.enforceLicensesWhiteList)) {
181+
if (
182+
!(
183+
ArrayUtils.contains(this.enforceLicensesBlackList, license.getId())
184+
|| ArrayUtils.contains(this.enforceLicensesBlackList, license.getName())
185+
)
186+
) {
187+
stringBuilder
188+
.append("[ERROR]License not in whiteList for ")
189+
.append(group)
190+
.append(":")
191+
.append(name)
192+
.append(" , license : ")
193+
.append(license.getId())
194+
.append("\n");
195+
}
196+
}
197+
}
198+
}
199+
}
200+
if (stringBuilder.length() > 0) {
201+
throw new MojoFailureException(stringBuilder.toString());
202+
}
203+
}
204+
}
205+
206+
}

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

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,14 @@
2020

2121
import org.apache.maven.artifact.Artifact;
2222
import org.apache.maven.plugin.MojoExecutionException;
23-
import org.apache.maven.plugins.annotations.Execute;
2423
import org.apache.maven.plugins.annotations.LifecyclePhase;
2524
import org.apache.maven.plugins.annotations.Mojo;
2625
import org.apache.maven.plugins.annotations.ResolutionScope;
2726
import org.apache.maven.project.MavenProject;
2827
import org.apache.maven.shared.dependency.analyzer.ProjectDependencyAnalysis;
2928
import org.cyclonedx.model.Component;
3029
import org.cyclonedx.model.Dependency;
30+
import org.jetbrains.annotations.NotNull;
3131

3232
import java.util.Arrays;
3333
import java.util.LinkedHashMap;
@@ -45,17 +45,20 @@
4545
)
4646
public class CycloneDxAggregateMojo extends BaseCycloneDxMojo {
4747

48-
protected boolean shouldExclude(MavenProject mavenProject) {
49-
boolean shouldExclude = false;
48+
protected boolean shouldExclude(@NotNull MavenProject mavenProject) {
49+
return this.shouldExclude(mavenProject.getArtifactId());
50+
}
51+
52+
protected boolean shouldExclude(@NotNull String mavenProjectArtifactId) {
5053
if (excludeArtifactId != null && excludeArtifactId.length > 0) {
51-
shouldExclude = Arrays.asList(excludeArtifactId).contains(mavenProject.getArtifactId());
52-
}
53-
if (excludeTestProject && mavenProject.getArtifactId().contains("test")) {
54-
shouldExclude = true;
54+
if (Arrays.asList(excludeArtifactId).contains(mavenProjectArtifactId)) {
55+
return true;
56+
}
5557
}
56-
return shouldExclude;
58+
return excludeTestProject && mavenProjectArtifactId.contains("test");
5759
}
5860

61+
@Override
5962
public void execute() throws MojoExecutionException {
6063
final boolean shouldSkip = Boolean.parseBoolean(System.getProperty("cyclonedx.skip", Boolean.toString(getSkip())));
6164
if (shouldSkip) {

0 commit comments

Comments
 (0)