From 60d0a75cc31958e5d83ecc11f5409ad3d8a0fce9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Kornefalk?= Date: Wed, 20 Nov 2024 15:09:28 +0100 Subject: [PATCH 1/5] Add manufacturer information MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Björn Kornefalk --- .../cyclonedx/maven/BaseCycloneDxMojo.java | 91 +++++++++ src/site/markdown/manufacturer.md | 111 ++++++++++ src/site/site.xml | 1 + .../maven/BaseCycloneDxMojoTest.java | 191 ++++++++++++++++++ 4 files changed, 394 insertions(+) create mode 100644 src/site/markdown/manufacturer.md create mode 100644 src/test/java/org/cyclonedx/maven/BaseCycloneDxMojoTest.java diff --git a/src/main/java/org/cyclonedx/maven/BaseCycloneDxMojo.java b/src/main/java/org/cyclonedx/maven/BaseCycloneDxMojo.java index fe6f21f2..0c8d1545 100644 --- a/src/main/java/org/cyclonedx/maven/BaseCycloneDxMojo.java +++ b/src/main/java/org/cyclonedx/maven/BaseCycloneDxMojo.java @@ -42,7 +42,10 @@ import org.cyclonedx.model.LifecycleChoice; import org.cyclonedx.model.Lifecycles; import org.cyclonedx.model.Metadata; +import org.cyclonedx.model.OrganizationalContact; +import org.cyclonedx.model.OrganizationalEntity; import org.cyclonedx.model.Property; +import org.cyclonedx.model.organization.PostalAddress; import org.cyclonedx.parsers.JsonParser; import org.cyclonedx.parsers.Parser; import org.cyclonedx.parsers.XmlParser; @@ -57,6 +60,7 @@ import java.util.LinkedHashSet; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; import java.util.UUID; @@ -247,6 +251,13 @@ public abstract class BaseCycloneDxMojo extends AbstractMojo { @Parameter private ExternalReference[] externalReferences; + /** + * Manufacturer information for automatic creator information + * @since 2.9.1 + */ + @Parameter(property = "cyclonedx.manufacturer", required = false) + private OrganizationalEntity manufacturer = null; + @org.apache.maven.plugins.annotations.Component private MavenProjectHelper mavenProjectHelper; @@ -351,6 +362,10 @@ public void execute() throws MojoExecutionException { if (detectUnusedForOptionalScope) { metadata.addProperty(newProperty("maven.optional.unused", Boolean.toString(detectUnusedForOptionalScope))); } + + if (hasManufacturerInformation()) { + metadata.setManufacturer(manufacturer); + } } final Component rootComponent = metadata.getComponent(); @@ -362,6 +377,82 @@ public void execute() throws MojoExecutionException { } } + /** + * Check the mojo configuration for the optional manufacturer contents. + * + * @return {@code true} if there is any manufacturer information configured. + */ + boolean hasManufacturerInformation() { + if (manufacturer == null) { + return false; + } + + return isNotNullOrEmpty(manufacturer.getAddress()) || + isNotNullOrEmpty(manufacturer.getName()) || + isNotNullOrEmptyContacts(manufacturer.getContacts()) || + isNotNullOrEmptyString(manufacturer.getUrls()); + } + + /** + * @param text Some text + * @return {@code true} if there is any text + */ + boolean isNotNullOrEmpty(String text) { + return text != null && !text.trim().isEmpty(); + } + + /** + * @param list A list of text + * @return {@code true} if there is any element has a text value + */ + boolean isNotNullOrEmptyString(List list) { + if (list != null && !list.isEmpty()) { + return list.stream().filter(Objects::nonNull).anyMatch(this::isNotNullOrEmpty); + } + return false; + } + + /** + * @param list A list of contacts + * @return {@code true} if there is any contact has something configured + */ + boolean isNotNullOrEmptyContacts(List list) { + if (list != null && !list.isEmpty()) { + return list.stream().filter(Objects::nonNull).anyMatch(this::isNotNullOrEmpty); + + } + return false; + } + + /** + * @param address A postal address entry + * @return {@code true} if there is any postal address information exists + */ + boolean isNotNullOrEmpty(PostalAddress address) { + if (address == null) { + return false; + } + return isNotNullOrEmpty(address.getStreetAddress()) || + isNotNullOrEmpty(address.getCountry()) || + isNotNullOrEmpty(address.getPostalCode()) || + isNotNullOrEmpty(address.getLocality()) || + isNotNullOrEmpty(address.getPostOfficeBoxNumber()) || + isNotNullOrEmpty(address.getRegion()); + } + + /** + * @param contact A contact entry + * @return {@code true} if there is any contact information exists + */ + boolean isNotNullOrEmpty(OrganizationalContact contact) { + if (null == contact) { + return false; + } + return isNotNullOrEmpty(contact.getName()) || + isNotNullOrEmpty(contact.getEmail()) || + isNotNullOrEmpty(contact.getPhone()); + } + private Property newProperty(String name, String value) { Property property = new Property(); property.setName(name); diff --git a/src/site/markdown/manufacturer.md b/src/site/markdown/manufacturer.md new file mode 100644 index 00000000..4a4ad6d0 --- /dev/null +++ b/src/site/markdown/manufacturer.md @@ -0,0 +1,111 @@ +# Manufacturer +Manufacturer is common in BOMs created through automated processes. + +When creating a number of BOMs for several projects within one organization +or company, it is convenient to attach this information at one place. + +This will also conform to upcoming EU regulation that all SBOM files shall + +>At minimum, the product with digital elements shall be accompanied by: +>1. the name, registered trade name or registered trademark of the manufacturer, and the + > postal address, the email address or other digital contact as well as, where + > available, the website at which the manufacturer can be contacted; +>2. the single point of contact where information about vulnerabilities of the product + > with digital elements can be reported and received, and where the manufacturer’s + > policy on coordinated vulnerability disclosure can be found +>3. name and type and any additional information enabling the unique identification + > of the product with digital elements +>4. the intended purpose of the product with digital elements, including the security + > environment provided by the manufacturer, as well as the product’s essential + > functionalities and information about the security properties + +## Configuration +The configuration is optional. If none is specified, the manufacturer information is not visible. +See https://cyclonedx.org/docs/latest/json/#metadata_manufacturer for more information. + +### name +The name of the organization, + +### address +The physical address (location) of the organization. +##### country +The country name or the two-letter ISO 3166-1 country code. +##### region +The region or state in the country. +##### locality +The locality or city within the country. +##### postOfficeBoxNumber +The post office box number. +##### postalCode +The postal code. +##### streetAddress +The street address. + +### url +The URL of the organization. Multiple URLs are allowed. + +# contact +A contact at the organization. Multiple contacts are allowed. + +##### name +The name of a contact +##### email +The email address of the contact. +##### phone +The phone number of the contact. + +## Example of configuration + +```xml + + + + + org.cyclonedx + cyclonedx-maven-plugin + ${cyclonedx-maven-plugin.version} + + + Example Company + https://www.example.com/contact + + + Steve Springett + Steve.Springett@owasp.org + + + Another contact + 1-800-555-1111 + + + + + + + + +``` + +This configuration will add the following to the BOM file (JSON format): +```json + "manufacturer" : { + "name" : "Example Company", + "url" : [ + "https://www.example.com/contact" + ], + "contact" : [ + { + "name" : "Steve Springett", + "email" : "Steve.Springett@owasp.org" + }, + { + "name" : "Another contact", + "phone" : "1-800-555-1111" + } + ] + } +``` + +## Links +- [EU regulation proposal about SBOM generation.](https://www.europarl.europa.eu/doceo/document/TA-9-2024-0130_EN.pdf) + diff --git a/src/site/site.xml b/src/site/site.xml index c094cc27..3e8abdca 100644 --- a/src/site/site.xml +++ b/src/site/site.xml @@ -26,6 +26,7 @@ + diff --git a/src/test/java/org/cyclonedx/maven/BaseCycloneDxMojoTest.java b/src/test/java/org/cyclonedx/maven/BaseCycloneDxMojoTest.java new file mode 100644 index 00000000..a52f1daa --- /dev/null +++ b/src/test/java/org/cyclonedx/maven/BaseCycloneDxMojoTest.java @@ -0,0 +1,191 @@ +package org.cyclonedx.maven; + +import static org.junit.jupiter.api.Assertions.*; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import org.apache.maven.plugin.MojoExecutionException; +import org.cyclonedx.model.Component; +import org.cyclonedx.model.Dependency; +import org.cyclonedx.model.OrganizationalContact; +import org.cyclonedx.model.OrganizationalEntity; +import org.cyclonedx.model.organization.PostalAddress; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class BaseCycloneDxMojoTest { + + /** + * Minimal test class that can create an instance of BaseCycloneDxMojo + * so we can do some unit testing. + */ + private static class BaseCycloneDxMojoImpl extends BaseCycloneDxMojo { + + @Override + protected String extractComponentsAndDependencies(Set topLevelComponents, + Map components, Map dependencies) throws MojoExecutionException { + return ""; + } + } + + @Test + @DisplayName("Verify that the default configuration does not have manufacturer information ") + void hasNoManufacturerInformation() { + BaseCycloneDxMojoImpl mojo = new BaseCycloneDxMojoImpl(); + assertFalse(mojo.hasManufacturerInformation()); + } + + @Test + @DisplayName("Verify that the function hasManufacturerInformation works as expected") + void hasManufacturerInformation() throws NoSuchFieldException, IllegalAccessException { + BaseCycloneDxMojoImpl mojo = new BaseCycloneDxMojoImpl(); + OrganizationalEntity manufacturer = new OrganizationalEntity(); + manufacturer.setName("Manufacturer"); + setParentParameter(mojo, "manufacturer", manufacturer); + assertTrue(mojo.hasManufacturerInformation()); + + manufacturer = new OrganizationalEntity(); + setParentParameter(mojo, "manufacturer", manufacturer); + PostalAddress address = new PostalAddress(); + address.setCountry("UK"); + manufacturer.setAddress(address); + assertTrue(mojo.hasManufacturerInformation()); + + manufacturer = new OrganizationalEntity(); + setParentParameter(mojo, "manufacturer", manufacturer); + OrganizationalContact contact = new OrganizationalContact(); + contact.setName("Contact"); + List contacts = new ArrayList<>(); + contacts.add(contact); + manufacturer.setContacts(contacts); + assertTrue(mojo.hasManufacturerInformation()); + + manufacturer = new OrganizationalEntity(); + setParentParameter(mojo, "manufacturer", manufacturer); + List urls = new ArrayList<>(); + urls.add("https://www.owasp.org"); + manufacturer.setUrls(urls); + assertTrue(mojo.hasManufacturerInformation()); + + } + + + + @Test + @DisplayName("Verify that check of String content works") + void isNotNullOrEmpty() { + BaseCycloneDxMojoImpl mojo = new BaseCycloneDxMojoImpl(); + String value = null; + assertFalse(mojo.isNotNullOrEmpty(value)); + value = ""; + assertFalse(mojo.isNotNullOrEmpty(value)); + value = "null"; + assertTrue(mojo.isNotNullOrEmpty(value)); + } + + @Test + @DisplayName("Verify that a list of strings works as expected") + void isNotNullOrEmptyString() { + List list = null; + BaseCycloneDxMojoImpl mojo = new BaseCycloneDxMojoImpl(); + assertFalse(mojo.isNotNullOrEmptyString(list)); + list = new ArrayList<>(); + assertFalse(mojo.isNotNullOrEmptyString(list)); + String value = null; + list.add(value); + assertFalse(mojo.isNotNullOrEmptyString(list)); + list.add(""); + assertFalse(mojo.isNotNullOrEmptyString(list)); + list.add("null"); + assertTrue(mojo.isNotNullOrEmptyString(list)); + } + + @Test + @DisplayName("Verify that a list of contacts works as expected") + void isNotNullOrEmptyContacts() { + BaseCycloneDxMojoImpl mojo = new BaseCycloneDxMojoImpl(); + List list = null; + assertFalse(mojo.isNotNullOrEmptyContacts(list)); + list = new ArrayList<>(); + assertFalse(mojo.isNotNullOrEmptyContacts(list)); + OrganizationalContact contact = new OrganizationalContact(); + contact.setName("Contact"); + list.add(contact); + assertTrue(mojo.isNotNullOrEmptyContacts(list)); + } + + @Test + @DisplayName("Verify that check of address works as expected") + void testIsNotNullOrEmpty() { + BaseCycloneDxMojoImpl mojo = new BaseCycloneDxMojoImpl(); + PostalAddress address = new PostalAddress(); + assertFalse(mojo.isNotNullOrEmpty(address)); + address.setRegion("AL"); + assertTrue(mojo.isNotNullOrEmpty(address)); + + address = new PostalAddress(); + address.setPostOfficeBoxNumber("12345"); + assertTrue(mojo.isNotNullOrEmpty(address)); + + address = new PostalAddress(); + address.setLocality("my locality"); + assertTrue(mojo.isNotNullOrEmpty(address)); + + address = new PostalAddress(); + address.setPostalCode("12345"); + assertTrue(mojo.isNotNullOrEmpty(address)); + + address = new PostalAddress(); + address.setCountry("US"); + assertTrue(mojo.isNotNullOrEmpty(address)); + + address = new PostalAddress(); + address.setStreetAddress("Main street"); + assertTrue(mojo.isNotNullOrEmpty(address)); + } + + @Test + @DisplayName("Verify that test of contact works as expected") + void testIsNotNullOrEmpty1() { + BaseCycloneDxMojoImpl mojo = new BaseCycloneDxMojoImpl(); + OrganizationalContact contact = null; + assertFalse(mojo.isNotNullOrEmpty(contact)); + contact = new OrganizationalContact(); + assertFalse(mojo.isNotNullOrEmpty(contact)); + contact.setPhone("1-555-888-1234"); + assertTrue(mojo.isNotNullOrEmpty(contact)); + + contact = new OrganizationalContact(); + contact.setEmail("info@example.com"); + assertTrue(mojo.isNotNullOrEmpty(contact)); + + contact = new OrganizationalContact(); + contact.setName("Contact"); + assertTrue(mojo.isNotNullOrEmpty(contact)); + } + + /** + * Inject a parameter value to a superclass (even private parameters). + *

example:

+ *
{@code class B extends A;} + *
{@code class A { private Type a; } } + *
+ *
{@code setParentParameter(new B(), "a", new Type()); } + * + * @param cc The class instance + * @param fieldName The field name + * @param value The value + * @throws NoSuchFieldException If the field does not exist + * @throws IllegalAccessException If the value is not able to be modified + */ + public static void setParentParameter(Object cc, String fieldName, Object value) + throws NoSuchFieldException, IllegalAccessException { + Field field = cc.getClass().getSuperclass().getDeclaredField(fieldName); + field.setAccessible(true); + field.set(cc, value); + } + +} \ No newline at end of file From c9ecde58f5d2b96ade8035353ddd9effae18479d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Kornefalk?= Date: Wed, 20 Nov 2024 17:50:47 +0100 Subject: [PATCH 2/5] Rework after review to use Maven configuraitons instead MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Björn Kornefalk --- .../cyclonedx/maven/BaseCycloneDxMojo.java | 131 +++++++-------- src/site/markdown/manufacturer.md | 111 ------------ src/site/site.xml | 1 - .../maven/BaseCycloneDxMojoTest.java | 159 ++++++------------ 4 files changed, 114 insertions(+), 288 deletions(-) delete mode 100644 src/site/markdown/manufacturer.md diff --git a/src/main/java/org/cyclonedx/maven/BaseCycloneDxMojo.java b/src/main/java/org/cyclonedx/maven/BaseCycloneDxMojo.java index 0c8d1545..2fd18f36 100644 --- a/src/main/java/org/cyclonedx/maven/BaseCycloneDxMojo.java +++ b/src/main/java/org/cyclonedx/maven/BaseCycloneDxMojo.java @@ -18,8 +18,11 @@ */ package org.cyclonedx.maven; +import java.util.Properties; import org.apache.commons.io.FileUtils; import org.apache.maven.artifact.Artifact; +import org.apache.maven.model.Developer; +import org.apache.maven.model.Organization; import org.apache.maven.model.Plugin; import org.apache.maven.model.PluginExecution; import org.apache.maven.plugin.AbstractMojo; @@ -251,13 +254,6 @@ public abstract class BaseCycloneDxMojo extends AbstractMojo { @Parameter private ExternalReference[] externalReferences; - /** - * Manufacturer information for automatic creator information - * @since 2.9.1 - */ - @Parameter(property = "cyclonedx.manufacturer", required = false) - private OrganizationalEntity manufacturer = null; - @org.apache.maven.plugins.annotations.Component private MavenProjectHelper mavenProjectHelper; @@ -363,8 +359,13 @@ public void execute() throws MojoExecutionException { metadata.addProperty(newProperty("maven.optional.unused", Boolean.toString(detectUnusedForOptionalScope))); } - if (hasManufacturerInformation()) { - metadata.setManufacturer(manufacturer); + List developers = project.getDevelopers(); + Organization organization = project.getOrganization(); + if (organization != null || (developers != null && !developers.isEmpty())) { + metadata.setManufacturer(createManufacturer(organization, developers)); + } + if ((developers != null && !developers.isEmpty())) { + metadata.setAuthors(createListOfAuthors(null, developers)); } } @@ -377,80 +378,76 @@ public void execute() throws MojoExecutionException { } } - /** - * Check the mojo configuration for the optional manufacturer contents. - * - * @return {@code true} if there is any manufacturer information configured. - */ - boolean hasManufacturerInformation() { - if (manufacturer == null) { - return false; + OrganizationalEntity createManufacturer(Organization organization, List developers) { + OrganizationalEntity manufacturer = new OrganizationalEntity(); + if (organization != null) { + if (isNotNullOrEmpty(organization.getName())) { + manufacturer.setName(organization.getName()); + } + if (isNotNullOrEmpty(organization.getUrl())) { + addUrl(manufacturer, organization.getUrl()); + } } - - return isNotNullOrEmpty(manufacturer.getAddress()) || - isNotNullOrEmpty(manufacturer.getName()) || - isNotNullOrEmptyContacts(manufacturer.getContacts()) || - isNotNullOrEmptyString(manufacturer.getUrls()); - } - - /** - * @param text Some text - * @return {@code true} if there is any text - */ - boolean isNotNullOrEmpty(String text) { - return text != null && !text.trim().isEmpty(); + if (developers != null) { + addContacts(manufacturer, developers); + } + return manufacturer; } - /** - * @param list A list of text - * @return {@code true} if there is any element has a text value - */ - boolean isNotNullOrEmptyString(List list) { - if (list != null && !list.isEmpty()) { - return list.stream().filter(Objects::nonNull).anyMatch(this::isNotNullOrEmpty); + List createListOfAuthors(OrganizationalEntity manufacturer, List developers) { + List list = new ArrayList<>(); + for (Developer developer : developers) { + OrganizationalContact contact = new OrganizationalContact(); + if (isNotNullOrEmpty(developer.getName())) { + contact.setName(developer.getName()); + } + if (isNotNullOrEmpty(developer.getEmail())) { + contact.setEmail(developer.getEmail()); + } + if (manufacturer != null) { + if (isNullOrEmpty(manufacturer.getName()) && isNotNullOrEmpty(developer.getOrganization())) { + manufacturer.setName(developer.getOrganization()); + } + if (isNotNullOrEmpty(developer.getOrganizationUrl())) { + addUrl(manufacturer, developer.getOrganizationUrl()); + } + if (isNotNullOrEmpty(developer.getUrl())) { + addUrl(manufacturer, developer.getUrl()); + } + } + list.add(contact); } - return false; + return list; } - /** - * @param list A list of contacts - * @return {@code true} if there is any contact has something configured - */ - boolean isNotNullOrEmptyContacts(List list) { - if (list != null && !list.isEmpty()) { - return list.stream().filter(Objects::nonNull).anyMatch(this::isNotNullOrEmpty); + void addContacts(OrganizationalEntity manufacturer, List developers) { + manufacturer.setContacts(createListOfAuthors(manufacturer, developers)); + } + + void addUrl(OrganizationalEntity manufacturer, String url) { + List urls = manufacturer.getUrls(); + if (urls == null) { + urls = new ArrayList<>(); } - return false; + urls.add(url); + manufacturer.setUrls(urls); } /** - * @param address A postal address entry - * @return {@code true} if there is any postal address information exists + * @param text Some text + * @return {@code true} if there is any text */ - boolean isNotNullOrEmpty(PostalAddress address) { - if (address == null) { - return false; - } - return isNotNullOrEmpty(address.getStreetAddress()) || - isNotNullOrEmpty(address.getCountry()) || - isNotNullOrEmpty(address.getPostalCode()) || - isNotNullOrEmpty(address.getLocality()) || - isNotNullOrEmpty(address.getPostOfficeBoxNumber()) || - isNotNullOrEmpty(address.getRegion()); + boolean isNotNullOrEmpty(String text) { + return text != null && !text.trim().isEmpty(); } /** - * @param contact A contact entry - * @return {@code true} if there is any contact information exists + * @param text Some text + * @return {@code true} if there is any text */ - boolean isNotNullOrEmpty(OrganizationalContact contact) { - if (null == contact) { - return false; - } - return isNotNullOrEmpty(contact.getName()) || - isNotNullOrEmpty(contact.getEmail()) || - isNotNullOrEmpty(contact.getPhone()); + boolean isNullOrEmpty(String text) { + return !isNotNullOrEmpty(text); } private Property newProperty(String name, String value) { diff --git a/src/site/markdown/manufacturer.md b/src/site/markdown/manufacturer.md deleted file mode 100644 index 4a4ad6d0..00000000 --- a/src/site/markdown/manufacturer.md +++ /dev/null @@ -1,111 +0,0 @@ -# Manufacturer -Manufacturer is common in BOMs created through automated processes. - -When creating a number of BOMs for several projects within one organization -or company, it is convenient to attach this information at one place. - -This will also conform to upcoming EU regulation that all SBOM files shall - ->At minimum, the product with digital elements shall be accompanied by: ->1. the name, registered trade name or registered trademark of the manufacturer, and the - > postal address, the email address or other digital contact as well as, where - > available, the website at which the manufacturer can be contacted; ->2. the single point of contact where information about vulnerabilities of the product - > with digital elements can be reported and received, and where the manufacturer’s - > policy on coordinated vulnerability disclosure can be found ->3. name and type and any additional information enabling the unique identification - > of the product with digital elements ->4. the intended purpose of the product with digital elements, including the security - > environment provided by the manufacturer, as well as the product’s essential - > functionalities and information about the security properties - -## Configuration -The configuration is optional. If none is specified, the manufacturer information is not visible. -See https://cyclonedx.org/docs/latest/json/#metadata_manufacturer for more information. - -### name -The name of the organization, - -### address -The physical address (location) of the organization. -##### country -The country name or the two-letter ISO 3166-1 country code. -##### region -The region or state in the country. -##### locality -The locality or city within the country. -##### postOfficeBoxNumber -The post office box number. -##### postalCode -The postal code. -##### streetAddress -The street address. - -### url -The URL of the organization. Multiple URLs are allowed. - -# contact -A contact at the organization. Multiple contacts are allowed. - -##### name -The name of a contact -##### email -The email address of the contact. -##### phone -The phone number of the contact. - -## Example of configuration - -```xml - - - - - org.cyclonedx - cyclonedx-maven-plugin - ${cyclonedx-maven-plugin.version} - - - Example Company - https://www.example.com/contact - - - Steve Springett - Steve.Springett@owasp.org - - - Another contact - 1-800-555-1111 - - - - - - - - -``` - -This configuration will add the following to the BOM file (JSON format): -```json - "manufacturer" : { - "name" : "Example Company", - "url" : [ - "https://www.example.com/contact" - ], - "contact" : [ - { - "name" : "Steve Springett", - "email" : "Steve.Springett@owasp.org" - }, - { - "name" : "Another contact", - "phone" : "1-800-555-1111" - } - ] - } -``` - -## Links -- [EU regulation proposal about SBOM generation.](https://www.europarl.europa.eu/doceo/document/TA-9-2024-0130_EN.pdf) - diff --git a/src/site/site.xml b/src/site/site.xml index 3e8abdca..c094cc27 100644 --- a/src/site/site.xml +++ b/src/site/site.xml @@ -26,7 +26,6 @@ - diff --git a/src/test/java/org/cyclonedx/maven/BaseCycloneDxMojoTest.java b/src/test/java/org/cyclonedx/maven/BaseCycloneDxMojoTest.java index a52f1daa..bb1c2a64 100644 --- a/src/test/java/org/cyclonedx/maven/BaseCycloneDxMojoTest.java +++ b/src/test/java/org/cyclonedx/maven/BaseCycloneDxMojoTest.java @@ -7,6 +7,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import org.apache.maven.model.Developer; import org.apache.maven.plugin.MojoExecutionException; import org.cyclonedx.model.Component; import org.cyclonedx.model.Dependency; @@ -33,45 +34,64 @@ protected String extractComponentsAndDependencies(Set topLevelComponents @Test @DisplayName("Verify that the default configuration does not have manufacturer information ") - void hasNoManufacturerInformation() { + void createManufacturer() { BaseCycloneDxMojoImpl mojo = new BaseCycloneDxMojoImpl(); - assertFalse(mojo.hasManufacturerInformation()); + OrganizationalEntity manufacturer = mojo.createManufacturer(null, null); + assertNotNull(manufacturer); + assertNull(manufacturer.getAddress()); + assertNull(manufacturer.getContacts()); + assertNull(manufacturer.getName()); + assertNull(manufacturer.getUrls()); + assertNull(manufacturer.getBomRef()); } @Test - @DisplayName("Verify that the function hasManufacturerInformation works as expected") - void hasManufacturerInformation() throws NoSuchFieldException, IllegalAccessException { + @DisplayName("") + void createListOfAuthors() { BaseCycloneDxMojoImpl mojo = new BaseCycloneDxMojoImpl(); OrganizationalEntity manufacturer = new OrganizationalEntity(); - manufacturer.setName("Manufacturer"); - setParentParameter(mojo, "manufacturer", manufacturer); - assertTrue(mojo.hasManufacturerInformation()); - - manufacturer = new OrganizationalEntity(); - setParentParameter(mojo, "manufacturer", manufacturer); - PostalAddress address = new PostalAddress(); - address.setCountry("UK"); - manufacturer.setAddress(address); - assertTrue(mojo.hasManufacturerInformation()); - - manufacturer = new OrganizationalEntity(); - setParentParameter(mojo, "manufacturer", manufacturer); - OrganizationalContact contact = new OrganizationalContact(); - contact.setName("Contact"); - List contacts = new ArrayList<>(); - contacts.add(contact); - manufacturer.setContacts(contacts); - assertTrue(mojo.hasManufacturerInformation()); - - manufacturer = new OrganizationalEntity(); - setParentParameter(mojo, "manufacturer", manufacturer); - List urls = new ArrayList<>(); - urls.add("https://www.owasp.org"); - manufacturer.setUrls(urls); - assertTrue(mojo.hasManufacturerInformation()); + List developers = new ArrayList<>(); + Developer developer = new Developer(); + developer.setName("Developer"); + developers.add(developer); + developer = new Developer(); + developer.setEmail("Developer@foo.com"); + developers.add(developer); + developer = new Developer(); + developer.setOrganization("My Organization"); + developers.add(developer); + developer = new Developer(); + developer.setOrganizationUrl("http://foo.com"); + developers.add(developer); + List listOfAuthors = mojo.createListOfAuthors(manufacturer, developers); + assertNotNull(listOfAuthors); + assertEquals(4, listOfAuthors.size()); + assertEquals("Developer", listOfAuthors.get(0).getName()); + } + @Test + @DisplayName("Verify addContacts") + void addContacts() { + BaseCycloneDxMojoImpl mojo = new BaseCycloneDxMojoImpl(); + OrganizationalEntity manufacturer = new OrganizationalEntity(); + List developers = new ArrayList<>(); + mojo.addContacts(manufacturer, developers); + assertNotNull(manufacturer.getContacts()); + assertTrue(manufacturer.getContacts().isEmpty()); } + @Test + @DisplayName("Verify addUrl") + void addUrl() { + BaseCycloneDxMojoImpl mojo = new BaseCycloneDxMojoImpl(); + OrganizationalEntity manufacturer = new OrganizationalEntity(); + assertNull(manufacturer.getUrls()); + mojo.addUrl(manufacturer, "http://foo.com"); + assertNotNull(manufacturer.getUrls()); + assertEquals(1, manufacturer.getUrls().size()); + mojo.addUrl(manufacturer, "http://example.com"); + assertEquals(2, manufacturer.getUrls().size()); + } @Test @@ -86,86 +106,7 @@ void isNotNullOrEmpty() { assertTrue(mojo.isNotNullOrEmpty(value)); } - @Test - @DisplayName("Verify that a list of strings works as expected") - void isNotNullOrEmptyString() { - List list = null; - BaseCycloneDxMojoImpl mojo = new BaseCycloneDxMojoImpl(); - assertFalse(mojo.isNotNullOrEmptyString(list)); - list = new ArrayList<>(); - assertFalse(mojo.isNotNullOrEmptyString(list)); - String value = null; - list.add(value); - assertFalse(mojo.isNotNullOrEmptyString(list)); - list.add(""); - assertFalse(mojo.isNotNullOrEmptyString(list)); - list.add("null"); - assertTrue(mojo.isNotNullOrEmptyString(list)); - } - @Test - @DisplayName("Verify that a list of contacts works as expected") - void isNotNullOrEmptyContacts() { - BaseCycloneDxMojoImpl mojo = new BaseCycloneDxMojoImpl(); - List list = null; - assertFalse(mojo.isNotNullOrEmptyContacts(list)); - list = new ArrayList<>(); - assertFalse(mojo.isNotNullOrEmptyContacts(list)); - OrganizationalContact contact = new OrganizationalContact(); - contact.setName("Contact"); - list.add(contact); - assertTrue(mojo.isNotNullOrEmptyContacts(list)); - } - - @Test - @DisplayName("Verify that check of address works as expected") - void testIsNotNullOrEmpty() { - BaseCycloneDxMojoImpl mojo = new BaseCycloneDxMojoImpl(); - PostalAddress address = new PostalAddress(); - assertFalse(mojo.isNotNullOrEmpty(address)); - address.setRegion("AL"); - assertTrue(mojo.isNotNullOrEmpty(address)); - - address = new PostalAddress(); - address.setPostOfficeBoxNumber("12345"); - assertTrue(mojo.isNotNullOrEmpty(address)); - - address = new PostalAddress(); - address.setLocality("my locality"); - assertTrue(mojo.isNotNullOrEmpty(address)); - - address = new PostalAddress(); - address.setPostalCode("12345"); - assertTrue(mojo.isNotNullOrEmpty(address)); - - address = new PostalAddress(); - address.setCountry("US"); - assertTrue(mojo.isNotNullOrEmpty(address)); - - address = new PostalAddress(); - address.setStreetAddress("Main street"); - assertTrue(mojo.isNotNullOrEmpty(address)); - } - - @Test - @DisplayName("Verify that test of contact works as expected") - void testIsNotNullOrEmpty1() { - BaseCycloneDxMojoImpl mojo = new BaseCycloneDxMojoImpl(); - OrganizationalContact contact = null; - assertFalse(mojo.isNotNullOrEmpty(contact)); - contact = new OrganizationalContact(); - assertFalse(mojo.isNotNullOrEmpty(contact)); - contact.setPhone("1-555-888-1234"); - assertTrue(mojo.isNotNullOrEmpty(contact)); - - contact = new OrganizationalContact(); - contact.setEmail("info@example.com"); - assertTrue(mojo.isNotNullOrEmpty(contact)); - - contact = new OrganizationalContact(); - contact.setName("Contact"); - assertTrue(mojo.isNotNullOrEmpty(contact)); - } /** * Inject a parameter value to a superclass (even private parameters). From 2ad6e674519522c70d6bcb6ee0496895f14f135f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Kornefalk?= Date: Wed, 20 Nov 2024 18:08:00 +0100 Subject: [PATCH 3/5] Remove setParentParameter MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Björn Kornefalk --- .../maven/BaseCycloneDxMojoTest.java | 28 +------------------ 1 file changed, 1 insertion(+), 27 deletions(-) diff --git a/src/test/java/org/cyclonedx/maven/BaseCycloneDxMojoTest.java b/src/test/java/org/cyclonedx/maven/BaseCycloneDxMojoTest.java index bb1c2a64..00b6cad9 100644 --- a/src/test/java/org/cyclonedx/maven/BaseCycloneDxMojoTest.java +++ b/src/test/java/org/cyclonedx/maven/BaseCycloneDxMojoTest.java @@ -2,18 +2,15 @@ import static org.junit.jupiter.api.Assertions.*; -import java.lang.reflect.Field; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Set; import org.apache.maven.model.Developer; -import org.apache.maven.plugin.MojoExecutionException; import org.cyclonedx.model.Component; import org.cyclonedx.model.Dependency; import org.cyclonedx.model.OrganizationalContact; import org.cyclonedx.model.OrganizationalEntity; -import org.cyclonedx.model.organization.PostalAddress; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -27,7 +24,7 @@ private static class BaseCycloneDxMojoImpl extends BaseCycloneDxMojo { @Override protected String extractComponentsAndDependencies(Set topLevelComponents, - Map components, Map dependencies) throws MojoExecutionException { + Map components, Map dependencies) { return ""; } } @@ -106,27 +103,4 @@ void isNotNullOrEmpty() { assertTrue(mojo.isNotNullOrEmpty(value)); } - - - /** - * Inject a parameter value to a superclass (even private parameters). - *

example:

- *
{@code class B extends A;} - *
{@code class A { private Type a; } } - *
- *
{@code setParentParameter(new B(), "a", new Type()); } - * - * @param cc The class instance - * @param fieldName The field name - * @param value The value - * @throws NoSuchFieldException If the field does not exist - * @throws IllegalAccessException If the value is not able to be modified - */ - public static void setParentParameter(Object cc, String fieldName, Object value) - throws NoSuchFieldException, IllegalAccessException { - Field field = cc.getClass().getSuperclass().getDeclaredField(fieldName); - field.setAccessible(true); - field.set(cc, value); - } - } \ No newline at end of file From 3aebea03641b85ab625207bd049b6f8b9ba4218b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Kornefalk?= Date: Wed, 20 Nov 2024 18:24:05 +0100 Subject: [PATCH 4/5] Remove code that does not make sense MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Björn Kornefalk --- .../java/org/cyclonedx/maven/BaseCycloneDxMojo.java | 3 --- .../org/cyclonedx/maven/BaseCycloneDxMojoTest.java | 13 ------------- 2 files changed, 16 deletions(-) diff --git a/src/main/java/org/cyclonedx/maven/BaseCycloneDxMojo.java b/src/main/java/org/cyclonedx/maven/BaseCycloneDxMojo.java index 2fd18f36..d6b1ae63 100644 --- a/src/main/java/org/cyclonedx/maven/BaseCycloneDxMojo.java +++ b/src/main/java/org/cyclonedx/maven/BaseCycloneDxMojo.java @@ -18,7 +18,6 @@ */ package org.cyclonedx.maven; -import java.util.Properties; import org.apache.commons.io.FileUtils; import org.apache.maven.artifact.Artifact; import org.apache.maven.model.Developer; @@ -48,7 +47,6 @@ import org.cyclonedx.model.OrganizationalContact; import org.cyclonedx.model.OrganizationalEntity; import org.cyclonedx.model.Property; -import org.cyclonedx.model.organization.PostalAddress; import org.cyclonedx.parsers.JsonParser; import org.cyclonedx.parsers.Parser; import org.cyclonedx.parsers.XmlParser; @@ -63,7 +61,6 @@ import java.util.LinkedHashSet; import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.Set; import java.util.UUID; diff --git a/src/test/java/org/cyclonedx/maven/BaseCycloneDxMojoTest.java b/src/test/java/org/cyclonedx/maven/BaseCycloneDxMojoTest.java index 00b6cad9..392440ad 100644 --- a/src/test/java/org/cyclonedx/maven/BaseCycloneDxMojoTest.java +++ b/src/test/java/org/cyclonedx/maven/BaseCycloneDxMojoTest.java @@ -29,19 +29,6 @@ protected String extractComponentsAndDependencies(Set topLevelComponents } } - @Test - @DisplayName("Verify that the default configuration does not have manufacturer information ") - void createManufacturer() { - BaseCycloneDxMojoImpl mojo = new BaseCycloneDxMojoImpl(); - OrganizationalEntity manufacturer = mojo.createManufacturer(null, null); - assertNotNull(manufacturer); - assertNull(manufacturer.getAddress()); - assertNull(manufacturer.getContacts()); - assertNull(manufacturer.getName()); - assertNull(manufacturer.getUrls()); - assertNull(manufacturer.getBomRef()); - } - @Test @DisplayName("") void createListOfAuthors() { From 2c48356b67491f86a927660e8e0924986ce06529 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Kornefalk?= Date: Fri, 22 Nov 2024 13:36:01 +0100 Subject: [PATCH 5/5] Removed setAuthors, moved manufacurer to component section MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Björn Kornefalk --- .../cyclonedx/maven/BaseCycloneDxMojo.java | 60 +++++----- .../maven/CycloneDxAggregateMojo.java | 1 + .../cyclonedx/maven/CycloneDxPackageMojo.java | 1 + .../cyclonedx/maven/DeveloperInformation.java | 69 ++++++++++++ .../maven/BaseCycloneDxMojoTest.java | 104 +++++++++++++++--- 5 files changed, 191 insertions(+), 44 deletions(-) create mode 100644 src/main/java/org/cyclonedx/maven/DeveloperInformation.java diff --git a/src/main/java/org/cyclonedx/maven/BaseCycloneDxMojo.java b/src/main/java/org/cyclonedx/maven/BaseCycloneDxMojo.java index d6b1ae63..1f073fbe 100644 --- a/src/main/java/org/cyclonedx/maven/BaseCycloneDxMojo.java +++ b/src/main/java/org/cyclonedx/maven/BaseCycloneDxMojo.java @@ -18,6 +18,7 @@ */ package org.cyclonedx.maven; +import java.util.stream.Collectors; import org.apache.commons.io.FileUtils; import org.apache.maven.artifact.Artifact; import org.apache.maven.model.Developer; @@ -355,19 +356,11 @@ public void execute() throws MojoExecutionException { if (detectUnusedForOptionalScope) { metadata.addProperty(newProperty("maven.optional.unused", Boolean.toString(detectUnusedForOptionalScope))); } - - List developers = project.getDevelopers(); - Organization organization = project.getOrganization(); - if (organization != null || (developers != null && !developers.isEmpty())) { - metadata.setManufacturer(createManufacturer(organization, developers)); - } - if ((developers != null && !developers.isEmpty())) { - metadata.setAuthors(createListOfAuthors(null, developers)); - } } final Component rootComponent = metadata.getComponent(); componentMap.remove(rootComponent.getPurl()); + setManufacturer(project, rootComponent); projectDependenciesConverter.cleanupBomDependencies(metadata, componentMap, dependencyMap); @@ -375,6 +368,16 @@ public void execute() throws MojoExecutionException { } } + protected void setManufacturer(MavenProject mavenProject, Component projectBomComponent) { + getLog().debug("setManufacturer for " + mavenProject.getGroupId() + ":" + + mavenProject.getArtifactId() + ":" + mavenProject.getVersion()); + List developers = mavenProject.getDevelopers(); + Organization organization = mavenProject.getOrganization(); + if (organization != null || (developers != null && !developers.isEmpty())) { + projectBomComponent.setManufacturer(createManufacturer(organization, developers)); + } + } + OrganizationalEntity createManufacturer(Organization organization, List developers) { OrganizationalEntity manufacturer = new OrganizationalEntity(); if (organization != null) { @@ -386,13 +389,23 @@ OrganizationalEntity createManufacturer(Organization organization, List" is missing, see if we can find any organization information from + * a developers section + * @param organization The organization name + */ + void setOrganization(String organization) { + if (this.organization == null && organization != null) { + this.organization = organization; + } + } + + /** + * Add a defined url + * @param url The url + */ + void addUrl(String url) { + if (url != null) { + urls.add(url); + } + } + + /** + * @return List of contacts + */ + public List getContacts() { + return contacts; + } + + /** + * @return First organization name if found + */ + public String getOrganization() { + return organization; + } + + /** + * @return List of configured urls + */ + public List getUrls() { + return urls; + } +} diff --git a/src/test/java/org/cyclonedx/maven/BaseCycloneDxMojoTest.java b/src/test/java/org/cyclonedx/maven/BaseCycloneDxMojoTest.java index 392440ad..08fbb44f 100644 --- a/src/test/java/org/cyclonedx/maven/BaseCycloneDxMojoTest.java +++ b/src/test/java/org/cyclonedx/maven/BaseCycloneDxMojoTest.java @@ -7,6 +7,8 @@ import java.util.Map; import java.util.Set; import org.apache.maven.model.Developer; +import org.apache.maven.model.Organization; +import org.apache.maven.project.MavenProject; import org.cyclonedx.model.Component; import org.cyclonedx.model.Dependency; import org.cyclonedx.model.OrganizationalContact; @@ -30,10 +32,9 @@ protected String extractComponentsAndDependencies(Set topLevelComponents } @Test - @DisplayName("") - void createListOfAuthors() { + @DisplayName("Using developers information only") + void setManufacturer1() { BaseCycloneDxMojoImpl mojo = new BaseCycloneDxMojoImpl(); - OrganizationalEntity manufacturer = new OrganizationalEntity(); List developers = new ArrayList<>(); Developer developer = new Developer(); developer.setName("Developer"); @@ -47,21 +48,96 @@ void createListOfAuthors() { developer = new Developer(); developer.setOrganizationUrl("http://foo.com"); developers.add(developer); - List listOfAuthors = mojo.createListOfAuthors(manufacturer, developers); - assertNotNull(listOfAuthors); - assertEquals(4, listOfAuthors.size()); - assertEquals("Developer", listOfAuthors.get(0).getName()); + Component projectBomComponent = new Component(); + MavenProject mavenProject = new MavenProject(); + mavenProject.setDevelopers(developers); + mojo.setManufacturer(mavenProject, projectBomComponent); + OrganizationalEntity manufacturer = projectBomComponent.getManufacturer(); + assertNotNull(manufacturer); + assertEquals(4, manufacturer.getContacts().size()); + assertEquals("Developer", manufacturer.getContacts().get(0).getName()); + assertEquals("My Organization", manufacturer.getName()); } @Test - @DisplayName("Verify addContacts") - void addContacts() { + @DisplayName("Using developers information with empty organization") + void setManufacturer2() { BaseCycloneDxMojoImpl mojo = new BaseCycloneDxMojoImpl(); - OrganizationalEntity manufacturer = new OrganizationalEntity(); - List developers = new ArrayList<>(); - mojo.addContacts(manufacturer, developers); - assertNotNull(manufacturer.getContacts()); - assertTrue(manufacturer.getContacts().isEmpty()); + List developers = new ArrayList<>(); + Developer developer = new Developer(); + developer.setName("Developer"); + developers.add(developer); + developer = new Developer(); + developer.setEmail("Developer@foo.com"); + developers.add(developer); + developer = new Developer(); + developer.setOrganization("My Organization"); + developers.add(developer); + developer = new Developer(); + developer.setOrganizationUrl("http://foo.com"); + developers.add(developer); + Component projectBomComponent = new Component(); + MavenProject mavenProject = new MavenProject(); + mavenProject.setDevelopers(developers); + mavenProject.setOrganization(new Organization()); + mojo.setManufacturer(mavenProject, projectBomComponent); + OrganizationalEntity manufacturer = projectBomComponent.getManufacturer(); + assertNotNull(manufacturer); + assertEquals(4, manufacturer.getContacts().size()); + assertEquals("Developer", manufacturer.getContacts().get(0).getName()); + assertEquals("My Organization", manufacturer.getName()); + } + + @Test + @DisplayName("Using developers and organization information") + void setManufacturer3() { + BaseCycloneDxMojoImpl mojo = new BaseCycloneDxMojoImpl(); + + MavenProject mavenProject = new MavenProject(); + List developers = new ArrayList<>(); + Developer developer = new Developer(); + developer.setName("Developer 2"); + developer.setEmail("Developer@foo.com"); + developer.setOrganization("My Organization"); + developer.setOrganizationUrl("http://foo.com"); + developers.add(developer); + mavenProject.setDevelopers(developers); + + Organization organization = new Organization(); + organization.setName("My Company"); + organization.setUrl("http://example.com"); + mavenProject.setOrganization(organization); + + Component projectBomComponent = new Component(); + mojo.setManufacturer(mavenProject, projectBomComponent); + OrganizationalEntity manufacturer = projectBomComponent.getManufacturer(); + assertNotNull(manufacturer); + assertEquals(1, manufacturer.getContacts().size()); + assertEquals("Developer 2", manufacturer.getContacts().get(0).getName()); + assertEquals("My Company", manufacturer.getName()); + } + + @Test + @DisplayName("Using organization information only") + void setManufacturer4() { + BaseCycloneDxMojoImpl mojo = new BaseCycloneDxMojoImpl(); + + MavenProject mavenProject = new MavenProject(); + List developers = new ArrayList<>(); + Organization organization = new Organization(); + organization.setName("My Organization"); + organization.setUrl("http://example.org"); + mavenProject.setOrganization(organization); + + mavenProject.setDevelopers(developers); + mavenProject.setOrganization(organization); + + Component projectBomComponent = new Component(); + mojo.setManufacturer(mavenProject, projectBomComponent); + OrganizationalEntity manufacturer = projectBomComponent.getManufacturer(); + assertNotNull(manufacturer); + assertNull(manufacturer.getContacts()); + assertEquals("My Organization", manufacturer.getName()); } @Test