Skip to content

Commit c63745b

Browse files
authored
Merge pull request #3969 from 2000rosser/issue-3936
Add support for authors field
2 parents ca3ef44 + 55e5a71 commit c63745b

15 files changed

Lines changed: 250 additions & 36 deletions

File tree

src/main/java/org/dependencytrack/model/Component.java

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,10 @@
3030
import io.swagger.v3.oas.annotations.media.Schema;
3131
import org.apache.commons.lang3.StringUtils;
3232
import org.dependencytrack.model.validation.ValidSpdxExpression;
33+
import org.dependencytrack.persistence.converter.OrganizationalContactsJsonConverter;
3334
import org.dependencytrack.persistence.converter.OrganizationalEntityJsonConverter;
3435
import org.dependencytrack.resources.v1.serializers.CustomPackageURLSerializer;
36+
import org.dependencytrack.parser.cyclonedx.util.ModelConverter;
3537

3638
import jakarta.json.JsonObject;
3739
import jakarta.validation.constraints.NotBlank;
@@ -109,10 +111,10 @@ public enum FetchGroup {
109111
@JsonIgnore
110112
private long id;
111113

112-
@Persistent
113-
@Column(name = "AUTHOR", jdbcType = "CLOB")
114-
@Pattern(regexp = RegexSequence.Definition.PRINTABLE_CHARS, message = "The author may only contain printable characters")
115-
private String author;
114+
@Persistent(defaultFetchGroup = "true")
115+
@Convert(OrganizationalContactsJsonConverter.class)
116+
@Column(name = "AUTHORS", jdbcType = "CLOB", allowsNull = "true")
117+
private List<OrganizationalContact> authors;
116118

117119
@Persistent
118120
@Column(name = "PUBLISHER", jdbcType = "VARCHAR")
@@ -386,18 +388,36 @@ public void setId(long id) {
386388
this.id = id;
387389
}
388390

389-
public String getAuthor() {
390-
return author;
391+
public List<OrganizationalContact> getAuthors() {
392+
return authors;
391393
}
392394

393-
public void setAuthor(String author) {
394-
this.author = author;
395+
public void setAuthors(List<OrganizationalContact> authors) {
396+
this.authors = authors;
395397
}
396398

397399
public String getPublisher() {
398400
return publisher;
399401
}
400402

403+
@Deprecated
404+
@JsonInclude(JsonInclude.Include.NON_EMPTY)
405+
public String getAuthor(){
406+
return ModelConverter.convertContactsToString(this.authors);
407+
}
408+
409+
@Deprecated
410+
public void setAuthor(String author){
411+
if(this.authors==null){
412+
this.authors = new ArrayList<>();
413+
} else {
414+
this.authors.clear();
415+
}
416+
this.authors.add(new OrganizationalContact() {{
417+
setName(author);
418+
}});
419+
}
420+
401421
public void setPublisher(String publisher) {
402422
this.publisher = publisher;
403423
}

src/main/java/org/dependencytrack/model/Project.java

Lines changed: 30 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@
3232
import com.github.packageurl.MalformedPackageURLException;
3333
import com.github.packageurl.PackageURL;
3434
import io.swagger.v3.oas.annotations.media.Schema;
35+
36+
import org.dependencytrack.parser.cyclonedx.util.ModelConverter;
37+
import org.dependencytrack.persistence.converter.OrganizationalContactsJsonConverter;
3538
import org.dependencytrack.persistence.converter.OrganizationalEntityJsonConverter;
3639
import org.dependencytrack.resources.v1.serializers.CustomPackageURLSerializer;
3740

@@ -74,7 +77,7 @@
7477
@FetchGroups({
7578
@FetchGroup(name = "ALL", members = {
7679
@Persistent(name = "name"),
77-
@Persistent(name = "author"),
80+
@Persistent(name = "authors"),
7881
@Persistent(name = "publisher"),
7982
@Persistent(name = "supplier"),
8083
@Persistent(name = "group"),
@@ -125,12 +128,10 @@ public enum FetchGroup {
125128
@JsonIgnore
126129
private long id;
127130

128-
@Persistent
129-
@Column(name = "AUTHOR", jdbcType = "VARCHAR")
130-
@Size(max = 255)
131-
@JsonDeserialize(using = TrimmedStringDeserializer.class)
132-
@Pattern(regexp = RegexSequence.Definition.PRINTABLE_CHARS, message = "The author may only contain printable characters")
133-
private String author;
131+
@Persistent(defaultFetchGroup = "true")
132+
@Convert(OrganizationalContactsJsonConverter.class)
133+
@Column(name = "AUTHORS", jdbcType = "CLOB", allowsNull = "true")
134+
private List<OrganizationalContact> authors;
134135

135136
@Persistent
136137
@Column(name = "PUBLISHER", jdbcType = "VARCHAR")
@@ -297,12 +298,30 @@ public void setId(long id) {
297298
this.id = id;
298299
}
299300

300-
public String getAuthor() {
301-
return author;
301+
public List<OrganizationalContact> getAuthors() {
302+
return authors;
302303
}
303304

304-
public void setAuthor(String author) {
305-
this.author = author;
305+
public void setAuthors(List<OrganizationalContact> authors) {
306+
this.authors = authors;
307+
}
308+
309+
@Deprecated
310+
@JsonInclude(JsonInclude.Include.NON_EMPTY)
311+
public String getAuthor(){
312+
return ModelConverter.convertContactsToString(this.authors);
313+
}
314+
315+
@Deprecated
316+
public void setAuthor(String author){
317+
if(this.authors==null){
318+
this.authors = new ArrayList<>();
319+
} else{
320+
this.authors.clear();
321+
}
322+
this.authors.add(new OrganizationalContact() {{
323+
setName(author);
324+
}});
306325
}
307326

308327
public String getPublisher() {

src/main/java/org/dependencytrack/parser/cyclonedx/util/ModelConverter.java

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,6 @@ public static Project convertToProject(final org.cyclonedx.model.Metadata cdxMet
116116
public static Project convertToProject(final org.cyclonedx.model.Component cdxComponent) {
117117
final var project = new Project();
118118
project.setBomRef(useOrGenerateRandomBomRef(cdxComponent.getBomRef()));
119-
project.setAuthor(trimToNull(cdxComponent.getAuthor()));
120119
project.setPublisher(trimToNull(cdxComponent.getPublisher()));
121120
project.setSupplier(convert(cdxComponent.getSupplier()));
122121
project.setClassifier(convertClassifier(cdxComponent.getType()).orElse(Classifier.APPLICATION));
@@ -126,6 +125,17 @@ public static Project convertToProject(final org.cyclonedx.model.Component cdxCo
126125
project.setDescription(trimToNull(cdxComponent.getDescription()));
127126
project.setExternalReferences(convertExternalReferences(cdxComponent.getExternalReferences()));
128127

128+
List<OrganizationalContact> contacts = new ArrayList<>();
129+
if(cdxComponent.getAuthor()!=null){
130+
contacts.add(new OrganizationalContact() {{
131+
setName(cdxComponent.getAuthor());
132+
}});
133+
}
134+
if(cdxComponent.getAuthors()!=null){
135+
contacts.addAll(convertCdxContacts(cdxComponent.getAuthors()));
136+
}
137+
project.setAuthors(contacts);
138+
129139
if (cdxComponent.getPurl() != null) {
130140
try {
131141
final var purl = new PackageURL(cdxComponent.getPurl());
@@ -153,7 +163,6 @@ public static List<Component> convertComponents(final List<org.cyclonedx.model.C
153163
public static Component convertComponent(final org.cyclonedx.model.Component cdxComponent) {
154164
final var component = new Component();
155165
component.setBomRef(useOrGenerateRandomBomRef(cdxComponent.getBomRef()));
156-
component.setAuthor(trimToNull(cdxComponent.getAuthor()));
157166
component.setPublisher(trimToNull(cdxComponent.getPublisher()));
158167
component.setSupplier(convert(cdxComponent.getSupplier()));
159168
component.setClassifier(convertClassifier(cdxComponent.getType()).orElse(Classifier.LIBRARY));
@@ -166,6 +175,17 @@ public static Component convertComponent(final org.cyclonedx.model.Component cdx
166175
component.setExternalReferences(convertExternalReferences(cdxComponent.getExternalReferences()));
167176
component.setProperties(convertToComponentProperties(cdxComponent.getProperties()));
168177

178+
List<OrganizationalContact> contacts = new ArrayList<>();
179+
if(cdxComponent.getAuthor()!=null){
180+
contacts.add(new OrganizationalContact() {{
181+
setName(cdxComponent.getAuthor());
182+
}});
183+
}
184+
if(cdxComponent.getAuthors()!=null){
185+
contacts.addAll(convertCdxContacts(cdxComponent.getAuthors()));
186+
}
187+
component.setAuthors(contacts);
188+
169189
if (cdxComponent.getPurl() != null) {
170190
try {
171191
final var purl = new PackageURL(cdxComponent.getPurl());
@@ -525,7 +545,7 @@ public static org.cyclonedx.model.Component convert(final QueryManager qm, final
525545
cycloneComponent.setDescription(StringUtils.trimToNull(component.getDescription()));
526546
cycloneComponent.setCopyright(StringUtils.trimToNull(component.getCopyright()));
527547
cycloneComponent.setCpe(StringUtils.trimToNull(component.getCpe()));
528-
cycloneComponent.setAuthor(StringUtils.trimToNull(component.getAuthor()));
548+
cycloneComponent.setAuthor(StringUtils.trimToNull(convertContactsToString(component.getAuthors())));
529549
cycloneComponent.setSupplier(convert(component.getSupplier()));
530550
cycloneComponent.setProperties(convert(component.getProperties()));
531551

@@ -654,6 +674,23 @@ private static <T extends IConfigProperty> List<org.cyclonedx.model.Property> co
654674
return cdxProperties;
655675
}
656676

677+
public static String convertContactsToString(List<OrganizationalContact> authors) {
678+
if (authors == null || authors.isEmpty()) {
679+
return "";
680+
}
681+
StringBuilder stringBuilder = new StringBuilder();
682+
for (OrganizationalContact author : authors) {
683+
if (author != null && author.getName() != null) {
684+
stringBuilder.append(author.getName()).append(", ");
685+
}
686+
}
687+
//remove trailing comma and space
688+
if (stringBuilder.length() > 0) {
689+
stringBuilder.setLength(stringBuilder.length() - 2);
690+
}
691+
return stringBuilder.toString();
692+
}
693+
657694
public static org.cyclonedx.model.Metadata createMetadata(final Project project) {
658695
final org.cyclonedx.model.Metadata metadata = new org.cyclonedx.model.Metadata();
659696
final org.cyclonedx.model.Tool tool = new org.cyclonedx.model.Tool();
@@ -666,7 +703,7 @@ public static org.cyclonedx.model.Metadata createMetadata(final Project project)
666703

667704
final org.cyclonedx.model.Component cycloneComponent = new org.cyclonedx.model.Component();
668705
cycloneComponent.setBomRef(project.getUuid().toString());
669-
cycloneComponent.setAuthor(StringUtils.trimToNull(project.getAuthor()));
706+
cycloneComponent.setAuthor(StringUtils.trimToNull(convertContactsToString(project.getAuthors())));
670707
cycloneComponent.setPublisher(StringUtils.trimToNull(project.getPublisher()));
671708
cycloneComponent.setGroup(StringUtils.trimToNull(project.getGroup()));
672709
cycloneComponent.setName(StringUtils.trimToNull(project.getName()));
@@ -704,6 +741,7 @@ public static org.cyclonedx.model.Metadata createMetadata(final Project project)
704741
cycloneComponent.setExternalReferences(references);
705742
}
706743
cycloneComponent.setSupplier(convert(project.getSupplier()));
744+
cycloneComponent.setAuthors(convertContacts(project.getAuthors()));
707745

708746
// NB: Project properties are currently used to configure integrations
709747
// such as Defect Dojo. They can also contain encrypted values that most

src/main/java/org/dependencytrack/persistence/ComponentQueryManager.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -377,7 +377,7 @@ public Component cloneComponent(Component sourceComponent, Project destinationPr
377377
component.setLicenseExpression(sourceComponent.getLicenseExpression());
378378
component.setLicenseUrl(sourceComponent.getLicenseUrl());
379379
component.setResolvedLicense(sourceComponent.getResolvedLicense());
380-
component.setAuthor(sourceComponent.getAuthor());
380+
component.setAuthors(sourceComponent.getAuthors());
381381
component.setSupplier(sourceComponent.getSupplier());
382382
// TODO Add support for parent component and children components
383383
component.setProject(destinationProject);
@@ -412,7 +412,7 @@ public Component updateComponent(Component transientComponent, boolean commitInd
412412
component.setCpe(transientComponent.getCpe());
413413
component.setPurl(transientComponent.getPurl());
414414
component.setInternal(transientComponent.isInternal());
415-
component.setAuthor(transientComponent.getAuthor());
415+
component.setAuthors(transientComponent.getAuthors());
416416
component.setSupplier(transientComponent.getSupplier());
417417
component.setExternalReferences(transientComponent.getExternalReferences());
418418
final Component result = persist(component);

src/main/java/org/dependencytrack/persistence/ProjectQueryManager.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -532,7 +532,7 @@ public Project createProject(final Project project, List<Tag> tags, boolean comm
532532
@Override
533533
public Project updateProject(Project transientProject, boolean commitIndex) {
534534
final Project project = getObjectByUuid(Project.class, transientProject.getUuid());
535-
project.setAuthor(transientProject.getAuthor());
535+
project.setAuthors(transientProject.getAuthors());
536536
project.setPublisher(transientProject.getPublisher());
537537
project.setManufacturer(transientProject.getManufacturer());
538538
project.setSupplier(transientProject.getSupplier());
@@ -603,7 +603,7 @@ public Project clone(
603603
return null;
604604
}
605605
Project project = new Project();
606-
project.setAuthor(source.getAuthor());
606+
project.setAuthors(source.getAuthors());
607607
project.setManufacturer(source.getManufacturer());
608608
project.setSupplier(source.getSupplier());
609609
project.setPublisher(source.getPublisher());

src/main/java/org/dependencytrack/resources/v1/ComponentResource.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,7 @@ public Response createComponent(@Parameter(description = "The UUID of the projec
284284
@PathParam("uuid") @ValidUuid String uuid, Component jsonComponent) {
285285
final Validator validator = super.getValidator();
286286
failOnValidationError(
287+
validator.validateProperty(jsonComponent, "authors"),
287288
validator.validateProperty(jsonComponent, "author"),
288289
validator.validateProperty(jsonComponent, "publisher"),
289290
validator.validateProperty(jsonComponent, "name"),
@@ -323,7 +324,7 @@ public Response createComponent(@Parameter(description = "The UUID of the projec
323324
final License resolvedLicense = qm.getLicense(jsonComponent.getLicense());
324325
Component component = new Component();
325326
component.setProject(project);
326-
component.setAuthor(StringUtils.trimToNull(jsonComponent.getAuthor()));
327+
component.setAuthors(jsonComponent.getAuthors());
327328
component.setPublisher(StringUtils.trimToNull(jsonComponent.getPublisher()));
328329
component.setName(StringUtils.trimToNull(jsonComponent.getName()));
329330
component.setVersion(StringUtils.trimToNull(jsonComponent.getVersion()));
@@ -426,7 +427,7 @@ public Response updateComponent(Component jsonComponent) {
426427
if (name != null) {
427428
component.setName(name);
428429
}
429-
component.setAuthor(StringUtils.trimToNull(jsonComponent.getAuthor()));
430+
component.setAuthors(jsonComponent.getAuthors());
430431
component.setPublisher(StringUtils.trimToNull(jsonComponent.getPublisher()));
431432
component.setVersion(StringUtils.trimToNull(jsonComponent.getVersion()));
432433
component.setGroup(StringUtils.trimToNull(jsonComponent.getGroup()));

src/main/java/org/dependencytrack/resources/v1/ProjectResource.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,7 @@ public Response createProject(Project jsonProject) {
290290
final Validator validator = super.getValidator();
291291
failOnValidationError(
292292
validator.validateProperty(jsonProject, "author"),
293+
validator.validateProperty(jsonProject, "authors"),
293294
validator.validateProperty(jsonProject, "publisher"),
294295
validator.validateProperty(jsonProject, "group"),
295296
validator.validateProperty(jsonProject, "name"),
@@ -353,7 +354,7 @@ public Response createProject(Project jsonProject) {
353354
public Response updateProject(Project jsonProject) {
354355
final Validator validator = super.getValidator();
355356
failOnValidationError(
356-
validator.validateProperty(jsonProject, "author"),
357+
validator.validateProperty(jsonProject, "authors"),
357358
validator.validateProperty(jsonProject, "publisher"),
358359
validator.validateProperty(jsonProject, "group"),
359360
validator.validateProperty(jsonProject, "name"),
@@ -430,6 +431,7 @@ public Response patchProject(
430431
final Validator validator = getValidator();
431432
failOnValidationError(
432433
validator.validateProperty(jsonProject, "author"),
434+
validator.validateProperty(jsonProject, "authors"),
433435
validator.validateProperty(jsonProject, "publisher"),
434436
validator.validateProperty(jsonProject, "group"),
435437
jsonProject.getName() != null ? validator.validateProperty(jsonProject, "name") : Set.of(),
@@ -455,7 +457,7 @@ public Response patchProject(
455457
if (modified && qm.doesProjectExist(project.getName(), project.getVersion())) {
456458
return Response.status(Response.Status.CONFLICT).entity("A project with the specified name and version already exists.").build();
457459
}
458-
modified |= setIfDifferent(jsonProject, project, Project::getAuthor, Project::setAuthor);
460+
modified |= setIfDifferent(jsonProject, project, Project::getAuthors, Project::setAuthors);
459461
modified |= setIfDifferent(jsonProject, project, Project::getPublisher, Project::setPublisher);
460462
modified |= setIfDifferent(jsonProject, project, Project::getGroup, Project::setGroup);
461463
modified |= setIfDifferent(jsonProject, project, Project::getDescription, Project::setDescription);

src/main/java/org/dependencytrack/tasks/BomUploadProcessingTask.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -323,7 +323,7 @@ private Project processProject(
323323
boolean hasChanged = false;
324324
if (project != null) {
325325
persistentProject.setBomRef(project.getBomRef()); // Transient
326-
hasChanged |= applyIfChanged(persistentProject, project, Project::getAuthor, persistentProject::setAuthor);
326+
hasChanged |= applyIfChanged(persistentProject, project, Project::getAuthors, persistentProject::setAuthors);
327327
hasChanged |= applyIfChanged(persistentProject, project, Project::getPublisher, persistentProject::setPublisher);
328328
hasChanged |= applyIfChanged(persistentProject, project, Project::getManufacturer, persistentProject::setManufacturer);
329329
hasChanged |= applyIfChanged(persistentProject, project, Project::getSupplier, persistentProject::setSupplier);
@@ -406,7 +406,7 @@ private Map<ComponentIdentity, Component> processComponents(
406406
component.setNew(true); // Transient
407407
} else {
408408
persistentComponent.setBomRef(component.getBomRef()); // Transient
409-
applyIfChanged(persistentComponent, component, Component::getAuthor, persistentComponent::setAuthor);
409+
applyIfChanged(persistentComponent, component, Component::getAuthors, persistentComponent::setAuthors);
410410
applyIfChanged(persistentComponent, component, Component::getPublisher, persistentComponent::setPublisher);
411411
applyIfChanged(persistentComponent, component, Component::getSupplier, persistentComponent::setSupplier);
412412
applyIfChanged(persistentComponent, component, Component::getClassifier, persistentComponent::setClassifier);

src/test/java/org/dependencytrack/resources/v1/BomResourceTest.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@
5959
import jakarta.ws.rs.core.MediaType;
6060
import jakarta.ws.rs.core.Response;
6161
import java.nio.charset.StandardCharsets;
62+
import java.util.ArrayList;
6263
import java.util.Base64;
6364
import java.util.List;
6465
import java.util.UUID;
@@ -130,6 +131,11 @@ public void exportProjectAsCycloneDxInventoryTest() {
130131
project.setClassifier(Classifier.APPLICATION);
131132
project.setManufacturer(projectManufacturer);
132133
project.setSupplier(projectSupplier);
134+
List<OrganizationalContact> authors = new ArrayList<>();
135+
authors.add(new OrganizationalContact() {{
136+
setName("SampleAuthor");
137+
}});
138+
project.setAuthors(authors);
133139
project = qm.createProject(project, null, false);
134140

135141
final var projectProperty = new ProjectProperty();
@@ -237,6 +243,7 @@ public void exportProjectAsCycloneDxInventoryTest() {
237243
"component": {
238244
"type": "application",
239245
"bom-ref": "${json-unit.matches:projectUuid}",
246+
"author": "SampleAuthor",
240247
"supplier": {
241248
"name": "projectSupplier"
242249
},

0 commit comments

Comments
 (0)