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

Global Audit View: Policy Violations #3544

Merged
138 changes: 132 additions & 6 deletions src/main/java/org/dependencytrack/persistence/PolicyQueryManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,13 @@
*/
package org.dependencytrack.persistence;

import alpine.model.ApiKey;
import alpine.model.Team;
import alpine.model.UserPrincipal;
import alpine.persistence.PaginatedResult;
import alpine.resources.AlpineRequest;
import org.dependencytrack.model.Component;
import org.dependencytrack.model.ConfigPropertyConstants;
import org.dependencytrack.model.License;
import org.dependencytrack.model.LicenseGroup;
import org.dependencytrack.model.Policy;
Expand All @@ -31,13 +35,17 @@
import org.dependencytrack.model.ViolationAnalysis;
import org.dependencytrack.model.ViolationAnalysisComment;
import org.dependencytrack.model.ViolationAnalysisState;
import org.dependencytrack.util.DateUtil;

import javax.jdo.PersistenceManager;
import javax.jdo.Query;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;

import static org.dependencytrack.util.PersistenceUtil.assertPersistent;
import static org.dependencytrack.util.PersistenceUtil.assertPersistentAll;
Expand Down Expand Up @@ -351,22 +359,31 @@ public PaginatedResult getPolicyViolations(final Component component, boolean in
}

/**
* Returns a List of all Policy violations for the entire portfolio.
* Returns a List of all Policy violations for the entire portfolio filtered by ACL and other optional filters.
* @return a List of all Policy violations
*/
@SuppressWarnings("unchecked")
public PaginatedResult getPolicyViolations(boolean includeSuppressed) {
public PaginatedResult getPolicyViolations(boolean includeSuppressed, boolean showInactive, Map<String, String> filters) {
final PaginatedResult result;
final Query<PolicyViolation> query = pm.newQuery(PolicyViolation.class);
final Map<String, Object> params = new HashMap<>();
final List<String> filterCriteria = new ArrayList<>();
if (!includeSuppressed) {
query.setFilter("analysis.suppressed == false || analysis.suppressed == null");
filterCriteria.add("(analysis.suppressed == false || analysis.suppressed == null)");
}
if (!showInactive) {
filterCriteria.add("(project.active == true || project.active == null)");
}
processViolationsFilters(filters, params, filterCriteria);
if (orderBy == null) {
query.setOrdering("timestamp desc, project.name, project.version, component.name, component.version");
}
final PaginatedResult result = execute(query);
final String queryFilter = String.join(" && ", filterCriteria);
preprocessACLs(query, queryFilter, params, false);
result = execute(query, params);
for (final PolicyViolation violation: result.getList(PolicyViolation.class)) {
violation.getPolicyCondition().getPolicy(); // force policy to ne included since its not the default
violation.getComponent().getResolvedLicense(); // force resolved license to ne included since its not the default
violation.getPolicyCondition().getPolicy(); // force policy to be included since it's not the default
violation.getComponent().getResolvedLicense(); // force resolved license to be included since it's not the default
violation.setAnalysis(getViolationAnalysis(violation.getComponent(), violation)); // Include the violation analysis by default
}
return result;
Expand Down Expand Up @@ -616,6 +633,115 @@ public long getAuditedCount(final Component component, final PolicyViolation.Typ
return getCount(query, component, type, ViolationAnalysisState.NOT_SET);
}

private void processViolationsFilters(Map<String, String> filters, Map<String, Object> params, List<String> filterCriteria) {
for (Map.Entry<String, String> filter : filters.entrySet()) {
switch (filter.getKey()) {
case "violationState" -> processArrayFilter(params, filterCriteria, "violationState", filter.getValue(), "policyCondition.policy.violationState");
case "riskType" -> processArrayFilter(params, filterCriteria, "riskType", filter.getValue(), "type");
case "policy" -> processArrayFilter(params, filterCriteria, "policy", filter.getValue(), "policyCondition.policy.uuid");
case "analysisState" -> processArrayFilter(params, filterCriteria, "analysisState", filter.getValue(), "analysis.analysisState");
case "occurredOnDateFrom" -> processDateFilter(params, filterCriteria, "occuredOnDateFrom", filter.getValue(), true);
case "occurredOnDateTo" -> processDateFilter(params, filterCriteria, "occuredOnDateTo", filter.getValue(), false);
case "textSearchField" -> processInputFilter(params, filterCriteria, "textInput", filter.getValue(), filters.get("textSearchInput"));
}
}
}

private void processArrayFilter(Map<String, Object> params, List<String> filterCriteria, String paramName, String filter, String column) {
if (filter != null && !filter.isEmpty()) {
StringBuilder filterBuilder = new StringBuilder("(");
String[] arrayFilter = filter.split(",");
for (int i = 0, arrayFilterLength = arrayFilter.length; i < arrayFilterLength; i++) {
filterBuilder.append(column).append(" == :").append(paramName).append(i);
switch (paramName) {
case "violationState" -> params.put(paramName + i, Policy.ViolationState.valueOf(arrayFilter[i]));
case "riskType" -> params.put(paramName + i, PolicyViolation.Type.valueOf(arrayFilter[i]));
case "policy" -> params.put(paramName + i, UUID.fromString(arrayFilter[i]));
case "analysisState" -> {
if (arrayFilter[i].equals("NOT_SET")) {
filterBuilder.append(" || ").append(column).append(" == null");
}
params.put(paramName + i, ViolationAnalysisState.valueOf(arrayFilter[i]));
}
}
if (i < arrayFilterLength - 1) {
filterBuilder.append(" || ");
}
}
filterBuilder.append(")");
filterCriteria.add(filterBuilder.toString());
}
}

private void processDateFilter(Map<String, Object> params, List<String> filterCriteria, String paramName, String filter, boolean fromValue) {
if (filter != null && !filter.isEmpty()) {
params.put(paramName, DateUtil.fromISO8601(filter + (fromValue ? "T00:00:00" : "T23:59:59")));
filterCriteria.add("(timestamp " + (fromValue ? ">= :" : "<= :") + paramName + ")");
}
}

private void processInputFilter(Map<String, Object> params, List<String> filterCriteria, String paramName, String filter, String input) {
if (filter != null && !filter.isEmpty() && input != null && !input.isEmpty()) {
StringBuilder filterBuilder = new StringBuilder("(");
String[] inputFilter = filter.split(",");
for (int i = 0, inputFilterLength = inputFilter.length; i < inputFilterLength; i++) {
switch (inputFilter[i].toLowerCase()) {
case "policy_name" -> filterBuilder.append("policyCondition.policy.name");
case "component" -> filterBuilder.append("component.name");
case "license" -> filterBuilder.append("component.resolvedLicense.licenseId.toLowerCase().matches(:").append(paramName).append(") || component.license");
case "project_name" -> filterBuilder.append("project.name.toLowerCase().matches(:").append(paramName).append(") || project.version");
}
filterBuilder.append(".toLowerCase().matches(:").append(paramName).append(")");
if (i < inputFilterLength - 1) {
filterBuilder.append(" || ");
}
}
params.put(paramName, ".*" + input.toLowerCase() + ".*");
filterBuilder.append(")");
filterCriteria.add(filterBuilder.toString());
}
}

@Override
void preprocessACLs(final Query<?> query, final String inputFilter, final Map<String, Object> params, final boolean bypass) {
if (super.principal != null && isEnabled(ConfigPropertyConstants.ACCESS_MANAGEMENT_ACL_ENABLED) && !bypass) {
final List<Team> teams;
if (super.principal instanceof UserPrincipal) {
final UserPrincipal userPrincipal = ((UserPrincipal) super.principal);
teams = userPrincipal.getTeams();
if (super.hasAccessManagementPermission(userPrincipal)) {
query.setFilter(inputFilter);
return;
}
} else {
final ApiKey apiKey = ((ApiKey) super.principal);
teams = apiKey.getTeams();
if (super.hasAccessManagementPermission(apiKey)) {
query.setFilter(inputFilter);
return;
}
}
if (teams != null && teams.size() > 0) {
final StringBuilder sb = new StringBuilder();
for (int i = 0, teamsSize = teams.size(); i < teamsSize; i++) {
final Team team = super.getObjectById(Team.class, teams.get(i).getId());
sb.append(" project.accessTeams.contains(:team").append(i).append(") ");
params.put("team" + i, team);
if (i < teamsSize-1) {
sb.append(" || ");
}
}
if (inputFilter != null) {
query.setFilter(inputFilter + " && (" + sb.toString() + ")");
} else {
query.setFilter(sb.toString());
}
}
} else {
query.setFilter(inputFilter);
}
}

/**
* @since 4.12.0
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -731,8 +731,8 @@ public PaginatedResult getPolicyViolations(final Component component, boolean in
return getPolicyQueryManager().getPolicyViolations(component, includeSuppressed);
}

public PaginatedResult getPolicyViolations(boolean includeSuppressed) {
return getPolicyQueryManager().getPolicyViolations(includeSuppressed);
public PaginatedResult getPolicyViolations(boolean includeSuppressed, boolean showInactive, Map<String, String> filters) {
return getPolicyQueryManager().getPolicyViolations(includeSuppressed, showInactive, filters);
}

public ViolationAnalysis getViolationAnalysis(Component component, PolicyViolation policyViolation) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@
import javax.jdo.FetchPlan;
import javax.jdo.PersistenceManager;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

/**
* JAX-RS resources for processing policy violations.
Expand Down Expand Up @@ -83,9 +85,36 @@ public class PolicyViolationResource extends AlpineResource {
})
@PermissionRequired(Permissions.Constants.VIEW_POLICY_VIOLATION)
public Response getViolations(@Parameter(description = "Optionally includes suppressed violations")
@QueryParam("suppressed") boolean suppressed) {
@QueryParam("suppressed") boolean suppressed,
@PathParam(value = "Optionally includes inactive projects")
@QueryParam("showInactive") boolean showInactive,
@PathParam(value = "Filter by violation state")
@QueryParam("violationState") String violationState,
@PathParam(value = "Filter by risk type")
@QueryParam("riskType") String riskType,
@PathParam(value = "Filter by policy")
@QueryParam("policy") String policy,
@PathParam(value = "Filter by analysis state")
@QueryParam("analysisState") String analysisState,
@PathParam(value = "Filter occurred on from")
@QueryParam("occurredOnDateFrom") String occurredOnDateFrom,
@PathParam(value = "Filter occurred on to")
@QueryParam("occurredOnDateTo") String occurredOnDateTo,
@PathParam(value = "Filter the text input in these fields")
@QueryParam("textSearchField") String textSearchField,
@PathParam(value = "Filter by this text input")
@QueryParam("textSearchInput") String textSearchInput) {
rbt-mm marked this conversation as resolved.
Show resolved Hide resolved
try (QueryManager qm = new QueryManager(getAlpineRequest())) {
final PaginatedResult result = qm.getPolicyViolations(suppressed);
Map<String, String> filters = new HashMap<>();
filters.put("violationState", violationState);
filters.put("riskType", riskType);
filters.put("policy", policy);
filters.put("analysisState", analysisState);
filters.put("occurredOnDateFrom", occurredOnDateFrom);
filters.put("occurredOnDateTo", occurredOnDateTo);
filters.put("textSearchField", textSearchField);
filters.put("textSearchInput", textSearchInput);
final PaginatedResult result = qm.getPolicyViolations(suppressed, showInactive, filters);
return Response.ok(detachViolations(qm, result.getList(PolicyViolation.class)))
.header(TOTAL_COUNT_HEADER, result.getTotal())
.build();
Expand Down
Loading
Loading