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

Include team name in audit trail for API-submitted audit changes #4154

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
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,8 @@

import alpine.common.validation.RegexSequence;
import alpine.common.validation.ValidationTask;
import alpine.model.LdapUser;
import alpine.model.ManagedUser;
import alpine.model.OidcUser;
import alpine.model.ApiKey;
import alpine.model.Team;
import alpine.model.UserPrincipal;
import alpine.server.auth.PermissionRequired;
import alpine.server.resources.AlpineResource;
Expand All @@ -35,6 +34,10 @@
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 java.util.ArrayList;
import java.util.List;

import org.apache.commons.lang3.StringUtils;
import org.dependencytrack.auth.Permissions;
import org.dependencytrack.model.Analysis;
Expand Down Expand Up @@ -166,8 +169,13 @@ public Response updateAnalysis(AnalysisRequest request) {
}

String commenter = null;
if (getPrincipal() instanceof LdapUser || getPrincipal() instanceof ManagedUser || getPrincipal() instanceof OidcUser) {
commenter = ((UserPrincipal) getPrincipal()).getUsername();
if (getPrincipal() instanceof UserPrincipal principal) {
commenter = principal.getUsername();
} else if (getPrincipal() instanceof ApiKey apiKey) {
List<Team> teams = apiKey.getTeams();
List<String> teamNames = new ArrayList<String>();
teams.forEach(team -> teamNames.add(team.getName()));
commenter = String.join(", ", teamNames);
nscuro marked this conversation as resolved.
Show resolved Hide resolved
}

boolean analysisStateChange = false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,13 @@
*/
package org.dependencytrack.resources.v1;

import alpine.model.ManagedUser;
import alpine.notification.Notification;
import alpine.notification.NotificationLevel;
import alpine.notification.NotificationService;
import alpine.notification.Subscriber;
import alpine.notification.Subscription;
import alpine.server.auth.JsonWebToken;
import alpine.server.filters.ApiFilter;
import alpine.server.filters.AuthenticationFilter;
import alpine.server.filters.AuthorizationFilter;
Expand Down Expand Up @@ -353,10 +355,70 @@ public void updateAnalysisCreateNewTest() throws Exception {
assertThat(responseJson.getJsonArray("analysisComments")).hasSize(2);
assertThat(responseJson.getJsonArray("analysisComments").getJsonObject(0))
.hasFieldOrPropertyWithValue("comment", Json.createValue("Analysis: NOT_SET → NOT_AFFECTED"))
.doesNotContainKey("commenter"); // Not set when authenticating via API key
.hasFieldOrPropertyWithValue("commenter", Json.createValue("Test Users"));
assertThat(responseJson.getJsonArray("analysisComments").getJsonObject(1))
.hasFieldOrPropertyWithValue("comment", Json.createValue("Analysis comment here"))
.doesNotContainKey("commenter"); // Not set when authenticating via API key
.hasFieldOrPropertyWithValue("commenter", Json.createValue("Test Users"));
assertThat(responseJson.getBoolean("isSuppressed")).isTrue();

assertConditionWithTimeout(() -> NOTIFICATIONS.size() == 2, Duration.ofSeconds(5));
final Notification projectNotification = NOTIFICATIONS.poll();
assertThat(projectNotification).isNotNull();
final Notification notification = NOTIFICATIONS.poll();
assertThat(notification).isNotNull();
assertThat(notification.getScope()).isEqualTo(NotificationScope.PORTFOLIO.name());
assertThat(notification.getGroup()).isEqualTo(NotificationGroup.PROJECT_AUDIT_CHANGE.name());
assertThat(notification.getLevel()).isEqualTo(NotificationLevel.INFORMATIONAL);
assertThat(notification.getTitle()).isEqualTo(NotificationUtil.generateNotificationTitle(NotificationConstants.Title.ANALYSIS_DECISION_NOT_AFFECTED, project));
assertThat(notification.getContent()).isEqualTo("An analysis decision was made to a finding affecting a project");
}

@Test
public void updateAnalysisCreateNewWithUserTest() throws Exception {
initializeWithPermissions(Permissions.VULNERABILITY_ANALYSIS);
ManagedUser testUser = qm.createManagedUser("testuser", TEST_USER_PASSWORD_HASH);
String jwt = new JsonWebToken().createToken(testUser);
qm.addUserToTeam(testUser, team);

final Project project = qm.createProject("Acme Example", null, "1.0", null, null, null, true, false);

var component = new Component();
component.setProject(project);
component.setName("Acme Component");
component.setVersion("1.0");
component = qm.createComponent(component, false);

var vulnerability = new Vulnerability();
vulnerability.setVulnId("INT-001");
vulnerability.setSource(Vulnerability.Source.INTERNAL);
vulnerability.setSeverity(Severity.HIGH);
vulnerability.setComponents(List.of(component));
vulnerability = qm.createVulnerability(vulnerability, false);

final var analysisRequest = new AnalysisRequest(project.getUuid().toString(), component.getUuid().toString(),
vulnerability.getUuid().toString(), AnalysisState.NOT_AFFECTED, AnalysisJustification.CODE_NOT_REACHABLE,
AnalysisResponse.WILL_NOT_FIX, "Analysis details here", "Analysis comment here", true);

final Response response = jersey.target(V1_ANALYSIS)
.request()
.header("Authorization", "Bearer " + jwt)
.put(Entity.entity(analysisRequest, MediaType.APPLICATION_JSON));
assertThat(response.getStatus()).isEqualTo(HttpStatus.SC_OK);
assertThat(response.getHeaderString(TOTAL_COUNT_HEADER)).isNull();

final JsonObject responseJson = parseJsonObject(response);
assertThat(responseJson).isNotNull();
assertThat(responseJson.getString("analysisState")).isEqualTo(AnalysisState.NOT_AFFECTED.name());
assertThat(responseJson.getString("analysisJustification")).isEqualTo(AnalysisJustification.CODE_NOT_REACHABLE.name());
assertThat(responseJson.getString("analysisResponse")).isEqualTo(AnalysisResponse.WILL_NOT_FIX.name());
assertThat(responseJson.getString("analysisDetails")).isEqualTo("Analysis details here");
assertThat(responseJson.getJsonArray("analysisComments")).hasSize(2);
assertThat(responseJson.getJsonArray("analysisComments").getJsonObject(0))
.hasFieldOrPropertyWithValue("comment", Json.createValue("Analysis: NOT_SET → NOT_AFFECTED"))
.hasFieldOrPropertyWithValue("commenter", Json.createValue("testuser"));
assertThat(responseJson.getJsonArray("analysisComments").getJsonObject(1))
.hasFieldOrPropertyWithValue("comment", Json.createValue("Analysis comment here"))
.hasFieldOrPropertyWithValue("commenter", Json.createValue("testuser"));
assertThat(responseJson.getBoolean("isSuppressed")).isTrue();

assertConditionWithTimeout(() -> NOTIFICATIONS.size() == 2, Duration.ofSeconds(5));
Expand Down Expand Up @@ -469,22 +531,22 @@ public void updateAnalysisUpdateExistingTest() throws Exception {
.hasFieldOrPropertyWithValue("commenter", Json.createValue("Jane Doe"));
assertThat(analysisComments.getJsonObject(1))
.hasFieldOrPropertyWithValue("comment", Json.createValue("Analysis: NOT_AFFECTED → EXPLOITABLE"))
.doesNotContainKey("commenter"); // Not set when authenticating via API key
.hasFieldOrPropertyWithValue("commenter", Json.createValue("Test Users"));
assertThat(analysisComments.getJsonObject(2))
.hasFieldOrPropertyWithValue("comment", Json.createValue("Justification: CODE_NOT_REACHABLE → NOT_SET"))
.doesNotContainKey("commenter"); // Not set when authenticating via API key
.hasFieldOrPropertyWithValue("commenter", Json.createValue("Test Users"));
assertThat(analysisComments.getJsonObject(3))
.hasFieldOrPropertyWithValue("comment", Json.createValue("Vendor Response: WILL_NOT_FIX → UPDATE"))
.doesNotContainKey("commenter"); // Not set when authenticating via API key
.hasFieldOrPropertyWithValue("commenter", Json.createValue("Test Users"));
assertThat(analysisComments.getJsonObject(4))
.hasFieldOrPropertyWithValue("comment", Json.createValue("Details: New analysis details here"))
.doesNotContainKey("commenter"); // Not set when authenticating via API key
.hasFieldOrPropertyWithValue("commenter", Json.createValue("Test Users"));
assertThat(analysisComments.getJsonObject(5))
.hasFieldOrPropertyWithValue("comment", Json.createValue("Unsuppressed"))
.doesNotContainKey("commenter"); // Not set when authenticating via API key
.hasFieldOrPropertyWithValue("commenter", Json.createValue("Test Users"));
assertThat(analysisComments.getJsonObject(6))
.hasFieldOrPropertyWithValue("comment", Json.createValue("New analysis comment here"))
.doesNotContainKey("commenter"); // Not set when authenticating via API key
.hasFieldOrPropertyWithValue("commenter", Json.createValue("Test Users"));
assertThat(responseJson.getBoolean("isSuppressed")).isFalse();

assertConditionWithTimeout(() -> NOTIFICATIONS.size() == 2, Duration.ofSeconds(5));
Expand Down Expand Up @@ -596,13 +658,13 @@ public void updateAnalysisUpdateExistingWithEmptyRequestTest() throws Exception
.hasFieldOrPropertyWithValue("commenter", Json.createValue("Jane Doe"));
assertThat(analysisComments.getJsonObject(1))
.hasFieldOrPropertyWithValue("comment", Json.createValue("Analysis: NOT_AFFECTED → NOT_SET"))
.doesNotContainKey("commenter"); // Not set when authenticating via API key
.hasFieldOrPropertyWithValue("commenter", Json.createValue("Test Users"));
assertThat(analysisComments.getJsonObject(2))
.hasFieldOrPropertyWithValue("comment", Json.createValue("Justification: CODE_NOT_REACHABLE → NOT_SET"))
.doesNotContainKey("commenter"); // Not set when authenticating via API key
.hasFieldOrPropertyWithValue("commenter", Json.createValue("Test Users"));
assertThat(analysisComments.getJsonObject(3))
.hasFieldOrPropertyWithValue("comment", Json.createValue("Vendor Response: WILL_NOT_FIX → NOT_SET"))
.doesNotContainKey("commenter"); // Not set when authenticating via API key
.hasFieldOrPropertyWithValue("commenter", Json.createValue("Test Users"));

assertConditionWithTimeout(() -> NOTIFICATIONS.size() == 2, Duration.ofSeconds(5));
final Notification projectNotification = NOTIFICATIONS.poll();
Expand Down Expand Up @@ -759,19 +821,19 @@ public void updateAnalysisIssue1409Test() throws InterruptedException {
assertThat(analysisComments).hasSize(5);
assertThat(analysisComments.getJsonObject(0))
.hasFieldOrPropertyWithValue("comment", Json.createValue("Analysis: IN_TRIAGE → NOT_AFFECTED"))
.doesNotContainKey("commenter"); // Not set when authenticating via API key
.hasFieldOrPropertyWithValue("commenter", Json.createValue("Test Users"));
assertThat(analysisComments.getJsonObject(1))
.hasFieldOrPropertyWithValue("comment", Json.createValue("Justification: NOT_SET → PROTECTED_BY_MITIGATING_CONTROL"))
.doesNotContainKey("commenter"); // Not set when authenticating via API key
.hasFieldOrPropertyWithValue("commenter", Json.createValue("Test Users"));
assertThat(analysisComments.getJsonObject(2))
.hasFieldOrPropertyWithValue("comment", Json.createValue("Vendor Response: NOT_SET → UPDATE"))
.doesNotContainKey("commenter"); // Not set when authenticating via API key
.hasFieldOrPropertyWithValue("commenter", Json.createValue("Test Users"));
assertThat(analysisComments.getJsonObject(3))
.hasFieldOrPropertyWithValue("comment", Json.createValue("Details: New analysis details here"))
.doesNotContainKey("commenter"); // Not set when authenticating via API key
.hasFieldOrPropertyWithValue("commenter", Json.createValue("Test Users"));
assertThat(analysisComments.getJsonObject(4))
.hasFieldOrPropertyWithValue("comment", Json.createValue("New analysis comment here"))
.doesNotContainKey("commenter"); // Not set when authenticating via API key
.hasFieldOrPropertyWithValue("commenter", Json.createValue("Test Users"));
assertThat(responseJson.getBoolean("isSuppressed")).isFalse();

assertConditionWithTimeout(() -> NOTIFICATIONS.size() == 2, Duration.ofSeconds(5));
Expand Down
Loading