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

Extend CycloneDX metadata by custom properties #4328

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
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
71 changes: 71 additions & 0 deletions src/main/java/org/dependencytrack/model/MetadataProperty.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/*
* This file is part of Dependency-Track.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
* Copyright (c) OWASP Foundation. All Rights Reserved.
*/
package org.dependencytrack.model;

import java.io.Serializable;
import java.util.Objects;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;

import alpine.server.json.TrimmedStringDeserializer;

/**
* Model class for tracking metadata properties.
*
* @author Eric Klatzer
*/
@JsonInclude(JsonInclude.Include.NON_NULL)
public class MetadataProperty implements Serializable {

@JsonDeserialize(using = TrimmedStringDeserializer.class)
private String name;

@JsonDeserialize(using = TrimmedStringDeserializer.class)
private String value;

public String getName() {
return name;
}

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

public String getValue() {
return value;
}

public void setValue(String value) {
this.value = value;
}

@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final MetadataProperty that = (MetadataProperty) o;
return Objects.equals(name, that.name) && Objects.equals(value, that.value);
}

@Override
public int hashCode() {
return Objects.hash(name, value);
}
}
27 changes: 21 additions & 6 deletions src/main/java/org/dependencytrack/model/ProjectMetadata.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,7 @@
*/
package org.dependencytrack.model;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import org.dependencytrack.persistence.converter.OrganizationalContactsJsonConverter;
import org.dependencytrack.persistence.converter.OrganizationalEntityJsonConverter;
import java.util.List;

import javax.jdo.annotations.Column;
import javax.jdo.annotations.Convert;
Expand All @@ -31,7 +27,14 @@
import javax.jdo.annotations.Persistent;
import javax.jdo.annotations.PrimaryKey;
import javax.jdo.annotations.Unique;
import java.util.List;

import org.dependencytrack.persistence.converter.MetadataPropertyJsonConverter;
import org.dependencytrack.persistence.converter.OrganizationalContactsJsonConverter;
import org.dependencytrack.persistence.converter.OrganizationalEntityJsonConverter;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;

/**
* Metadata that relates to, but does not directly describe, a {@link Project}.
Expand Down Expand Up @@ -66,6 +69,11 @@ public class ProjectMetadata {
@Column(name = "AUTHORS", jdbcType = "CLOB", allowsNull = "true")
private List<OrganizationalContact> authors;

@Persistent(defaultFetchGroup = "true")
@Convert(MetadataPropertyJsonConverter.class)
@Column(name = "PROPERTIES", jdbcType = "CLOB", allowsNull = "true")
private List<MetadataProperty> properties;

public long getId() {
return id;
}
Expand Down Expand Up @@ -98,4 +106,11 @@ public void setAuthors(final List<OrganizationalContact> authors) {
this.authors = authors;
}

public List<MetadataProperty> getProperties() {
return properties;
}

public void setProperties(final List<MetadataProperty> properties) {
this.properties = properties;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,15 @@
import alpine.common.logging.Logger;
import alpine.model.IConfigProperty;
import alpine.model.IConfigProperty.PropertyType;

import com.github.packageurl.MalformedPackageURLException;
import com.github.packageurl.PackageURL;

import jakarta.json.Json;
import jakarta.json.JsonArray;
import jakarta.json.JsonObject;
import jakarta.json.JsonValue;

import org.apache.commons.collections4.MultiValuedMap;
import org.apache.commons.collections4.multimap.HashSetValuedHashMap;
import org.apache.commons.lang3.StringUtils;
Expand Down Expand Up @@ -76,9 +79,11 @@
import java.util.function.Function;

import static java.util.Objects.requireNonNullElse;

import static org.apache.commons.lang3.StringUtils.isNotBlank;
import static org.apache.commons.lang3.StringUtils.trim;
import static org.apache.commons.lang3.StringUtils.trimToNull;
import org.dependencytrack.model.MetadataProperty;
import static org.dependencytrack.util.PurlUtil.silentPurlCoordinatesOnly;

public class ModelConverter {
Expand All @@ -97,6 +102,7 @@ public static ProjectMetadata convertToProjectMetadata(final org.cyclonedx.model
}

final var projectMetadata = new ProjectMetadata();
projectMetadata.setProperties(convertCdxProperties(cdxMetadata.getProperties()));
projectMetadata.setAuthors(convertCdxContacts(cdxMetadata.getAuthors()));
projectMetadata.setSupplier(convert(cdxMetadata.getSupplier()));

Expand Down Expand Up @@ -488,6 +494,27 @@ public static List<OrganizationalContact> convertCdxContacts(final List<org.cycl
return cdxContacts.stream().map(ModelConverter::convert).toList();
}


public static List<MetadataProperty> convertCdxProperties(final List<org.cyclonedx.model.Property> cdxProperties) {
if (cdxProperties == null) {
return null;
}

return cdxProperties.stream().map(ModelConverter::convert).toList();
}


private static MetadataProperty convert(final org.cyclonedx.model.Property cdxProperty) {
if (cdxProperty == null) {
return null;
}

final var property = new MetadataProperty();
property.setName(StringUtils.trimToNull(cdxProperty.getName()));
property.setValue(StringUtils.trimToNull(cdxProperty.getValue()));
return property;
}

private static OrganizationalContact convert(final org.cyclonedx.model.OrganizationalContact cdxContact) {
if (cdxContact == null) {
return null;
Expand All @@ -508,6 +535,14 @@ private static List<org.cyclonedx.model.OrganizationalContact> convertContacts(f
return dtContacts.stream().map(ModelConverter::convert).toList();
}

private static List<org.cyclonedx.model.Property> convertMetadataProperties(final List<MetadataProperty> dtProperties) {
if (dtProperties == null) {
return null;
}

return dtProperties.stream().map(ModelConverter::convert).toList();
}

private static org.cyclonedx.model.OrganizationalEntity convert(final OrganizationalEntity dtEntity) {
if (dtEntity == null) {
return null;
Expand All @@ -525,6 +560,18 @@ private static org.cyclonedx.model.OrganizationalEntity convert(final Organizati
return cdxEntity;
}

private static org.cyclonedx.model.Property convert(final MetadataProperty dtProperty) {
if (dtProperty == null) {
return null;
}

final var cdxProperty = new org.cyclonedx.model.Property();
cdxProperty.setName(StringUtils.trimToNull(dtProperty.getName()));
cdxProperty.setValue(StringUtils.trimToNull(dtProperty.getValue()));

return cdxProperty;
}

private static org.cyclonedx.model.OrganizationalContact convert(final OrganizationalContact dtContact) {
if (dtContact == null) {
return null;
Expand Down Expand Up @@ -756,6 +803,7 @@ public static org.cyclonedx.model.Metadata createMetadata(final Project project)
if (project.getMetadata() != null) {
metadata.setAuthors(convertContacts(project.getMetadata().getAuthors()));
metadata.setSupplier(convert(project.getMetadata().getSupplier()));
metadata.setProperties(convertMetadataProperties(project.getMetadata().getProperties()));
}
}
return metadata;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* This file is part of Dependency-Track.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
* Copyright (c) OWASP Foundation. All Rights Reserved.
*/
package org.dependencytrack.persistence.converter;

import java.util.List;

import org.dependencytrack.model.MetadataProperty;

import com.fasterxml.jackson.core.type.TypeReference;

public class MetadataPropertyJsonConverter extends AbstractJsonConverter<List<MetadataProperty>> {

public MetadataPropertyJsonConverter() {
super(new TypeReference<>() {});
}

@Override
public String convertToDatastore(final List<MetadataProperty> attributeValue) {
// Overriding is required for DataNucleus to correctly detect the return type.
return super.convertToDatastore(attributeValue);
}

@Override
public List<MetadataProperty> convertToAttribute(final String datastoreValue) {
// Overriding is required for DataNucleus to correctly detect the return type.
return super.convertToAttribute(datastoreValue);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ public Response exportProjectAsCycloneDx (
if (project == null) {
return Response.status(Response.Status.NOT_FOUND).entity("The project could not be found.").build();
}

if (! qm.hasAccess(super.getPrincipal(), project)) {
return Response.status(Response.Status.FORBIDDEN).entity("Access to the specified project is forbidden").build();
}
Expand Down
Loading