diff --git a/README.md b/README.md index 4d899f17..d0631ea7 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,15 @@ # PlugFest Tooling > A collection of tools to compare the usage and quality of different SBOM generators > -> ## v3.1.0 -- 5/2/23 +> ## v3.2.0 -- 5/9/23 > ### API -> - Fixed bug that prevented non-ASCII characters from being processed +> - Fixed another bug preventing non-ASCII characters from being processed > ### Comparison -> - Fix bug that showed duplicate UIDs in the comparison report +> - Allow marking of components as appearing in target SBOM > ### Metrics -> - Added support for non-ASCII characters when pulling from package manager databases -> - Remove all empty tests to prevent duplicated component lists +> - Fix bug causing formatting issues with the data verification test > ### GUI -> - Display which SBOM an identifier or quality came from +> - Added individual loading spinners for each uploaded SBOM ## Differ - Compares two SBOMs supporting CycloneDX XML and SPDX Tag-Value @@ -24,36 +23,56 @@ - SWIDs - Summarizes the report in a Unix-diff-like print -## Metrics -- Appropriate Amount Test - - Checks to ensure that each attribute within an SBOM does not exceed the maximum line length in Java +## Comparison +- Generate detailed DiffReports from a target SBOM and a list of SBOMs. + +## Quality Attributes +- Actionable Test + - Tests fields to ensure data contained is usable. - Completeness Test - Checks to make sure components have a name, publisher, version - Checks if attributes are formatted correctly and checks CPE and PURL formatting -- Timeliness Test +- Data Verification Test - Uses PURLs to search for information about the package using package manager APIs - Confirms that name and publisher match resource - Also checks to see if the assigned version number exists in resource - + +## Translator +- Parse SBOMS from files and deserialize from formats: + - CycloneDX + > .xml and .json + - SPDX + > .spdx ## System Requirements - Java 17 > Check: `java -version` ## Quick Start -1. `./gradlew jar` -2. `java -jar app.jar [OPTIONS]` +### Backend +1. `./gradlew bootJar` +2. `java -jar .\api\build\libs\api-3.1.0.jar` +### Frontend +1. `cd gui` +2. `npm install` +3. `npm start` ## Contributors -- [Derek Garcia](mailto:dlg1206@rit.edu) -- [Matt London](mailto:mrl2534@rit.edu) +**Principal Investigator:** [Mehdi Mirakhorli](mailto:mxmvse@rit.edu) + +**Senior Project Manager:** [Chris Enoch](mailto:ctevse@rit.edu) + +**Senior Developer Team Lead:** [Derek Garcia](mailto:dlg1206@rit.edu) + +**Developer Team Leads** - [Tina DiLorenzo](mailto:tnd3015@rit.edu) +- [Matt London](mailto:mrl2534@rit.edu) +- [Dylan Mulligan](mailto:dtm5568@rit.edu) + +**Developer Team** - [Tyler Drake](mailto:txd3634@rit.edu) +- [Ian Dunn](mailto:itd3516@rit.edu) - [Asa Horn](mailto:aoh9470@rit.edu) - [Justin Jantzi](mailto:jwj7297@rit.edu) +- [Henry Orsagh](mailto:hco4630@rit.edu) - [Juan Francisco Patino](mailto:jfp6815@rit.edu) - [Max Stein](mailto:mhs8558@rit.edu) -- [Ian Dunn](mailto:itd3516@rit.edu) -- [Henry Keena](mailto:htk4363@rit.edu) -- [Henry Lu](mailto:hyl2415@rit.edu) -- [Chris Enoch](mailto:cte6149@rit.edu) -- [Ping Liu](mailto:htk4363@rit.edu) \ No newline at end of file diff --git a/api/build.gradle b/api/build.gradle index ca9d78f3..51fca499 100644 --- a/api/build.gradle +++ b/api/build.gradle @@ -5,7 +5,7 @@ plugins { } group = 'org.nvip.plugfest' -version = '1.0' +version = '3.1.0' application { mainClassName = 'org.nvip.plugfest.tooling.APIApplication' diff --git a/api/src/main/java/org/nvip/plugfest/tooling/APIApplication.java b/api/src/main/java/org/nvip/plugfest/tooling/APIApplication.java index 6fd7baed..ad5c63a0 100644 --- a/api/src/main/java/org/nvip/plugfest/tooling/APIApplication.java +++ b/api/src/main/java/org/nvip/plugfest/tooling/APIApplication.java @@ -3,9 +3,13 @@ import org.springframework.boot.WebApplicationType; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.builder.SpringApplicationBuilder; +import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory; +import org.springframework.boot.web.server.WebServerFactoryCustomizer; +import org.springframework.context.annotation.Bean; /** * This class contains the main function which runs the API as a spring boot application + * @author Justin Jantzi */ @SpringBootApplication public class APIApplication { @@ -14,4 +18,16 @@ public static void main(String[] args) { .web(WebApplicationType.SERVLET) //This is required to prevent "No valid webserver" error. .run(args); } -} + + /*** + * Overwrites the default max post size default of 2MB + */ + @Bean + public WebServerFactoryCustomizer tomcatCustomizer() { + return (factory) -> { + factory.addConnectorCustomizers((connector) -> { + connector.setMaxPostSize(Integer.MAX_VALUE); //About 2GB + }); + }; + } +} \ No newline at end of file diff --git a/api/src/main/java/org/nvip/plugfest/tooling/APIController.java b/api/src/main/java/org/nvip/plugfest/tooling/APIController.java index 3d0997f9..644282b0 100644 --- a/api/src/main/java/org/nvip/plugfest/tooling/APIController.java +++ b/api/src/main/java/org/nvip/plugfest/tooling/APIController.java @@ -2,6 +2,7 @@ import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.servlet.http.HttpServletRequest; import org.nvip.plugfest.tooling.differ.Comparison; import org.nvip.plugfest.tooling.qa.QAPipeline; import org.nvip.plugfest.tooling.qa.QualityReport; @@ -86,7 +87,14 @@ public ResponseEntity compare(@RequestParam("contents") String conte * @return - wrapped QualityReport object, null if failed */ @PostMapping("qa") - public ResponseEntity qa(@RequestParam("contents") String contents, @RequestParam("fileName") String fileName) { + public ResponseEntity qa(@RequestParam("contents") String contents, @RequestParam("fileName") String fileName, HttpServletRequest servletRequest) { + try { + servletRequest.setCharacterEncoding("UTF-8"); + } + catch (Exception e) { + // This will not happen as we are hardcoding UTF-8 + System.out.println("Failed to set encoding"); + } SBOM sbom = TranslatorPlugFest.translateContents(contents, fileName); diff --git a/changelog.md b/changelog.md index 66394a9e..73c74a51 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,15 @@ # Plugfest Changelog +## v3.2.0 -- 5/9/23 +### API +- Fixed another bug preventing non-ASCII characters from being processed +### Comparison +- Allow marking of components as appearing in target SBOM +### Metrics +- Fix bug causing formatting issues with the data verification test +### GUI +- Added individual loading spinners for each uploaded SBOM + ## v3.1.0 -- 5/2/23 ### API - Fixed bug that prevented non-ASCII characters from being processed diff --git a/core/src/main/java/org/nvip/plugfest/tooling/differ/Comparison.java b/core/src/main/java/org/nvip/plugfest/tooling/differ/Comparison.java index 210fe98b..df4585d2 100644 --- a/core/src/main/java/org/nvip/plugfest/tooling/differ/Comparison.java +++ b/core/src/main/java/org/nvip/plugfest/tooling/differ/Comparison.java @@ -96,7 +96,17 @@ public void assignComponents(SBOM current_sbom, int SBOM_index) { // Get all ComponentVersions that match the temporary ComponentVersion's version List matching_cv_list = current_cv_list .stream() - .filter(x -> (Objects.equals(x.getComponentVersion(), current_component.getVersion()) || x.getComponentVersion().contains(current_component.getVersion()))) + .filter(x -> { + boolean objEqual = Objects.equals(x.getComponentVersion(), current_component.getVersion()); + boolean someNull = x.getComponentVersion() == null || current_component.getVersion() == null; + // This means one of the versions is null and the other is not + if (someNull && !objEqual) { + return false; + } + boolean containsCurrent = x.getComponentVersion() != null && x.getComponentVersion().contains(current_component.getVersion()); + boolean containsX = current_component.getVersion() != null && current_component.getVersion().contains(x.getComponentVersion()); + return objEqual || containsCurrent || containsX; + }) .toList(); // If there are no matching ComponentVersion objects in the Set for that package name diff --git a/core/src/main/java/org/nvip/plugfest/tooling/qa/processors/CompletenessTest.java b/core/src/main/java/org/nvip/plugfest/tooling/qa/processors/CompletenessTest.java index 15a2ddff..12f3d18d 100644 --- a/core/src/main/java/org/nvip/plugfest/tooling/qa/processors/CompletenessTest.java +++ b/core/src/main/java/org/nvip/plugfest/tooling/qa/processors/CompletenessTest.java @@ -51,10 +51,11 @@ protected CompletenessTest() { this.publisherEmailRegex = Pattern.compile("(?:(Person|Organization)?: (.*?))? ?]+)>?", Pattern.MULTILINE); /* - Regex101: https://regex101.com/r/wzJeIq/4 + Regex101: https://regex101.com/r/BjMJCP/1 Checks if version is in form: "12.*" | "4:*", version format varies a lot + Also supports git commit hashes (for example docker compose uses this) */ - this.componentVersionRegex = Pattern.compile("^([0-9]+[\\.:\\-].*)", Pattern.MULTILINE); + this.componentVersionRegex = Pattern.compile("^(v?[0-9]+[\\.:\\-].*|[0-9a-fA-F]{7,40})$", Pattern.MULTILINE); // TODO for these patterns: check if name, version, etc matches component name, version, etc. Make classes? diff --git a/core/src/main/java/org/nvip/plugfest/tooling/qa/processors/DataVerificationTest.java b/core/src/main/java/org/nvip/plugfest/tooling/qa/processors/DataVerificationTest.java index d536c1a8..a0b2d23d 100644 --- a/core/src/main/java/org/nvip/plugfest/tooling/qa/processors/DataVerificationTest.java +++ b/core/src/main/java/org/nvip/plugfest/tooling/qa/processors/DataVerificationTest.java @@ -73,16 +73,16 @@ public TestResults test(Component c) { // check whatever is online at least contains this component, or vice versa if(name == null || !((name.contains(nameFoundOnline)|| nameFoundOnline.contains(name)))) - testResults.addTest(new Test(false, "Name ", name, "does not match ", - nameFoundOnline, " in ", packageManagerName)); + testResults.addTest(new Test(false, "Name '", name, "' does not match '", + nameFoundOnline, "' in ", packageManagerName)); if(version == null || !versionFoundOnline.contains(version)) - testResults.addTest(new Test(false,"Version ",version," not found in ", + testResults.addTest(new Test(false,"Version '",version,"' not found in ", packageManagerName, " database")); if(!((publisher.contains(publisherFoundOnline)|| publisherFoundOnline.contains(publisher)))) - testResults.addTest(new Test(false,"Publisher Name ", publisher, - " does not match ", publisherFoundOnline," in ", packageManagerName, " database")); + testResults.addTest(new Test(false,"Publisher Name '", publisher, + "' does not match '", publisherFoundOnline,"' in ", packageManagerName, " database")); } catch(IOException e){ testResults.addTest(new Test(true,"Error accessing ", diff --git a/core/src/main/java/org/nvip/plugfest/tooling/translator/TranslatorCDXJSON.java b/core/src/main/java/org/nvip/plugfest/tooling/translator/TranslatorCDXJSON.java index 85c69413..4eb0eb25 100644 --- a/core/src/main/java/org/nvip/plugfest/tooling/translator/TranslatorCDXJSON.java +++ b/core/src/main/java/org/nvip/plugfest/tooling/translator/TranslatorCDXJSON.java @@ -1,6 +1,6 @@ package org.nvip.plugfest.tooling.translator; -import org.nvip.plugfest.tooling.sbom.*; +import org.nvip.plugfest.tooling.sbom.*; import org.cyclonedx.exception.ParseException; import org.cyclonedx.model.*; @@ -9,10 +9,8 @@ import org.nvip.plugfest.tooling.sbom.Component; import org.nvip.plugfest.tooling.sbom.SBOM; - -import java.io.File; - import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Paths; import java.util.*; @@ -41,11 +39,13 @@ public static SBOM translatorCDXJSONContents(String fileContents, String file_pa // Top level component for SBOM's dependencyTree Component top_component = null; + Set components_left = new HashSet<>(); + // Initialize JSON Parser JsonParser parser = new JsonParser(); // Use JSON Parser to parse cdx.json file and store into cyclonedx Bom Object - Bom json_sbom = parser.parse(fileContents.getBytes()); + Bom json_sbom = parser.parse(fileContents.getBytes(StandardCharsets.UTF_8)); // Attempt to create the SBOM object. If information isn't found, cancel process and return a null object, try { @@ -77,7 +77,7 @@ public static SBOM translatorCDXJSONContents(String fileContents, String file_pa top_component_meta.getName(), top_component_meta.getPublisher(), top_component_meta.getVersion(), - top_component_meta.getBomRef() + top_component_meta.getBomRef().replaceAll("@", "") ); } catch(Exception e) { System.err.println("Could not create top-level component from MetaData.\n " + @@ -123,10 +123,18 @@ public static SBOM translatorCDXJSONContents(String fileContents, String file_pa } // Set the component's unique ID - new_component.setUniqueID(cdx_component.getBomRef()); + try { + new_component.setUniqueID(cdx_component.getBomRef().replaceAll("@", "")); + } catch (NullPointerException nullPointerException) { + System.err.println("Could not find component Unique ID (bom-ref), defaulting to component name."); + new_component.setUniqueID(cdx_component.getName()); + } catch (Exception e) { + System.err.println("Unexpected error trying to get Unique ID for component"); + } // Add component to component list - components.put(new_component.getUniqueID(), new_component); + components.put(new_component.getUniqueID().replaceAll("@", ""), new_component); + components_left.add(new_component.getUniqueID().replaceAll("@", "")); // If a top component doesn't exist, make this new component the top component top_component = top_component == null ? new_component : top_component; @@ -146,23 +154,31 @@ public static SBOM translatorCDXJSONContents(String fileContents, String file_pa .stream() .collect( Collectors.toMap( - Dependency::getRef, - Dependency::getDependencies + x -> { + return x.getRef().replaceAll("@", ""); + }, + Dependency::getDependencies, + (x, y) -> x ) ); } catch (NullPointerException nullPointerException) { - // I failed, ourput error message and default dependencies to null + // It failed, output error message and default dependencies to null System.err.println("Could not find dependencies from CycloneDX Object. " + "Defaulting all components to point to head component. File: " + file_path); dependencies = null; + } catch (IllegalStateException illegalStateException) { + System.err.println("Error: duplicate keys found in dependencies and could not be handled in: " + file_path); + dependencies = null; + } catch (Exception exception) { + System.err.println("Error: unexpected error when attempting to get dependencies: " + file_path); + dependencies = null; } - // If the dependency list isn't empty, call dependencyBuilder to construct dependencyTree // Otherwise, default the dependencyTree by adding all subcomponents as children to the top component if( dependencies != null ) { try { - dependencyBuilder(dependencies, components, top_component, sbom, null); + dependencyBuilder(dependencies, components, components_left, top_component, sbom, null); } catch (Exception e) { System.out.println("Error building dependency tree. Dependency tree may be incomplete for: " + file_path); } @@ -177,6 +193,12 @@ public static SBOM translatorCDXJSONContents(String fileContents, String file_pa } } + // This will take all the components that were not added in the dependencyTree through + // dependencyBuilder and will tack each remaining component to the top component by default + for(String remaining_component : components_left) { + sbom.addComponent(top_component.getUUID(), components.get(remaining_component)); + } + // Return SBOM object return sbom; @@ -211,19 +233,26 @@ public static SBOM translatorCDXJSON(String file_path) throws ParseException { * @param parent Parent component to have dependencies connected to * @param sbom The SBOM object */ - public static void dependencyBuilder(Map dependencies, HashMap components, Component parent, SBOM sbom, Set visited) { + public static void dependencyBuilder( + Map dependencies, + HashMap components, + Collection components_left, + Component parent, + SBOM sbom, + Set visited) + { // If top component is null, return. There is nothing to process. if (parent == null) { return; } if (visited != null) { // Add this parent to the visited set - visited.add(parent.getUniqueID()); + visited.add(parent.getUniqueID().replaceAll("@", "")); } // Get the parent's dependencies as a list - String parent_id = parent.getUniqueID(); - List children_ref = (List) dependencies.get(parent_id); + String parent_id = parent.getUniqueID().replaceAll("@", ""); + List children_ref = (List) dependencies.get(parent_id.replaceAll("@", "")); // If there are no if( children_ref == null ) { return; } @@ -231,7 +260,7 @@ public static void dependencyBuilder(Map dependencies, HashMap components, Compo // Cycle through each dependency the parent component has for (Dependency child_ref: children_ref) { // Retrieve the component the parent has a dependency for - Component child = (Component) components.get(child_ref.getRef()); + Component child = (Component) components.get(child_ref.getRef().replaceAll("@", "")); // If component is already in the dependency tree, add it as a child to the parent // Else, add it to the dependency tree while setting the parent @@ -241,17 +270,23 @@ public static void dependencyBuilder(Map dependencies, HashMap components, Compo sbom.addComponent(parent.getUUID(), child); } + // If this is the first time this component is being added/referenced on dependencyTree + // Remove the component from remaining component list + if(components_left.contains(child_ref)) { + components_left.remove(child_ref); + } + if (visited == null) { // This means we are in the top level component // Pass in a new hashset instead of the visited set visited = new HashSet<>(); - dependencyBuilder(dependencies, components, child, sbom, new HashSet<>()); + dependencyBuilder(dependencies, components, components_left, child, sbom, new HashSet<>()); } else { // Only explore if we haven't already visited this component if (!visited.contains(child.getUniqueID())) { // Pass the child component as the new parent into dependencyBuilder - dependencyBuilder(dependencies, components, child, sbom, visited); + dependencyBuilder(dependencies, components, components_left, child, sbom, visited); } } } diff --git a/core/src/main/java/org/nvip/plugfest/tooling/translator/TranslatorSPDX.java b/core/src/main/java/org/nvip/plugfest/tooling/translator/TranslatorSPDX.java index 6eccac63..52b2d6f7 100644 --- a/core/src/main/java/org/nvip/plugfest/tooling/translator/TranslatorSPDX.java +++ b/core/src/main/java/org/nvip/plugfest/tooling/translator/TranslatorSPDX.java @@ -79,6 +79,9 @@ public static SBOM translatorSPDXContents(String fileContents, String file_path) // Key (SPDX_ID) = Component, Values (SPDX_ID) = Components it needs ArrayListMultimap dependencies = ArrayListMultimap.create(); + // Collection of component names used by dependencyTree + Set components_left = new HashSet<>(); + // Collection of packages, used for adding head components to top component if in the header (SPDXRef-DOCUMENT) // Value (SPDX_ID) List packages = new ArrayList<>(); @@ -188,11 +191,7 @@ public static SBOM translatorSPDXContents(String fileContents, String file_path) * Parse through components, add them to components HashSet */ // Loop through every Package until Relationships or end of file - while ( current_line != null - && !current_line.contains(RELATIONSHIP_TAG) - && !current_line.contains(RELATIONSHIP_KEY) - ) { - if (current_line.contains(RELATIONSHIP_TAG)) break; + while ( current_line != null ) { // Temporary component collection of materials HashMap component_materials = new HashMap<>(); @@ -282,34 +281,20 @@ else if (current_line.contains("ExternalRef: PACKAGE-MANAGER purl")) { // Add packaged component to components list components.put(component.getUniqueID(), component); + components_left.add(component.getUniqueID()); // Add packaged component to packages list as well packages.add(component.getUniqueID()); - } else { - // if no package/component is found, get next line - current_line = br.readLine(); } - } - - // Parse through what is left (relationships, if there are any) - while(current_line != null) { - // If relationship key is found - if(current_line.contains(RELATIONSHIP_KEY)) { + else if(current_line.contains(RELATIONSHIP_KEY)) { // Split and get and value of the line String relationship = current_line.split(RELATIONSHIP_KEY, 2)[1]; // Split dependency relationship and store into relationships map depends on relationship type - if(current_line.contains("CONTAINS")) { - - dependencies.put( - relationship.split(" CONTAINS ")[0], - relationship.split(" CONTAINS ")[1] - ); - - } else if (current_line.contains("DEPENDS_ON")) { + if (current_line.contains("DEPENDS_ON")) { dependencies.put( relationship.split(" DEPENDS_ON ")[0], @@ -323,14 +308,7 @@ else if (current_line.contains("ExternalRef: PACKAGE-MANAGER purl")) { relationship.split(" DEPENDENCY_OF ")[0] ); - } else if (current_line.contains("OTHER")) { - - dependencies.put( - relationship.split(" OTHER ")[1], - relationship.split(" OTHER ")[0] - ); - - } else if (current_line.contains("DESCRIBES")) { + } else if (current_line.contains("DESCRIBES")) { // If document references itself as top component, make it the top component using sbom head information // Otherwise, get the SPDXID for the component it references and make that the top component @@ -345,12 +323,17 @@ else if (current_line.contains("ExternalRef: PACKAGE-MANAGER purl")) { dependencies.remove(top_component.getUniqueID(), top_component.getUniqueID()); } } - } - current_line = br.readLine(); + current_line = br.readLine(); + } + else { + // if no package/component is found, get next line + current_line = br.readLine(); + } } + // Create the new SBOM Object with top level data try { sbom = new SBOM( @@ -373,11 +356,17 @@ else if (current_line.contains("ExternalRef: PACKAGE-MANAGER purl")) { // Create the top level component // Build the dependency tree using dependencyBuilder try { - dependencyBuilder(dependencies, components, top_component, sbom, null); + dependencyBuilder(dependencies, components, components_left, top_component, sbom, null); } catch (Exception e) { System.err.println("Error processing dependency tree."); } + // This will take all the components that were not added in the dependencyTree through + // dependencyBuilder and will tack each remaining component to the top component by default + for(String remaining_component : components_left) { + sbom.addComponent(top_component.getUUID(), components.get(remaining_component)); + } + br.close(); // Return SBOM object @@ -413,7 +402,14 @@ public static SBOM translatorSPDX(String file_path) throws IOException { * @param parent Parent component to have dependencies connected to * @param sbom The SBOM object */ - public static void dependencyBuilder(Multimap dependencies, HashMap components, Component parent, SBOM sbom, Set visited) { + private static void dependencyBuilder( + Multimap dependencies, + HashMap components, + Collection components_left, + Component parent, + SBOM sbom, + Set visited) + { // If top component is null, return. There is nothing to process. if (parent == null) { return; } @@ -440,17 +436,24 @@ public static void dependencyBuilder(Multimap dependencies, HashMap components, sbom.addComponent(parent.getUUID(), child); } + // If this is the first time this component is being added/referenced on dependencyTree + // Remove the component from remaining component list + if(components_left.contains(child_SPDX)) { + components_left.remove(child_SPDX); + } + + if (visited == null) { // This means we are in the top level component // Pass in a new hashset instead of the visited set visited = new HashSet<>(); - dependencyBuilder(dependencies, components, child, sbom, new HashSet<>()); + dependencyBuilder(dependencies, components, components_left, child, sbom, new HashSet<>()); } else { // Only explore if we haven't already visited this component if (!visited.contains(child.getUniqueID())) { // Pass the child component as the new parent into dependencyBuilder - dependencyBuilder(dependencies, components, child, sbom, visited); + dependencyBuilder(dependencies, components, components_left, child, sbom, visited); } } } diff --git a/core/src/test/java/org/nvip/plugfest/tooling/integration/TranslatorDifferTest.java b/core/src/test/java/org/nvip/plugfest/tooling/integration/TranslatorDifferTest.java index 89e9abf0..dce02b6d 100644 --- a/core/src/test/java/org/nvip/plugfest/tooling/integration/TranslatorDifferTest.java +++ b/core/src/test/java/org/nvip/plugfest/tooling/integration/TranslatorDifferTest.java @@ -38,7 +38,7 @@ public class TranslatorDifferTest { private static final int EXPECTED_CONFLICTS_SMALL_LARGE_SPDX = 447; - private static final int EXPECTED_CONFLICTS_SMALL_LARGE_CDX = 448; + private static final int EXPECTED_CONFLICTS_SMALL_LARGE_CDX = 449; private static final String EXPECTED_VERSION_MISTMATCH_SPDX_2_3_SPDX_2_2 = "Schema Version Mismatch:\n" + diff --git a/core/src/test/java/org/nvip/plugfest/tooling/translator/TranslatorPlugFestTest.java b/core/src/test/java/org/nvip/plugfest/tooling/translator/TranslatorPlugFestTest.java index 1b0e135e..0d2c6ef5 100644 --- a/core/src/test/java/org/nvip/plugfest/tooling/translator/TranslatorPlugFestTest.java +++ b/core/src/test/java/org/nvip/plugfest/tooling/translator/TranslatorPlugFestTest.java @@ -206,18 +206,18 @@ public void driver_translates_spdx_spec_version() { @Test public void driver_translates_xml_children() { - SBOM sbom = TranslatorPlugFest.translate(TEST_SPDX); + SBOM sbom = TranslatorPlugFest.translate(TEST_XML); assertNotNull(sbom); assertNotNull(sbom.getHeadUUID()); - assertEquals(135, sbom.getChildrenUUIDs(sbom.getHeadUUID()).size()); + assertEquals(17, sbom.getChildrenUUIDs(sbom.getHeadUUID()).size()); } @Test public void driver_translates_json_spec_children() { - SBOM sbom = TranslatorPlugFest.translate(TEST_SPDX); + SBOM sbom = TranslatorPlugFest.translate(TEST_JSON); assertNotNull(sbom); assertNotNull(sbom.getHeadUUID()); - assertEquals(135, sbom.getChildrenUUIDs(sbom.getHeadUUID()).size()); + assertEquals(11, sbom.getChildrenUUIDs(sbom.getHeadUUID()).size()); } @Test @@ -225,6 +225,6 @@ public void driver_translates_spdx_children() { SBOM sbom = TranslatorPlugFest.translate(TEST_SPDX); assertNotNull(sbom); assertNotNull(sbom.getHeadUUID()); - assertEquals(135, sbom.getChildrenUUIDs(sbom.getHeadUUID()).size()); + assertEquals(136, sbom.getChildrenUUIDs(sbom.getHeadUUID()).size()); } } diff --git a/core/src/test/java/org/nvip/plugfest/tooling/translator/TranslatorSPDXTest.java b/core/src/test/java/org/nvip/plugfest/tooling/translator/TranslatorSPDXTest.java index e351dfc9..b1c461a0 100644 --- a/core/src/test/java/org/nvip/plugfest/tooling/translator/TranslatorSPDXTest.java +++ b/core/src/test/java/org/nvip/plugfest/tooling/translator/TranslatorSPDXTest.java @@ -45,7 +45,7 @@ public void builder_makes_SBOM_test() throws IOException { Assertions.assertEquals(SBOMType.SPDX, test.getOriginFormat()); assertEquals("1", test.getSbomVersion()); assertEquals("SPDX-2.3", test.getSpecVersion()); - assertEquals(94, test.getAllComponents().size()); + assertEquals(17, test.getAllComponents().size()); } @Test @@ -66,7 +66,7 @@ public void builder_makes_large_SBOM_test() throws IOException { assertEquals(SBOMType.SPDX, test.getOriginFormat()); assertEquals("1", test.getSbomVersion()); assertEquals("SPDX-2.3", test.getSpecVersion()); - assertEquals(19037, test.getAllComponents().size()); + assertEquals(433, test.getAllComponents().size()); } diff --git a/doc/APICalls.txt b/doc/APICalls.txt index 4678afc7..516a4863 100644 --- a/doc/APICalls.txt +++ b/doc/APICalls.txt @@ -24,6 +24,12 @@ Responses: [ diffReportList: List comparisons: Map> } + ], + Response Code: 400 + Response Body: [ + { + message: String + } ] ] @@ -33,12 +39,12 @@ Request Type: POST Params: [ { Name: contents - Type: String + Type: JSON array of String Required: True }, { Name: fileName - Type: String + Type: Json array of String Required: True } ] @@ -51,5 +57,9 @@ Responses: [ testResults: ArrayList serialNumber: String } + ], + Response Code: 200 + Response Body: [ + {} ] ] diff --git a/doc/InsomniaAPIDocs.json b/doc/InsomniaAPIDocs.json new file mode 100644 index 00000000..c54c12da --- /dev/null +++ b/doc/InsomniaAPIDocs.json @@ -0,0 +1 @@ +{"_type":"export","__export_format":4,"__export_date":"2023-05-04T20:46:54.132Z","__export_source":"insomnia.desktop.app:v2023.2.0","resources":[{"_id":"req_41d4a35cd600461194600c57a192518c","parentId":"wrk_b7a8bbc992dc446eb0d39bacd41e3a53","modified":1683231834426,"created":1683052510628,"url":"http://localhost:8080/plugfest/compare","name":"Compare example","description":"","method":"POST","body":{},"parameters":[{"id":"pair_4ea299bb50f04f1fa812459262b56630","name":"contents","value":"[\"SPDXVersion: SPDX-2.2\\nDataLicense: CC0-1.0\\nSPDXID: SPDXRef-DOCUMENT\\nDocumentName: hello\\nDocumentNamespace: https://swinslow.net/spdx-examples/example1/hello-v3\\nCreator: Person: Steve Winslow (steve@swinslow.net)\\nCreator: Tool: github.com/spdx/tools-golang/builder\\nCreator: Tool: github.com/spdx/tools-golang/idsearcher\\nCreated: 2021-08-26T01:46:00Z\\n\\n##### Package: hello\\n\\nPackageName: hello\\nSPDXID: SPDXRef-Package-hello\\nPackageDownloadLocation: git+https://github.com/swinslow/spdx-examples.git#example1/content\\nFilesAnalyzed: true\\nPackageVerificationCode: 9d20237bb72087e87069f96afb41c6ca2fa2a342\\nPackageLicenseConcluded: GPL-3.0-or-later\\nPackageLicenseInfoFromFiles: GPL-3.0-or-later\\nPackageLicenseDeclared: GPL-3.0-or-later\\nPackageCopyrightText: NOASSERTION\\n\\nRelationship: SPDXRef-DOCUMENT DESCRIBES SPDXRef-Package-hello\\n\\nFileName: /build/hello\\nSPDXID: SPDXRef-hello-binary\\nFileType: BINARY\\nFileChecksum: SHA1: 20291a81ef065ff891b537b64d4fdccaf6f5ac02\\nFileChecksum: SHA256: 83a33ff09648bb5fc5272baca88cf2b59fd81ac4cc6817b86998136af368708e\\nFileChecksum: MD5: 08a12c966d776864cc1eb41fd03c3c3d\\nLicenseConcluded: GPL-3.0-or-later\\nLicenseInfoInFile: NOASSERTION\\nFileCopyrightText: NOASSERTION\\n\\nFileName: /src/Makefile\\nSPDXID: SPDXRef-Makefile\\nFileType: SOURCE\\nFileChecksum: SHA1: 69a2e85696fff1865c3f0686d6c3824b59915c80\\nFileChecksum: SHA256: 5da19033ba058e322e21c90e6d6d859c90b1b544e7840859c12cae5da005e79c\\nFileChecksum: MD5: 559424589a4f3f75fd542810473d8bc1\\nLicenseConcluded: GPL-3.0-or-later\\nLicenseInfoInFile: GPL-3.0-or-later\\nFileCopyrightText: NOASSERTION\\n\\nFileName: /src/hello.c\\nSPDXID: SPDXRef-hello-src\\nFileType: SOURCE\\nFileChecksum: SHA1: 20862a6d08391d07d09344029533ec644fac6b21\\nFileChecksum: SHA256: b4e5ca56d1f9110ca94ed0bf4e6d9ac11c2186eb7cd95159c6fdb50e8db5a823\\nFileChecksum: MD5: 935054fe899ca782e11003bbae5e166c\\nLicenseConcluded: GPL-3.0-or-later\\nLicenseInfoInFile: GPL-3.0-or-later\\nFileCopyrightText: Copyright Contributors to the spdx-examples project.\\n\\nRelationship: SPDXRef-hello-binary GENERATED_FROM SPDXRef-hello-src\\nRelationship: SPDXRef-hello-binary GENERATED_FROM SPDXRef-Makefile\\nRelationship: SPDXRef-Makefile BUILD_TOOL_OF SPDXRef-Package-hello\"\n, \n\"SPDXVersion: SPDX-2.2\\nDataLicense: CC0-1.0\\nSPDXID: SPDXRef-DOCUMENT\\nDocumentName: hello\\nDocumentNamespace: https://swinslow.net/spdx-examples/example1/hello-v3\\nCreator: Person: Steve Winslow (steve@swinslow.net)\\nCreator: Tool: github.com/spdx/tools-golang/builder\\nCreator: Tool: github.com/spdx/tools-golang/idsearcher\\nCreated: 2021-08-26T01:46:00Z\\n\\n##### Package: hello\\n\\nPackageName: hello\\nSPDXID: SPDXRef-Package-hello\\nPackageDownloadLocation: git+https://github.com/swinslow/spdx-examples.git#example1/content\\nFilesAnalyzed: true\\nPackageVerificationCode: 9d20237bb72087e87069f96afb41c6ca2fa2a342\\nPackageLicenseConcluded: GPL-3.0-or-later\\nPackageLicenseInfoFromFiles: GPL-3.0-or-later\\nPackageLicenseDeclared: GPL-3.0-or-later\\nPackageCopyrightText: NOASSERTION\\n\\nRelationship: SPDXRef-DOCUMENT DESCRIBES SPDXRef-Package-hello\\n\\nFileName: /build/hello\\nSPDXID: SPDXRef-hello-binary\\nFileType: BINARY\\nFileChecksum: SHA1: 20291a81ef065ff891b537b64d4fdccaf6f5ac02\\nFileChecksum: SHA256: 83a33ff09648bb5fc5272baca88cf2b59fd81ac4cc6817b86998136af368708e\\nFileChecksum: MD5: 08a12c966d776864cc1eb41fd03c3c3d\\nLicenseConcluded: GPL-3.0-or-later\\nLicenseInfoInFile: NOASSERTION\\nFileCopyrightText: NOASSERTION\\n\\nFileName: /src/Makefile\\nSPDXID: SPDXRef-Makefile\\nFileType: SOURCE\\nFileChecksum: SHA1: 69a2e85696fff1865c3f0686d6c3824b59915c80\\nFileChecksum: SHA256: 5da19033ba058e322e21c90e6d6d859c90b1b544e7840859c12cae5da005e79c\\nFileChecksum: MD5: 559424589a4f3f75fd542810473d8bc1\\nLicenseConcluded: GPL-3.0-or-later\\nLicenseInfoInFile: GPL-3.0-or-later\\nFileCopyrightText: NOASSERTION\\n\\nFileName: /src/hello.c\\nSPDXID: SPDXRef-hello-src\\nFileType: SOURCE\\nFileChecksum: SHA1: 20862a6d08391d07d09344029533ec644fac6b21\\nFileChecksum: SHA256: b4e5ca56d1f9110ca94ed0bf4e6d9ac11c2186eb7cd95159c6fdb50e8db5a823\\nFileChecksum: MD5: 935054fe899ca782e11003bbae5e166c\\nLicenseConcluded: GPL-3.0-or-later\\nLicenseInfoInFile: GPL-3.0-or-later\\nFileCopyrightText: Copyright Contributors to the spdx-examples project.\\n\\nRelationship: SPDXRef-hello-binary GENERATED_FROM SPDXRef-hello-src\\nRelationship: SPDXRef-hello-binary GENERATED_FROM SPDXRef-Makefile\\nRelationship: SPDXRef-Makefile BUILD_TOOL_OF SPDXRef-Package-hello\"]","description":"the file contents as a JSON list","type":"text","multiline":true,"disabled":false},{"id":"pair_82769fd07ab94855b80c7f26a264a052","name":"fileNames","value":"[\"example.sbom.spdx\",\"example2.sbom.spdx\"]","description":"file names as a JSON list"}],"headers":[],"authentication":{},"metaSortKey":-1683052510628,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"wrk_b7a8bbc992dc446eb0d39bacd41e3a53","parentId":null,"modified":1683151282841,"created":1683052489785,"name":"plugfest API calls","description":"","scope":"collection","_type":"workspace"},{"_id":"req_05915793438a4abb991dc1f020ed2b6e","parentId":"wrk_b7a8bbc992dc446eb0d39bacd41e3a53","modified":1683149314358,"created":1683052527364,"url":"http://localhost:8080/plugfest/qa","name":"QA example","description":"","method":"POST","body":{},"parameters":[{"id":"pair_43450bc32c00409896fd0dbedbf0ba3a","name":"fileName","value":"example.sbom.spdx","description":"the name of the file to QA"},{"id":"pair_15276cf83c40415abbeaf1d9ec1e1668","name":"contents","value":"SPDXVersion: SPDX-2.2\nDataLicense: CC0-1.0\nSPDXID: SPDXRef-DOCUMENT\nDocumentName: hello\nDocumentNamespace: https://swinslow.net/spdx-examples/example1/hello-v3\nCreator: Person: Steve Winslow (steve@swinslow.net)\nCreator: Tool: github.com/spdx/tools-golang/builder\nCreator: Tool: github.com/spdx/tools-golang/idsearcher\nCreated: 2021-08-26T01:46:00Z\n\n##### Package: hello\n\nPackageName: hello\nSPDXID: SPDXRef-Package-hello\nPackageDownloadLocation: git+https://github.com/swinslow/spdx-examples.git#example1/content\nFilesAnalyzed: true\nPackageVerificationCode: 9d20237bb72087e87069f96afb41c6ca2fa2a342\nPackageLicenseConcluded: GPL-3.0-or-later\nPackageLicenseInfoFromFiles: GPL-3.0-or-later\nPackageLicenseDeclared: GPL-3.0-or-later\nPackageCopyrightText: NOASSERTION\n\nRelationship: SPDXRef-DOCUMENT DESCRIBES SPDXRef-Package-hello\n\nFileName: /build/hello\nSPDXID: SPDXRef-hello-binary\nFileType: BINARY\nFileChecksum: SHA1: 20291a81ef065ff891b537b64d4fdccaf6f5ac02\nFileChecksum: SHA256: 83a33ff09648bb5fc5272baca88cf2b59fd81ac4cc6817b86998136af368708e\nFileChecksum: MD5: 08a12c966d776864cc1eb41fd03c3c3d\nLicenseConcluded: GPL-3.0-or-later\nLicenseInfoInFile: NOASSERTION\nFileCopyrightText: NOASSERTION\n\nFileName: /src/Makefile\nSPDXID: SPDXRef-Makefile\nFileType: SOURCE\nFileChecksum: SHA1: 69a2e85696fff1865c3f0686d6c3824b59915c80\nFileChecksum: SHA256: 5da19033ba058e322e21c90e6d6d859c90b1b544e7840859c12cae5da005e79c\nFileChecksum: MD5: 559424589a4f3f75fd542810473d8bc1\nLicenseConcluded: GPL-3.0-or-later\nLicenseInfoInFile: GPL-3.0-or-later\nFileCopyrightText: NOASSERTION\n\nFileName: /src/hello.c\nSPDXID: SPDXRef-hello-src\nFileType: SOURCE\nFileChecksum: SHA1: 20862a6d08391d07d09344029533ec644fac6b21\nFileChecksum: SHA256: b4e5ca56d1f9110ca94ed0bf4e6d9ac11c2186eb7cd95159c6fdb50e8db5a823\nFileChecksum: MD5: 935054fe899ca782e11003bbae5e166c\nLicenseConcluded: GPL-3.0-or-later\nLicenseInfoInFile: GPL-3.0-or-later\nFileCopyrightText: Copyright Contributors to the spdx-examples project.\n\nRelationship: SPDXRef-hello-binary GENERATED_FROM SPDXRef-hello-src\nRelationship: SPDXRef-hello-binary GENERATED_FROM SPDXRef-Makefile\nRelationship: SPDXRef-Makefile BUILD_TOOL_OF SPDXRef-Package-hello","description":"the content of the file to QA","type":"text","multiline":true}],"headers":[],"authentication":{},"metaSortKey":-1683052527364,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_eb96ff42975b49bf879f675593b46299","parentId":"wrk_b7a8bbc992dc446eb0d39bacd41e3a53","modified":1683232748798,"created":1683231506918,"url":"http://localhost:8080/plugfest/qa","name":"QA explanation","description":"","method":"EXPL","body":{},"parameters":[{"id":"pair_ca932d296f8e403ebf7336dda3214598","name":"fileName","value":" \n\n(url encoded)\n\n-----\nIf the extension of the file is not recognised (currently .spdx, .json, and .xml) then a 200 with no content will be returned","description":"","type":"text","multiline":true},{"id":"pair_ad8f7948f274456386365a94d02bbf9b","name":"fileContents","value":"\n\nMake sure the entire field is URL encoded","description":"","type":"text","multiline":true}],"headers":[],"authentication":{},"metaSortKey":-1683231506918,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_5aab46a4c5c74cb7aa45c28757f9b270","parentId":"wrk_b7a8bbc992dc446eb0d39bacd41e3a53","modified":1683233182925,"created":1683231685699,"url":"http://localhost:8080/plugfest/compare","name":"Compare explanation","description":"","method":"EXPL","body":{},"parameters":[{"id":"pair_77185e51c2d64fe99a1993f6a45a968e","name":"fileNames","value":"[\"\"]\n\nMake sure to URL encode this entire string. \n\nIf there are less fileNames than contents you will get a 500\n\nThe first SBOM passed is considered the reference SBOM which all of the other SBOMs will be compared to. ","description":"","type":"text","multiline":true},{"id":"pair_ca0ee522c9684ad685a4da41ef4a874d","name":"contents","value":"[\"\", \"\"]\n\nThe first SBOM passed is considered the reference SBOM which all of the other SBOMs will be compared to. \n\nThere must be 2 or more SBOMs passed, 0 or 1 will get a 400\n\nThe contents should include the entire content of the files. Make sure to escape all \\n (new line) characters in the SBOM before putting them the above JSON list and URL encoding the entire thing","description":"","type":"text","multiline":true}],"headers":[],"authentication":{},"metaSortKey":-1683052518996,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_2d85899e32f347b8bc7580e35f37530a","parentId":"wrk_b7a8bbc992dc446eb0d39bacd41e3a53","modified":1683232832080,"created":1683232422965,"url":"http://localhost:8080/plugfest/qa","name":"QA error:badExtension","description":"","method":"POST","body":{},"parameters":[{"id":"pair_58cf6e0a1abd4babb5533ca2de2595ac","name":"fileName","value":"example.rand","description":"unknown extension"},{"id":"pair_da83551896584d1c8231ddc3d5ab53f8","name":"contents","value":"SPDXVersion: SPDX-2.2\nDataLicense: CC0-1.0\nSPDXID: SPDXRef-DOCUMENT\nDocumentName: hello\nDocumentNamespace: https://swinslow.net/spdx-examples/example1/hello-v3\nCreator: Person: Steve Winslow (steve@swinslow.net)\nCreator: Tool: github.com/spdx/tools-golang/builder\nCreator: Tool: github.com/spdx/tools-golang/idsearcher\nCreated: 2021-08-26T01:46:00Z\n\n##### Package: hello\n\nPackageName: hello\nSPDXID: SPDXRef-Package-hello\nPackageDownloadLocation: git+https://github.com/swinslow/spdx-examples.git#example1/content\nFilesAnalyzed: true\nPackageVerificationCode: 9d20237bb72087e87069f96afb41c6ca2fa2a342\nPackageLicenseConcluded: GPL-3.0-or-later\nPackageLicenseInfoFromFiles: GPL-3.0-or-later\nPackageLicenseDeclared: GPL-3.0-or-later\nPackageCopyrightText: NOASSERTION\n\nRelationship: SPDXRef-DOCUMENT DESCRIBES SPDXRef-Package-hello\n\nFileName: /build/hello\nSPDXID: SPDXRef-hello-binary\nFileType: BINARY\nFileChecksum: SHA1: 20291a81ef065ff891b537b64d4fdccaf6f5ac02\nFileChecksum: SHA256: 83a33ff09648bb5fc5272baca88cf2b59fd81ac4cc6817b86998136af368708e\nFileChecksum: MD5: 08a12c966d776864cc1eb41fd03c3c3d\nLicenseConcluded: GPL-3.0-or-later\nLicenseInfoInFile: NOASSERTION\nFileCopyrightText: NOASSERTION\n\nFileName: /src/Makefile\nSPDXID: SPDXRef-Makefile\nFileType: SOURCE\nFileChecksum: SHA1: 69a2e85696fff1865c3f0686d6c3824b59915c80\nFileChecksum: SHA256: 5da19033ba058e322e21c90e6d6d859c90b1b544e7840859c12cae5da005e79c\nFileChecksum: MD5: 559424589a4f3f75fd542810473d8bc1\nLicenseConcluded: GPL-3.0-or-later\nLicenseInfoInFile: GPL-3.0-or-later\nFileCopyrightText: NOASSERTION\n\nFileName: /src/hello.c\nSPDXID: SPDXRef-hello-src\nFileType: SOURCE\nFileChecksum: SHA1: 20862a6d08391d07d09344029533ec644fac6b21\nFileChecksum: SHA256: b4e5ca56d1f9110ca94ed0bf4e6d9ac11c2186eb7cd95159c6fdb50e8db5a823\nFileChecksum: MD5: 935054fe899ca782e11003bbae5e166c\nLicenseConcluded: GPL-3.0-or-later\nLicenseInfoInFile: GPL-3.0-or-later\nFileCopyrightText: Copyright Contributors to the spdx-examples project.\n\nRelationship: SPDXRef-hello-binary GENERATED_FROM SPDXRef-hello-src\nRelationship: SPDXRef-hello-binary GENERATED_FROM SPDXRef-Makefile\nRelationship: SPDXRef-Makefile BUILD_TOOL_OF SPDXRef-Package-hello","description":"valid sbom","type":"text","multiline":true}],"headers":[],"authentication":{},"metaSortKey":-1683052523180,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_f9eaf0c6e6cd4622b62eff53bc409621","parentId":"wrk_b7a8bbc992dc446eb0d39bacd41e3a53","modified":1683232987605,"created":1683232929338,"url":"http://localhost:8080/plugfest/compare","name":"Compare error:notEnoughSBOM","description":"","method":"POST","body":{},"parameters":[{"id":"pair_4ea299bb50f04f1fa812459262b56630","name":"contents","value":"[\"SPDXVersion: SPDX-2.2\\nDataLicense: CC0-1.0\\nSPDXID: SPDXRef-DOCUMENT\\nDocumentName: hello\\nDocumentNamespace: https://swinslow.net/spdx-examples/example1/hello-v3\\nCreator: Person: Steve Winslow (steve@swinslow.net)\\nCreator: Tool: github.com/spdx/tools-golang/builder\\nCreator: Tool: github.com/spdx/tools-golang/idsearcher\\nCreated: 2021-08-26T01:46:00Z\\n\\n##### Package: hello\\n\\nPackageName: hello\\nSPDXID: SPDXRef-Package-hello\\nPackageDownloadLocation: git+https://github.com/swinslow/spdx-examples.git#example1/content\\nFilesAnalyzed: true\\nPackageVerificationCode: 9d20237bb72087e87069f96afb41c6ca2fa2a342\\nPackageLicenseConcluded: GPL-3.0-or-later\\nPackageLicenseInfoFromFiles: GPL-3.0-or-later\\nPackageLicenseDeclared: GPL-3.0-or-later\\nPackageCopyrightText: NOASSERTION\\n\\nRelationship: SPDXRef-DOCUMENT DESCRIBES SPDXRef-Package-hello\\n\\nFileName: /build/hello\\nSPDXID: SPDXRef-hello-binary\\nFileType: BINARY\\nFileChecksum: SHA1: 20291a81ef065ff891b537b64d4fdccaf6f5ac02\\nFileChecksum: SHA256: 83a33ff09648bb5fc5272baca88cf2b59fd81ac4cc6817b86998136af368708e\\nFileChecksum: MD5: 08a12c966d776864cc1eb41fd03c3c3d\\nLicenseConcluded: GPL-3.0-or-later\\nLicenseInfoInFile: NOASSERTION\\nFileCopyrightText: NOASSERTION\\n\\nFileName: /src/Makefile\\nSPDXID: SPDXRef-Makefile\\nFileType: SOURCE\\nFileChecksum: SHA1: 69a2e85696fff1865c3f0686d6c3824b59915c80\\nFileChecksum: SHA256: 5da19033ba058e322e21c90e6d6d859c90b1b544e7840859c12cae5da005e79c\\nFileChecksum: MD5: 559424589a4f3f75fd542810473d8bc1\\nLicenseConcluded: GPL-3.0-or-later\\nLicenseInfoInFile: GPL-3.0-or-later\\nFileCopyrightText: NOASSERTION\\n\\nFileName: /src/hello.c\\nSPDXID: SPDXRef-hello-src\\nFileType: SOURCE\\nFileChecksum: SHA1: 20862a6d08391d07d09344029533ec644fac6b21\\nFileChecksum: SHA256: b4e5ca56d1f9110ca94ed0bf4e6d9ac11c2186eb7cd95159c6fdb50e8db5a823\\nFileChecksum: MD5: 935054fe899ca782e11003bbae5e166c\\nLicenseConcluded: GPL-3.0-or-later\\nLicenseInfoInFile: GPL-3.0-or-later\\nFileCopyrightText: Copyright Contributors to the spdx-examples project.\\n\\nRelationship: SPDXRef-hello-binary GENERATED_FROM SPDXRef-hello-src\\nRelationship: SPDXRef-hello-binary GENERATED_FROM SPDXRef-Makefile\\nRelationship: SPDXRef-Makefile BUILD_TOOL_OF SPDXRef-Package-hello\"]","description":"only one SBOM","type":"text","multiline":true,"disabled":false},{"id":"pair_82769fd07ab94855b80c7f26a264a052","name":"fileNames","value":"[\"example.sbom.spdx\"]","description":"only one SBOM"}],"headers":[],"authentication":{},"metaSortKey":-1683052510578,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_f059dc2c7b4d4a4d91671bc18061419b","parentId":"wrk_b7a8bbc992dc446eb0d39bacd41e3a53","modified":1683233086775,"created":1683233020761,"url":"http://localhost:8080/plugfest/compare","name":"Compare error:notEscapedSBOM","description":"","method":"POST","body":{},"parameters":[{"id":"pair_4ea299bb50f04f1fa812459262b56630","name":"contents","value":"[\"SPDXVersion: SPDX-2.2\nDataLicense: CC0-1.0\nSPDXID: SPDXRef-DOCUMENT\nDocumentName: hello\nDocumentNamespace: https://swinslow.net/spdx-examples/example1/hello-v3\nCreator: Person: Steve Winslow (steve@swinslow.net)\nCreator: Tool: github.com/spdx/tools-golang/builder\nCreator: Tool: github.com/spdx/tools-golang/idsearcher\nCreated: 2021-08-26T01:46:00Z\n\n##### Package: hello\n\nPackageName: hello\nSPDXID: SPDXRef-Package-hello\nPackageDownloadLocation: git+https://github.com/swinslow/spdx-examples.git#example1/content\nFilesAnalyzed: true\nPackageVerificationCode: 9d20237bb72087e87069f96afb41c6ca2fa2a342\nPackageLicenseConcluded: GPL-3.0-or-later\nPackageLicenseInfoFromFiles: GPL-3.0-or-later\nPackageLicenseDeclared: GPL-3.0-or-later\nPackageCopyrightText: NOASSERTION\n\nRelationship: SPDXRef-DOCUMENT DESCRIBES SPDXRef-Package-hello\n\nFileName: /build/hello\nSPDXID: SPDXRef-hello-binary\nFileType: BINARY\nFileChecksum: SHA1: 20291a81ef065ff891b537b64d4fdccaf6f5ac02\nFileChecksum: SHA256: 83a33ff09648bb5fc5272baca88cf2b59fd81ac4cc6817b86998136af368708e\nFileChecksum: MD5: 08a12c966d776864cc1eb41fd03c3c3d\nLicenseConcluded: GPL-3.0-or-later\nLicenseInfoInFile: NOASSERTION\nFileCopyrightText: NOASSERTION\n\nFileName: /src/Makefile\nSPDXID: SPDXRef-Makefile\nFileType: SOURCE\nFileChecksum: SHA1: 69a2e85696fff1865c3f0686d6c3824b59915c80\nFileChecksum: SHA256: 5da19033ba058e322e21c90e6d6d859c90b1b544e7840859c12cae5da005e79c\nFileChecksum: MD5: 559424589a4f3f75fd542810473d8bc1\nLicenseConcluded: GPL-3.0-or-later\nLicenseInfoInFile: GPL-3.0-or-later\nFileCopyrightText: NOASSERTION\n\nFileName: /src/hello.c\nSPDXID: SPDXRef-hello-src\nFileType: SOURCE\nFileChecksum: SHA1: 20862a6d08391d07d09344029533ec644fac6b21\nFileChecksum: SHA256: b4e5ca56d1f9110ca94ed0bf4e6d9ac11c2186eb7cd95159c6fdb50e8db5a823\nFileChecksum: MD5: 935054fe899ca782e11003bbae5e166c\nLicenseConcluded: GPL-3.0-or-later\nLicenseInfoInFile: GPL-3.0-or-later\nFileCopyrightText: Copyright Contributors to the spdx-examples project.\n\nRelationship: SPDXRef-hello-binary GENERATED_FROM SPDXRef-hello-src\nRelationship: SPDXRef-hello-binary GENERATED_FROM SPDXRef-Makefile\nRelationship: SPDXRef-Makefile BUILD_TOOL_OF SPDXRef-Package-hello\",\n\"SPDXVersion: SPDX-2.2\nDataLicense: CC0-1.0\nSPDXID: SPDXRef-DOCUMENT\nDocumentName: hello\nDocumentNamespace: https://swinslow.net/spdx-examples/example1/hello-v3\nCreator: Person: Steve Winslow (steve@swinslow.net)\nCreator: Tool: github.com/spdx/tools-golang/builder\nCreator: Tool: github.com/spdx/tools-golang/idsearcher\nCreated: 2021-08-26T01:46:00Z\n\n##### Package: hello\n\nPackageName: hello\nSPDXID: SPDXRef-Package-hello\nPackageDownloadLocation: git+https://github.com/swinslow/spdx-examples.git#example1/content\nFilesAnalyzed: true\nPackageVerificationCode: 9d20237bb72087e87069f96afb41c6ca2fa2a342\nPackageLicenseConcluded: GPL-3.0-or-later\nPackageLicenseInfoFromFiles: GPL-3.0-or-later\nPackageLicenseDeclared: GPL-3.0-or-later\nPackageCopyrightText: NOASSERTION\n\nRelationship: SPDXRef-DOCUMENT DESCRIBES SPDXRef-Package-hello\n\nFileName: /build/hello\nSPDXID: SPDXRef-hello-binary\nFileType: BINARY\nFileChecksum: SHA1: 20291a81ef065ff891b537b64d4fdccaf6f5ac02\nFileChecksum: SHA256: 83a33ff09648bb5fc5272baca88cf2b59fd81ac4cc6817b86998136af368708e\nFileChecksum: MD5: 08a12c966d776864cc1eb41fd03c3c3d\nLicenseConcluded: GPL-3.0-or-later\nLicenseInfoInFile: NOASSERTION\nFileCopyrightText: NOASSERTION\n\nFileName: /src/Makefile\nSPDXID: SPDXRef-Makefile\nFileType: SOURCE\nFileChecksum: SHA1: 69a2e85696fff1865c3f0686d6c3824b59915c80\nFileChecksum: SHA256: 5da19033ba058e322e21c90e6d6d859c90b1b544e7840859c12cae5da005e79c\nFileChecksum: MD5: 559424589a4f3f75fd542810473d8bc1\nLicenseConcluded: GPL-3.0-or-later\nLicenseInfoInFile: GPL-3.0-or-later\nFileCopyrightText: NOASSERTION\n\nFileName: /src/hello.c\nSPDXID: SPDXRef-hello-src\nFileType: SOURCE\nFileChecksum: SHA1: 20862a6d08391d07d09344029533ec644fac6b21\nFileChecksum: SHA256: b4e5ca56d1f9110ca94ed0bf4e6d9ac11c2186eb7cd95159c6fdb50e8db5a823\nFileChecksum: MD5: 935054fe899ca782e11003bbae5e166c\nLicenseConcluded: GPL-3.0-or-later\nLicenseInfoInFile: GPL-3.0-or-later\nFileCopyrightText: Copyright Contributors to the spdx-examples project.\n\nRelationship: SPDXRef-hello-binary GENERATED_FROM SPDXRef-hello-src\nRelationship: SPDXRef-hello-binary GENERATED_FROM SPDXRef-Makefile\nRelationship: SPDXRef-Makefile BUILD_TOOL_OF SPDXRef-Package-hello\"]","description":"not escaped \\n","type":"text","multiline":true,"disabled":false},{"id":"pair_82769fd07ab94855b80c7f26a264a052","name":"fileNames","value":"[\"example.sbom.spdx\", \"example.sbom.spdx\"]","description":"fine"}],"headers":[],"authentication":{},"metaSortKey":-1683052510528,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"env_6631ae80f12c1780dd9f40b0391758762ef96cb4","parentId":"wrk_b7a8bbc992dc446eb0d39bacd41e3a53","modified":1683052489792,"created":1683052489792,"name":"Base Environment","data":{},"dataPropertyOrder":null,"color":null,"isPrivate":false,"metaSortKey":1683052489792,"_type":"environment"},{"_id":"jar_6631ae80f12c1780dd9f40b0391758762ef96cb4","parentId":"wrk_b7a8bbc992dc446eb0d39bacd41e3a53","modified":1683052489795,"created":1683052489795,"name":"Default Jar","cookies":[],"_type":"cookie_jar"},{"_id":"spc_943a7301989447de9de9980993c10823","parentId":"wrk_b7a8bbc992dc446eb0d39bacd41e3a53","modified":1683052489786,"created":1683052489786,"fileName":"My Collection","contents":"","contentType":"yaml","_type":"api_spec"}]} \ No newline at end of file diff --git a/doc/README.md b/doc/README.md new file mode 100644 index 00000000..799c980c --- /dev/null +++ b/doc/README.md @@ -0,0 +1,5 @@ +#Insomnia Instructions + +To open the insomnia file download insomnia from https://insomnia.rest/download/ + +When the program opens go to Application -> preferences -> data -> import and drag the JSON file in. \ No newline at end of file diff --git a/gui/app.js b/gui/app.js index a4ba989d..ee57e338 100644 --- a/gui/app.js +++ b/gui/app.js @@ -8,14 +8,18 @@ let filePath = undefined; function createWindow () { mainWindow = new BrowserWindow({ - width: 800, - height: 600, + minWidth: 800, + minHeight: 600, + show: false, webPreferences: { nodeIntegration: true, contextIsolation: false, } }) + mainWindow.maximize(); + mainWindow.show(); + mainWindow.loadURL( url.format({ pathname: path.join(__dirname, 'dist/app/index.html'), diff --git a/gui/src/app/app.module.ts b/gui/src/app/app.module.ts index d32a304b..b5e8d24e 100644 --- a/gui/src/app/app.module.ts +++ b/gui/src/app/app.module.ts @@ -8,6 +8,8 @@ import { MatInputModule } from '@angular/material/input'; import { MatSelectModule } from '@angular/material/select'; import {MatProgressSpinnerModule} from '@angular/material/progress-spinner'; + +import { ModalComponent } from './shared/components/modal/modal.component'; import { AppComponent } from './app.component'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { SharedModule } from './shared/shared.module'; @@ -47,7 +49,8 @@ import { MatStepperModule } from '@angular/material/stepper'; ComparisonDialogComponent, MetricsSidePanelComponent, MetricsBodyComponent, - MetricsMainComponent + MetricsMainComponent, + ModalComponent ], imports: [ BrowserModule, diff --git a/gui/src/app/features/comparison/comparison-page/comparison-page.component.css b/gui/src/app/features/comparison/comparison-page/comparison-page.component.css index 252c760a..ce8f2d11 100644 --- a/gui/src/app/features/comparison/comparison-page/comparison-page.component.css +++ b/gui/src/app/features/comparison/comparison-page/comparison-page.component.css @@ -4,28 +4,47 @@ height: 84vh; } -.addSBOMScenter { +.content { + gap: 25px; + overflow-y: scroll; + overflow-x: hidden; + height: 100%; + width: auto; + align-items: center; display: flex; - justify-content: center; - margin-top: auto; - margin-bottom: 10px; + flex-direction: column; + justify-content: space-between; } -.content { +.tab-content{ gap: 25px; - overflow: hidden; height: 100%; align-items: center; + display: flex; + flex-direction: column; + justify-content: space-between; + flex-shrink: 0; } -.content > .header { +.tab-content > .header { margin-top: 25px; + align-items: center; + gap: 5px; +} + +.select-options { + margin-left: auto; + margin-right: 10px; + align-items: center; + gap: 5px; } .selection { width: 90%; height: fit-content; gap: 15px; + max-height: calc(100% - 110px); + overflow: hidden; } .selection > .content { @@ -51,3 +70,38 @@ margin-left: auto; cursor: pointer; } +.app-side-panel { + overflow: hidden; +} + +.app-side-panel__content { + height: 100%; + position: relative; +} + +.app-side-panel__content > .content { + height: 100%; +} + +.addSBOMScenter { + margin-bottom: 10px; +} + +.dropdown { + padding: 10px; + width: 100%; +} + +.item { + display: flex; + overflow-y: scroll; +} + +.item > .name { + width: 75px; +} + +.loading { + width: 100%; + height: 100%; +} \ No newline at end of file diff --git a/gui/src/app/features/comparison/comparison-page/comparison-page.component.html b/gui/src/app/features/comparison/comparison-page/comparison-page.component.html index 17a6148f..200da3d1 100644 --- a/gui/src/app/features/comparison/comparison-page/comparison-page.component.html +++ b/gui/src/app/features/comparison/comparison-page/comparison-page.component.html @@ -1,21 +1,28 @@
-
+
SBOM LIST
-
+
Target
-
+
-
Comparisons
+
+
Comparisons
+
+ Select All + Unselect All +
+
{{ getSBOMAlias(sbom) }} - +
@@ -43,5 +49,21 @@ > - +
+ +
+ +
+ + +
+ SBOM DETAILS +
+
+
+
File Path:
+
{{this.sbomInfoOpened}}
+
+
+
\ No newline at end of file diff --git a/gui/src/app/features/comparison/comparison-page/comparison-page.component.ts b/gui/src/app/features/comparison/comparison-page/comparison-page.component.ts index c678d24f..f960d3dc 100644 --- a/gui/src/app/features/comparison/comparison-page/comparison-page.component.ts +++ b/gui/src/app/features/comparison/comparison-page/comparison-page.component.ts @@ -19,9 +19,10 @@ import { DataHandlerService } from "@services/data-handler.service"; export class ComparisonPageComponent { collapsed: boolean = false; + sbomInfoOpened: string | null = null; + sboms: string[] = ["a", "b"]; targetSbom!: string; - compareTo!: string; constructor( public dialog: MatDialog, @@ -34,14 +35,40 @@ export class ComparisonPageComponent { this.targetSbom = $event; } - /** @TODO replace with inserting the associated diff report */ - selectComparison(value: string) { - this.compareTo = value; - } - // Display diff report compare() { - this.dataHandler.Compare(this.targetSbom, [this.compareTo]); + + if(!this.targetSbom) + return; + + if(this.IsLoadingComparison()) + return; + + // Get all the checkboxes in the DOM + const checkboxes = document.querySelectorAll('input[type="checkbox"]'); + + // Loop through each checkbox and check if it's selected and not disabled + const selectedCheckboxes = []; + for (let i = 0; i < checkboxes.length; i++) { + const checkbox = checkboxes[i] as HTMLInputElement; + if (checkbox.checked && !checkbox.disabled) { + selectedCheckboxes.push(checkbox.value); + } + } + + if(selectedCheckboxes.length === 0) + return; + + this.dataHandler.Compare(this.targetSbom, selectedCheckboxes); + } + + setAllSelected(value: boolean) { + const checkboxes = document.querySelectorAll('input[type="checkbox"]'); + + for (let i = 0; i < checkboxes.length; i++) { + const checkbox = checkboxes[i] as HTMLInputElement; + checkbox.checked = value; + } } openDialog(sbom: SBOM): void { @@ -54,6 +81,20 @@ export class ComparisonPageComponent { return this.dataHandler.GetValidSBOMs(); } + GetDropdownSBOMs() { + let keys = this.GetValidSBOMs(); + let data: { [id: string]: Object | null } = {}; + + for(let i = 0; i < keys.length; i++) { + let key = keys[i]; + let value = this.getSBOMAlias(key) as string; + + data[key] = value; + } + + return data; + } + getSBOMAlias(path: string) { return this.dataHandler.getSBOMAlias(path); } @@ -61,6 +102,14 @@ export class ComparisonPageComponent { GetComparison() { return this.dataHandler.comparison; } + + getSBOMInfo(path: string) { + this.sbomInfoOpened = path; + } + + IsLoadingComparison(): boolean { + return this.dataHandler.IsLoadingComparison(); + } } @Component({ @@ -85,4 +134,4 @@ export class ComparisonDialogComponent { public dialogRef: MatDialogRef, @Inject(MAT_DIALOG_DATA) public data: SBOM ) {} -} +} \ No newline at end of file diff --git a/gui/src/app/features/comparison/comparison.ts b/gui/src/app/features/comparison/comparison.ts index f1dd1081..332eb9f1 100644 --- a/gui/src/app/features/comparison/comparison.ts +++ b/gui/src/app/features/comparison/comparison.ts @@ -1,13 +1,11 @@ /** Author: Tina DiLorenzo */ +import { SBOM } from "@models/sbom"; /** @TODO */ // 1. CREATE A CONSTRUCTOR TAKING IN A JSON OBJECT to create comparisons // 2. CONVERT JSON // - readonly arrays TO SETS // - convert keys/values to maps -interface SBOM { - name: string; -} interface SBOMConflict { conflictTypes?: readonly any[]; @@ -37,7 +35,7 @@ export interface attributes { licenses?: readonly string[] | readonly [] | null; conflicts?: any[] | []; componentName?: string | null; - appearances?: readonly Number[] | readonly [] + appearances?: readonly Number[] componentVersion?: readonly Number[] | readonly [] | string; packageManager?: string | null; } @@ -62,7 +60,7 @@ interface UniqueIdOccurrence { } export interface Comparison { - targetSbom?: SBOM; + targetSBOM?: SBOM; diffReports: readonly DiffReport[]; comparisons: {[key: string]: readonly ComponentVersion[]}; } diff --git a/gui/src/app/features/comparison/comparison/comparison.component.css b/gui/src/app/features/comparison/comparison/comparison.component.css index ce717ba7..59a2b6dd 100644 --- a/gui/src/app/features/comparison/comparison/comparison.component.css +++ b/gui/src/app/features/comparison/comparison/comparison.component.css @@ -1,9 +1,9 @@ .comparison .path { - margin-left: 5px; + margin-left: 5px; } .comparison .path:hover { - text-decoration: underline; - cursor: pointer; + text-decoration: underline; + cursor: pointer; } :host { @@ -19,15 +19,11 @@ padding: 30px; margin-top: 20px; border-radius: 15px; - background-color: #1B1E21; + background-color: #1b1e21; display: flex; flex-direction: column; } -.comparison .app-accordion { - margin-bottom: 10px; -} - .header { display: flex; justify-content: left; @@ -37,4 +33,10 @@ margin-bottom: 10px; } +.buttons > app-button { + margin-left: 3px; +} +#attribute-body { + padding-top: 20px; +} diff --git a/gui/src/app/features/comparison/comparison/comparison.component.html b/gui/src/app/features/comparison/comparison/comparison.component.html index 2df5b20a..8fffd122 100644 --- a/gui/src/app/features/comparison/comparison/comparison.component.html +++ b/gui/src/app/features/comparison/comparison/comparison.component.html @@ -1,4 +1,14 @@ -{{filtered ? 'Show All' : 'Show Conflicts'}} +
+ {{ + filtered ? "Show All" : "Show Conflicts" + }} + + {{ targetMarked ? "Unmark" : "Mark" }} Target SBOM Values +
- {{ key }} + + {{ targetSBOM[key] && targetMarked ? "*" : "" }} {{ key }} +
- {{ version.componentVersion }} + + {{ + targetSBOM[pathTitles[1]] && targetMarked + ? targetSBOM[pathTitles[1]].indexOf(version.componentVersion) !== + -1 + ? "*" + : "" + : "" + }} + {{ version.componentVersion }} +
{{ attribute.key }} -
+
- {{ value.uniqueId }} + + {{ + value.appearances && targetMarked + ? value.appearances.indexOf(0) !== -1 + ? "*" + : "" + : "" + }} + {{ value.uniqueId }} +
- SBOM {{ sbom }} + {{ getAliasFromIndex(sbom) }}
diff --git a/gui/src/app/features/comparison/comparison/comparison.component.ts b/gui/src/app/features/comparison/comparison/comparison.component.ts index baa025fe..e625bbd2 100644 --- a/gui/src/app/features/comparison/comparison/comparison.component.ts +++ b/gui/src/app/features/comparison/comparison/comparison.component.ts @@ -1,6 +1,8 @@ -/** @Author Tina DiLorenzo */ +/** @Author Tina DiLorenzo, Justin Jantzi */ import { Component, Input, OnChanges, SimpleChanges } from "@angular/core"; import { Comparison, ComponentVersion, attributes } from "../comparison"; +import { DataHandlerService } from "@services/data-handler.service"; +import { SBOMComponent } from "@models/sbom"; @Component({ selector: "app-comparison", @@ -9,28 +11,57 @@ import { Comparison, ComponentVersion, attributes } from "../comparison"; }) export class ComparisonComponent implements OnChanges { @Input() comparison: Comparison | null = null; - @Input() comparedSBOMS: string[] = []; display: { [key: string]: readonly ComponentVersion[] } = {}; + targetSBOM: { + [key: string]: string[]; + } = {}; keys: string[] = []; path: any[] = []; pathTitles: string[] = []; filtered: boolean = false; - attributes: { [key: string]: attributes[] } = { + targetMarked: boolean = false; + attributes: { [key: string]: attributes[] | undefined } = { purls: [], cpes: [], swids: [], }; + constructor(private dataHandler: DataHandlerService) {} + ngOnChanges(changes: SimpleChanges): void { if (this.comparison) { - this.display = {...this.comparison?.comparisons}; + this.display = { ...this.comparison?.comparisons }; this.path = [this.comparison.comparisons]; this.pathTitles = ["Components"]; this.keys = Object.keys(this.display); + this.getTargetSBOMValues(); } } + getAliasFromIndex(index: any) { + let path = this.dataHandler.lastSentFilePaths[index]; + return this.dataHandler.getSBOMAlias(path); + } + + getTargetSBOMValues() { + if (!this.comparison?.targetSBOM) { + return; + } + const targetSBOM = this.comparison.targetSBOM; + + targetSBOM.allComponents?.forEach((component) => { + if (component.name) { + if (!this.targetSBOM[component.name]) { + this.targetSBOM[component.name] = []; + } + if (component.version) { + this.targetSBOM[component.name].push(component.version); + } + } + }); + } + increaseDepth(newLocation: any, pathTitles: string) { this.path.push(newLocation); this.pathTitles.push(pathTitles); @@ -50,6 +81,18 @@ export class ComparisonComponent implements OnChanges { } } + getMatching(obj: ComponentVersion | attributes): string { + return obj.appearances + ? `${obj.appearances.length} / ${this.dataHandler.lastSentFilePaths.length} Contain` + : ""; + } + + matches(obj: ComponentVersion | attributes): boolean { + return obj.appearances + ? obj.appearances.length / this.dataHandler.lastSentFilePaths.length === 1 + : false; + } + filterConflicts() { if (!this.comparison) { return; @@ -60,9 +103,11 @@ export class ComparisonComponent implements OnChanges { (key) => { let isUnique = false; this.comparison?.comparisons[key].forEach((version) => { - // @TODO HOTFIX, REPLACE WHEN WE CAN ACTUALLY HAVE MORE THAN 2 SBOMS // Version is unique - if (version?.appearances?.length < 2) { + if ( + version?.appearances?.length < + this.dataHandler.lastSentFilePaths.length + ) { isUnique = true; } else { // @TODO HOTFIX, replace with attributeslist typescript is being annoying. @@ -71,14 +116,17 @@ export class ComparisonComponent implements OnChanges { ...Object.values(version.swids), ...Object.values(version.cpes), ]; - for (let attr of attributes) { + for (let attr of attributes) { if (attr.appearances) { - if (attr?.appearances?.length < 2) { + if ( + attr?.appearances?.length < + this.dataHandler.lastSentFilePaths.length + ) { isUnique = true; break; } } - }; + } } }); if (!isUnique) { @@ -90,12 +138,11 @@ export class ComparisonComponent implements OnChanges { this.display = this.display; this.keys = filtered; } else { - this.display = {...this.comparison?.comparisons}; + this.display = { ...this.comparison?.comparisons }; this.keys = Object.keys(this.comparison?.comparisons); - console.log(JSON.stringify(this.keys)) } - this.filtered = !this.filtered; - this.pathTitles = ["Components"]; - this.path = [this.display]; + this.filtered = !this.filtered; + this.pathTitles = ["Components"]; + this.path = [this.display]; } } diff --git a/gui/src/app/features/metrics/metrics-body/metrics-body.component.html b/gui/src/app/features/metrics/metrics-body/metrics-body.component.html index b9bbd925..eb28147a 100644 --- a/gui/src/app/features/metrics/metrics-body/metrics-body.component.html +++ b/gui/src/app/features/metrics/metrics-body/metrics-body.component.html @@ -5,7 +5,7 @@
- + {{testResult.component.name}} diff --git a/gui/src/app/features/metrics/metrics-body/metrics-body.component.ts b/gui/src/app/features/metrics/metrics-body/metrics-body.component.ts index a6ddde91..7d9fc884 100644 --- a/gui/src/app/features/metrics/metrics-body/metrics-body.component.ts +++ b/gui/src/app/features/metrics/metrics-body/metrics-body.component.ts @@ -17,4 +17,8 @@ export class MetricsBodyComponent { return this.handler.metrics[this.handler.selectedQualityReport]; } + + getPassedPercent(testResult: any) { + return (testResult.successfulTests / testResult.tests.length * 100).toFixed(2); + } } diff --git a/gui/src/app/features/metrics/metrics-side-panel/metrics-side-panel.component.css b/gui/src/app/features/metrics/metrics-side-panel/metrics-side-panel.component.css index a2a004f7..5c858bc1 100644 --- a/gui/src/app/features/metrics/metrics-side-panel/metrics-side-panel.component.css +++ b/gui/src/app/features/metrics/metrics-side-panel/metrics-side-panel.component.css @@ -3,24 +3,23 @@ overflow: hidden; height: 100%; align-items: center; -} - -.content > .header { + } + + .content > .header { margin-top: 25px; -} - -.selection { - margin-top: 30%; + } + + .selection { width: 90%; height: fit-content; gap: 15px; -} - -.selection > .content { + } + + .selection > .content { gap: 10px; -} - -.tab { + } + + .tab { position: absolute; right: 0; top: 50%; @@ -30,20 +29,20 @@ background-color: var(--accent); border-radius: 0px 16px 16px 0px; cursor: pointer; -} - -.tab > .text { + } + + .tab > .text { transform: rotate(-90deg); width: 100%; min-width: 100px; -} - -.compare-item { + } + + .compare-item { gap: 10px; width: 95%; -} - -.compare-item > .sbom { + } + + .compare-item > .sbom { padding: 0px 15px; background-color: var(--secondary); gap: 10px; @@ -51,9 +50,18 @@ height: 50px; border-radius: 12px; align-items: center; -} - -.compare-item > .sbom > * { + } + + .compare-item > .sbom > * { margin-left: auto; cursor: pointer; -} \ No newline at end of file +} + +.item { + display: flex; + overflow-y: scroll; +} + +.item > .name { + width: 75px; +} diff --git a/gui/src/app/features/metrics/metrics-side-panel/metrics-side-panel.component.html b/gui/src/app/features/metrics/metrics-side-panel/metrics-side-panel.component.html index 60239b02..faede51f 100644 --- a/gui/src/app/features/metrics/metrics-side-panel/metrics-side-panel.component.html +++ b/gui/src/app/features/metrics/metrics-side-panel/metrics-side-panel.component.html @@ -3,7 +3,7 @@
SBOM List
Select SBOM
-
+
{{ getSBOMAlias(item) }} - +
+ +
+ SBOM DETAILS +
+
+
+
File Path:
+
{{this.sbomInfoOpened}}
+
+
+
\ No newline at end of file diff --git a/gui/src/app/features/metrics/metrics-side-panel/metrics-side-panel.component.ts b/gui/src/app/features/metrics/metrics-side-panel/metrics-side-panel.component.ts index 4d5d897f..e630d7c5 100644 --- a/gui/src/app/features/metrics/metrics-side-panel/metrics-side-panel.component.ts +++ b/gui/src/app/features/metrics/metrics-side-panel/metrics-side-panel.component.ts @@ -1,4 +1,6 @@ import { Component } from '@angular/core'; +import { MatDialog } from '@angular/material/dialog'; +import { ModalComponent } from '@components/modal/modal.component'; import { DataHandlerService } from '@services/data-handler.service'; @Component({ @@ -8,7 +10,9 @@ import { DataHandlerService } from '@services/data-handler.service'; }) export class MetricsSidePanelComponent { - constructor(private handler: DataHandlerService) {} + sbomInfoOpened: string | null = null; + + constructor(private handler: DataHandlerService, public dialog: MatDialog) {} GetSBOMs() { return this.handler.GetValidSBOMs(); @@ -18,7 +22,11 @@ export class MetricsSidePanelComponent { this.handler.selectedQualityReport = bom; } - getSBOMAlias(path: string) { + getSBOMAlias(path: string) { return this.handler.getSBOMAlias(path); } + + getSBOMInfo(path: string) { + this.sbomInfoOpened = path; + } } diff --git a/gui/src/app/features/upload/upload.component.css b/gui/src/app/features/upload/upload.component.css index aa7a79b1..cac6014e 100644 --- a/gui/src/app/features/upload/upload.component.css +++ b/gui/src/app/features/upload/upload.component.css @@ -1,13 +1,15 @@ .upload-page { display: flex; + flex-direction: column; justify-content: center; align-items: center; - flex-direction: column; - height: 100%; text-align: center; + height: 100%; + margin-top: 2%; } .sbom { + align-items: center; padding: 0px 15px; color: white; background-color: var(--secondary); @@ -15,7 +17,6 @@ width: 100%; height: 50px; border-radius: 12px; - align-items: center; } .sbom > * { @@ -29,11 +30,22 @@ } .add-button { - margin-left: 0; - margin-top: 15px; + margin: 15px 0px; display: inline-block; } +.uploaded-files-container { + border: 1px solid #ccc; + border-radius: 10px; + background-color: var(--primary); + padding: 20px; + max-height: 30vh; + overflow-y: scroll; +} +.uploaded-files { + gap: 8px; +} + h1 { text-align: center; } diff --git a/gui/src/app/features/upload/upload.component.html b/gui/src/app/features/upload/upload.component.html index ae95d097..a1dd9781 100644 --- a/gui/src/app/features/upload/upload.component.html +++ b/gui/src/app/features/upload/upload.component.html @@ -1,18 +1,23 @@
-
+

UPLOAD SBOMS

-
-
- +
+
+
+ +
+ {{item.key}} + +
+
+
+
+
- {{item.key}} - + {{item}}
ADD SBOMS
-
- -
diff --git a/gui/src/app/features/upload/upload.component.ts b/gui/src/app/features/upload/upload.component.ts index df00c610..1ab0afae 100644 --- a/gui/src/app/features/upload/upload.component.ts +++ b/gui/src/app/features/upload/upload.component.ts @@ -1,4 +1,4 @@ -import { Component } from '@angular/core'; +import { Component, ViewChild, ElementRef } from '@angular/core'; import { DataHandlerService } from '@services/data-handler.service'; import { IpcRenderer } from 'electron'; @@ -10,7 +10,8 @@ import { IpcRenderer } from 'electron'; export class UploadComponent { private ipc!: IpcRenderer; isLoading = false; - + @ViewChild('container') container!: ElementRef; + constructor(private dataHandler: DataHandlerService) { if (window.require) { try { @@ -21,13 +22,14 @@ export class UploadComponent { } else { console.warn('App not running inside Electron!'); } - this.dataHandler.loading.subscribe(isLoading => this.isLoading = isLoading ) } browse() { this.ipc.invoke('selectFiles').then((files: string[]) => { if(files === undefined || files === null || files.length === 0) { + this.scrollToEnd(); return; + } this.dataHandler.AddFiles(files); @@ -35,15 +37,25 @@ export class UploadComponent { } ContainsFiles() { - return Object.keys(this.dataHandler.metrics).length > 0; + return Object.keys(this.dataHandler.metrics).length > 0 || this.GetLoadingFiles().length > 0; } GetFiles() { return this.dataHandler.metrics; } + GetLoadingFiles() { + return this.dataHandler.loadingFiles; + } + RemoveFile(file: string) { this.dataHandler.filePaths = this.dataHandler.filePaths.filter((x) => x != file); delete this.dataHandler.metrics[file]; } + + private scrollToEnd() { + setTimeout(() => { + this.container.nativeElement.scrollTop = this.container.nativeElement.scrollHeight; + }, 0); + } } diff --git a/gui/src/app/shared/components/accordion/accordion.component.html b/gui/src/app/shared/components/accordion/accordion.component.html index f078ba91..22074da3 100644 --- a/gui/src/app/shared/components/accordion/accordion.component.html +++ b/gui/src/app/shared/components/accordion/accordion.component.html @@ -1,16 +1,23 @@ - - - - - -
- {{item}} -
-
-
-
-
- -
-
\ No newline at end of file + + + + + +
+ {{ item }} +
+
+
+
+
+ +
+ diff --git a/gui/src/app/shared/components/components.css b/gui/src/app/shared/components/components.css index 80475ae5..1f07a21d 100644 --- a/gui/src/app/shared/components/components.css +++ b/gui/src/app/shared/components/components.css @@ -37,7 +37,7 @@ icon.ng-star-inserted { .dropdown { background: var(--secondary); color: white; - width: 25%; + width: 100%; margin: auto; height: 50px; align-items: center; @@ -77,3 +77,15 @@ WIP overriding style .accordion-title, .accordion-desc { color: white; } + +.mat-mdc-form-field { + --mat-mdc-form-field-floating-label-scale: 0.75; + /* display: inline-flex; */ + flex-direction: column; + width: 100%; + text-align: left; +} + +.mat-expansion-panel-header.mat-expanded:focus { + background-color: var(--gray); + } \ No newline at end of file diff --git a/gui/src/app/shared/components/dropdown/dropdown.component.html b/gui/src/app/shared/components/dropdown/dropdown.component.html index 168c590a..47546efd 100644 --- a/gui/src/app/shared/components/dropdown/dropdown.component.html +++ b/gui/src/app/shared/components/dropdown/dropdown.component.html @@ -2,9 +2,9 @@ - - {{item[key] || item}} - + + {{item.value}} +
diff --git a/gui/src/app/shared/components/dropdown/dropdown.component.ts b/gui/src/app/shared/components/dropdown/dropdown.component.ts index 2cebd89f..f3498c0b 100644 --- a/gui/src/app/shared/components/dropdown/dropdown.component.ts +++ b/gui/src/app/shared/components/dropdown/dropdown.component.ts @@ -1,4 +1,4 @@ -/** @Author Max Stein */ +/** @Author Max Stein, Justin Jantzi */ import { Component, Input, Output, EventEmitter, OnChanges, SimpleChanges } from '@angular/core'; @@ -8,7 +8,7 @@ import { Component, Input, Output, EventEmitter, OnChanges, SimpleChanges } from styleUrls: ['../components.css'] }) export class DropdownComponent { - @Input() options: any[] = []; + @Input() options: { [id: string]: Object | null } = {}; @Input() key: string = ''; @Output() selectionChange = new EventEmitter(); diff --git a/gui/src/app/shared/components/modal/modal.component.css b/gui/src/app/shared/components/modal/modal.component.css new file mode 100644 index 00000000..bb1100f6 --- /dev/null +++ b/gui/src/app/shared/components/modal/modal.component.css @@ -0,0 +1,46 @@ +.modal { + width: 100%; + height: 100%; + position: absolute; + left: 0; + top: 0; + display: flex; + align-items: center; + justify-content: center; + z-index: 100; +} + +.click-off { + width: 100%; + height: 100%; + background: #0000008e; +} + +.content { + width: auto; + height: auto; + display: flex; + flex-direction: column; + gap: 25px; + background: white; + position: absolute; + padding: 50px; +} + +.actions { + margin-top: auto; + display: flex; +} + +.title { + width: 100%; + display: flex; + justify-content: center; +} + +.close { + position: absolute; + right: 25px; + top: 25px; + cursor: pointer; +} \ No newline at end of file diff --git a/gui/src/app/shared/components/modal/modal.component.html b/gui/src/app/shared/components/modal/modal.component.html new file mode 100644 index 00000000..d8968105 --- /dev/null +++ b/gui/src/app/shared/components/modal/modal.component.html @@ -0,0 +1,15 @@ + \ No newline at end of file diff --git a/gui/src/app/shared/components/modal/modal.component.spec.ts b/gui/src/app/shared/components/modal/modal.component.spec.ts new file mode 100644 index 00000000..1b71c6b1 --- /dev/null +++ b/gui/src/app/shared/components/modal/modal.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ModalComponent } from './modal.component'; + +describe('ModalComponent', () => { + let component: ModalComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ ModalComponent ] + }) + .compileComponents(); + + fixture = TestBed.createComponent(ModalComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/gui/src/app/shared/components/modal/modal.component.ts b/gui/src/app/shared/components/modal/modal.component.ts new file mode 100644 index 00000000..267ee4a8 --- /dev/null +++ b/gui/src/app/shared/components/modal/modal.component.ts @@ -0,0 +1,22 @@ +/** @Author Justin Jantzi */ + +import { Component, Input, EventEmitter, Output } from '@angular/core'; +import { MatDialogRef } from '@angular/material/dialog'; + +@Component({ + selector: 'app-modal', + templateUrl: './modal.component.html', + styleUrls: ['./modal.component.css'] +}) + +export class ModalComponent { + @Input() opened: boolean = false; + @Output() close = new EventEmitter(); + + /** + * Closes the modal + */ + Close() { + this.close.emit(true); + } +} \ No newline at end of file diff --git a/gui/src/app/shared/models/sbom.ts b/gui/src/app/shared/models/sbom.ts index a55e0eb0..02b15903 100644 --- a/gui/src/app/shared/models/sbom.ts +++ b/gui/src/app/shared/models/sbom.ts @@ -10,8 +10,22 @@ export interface SBOM { supplier?: string; timestamp?: string; publisher?: string; + allComponents?: SBOMComponent[]; // @TODO: implement fully later dependencyTree?: any; Signature?: any; } + +export interface SBOMComponent { + uuid?: string | null; + uniqueID?: string | null; + name?: string | null; + version?: string | null; + unpackaged?: string | null; + publisher?: string | null; + licenses?: string[] | null; + cpes?: string[] | [] | null; + swids?: string[] | [] | null; + purls?: string[] | [] | null; +} \ No newline at end of file diff --git a/gui/src/app/shared/services/data-handler.service.ts b/gui/src/app/shared/services/data-handler.service.ts index ed7ae315..7b839732 100644 --- a/gui/src/app/shared/services/data-handler.service.ts +++ b/gui/src/app/shared/services/data-handler.service.ts @@ -1,3 +1,4 @@ +/**@author Justin Jantzi*/ import { Injectable } from '@angular/core'; import { ClientService } from './client.service'; import { HttpParams } from '@angular/common/http'; @@ -13,10 +14,14 @@ export class DataHandlerService { private ipc!: IpcRenderer; public filePaths: string[] = []; + public lastSentFilePaths: string[] = []; + public metrics: { [id: string]: Object | null } = {}; + public loadingFiles: string[] = []; + public comparison!: Comparison; - public loading = new BehaviorSubject(false); + private loadingComparison: boolean = false; public selectedQualityReport!: string; @@ -33,7 +38,7 @@ export class DataHandlerService { } AddFiles(paths: string[]) { - this.loading.next(true); + this.loadingFiles.push(...paths); this.filePaths.push(...paths); paths.forEach((path) => { @@ -47,15 +52,19 @@ export class DataHandlerService { }) } + IsLoadingComparison() { + return this.loadingComparison; + } + RunMetricsOnFile(path: string) { this.ipc.invoke('getFileData', path).then((data: any) => { this.client.post("qa", new HttpParams().set("contents",data).set("fileName", path)).subscribe((result) => { this.metrics[path] = result; - this.loading.next(false); + this.loadingFiles = this.loadingFiles.filter((x) => x !== path); }, (error) => { this.metrics[path] = null; - this.loading.next(false); + this.loadingFiles = this.loadingFiles.filter((x) => x !== path); }) }); } @@ -65,11 +74,13 @@ export class DataHandlerService { } getSBOMAlias(path: string) { - const index = this.filePaths.indexOf(path); - return `SBOM ${index}`; + const pathChar = path.indexOf('/') !== -1 ? '/' : '\\'; + return path.split(pathChar).pop(); } async Compare(main: string, others: string[]): Promise { + this.loadingComparison = true; + let toSend: { [path: string]: any } = {}; let total = others.length + 1; let i = 0; @@ -83,25 +94,31 @@ export class DataHandlerService { //last time running if(i == total) { - console.log("last running"); - let fileData: string[] = []; let filePaths: string[] = []; //Ensure that the compare is first in list - Object.keys(toSend).forEach((path) => { - console.log("insert path: " + path); - if(path === main) { - fileData.unshift(toSend[path]); - filePaths.unshift(path); - } else { - fileData.push(toSend[path]); - filePaths.push(path); - } - }) + + let keys = Object.keys(toSend); + + for(let i = 0; i < keys.length; i++) { + + let path = keys[i]; + + if(path === main) { + fileData.unshift(toSend[path]); + filePaths.unshift(path); + } else { + fileData.push(toSend[path]); + filePaths.push(path); + } + } + + this.lastSentFilePaths = filePaths; this.client.post("compare", new HttpParams().set('contents', JSON.stringify(fileData)).set('fileNames', JSON.stringify(filePaths))).subscribe((result: any) => { this.comparison = result; + this.loadingComparison = false; }) } }) diff --git a/gui/src/styles.css b/gui/src/styles.css index bca947fa..2def2de2 100644 --- a/gui/src/styles.css +++ b/gui/src/styles.css @@ -31,4 +31,4 @@ body { margin: 0; font-family: Roboto, "Helvetica Neue", sans-serif; } .hover:hover { transition-duration: .35s; transform: translate(0, -3px); -} +} \ No newline at end of file