Skip to content

Commit

Permalink
Merge pull request #3544 from rbt-mm/master-global-audit-view-policy-…
Browse files Browse the repository at this point in the history
…violations

Global Audit View: Policy Violations
  • Loading branch information
nscuro authored Sep 24, 2024
2 parents 8044252 + 1caf0d4 commit eada3c4
Show file tree
Hide file tree
Showing 4 changed files with 520 additions and 16 deletions.
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,
@Parameter(description = "Optionally includes inactive projects")
@QueryParam("showInactive") boolean showInactive,
@Parameter(description = "Filter by violation state")
@QueryParam("violationState") String violationState,
@Parameter(description = "Filter by risk type")
@QueryParam("riskType") String riskType,
@Parameter(description = "Filter by policy")
@QueryParam("policy") String policy,
@Parameter(description = "Filter by analysis state")
@QueryParam("analysisState") String analysisState,
@Parameter(description = "Filter occurred on from")
@QueryParam("occurredOnDateFrom") String occurredOnDateFrom,
@Parameter(description = "Filter occurred on to")
@QueryParam("occurredOnDateTo") String occurredOnDateTo,
@Parameter(description = "Filter the text input in these fields")
@QueryParam("textSearchField") String textSearchField,
@Parameter(description = "Filter by this text input")
@QueryParam("textSearchInput") String textSearchInput) {
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

0 comments on commit eada3c4

Please sign in to comment.