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
16 changes: 16 additions & 0 deletions src/main/java/org/dependencytrack/model/Project.java
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,14 @@ public enum FetchGroup {
@Element(column = "TAG_ID")
private Set<Tag> tags;

/**
* Date when the project was created.
*/
@Persistent
@Column(name = "CREATED", allowsNull = "true") // New column, must allow nulls on existing databases
@Schema(type = "integer", format = "int64", requiredMode = Schema.RequiredMode.NOT_REQUIRED, description = "UNIX epoch timestamp in milliseconds")
private Date created;

/**
* Convenience field which will contain the date of the last entry in the {@link Bom} table
*/
Expand Down Expand Up @@ -547,6 +555,14 @@ public void setTags(Set<Tag> tags) {
this.tags = tags;
}

public Date getCreated() {
return created;
}

public void setCreated(Date created) {
this.created = created;
}

public Date getLastBomImport() {
return lastBomImport;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -463,6 +463,9 @@ public Project createProject(final Project project, Collection<Tag> tags, boolea
if (project.getParent() != null && !Boolean.TRUE.equals(project.getParent().isActive())){
throw new IllegalArgumentException("An inactive Parent cannot be selected as parent");
}
if (project.getCreated() == null) {
project.setCreated(new Date());
}
final Project oldLatestProject = project.isLatest() ? getLatestProjectVersion(project.getName()) : null;
final Project result = callInTransaction(() -> {
// Remove isLatest flag from current latest project version, if the new project will be the latest
Expand Down Expand Up @@ -685,6 +688,7 @@ public Project clone(
oldLatestProject.set(null);
}
Project project = new Project();
project.setCreated(new Date());
project.setAuthors(source.getAuthors());
project.setManufacturer(source.getManufacturer());
project.setSupplier(source.getSupplier());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,39 @@
import java.util.Date;
import java.util.List;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.mockito.Mockito.times;

class ProjectQueryManagerTest extends PersistenceCapableTest {

@Test
void testCreateProjectSetsCreatedDate() {
final Date before = new Date();
Project project = qm.createProject("Example Project", null, "1.0", null, null, null, true, false);
final Date after = new Date();
assertThat(project.getCreated())
.isNotNull()
.isBetween(before, after);
}

@Test
void testCloneProjectSetsCreatedDate() throws Exception {
Project source = qm.createProject("Source", null, "1.0", null, null, null, true, false);
// Force an older created date on the source to verify the clone is independent.
source.setCreated(new Date(1700000000000L));
qm.persist(source);

final Date before = new Date();
Project cloned = qm.clone(source.getUuid(), "1.1.0", false, false,
false, false, false, false, false, false);
final Date after = new Date();

assertThat(cloned.getCreated())
.isNotNull()
.isBetween(before, after);
}

@Test
void testCloneProjectPreservesVulnerabilityAttributionDate() throws Exception {
Project project = qm.createProject("Example Project 1", "Description 1", "1.0", null, null, null, true, false);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,25 @@ void getProjectByUuidTest() {
""");
}

@Test
void getProjectByUuidExposesCreatedDateTest() {
// Issue #6094: GET /api/v1/project/<uuid> must return the project's creation timestamp.
final long beforeCreate = System.currentTimeMillis();
final Project project = qm.createProject("acme-app", null, "1.0.0", null, null, null, true, false);
final long afterCreate = System.currentTimeMillis();

final Response response = jersey.target(V1_PROJECT + "/" + project.getUuid())
.request()
.header(X_API_KEY, apiKey)
.get();
assertThat(response.getStatus()).isEqualTo(200);

final JsonObject json = parseJsonObject(response);
assertThat(json.containsKey("created")).isTrue();
final long created = json.getJsonNumber("created").longValueExact();
assertThat(created).isBetween(beforeCreate, afterCreate);
}

@Test
void getProjectByUuidNotPermittedTest() {
enablePortfolioAccessControl();
Expand Down Expand Up @@ -643,7 +662,8 @@ void createProjectAsUserWithAclEnabledAndExistingTeamByUuidTest() {
"properties": [],
"tags": [],
"active": true,
"isLatest":false
"isLatest":false,
"created": "${json-unit.any-number}"
}
""");

Expand Down Expand Up @@ -690,7 +710,8 @@ void createProjectAsUserWithAclEnabledAndExistingTeamByNameTest() {
"properties": [],
"tags": [],
"active": true,
"isLatest":false
"isLatest":false,
"created": "${json-unit.any-number}"
}
""");

Expand Down Expand Up @@ -732,7 +753,8 @@ void createProjectAsUserWithAclEnabledAndWithoutTeamTest() {
"properties": [],
"tags": [],
"active": true,
"isLatest":false
"isLatest":false,
"created": "${json-unit.any-number}"
}
""");

Expand Down Expand Up @@ -815,7 +837,8 @@ void createProjectAsUserWithAclEnabledAndNotMemberOfTeamAdminTest() {
"properties": [],
"tags": [],
"active": true,
"isLatest":false
"isLatest":false,
"created": "${json-unit.any-number}"
}
""");

Expand Down Expand Up @@ -926,7 +949,8 @@ void createProjectAsApiKeyWithAclEnabledAndWithExistentTeamTest() {
"properties": [],
"tags": [],
"active": true,
"isLatest":false
"isLatest":false,
"created": "${json-unit.any-number}"
}
""");

Expand Down Expand Up @@ -1606,7 +1630,8 @@ void patchProjectSuccessfullyPatchedTest() {
"active": false,
"isLatest":false,
"children": [],
"collectionLogic":"NONE"
"collectionLogic":"NONE",
"created": "${json-unit.any-number}"
}
""");
}
Expand Down Expand Up @@ -1680,7 +1705,8 @@ void patchProjectParentTest() {
"tags": [],
"active": true,
"isLatest":false,
"collectionLogic":"NONE"
"collectionLogic":"NONE",
"created": "${json-unit.any-number}"
}
""");

Expand Down Expand Up @@ -2311,14 +2337,16 @@ void issue3883RegressionTest() {
"uuid": "${json-unit.any-string}",
"active": true,
"isLatest":false,
"collectionLogic":"NONE"
"collectionLogic":"NONE",
"created": "${json-unit.any-number}"
}
],
"properties": [],
"tags": [],
"active": true,
"isLatest":false,
"collectionLogic":"NONE",
"created": "${json-unit.any-number}",
"versions": [
{
"uuid": "${json-unit.any-string}",
Expand Down Expand Up @@ -2351,6 +2379,7 @@ void issue3883RegressionTest() {
"active": true,
"isLatest":false,
"collectionLogic":"NONE",
"created": "${json-unit.any-number}",
"versions": [
{
"uuid": "${json-unit.any-string}",
Expand Down