Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .dockerignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
build/
build/
.env
30 changes: 30 additions & 0 deletions api/src/main/java/org/svip/api/config/JacksonConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package org.svip.api.config;

import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.StreamWriteConstraints;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class JacksonConfig {

@Bean
public ObjectMapper objectMapper() {
// Increase max nesting depth for writing deep JSON structures (e.g., VEX)
JsonFactory factory = JsonFactory.builder()
.streamWriteConstraints(StreamWriteConstraints.builder()
.maxNestingDepth(5_000)
.build())
.build();

ObjectMapper mapper = new ObjectMapper(factory);

// Configure to handle circular references globally
mapper.configure(SerializationFeature.FAIL_ON_SELF_REFERENCES, false);
mapper.configure(SerializationFeature.WRITE_SELF_REFERENCES_AS_NULL, true);

return mapper;
}
}
44 changes: 33 additions & 11 deletions api/src/main/java/org/svip/api/controller/OSIController.java
Original file line number Diff line number Diff line change
Expand Up @@ -155,24 +155,24 @@ public ResponseEntity<?> uploadProject(@RequestPart("project") MultipartFile pro
* possible tools will be used.
* @return The ID of the uploaded SBOM.
*/
@PostMapping(value = "")
@PostMapping(value = "", consumes = { MediaType.MULTIPART_FORM_DATA_VALUE })
public ResponseEntity<?> generateWithOSI(@RequestParam("projectName") String projectName,
@RequestParam("schema") SerializerFactory.Schema schema,
@RequestParam("format") SerializerFactory.Format format,
@RequestParam(value = "toolNames", required = false) String toolNames) {
@RequestParam(value = "toolNames", required = false) String toolNamesJson) {

HashMap<String, String> generatedSBOMs;
try {
// Run with requested tools, default to relevant ones
List<String> tools;
if (toolNames != null) {
/*
todo - this is a hotfix
tldr when gui sends multipart form "toolNames" is sent as string ( "["foo","bar"]" )
and not an actual String[]. This hotfix just converts the string to an array
*/
ObjectMapper mapper = new ObjectMapper();
tools = List.of(mapper.readValue(toolNames, String[].class));
if (toolNamesJson != null && !toolNamesJson.isBlank()) {
try {
ObjectMapper mapper = new ObjectMapper();
tools = mapper.readValue(toolNamesJson, List.class);
} catch (Exception parseEx) {
LOGGER.warn("POST /svip/generators/osi - Invalid toolNames JSON; defaulting to project tools. Error: {}", parseEx.getMessage());
tools = this.osiService.getTools("project");
}
} else {
tools = this.osiService.getTools("project");
}
Expand Down Expand Up @@ -258,7 +258,29 @@ public ResponseEntity<?> generateWithOSI(@RequestParam("projectName") String pro
return new ResponseEntity<>(e.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
}

// todo how to set file name using projectName
// Set descriptive filename: ProjectName-OSI-Schema-Format-Timestamp.ext
try {
String extension;
if (schema == SerializerFactory.Schema.SPDX23) {
extension = (format == SerializerFactory.Format.TAGVALUE) ? ".spdx" : ".json";
} else { // CDX14
extension = (format == SerializerFactory.Format.XML) ? ".xml" : ".json";
}

String schemaStr = (schema == SerializerFactory.Schema.SPDX23) ? "SPDX23" : "CDX14";
String formatStr = switch (format) {
case JSON -> "JSON";
case XML -> "XML";
case TAGVALUE -> "TAGVALUE";
};

String safeProject = (projectName == null ? "SBOM" : projectName).replaceAll("[^A-Za-z0-9._-]+", "-");
String ts = java.time.LocalDateTime.now().format(java.time.format.DateTimeFormatter.ofPattern("yyyyMMdd-HHmmss"));
String finalName = safeProject + "-OSI-" + schemaStr + "-" + formatStr + "-" + ts + extension;
sbomService.rename(convertedID, finalName);
} catch (Exception ignored) {
// keep auto name if rename fails
}

// Return ID
return new ResponseEntity<>(convertedID, HttpStatus.OK);
Expand Down
22 changes: 20 additions & 2 deletions api/src/main/java/org/svip/api/controller/ParserController.java
Original file line number Diff line number Diff line change
Expand Up @@ -135,9 +135,27 @@ public ResponseEntity<?> generateParsers(@RequestParam("zipFile") MultipartFile
// Convert & save according to overwrite boolean
SBOMFile converted;
try {
// Build descriptive filename: ProjectName-PARSERS-Schema-Format-Timestamp.ext
String extension;
if (schema == SerializerFactory.Schema.SPDX23) {
extension = (format == SerializerFactory.Format.TAGVALUE) ? ".spdx" : ".json";
} else { // CDX14
extension = (format == SerializerFactory.Format.XML) ? ".xml" : ".json";
}

String schemaStr = (schema == SerializerFactory.Schema.SPDX23) ? "SPDX23" : "CDX14";
String formatStr = switch (format) {
case JSON -> "JSON";
case XML -> "XML";
case TAGVALUE -> "TAGVALUE";
};

String safeProject = (projectName == null ? "SBOM" : projectName).replaceAll("[^A-Za-z0-9._-]+", "-");
String ts = java.time.LocalDateTime.now().format(java.time.format.DateTimeFormatter.ofPattern("yyyyMMdd-HHmmss"));
String finalName = safeProject + "-PARSERS-" + schemaStr + "-" + formatStr + "-" + ts + extension;

// convert result sbomfile to sbom
UploadSBOMFileInput u = new UploadSBOMFileInput(projectName + ((format == SerializerFactory.Format.JSON)
? ".json" : ".spdx"), contents);
UploadSBOMFileInput u = new UploadSBOMFileInput(finalName, contents);
converted = u.toSBOMFile();
sbomService.upload(converted);
} catch (JsonProcessingException e) {
Expand Down
6 changes: 4 additions & 2 deletions api/src/main/java/org/svip/api/controller/SBOMController.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.svip.api.dto.SBOMFileDTO;
import org.svip.api.entities.SBOMFile;
import org.svip.api.requests.UploadSBOMFileInput;
import org.svip.api.services.SBOMFileService;
Expand Down Expand Up @@ -202,7 +203,7 @@ public ResponseEntity<String> getSBOMObjectAsJSON(@RequestParam("id") Long id) {
* @return The contents of the SBOM file.
*/
@GetMapping("/sboms/content")
public ResponseEntity<SBOMFile> getContent(@RequestParam("id") Long id) {
public ResponseEntity<SBOMFileDTO> getContent(@RequestParam("id") Long id) {
// todo rename endpoint? Returns more than just content
// Get SBOM
SBOMFile sbomFile = this.sbomService.getSBOMFile(id);
Expand All @@ -216,7 +217,8 @@ public ResponseEntity<SBOMFile> getContent(@RequestParam("id") Long id) {
// Log
LOGGER.info("GET /svip/sboms/content?id=" + id + " - File: " + sbomFile.getName());

return new ResponseEntity<>(sbomFile, HttpStatus.OK);
// Return DTO to avoid circular references
return new ResponseEntity<>(new SBOMFileDTO(sbomFile), HttpStatus.OK);
}


Expand Down
105 changes: 105 additions & 0 deletions api/src/main/java/org/svip/api/dto/SBOMFileDTO.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
/**
* Copyright 2021 Rochester Institute of Technology (RIT). Developed with
* government support under contract 70RCSA22C00000008 awarded by the United
* States Department of Homeland Security for Cybersecurity and Infrastructure Security Agency.
* <p>
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* <p>
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
* <p>
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/

package org.svip.api.dto;

import com.fasterxml.jackson.annotation.JsonProperty;
import org.svip.api.entities.SBOMFile;

/**
* file: SBOMFileDTO.java
* <p>
* Data Transfer Object for SBOM Files - eliminates circular references
*
* @author Ibrahim Matar
**/
public class SBOMFileDTO {

@JsonProperty
private Long id;

@JsonProperty("fileName")
private String name;

@JsonProperty("contents")
private String content;

@JsonProperty
private SBOMFile.Schema schema;

@JsonProperty
private SBOMFile.FileType fileType;

// Constructors
public SBOMFileDTO() {}

public SBOMFileDTO(SBOMFile sbomFile) {
this.id = sbomFile.getId();
this.name = sbomFile.getName();
this.content = sbomFile.getContent();
this.schema = sbomFile.getSchema();
this.fileType = sbomFile.getFileType();
}

// Getters and setters
public Long getId() {
return id;
}

public void setId(Long id) {
this.id = id;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public String getContent() {
return content;
}

public void setContent(String content) {
this.content = content;
}

public SBOMFile.Schema getSchema() {
return schema;
}

public void setSchema(SBOMFile.Schema schema) {
this.schema = schema;
}

public SBOMFile.FileType getFileType() {
return fileType;
}

public void setFileType(SBOMFile.FileType fileType) {
this.fileType = fileType;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@

package org.svip.api.entities;

import com.fasterxml.jackson.annotation.JsonBackReference;
import jakarta.persistence.*;

/**
Expand All @@ -47,6 +48,7 @@ public class QualityReportFile {
private String content;

@OneToOne(mappedBy = "qualityReportFile") // name of field in SBOMFile NOT DB
@JsonBackReference("sbom-quality")
private SBOMFile sbomFile;


Expand Down
6 changes: 6 additions & 0 deletions api/src/main/java/org/svip/api/entities/SBOMFile.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonManagedReference;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.persistence.*;
Expand Down Expand Up @@ -72,15 +74,19 @@ public class SBOMFile {

@OneToOne(cascade = CascadeType.REMOVE, orphanRemoval = true) // delete all qa on sbom deletion
@JoinColumn(name = "qa_id", referencedColumnName = "id")
@JsonIgnore
private QualityReportFile qualityReportFile;
@OneToOne(cascade = CascadeType.REMOVE, orphanRemoval = true) // delete all vex on sbom deletion
@JoinColumn(name = "vex_id", referencedColumnName = "id")
@JsonIgnore
private VEXFile vexFile;
// Collection of comparisons where this was the target
@OneToMany(mappedBy = "targetSBOMFile", cascade = CascadeType.REMOVE, orphanRemoval = true)
@JsonIgnore
private Set<ComparisonFile> comparisonsAsTarget = new HashSet<>();
// Collection of comparisons where this was the other
@OneToMany(mappedBy = "otherSBOMFile", cascade = CascadeType.REMOVE, orphanRemoval = true)
@JsonIgnore
private Set<ComparisonFile> comparisonsAsOther = new HashSet<>();

/**
Expand Down
2 changes: 2 additions & 0 deletions api/src/main/java/org/svip/api/entities/VEXFile.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@

package org.svip.api.entities;

import com.fasterxml.jackson.annotation.JsonBackReference;
import jakarta.persistence.*;
import org.svip.vex.model.VEXType;

Expand Down Expand Up @@ -52,6 +53,7 @@ public class VEXFile {
private Database datasource;
/// Relationships
@OneToOne(mappedBy = "vexFile") // name of field in SBOMFile NOT DB
@JsonBackReference("sbom-vex")
private SBOMFile sbomFile;

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@

package org.svip.api.entities.diff;

import com.fasterxml.jackson.annotation.JsonBackReference;
import com.fasterxml.jackson.annotation.JsonManagedReference;
import jakarta.persistence.*;
import org.hibernate.annotations.OnDelete;
import org.hibernate.annotations.OnDeleteAction;
Expand Down Expand Up @@ -53,17 +55,19 @@ public class ComparisonFile {
@ManyToOne
@JoinColumn(name = "target_sbom_id", nullable = false)
@OnDelete(action = OnDeleteAction.CASCADE)
@JsonBackReference("sbom-comparison-target")
private SBOMFile targetSBOMFile;

// Other SBOM
@ManyToOne
@JoinColumn(name = "other_sbom_id", nullable = false)
@OnDelete(action = OnDeleteAction.CASCADE)
@JsonBackReference("sbom-comparison-other")
private SBOMFile otherSBOMFile;

// Conflict collection
// @OneToMany(mappedBy = "comparison", cascade = CascadeType.REMOVE, orphanRemoval = true)
@OneToMany(mappedBy = "comparison")
@OneToMany(mappedBy = "comparison", cascade = CascadeType.REMOVE, orphanRemoval = true)
@JsonManagedReference("comparison-conflicts")
private Set<ConflictFile> conflicts = new HashSet<>();


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,12 @@

package org.svip.api.entities.diff;

import com.fasterxml.jackson.annotation.JsonBackReference;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import jakarta.persistence.*;
import org.hibernate.annotations.OnDelete;
import org.hibernate.annotations.OnDeleteAction;
import org.svip.compare.conflicts.MismatchType;

/**
Expand Down Expand Up @@ -68,7 +71,9 @@ public class ConflictFile {

// source comparison
@ManyToOne
@OnDelete(action = OnDeleteAction.CASCADE)
@JoinColumn(name = "comparison_id", nullable = false)
@JsonBackReference("comparison-conflicts")
private ComparisonFile comparison;


Expand Down
Loading