Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix missing projectTags parameter for POST /v1/bom endpoint #3960

Merged
merged 1 commit into from
Jul 9, 2024
Merged
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
7 changes: 7 additions & 0 deletions src/main/java/org/dependencytrack/model/Tag.java
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,13 @@ public class Tag implements Serializable {
@Order(extensions = @Extension(vendorName = "datanucleus", key = "list-ordering", value = "name ASC"))
private List<Project> projects;

public Tag() {
}

public Tag(final String name) {
this.name = name;
}

public long getId() {
return id;
}
Expand Down
31 changes: 20 additions & 11 deletions src/main/java/org/dependencytrack/resources/v1/BomResource.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.security.SecurityRequirements;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.apache.commons.io.IOUtils;
import org.apache.commons.io.input.BOMInputStream;
import org.apache.commons.lang3.StringUtils;
Expand All @@ -45,6 +44,7 @@
import org.dependencytrack.model.Component;
import org.dependencytrack.model.ConfigPropertyConstants;
import org.dependencytrack.model.Project;
import org.dependencytrack.model.Tag;
import org.dependencytrack.model.validation.ValidUuid;
import org.dependencytrack.notification.NotificationConstants.Title;
import org.dependencytrack.notification.NotificationGroup;
Expand Down Expand Up @@ -80,19 +80,22 @@
import java.io.IOException;
import java.io.InputStream;
import java.security.Principal;
import java.util.Arrays;
import java.util.Base64;
import java.util.Collections;
import java.util.List;
import java.util.UUID;

import static java.util.function.Predicate.not;

/**
* JAX-RS resources for processing bill-of-material (bom) documents.
*
* @author Steve Springett
* @since 3.0.0
*/
@Path("/v1/bom")
@Tag(name = "bom")
@io.swagger.v3.oas.annotations.tags.Tag(name = "bom")
@SecurityRequirements({
@SecurityRequirement(name = "ApiKeyAuth"),
@SecurityRequirement(name = "BearerAuth")
Expand Down Expand Up @@ -367,14 +370,17 @@ public Response uploadBom(@Parameter(required = true) BomSubmitRequest request)
@ApiResponse(responseCode = "404", description = "The project could not be found")
})
@PermissionRequired(Permissions.Constants.BOM_UPLOAD)
public Response uploadBom(@FormDataParam("project") String projectUuid,
@DefaultValue("false") @FormDataParam("autoCreate") boolean autoCreate,
@FormDataParam("projectName") String projectName,
@FormDataParam("projectVersion") String projectVersion,
@FormDataParam("parentName") String parentName,
@FormDataParam("parentVersion") String parentVersion,
@FormDataParam("parentUUID") String parentUUID,
@Parameter(schema = @Schema(type = "string")) @FormDataParam("bom") final List<FormDataBodyPart> artifactParts) {
public Response uploadBom(
@FormDataParam("project") String projectUuid,
@DefaultValue("false") @FormDataParam("autoCreate") boolean autoCreate,
@FormDataParam("projectName") String projectName,
@FormDataParam("projectVersion") String projectVersion,
@FormDataParam("projectTags") String projectTags,
@FormDataParam("parentName") String parentName,
@FormDataParam("parentVersion") String parentVersion,
@FormDataParam("parentUUID") String parentUUID,
@Parameter(schema = @Schema(type = "string")) @FormDataParam("bom") final List<FormDataBodyPart> artifactParts
) {
if (projectUuid != null) { // behavior in v3.0.0
try (QueryManager qm = new QueryManager()) {
final Project project = qm.getObjectByUuid(Project.class, projectUuid);
Expand Down Expand Up @@ -404,7 +410,10 @@ public Response uploadBom(@FormDataParam("project") String projectUuid,
return Response.status(Response.Status.FORBIDDEN).entity("Access to the specified parent project is forbidden").build();
}
}
project = qm.createProject(trimmedProjectName, null, trimmedProjectVersion, null, parent, null, true, true);
final List<org.dependencytrack.model.Tag> tags = (projectTags != null && !projectTags.isBlank())
? Arrays.stream(projectTags.split(",")).map(String::trim).filter(not(String::isEmpty)).map(Tag::new).toList()
: null;
project = qm.createProject(trimmedProjectName, null, trimmedProjectVersion, tags, parent, null, true, true);
Principal principal = getPrincipal();
qm.updateNewProjectACL(project, principal);
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,16 +44,21 @@
import org.dependencytrack.resources.v1.exception.JsonMappingExceptionMapper;
import org.dependencytrack.resources.v1.vo.BomSubmitRequest;
import org.dependencytrack.tasks.scanners.AnalyzerIdentity;
import org.glassfish.jersey.client.ClientConfig;
import org.glassfish.jersey.client.HttpUrlConnectorProvider;
import org.glassfish.jersey.media.multipart.FormDataMultiPart;
import org.glassfish.jersey.media.multipart.MultiPartFeature;
import org.glassfish.jersey.server.ResourceConfig;
import org.junit.Assert;
import org.junit.ClassRule;
import org.junit.Test;

import jakarta.json.JsonObject;
import jakarta.ws.rs.client.ClientBuilder;
import jakarta.ws.rs.client.Entity;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.List;
import java.util.UUID;
Expand All @@ -63,6 +68,7 @@
import static net.javacrumbs.jsonunit.assertj.JsonAssertions.assertThatJson;
import static net.javacrumbs.jsonunit.assertj.JsonAssertions.json;
import static org.apache.commons.io.IOUtils.resourceToByteArray;
import static org.apache.commons.io.IOUtils.resourceToString;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatNoException;
import static org.dependencytrack.model.ConfigPropertyConstants.BOM_VALIDATION_ENABLED;
Expand Down Expand Up @@ -871,6 +877,39 @@ public void uploadBomAutoCreateWithTagsTest() throws Exception {
.containsExactlyInAnyOrder("tag1", "tag2");
}

@Test
public void uploadBomAutoCreateWithTagsMultipartTest() throws Exception {
initializeWithPermissions(Permissions.BOM_UPLOAD, Permissions.PROJECT_CREATION_UPLOAD);

final var multiPart = new FormDataMultiPart()
.field("bom", resourceToString("/unit/bom-1.xml", StandardCharsets.UTF_8), MediaType.APPLICATION_XML_TYPE)
.field("projectName", "Acme Example")
.field("projectVersion", "1.0")
.field("projectTags", "tag1,tag2")
.field("autoCreate", "true");

// NB: The GrizzlyConnectorProvider doesn't work with MultiPart requests.
// https://github.com/eclipse-ee4j/jersey/issues/5094
final var client = ClientBuilder.newClient(new ClientConfig()
.connectorProvider(new HttpUrlConnectorProvider()));

final Response response = client.target(jersey.target(V1_BOM).getUri()).request()
.header(X_API_KEY, apiKey)
.post(Entity.entity(multiPart, multiPart.getMediaType()));
assertThat(response.getStatus()).isEqualTo(200);
assertThatJson(getPlainTextBody(response)).isEqualTo("""
{
"token": "${json-unit.any-string}"
}
""");

final Project project = qm.getProject("Acme Example", "1.0");
assertThat(project).isNotNull();
assertThat(project.getTags())
.extracting(Tag::getName)
.containsExactlyInAnyOrder("tag1", "tag2");
}

@Test
public void uploadBomUnauthorizedTest() throws Exception {
String bomString = Base64.getEncoder().encodeToString(resourceToByteArray("/unit/bom-1.xml"));
Expand Down