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

Port : Include team name in audit trail for API-submitted audit changes #922

Merged
merged 3 commits into from
Sep 24, 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
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,8 @@ public enum ConfigPropertyConstants {
CUSTOM_RISK_SCORE_LOW("risk-score", "weight.low", "1", PropertyType.INTEGER, "Low severity vulnerability weight (between 1-10)", ConfigPropertyAccessMode.READ_WRITE),
CUSTOM_RISK_SCORE_UNASSIGNED("risk-score", "weight.unassigned", "5", PropertyType.INTEGER, "Unassigned severity vulnerability weight (between 1-10)", ConfigPropertyAccessMode.READ_WRITE),
WELCOME_MESSAGE("general", "welcome.message.html", "%20%3Chtml%3E%3Ch1%3EYour%20Welcome%20Message%3C%2Fh1%3E%3C%2Fhtml%3E", PropertyType.STRING, "Custom HTML Code that is displayed before login", ConfigPropertyAccessMode.READ_WRITE, true),
IS_WELCOME_MESSAGE("general", "welcome.message.enabled", "false", PropertyType.BOOLEAN, "Bool that says wheter to show the welcome message or not", ConfigPropertyAccessMode.READ_WRITE, true);
IS_WELCOME_MESSAGE("general", "welcome.message.enabled", "false", PropertyType.BOOLEAN, "Bool that says wheter to show the welcome message or not", ConfigPropertyAccessMode.READ_WRITE, true),
DEFAULT_LANGUAGE("general", "default.locale", null, PropertyType.STRING, "Determine the default Language to use", ConfigPropertyAccessMode.READ_WRITE);

private final String groupName;
private final String propertyName;
Expand Down
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 Down Expand Up @@ -58,6 +57,9 @@
import org.dependencytrack.util.AnalysisCommentUtil;
import org.dependencytrack.util.NotificationUtil;

import java.util.ArrayList;
import java.util.List;

import static org.dependencytrack.util.AnalysisCommentFormatter.formatComment;

/**
Expand Down Expand Up @@ -169,8 +171,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<>();
teams.forEach(team -> teamNames.add(team.getName()));
commenter = String.join(", ", teamNames);
}

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

import alpine.model.ManagedUser;
import alpine.server.auth.JsonWebToken;
import alpine.server.filters.ApiFilter;
import alpine.server.filters.AuthenticationFilter;
import alpine.server.filters.AuthorizationFilter;
import jakarta.json.Json;
import jakarta.json.JsonArray;
import jakarta.json.JsonObject;
import jakarta.ws.rs.client.Entity;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import net.jcip.annotations.NotThreadSafe;
import org.apache.http.HttpStatus;
import org.dependencytrack.JerseyTestRule;
Expand All @@ -47,12 +55,6 @@
import org.junit.ClassRule;
import org.junit.Test;

import jakarta.json.Json;
import jakarta.json.JsonArray;
import jakarta.json.JsonObject;
import jakarta.ws.rs.client.Entity;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import java.time.Duration;
import java.util.List;
import java.util.UUID;
Expand Down Expand Up @@ -326,10 +328,71 @@ 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"))
.hasFieldOrPropertyWithValue("commenter", Json.createValue("Test Users"));
assertThat(responseJson.getBoolean("isSuppressed")).isTrue();

assertConditionWithTimeout(() -> kafkaMockProducer.history().size() == 2, Duration.ofSeconds(5));
final Notification projectNotification = deserializeValue(KafkaTopics.NOTIFICATION_PROJECT_CREATED, kafkaMockProducer.history().get(0));
assertThat(projectNotification).isNotNull();
final Notification notification = deserializeValue(KafkaTopics.NOTIFICATION_PROJECT_AUDIT_CHANGE, kafkaMockProducer.history().get(1));
assertThat(notification).isNotNull();
assertThat(notification.getScope()).isEqualTo(SCOPE_PORTFOLIO);
assertThat(notification.getGroup()).isEqualTo(GROUP_PROJECT_AUDIT_CHANGE);
assertThat(notification.getLevel()).isEqualTo(LEVEL_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"))
.doesNotContainKey("commenter"); // Not set when authenticating via API key
.hasFieldOrPropertyWithValue("commenter", Json.createValue("testuser"));
assertThat(responseJson.getBoolean("isSuppressed")).isTrue();

assertConditionWithTimeout(() -> kafkaMockProducer.history().size() == 2, Duration.ofSeconds(5));
Expand Down Expand Up @@ -442,22 +505,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(() -> kafkaMockProducer.history().size() == 2, Duration.ofSeconds(5));
Expand Down Expand Up @@ -569,13 +632,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(() -> kafkaMockProducer.history().size() == 2, Duration.ofSeconds(5));
final Notification projectNotification = deserializeValue(KafkaTopics.NOTIFICATION_PROJECT_CREATED, kafkaMockProducer.history().get(0));
Expand Down Expand Up @@ -732,19 +795,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(() -> kafkaMockProducer.history().size() == 2, Duration.ofSeconds(5));
Expand Down
Loading