From 138691db074612a8052a885031e4debfca595fa3 Mon Sep 17 00:00:00 2001 From: RBickert Date: Tue, 14 Feb 2023 10:11:57 +0100 Subject: [PATCH 1/9] Global Audit View: Policy Violations Enhances the `getViolations` method in `PolicyViolationResource` to filter the result by ACL and to also allow the use of additional filters, so that a user can get more specific results. Signed-off-by: RBickert --- .../persistence/PolicyQueryManager.java | 253 +++++++++++++++++- .../persistence/QueryManager.java | 4 +- .../resources/v1/PolicyViolationResource.java | 33 ++- 3 files changed, 280 insertions(+), 10 deletions(-) diff --git a/src/main/java/org/dependencytrack/persistence/PolicyQueryManager.java b/src/main/java/org/dependencytrack/persistence/PolicyQueryManager.java index 8ec4aabea8..a11096a851 100644 --- a/src/main/java/org/dependencytrack/persistence/PolicyQueryManager.java +++ b/src/main/java/org/dependencytrack/persistence/PolicyQueryManager.java @@ -18,9 +18,16 @@ */ package org.dependencytrack.persistence; +import alpine.common.logging.Logger; +import alpine.model.ApiKey; +import alpine.model.Team; +import alpine.model.UserPrincipal; import alpine.persistence.PaginatedResult; import alpine.resources.AlpineRequest; +import alpine.server.util.DbUtil; +import org.apache.commons.lang3.StringUtils; 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; @@ -30,15 +37,40 @@ 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.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; import java.util.ArrayList; import java.util.Date; +import java.util.HashMap; import java.util.List; +import java.util.Map; +import java.util.UUID; final class PolicyQueryManager extends QueryManager implements IQueryManager { + public static final String QUERY_ACL_1 = """ + "DESCENDANTS" ("ID", "NAME") AS + (SELECT "PROJECT"."ID", + "PROJECT"."NAME" + FROM "PROJECT" + """; + + public static final String QUERY_ACL_2 = """ + UNION ALL + SELECT "CHILD"."ID", + "CHILD"."NAME" + FROM "PROJECT" "CHILD" + JOIN "DESCENDANTS" + ON "DESCENDANTS"."ID" = "CHILD"."PARENT_PROJECT_ID") + SELECT "DESCENDANTS"."ID", "DESCENDANTS"."NAME" FROM "DESCENDANTS" + """; + private static final Logger LOGGER = Logger.getLogger(PolicyQueryManager.class); + /** * Constructs a new QueryManager. * @param pm a PersistenceManager object @@ -346,27 +378,167 @@ 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 filters) { + final PaginatedResult result; final Query query = pm.newQuery(PolicyViolation.class); + final Map params = new HashMap<>(); + final List 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; } + private void preprocessACLs(final Query query, final String inputFilter, final Map params, final boolean bypass) { + if (super.principal != null && isEnabled(ConfigPropertyConstants.ACCESS_MANAGEMENT_ACL_ENABLED) && !bypass) { + final List 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; + } + } + + // Query every project that the teams have access to + final Map tempParams = new HashMap<>(); + final Query queryAclProjects = pm.newQuery(Project.class); + if (teams != null && teams.size() > 0) { + final StringBuilder stringBuilderAclProjects = new StringBuilder(); + for (int i = 0, teamsSize = teams.size(); i < teamsSize; i++) { + final Team team = super.getObjectById(Team.class, teams.get(i).getId()); + stringBuilderAclProjects.append(" accessTeams.contains(:team").append(i).append(") "); + tempParams.put("team" + i, team); + if (i < teamsSize-1) { + stringBuilderAclProjects.append(" || "); + } + } + queryAclProjects.setFilter(stringBuilderAclProjects.toString()); + } else { + if (inputFilter != null && !inputFilter.isEmpty()) { + query.setFilter(inputFilter + " && :false"); + } else { + query.setFilter(":false"); + } + params.put("false", false); + return; + } + List result = (List) queryAclProjects.executeWithMap(tempParams); + // Query the descendants of the projects that the teams have access to + if (result != null && !result.isEmpty()) { + final StringBuilder stringBuilderDescendants = new StringBuilder(); + final List parameters = new ArrayList<>(); + stringBuilderDescendants.append("WHERE"); + int i = 0, teamSize = result.size(); + for (Project project : result) { + stringBuilderDescendants.append(" \"ID\" = ?").append(" "); + parameters.add(project.getId()); + if (i < teamSize-1) { + stringBuilderDescendants.append(" OR"); + } + i++; + } + stringBuilderDescendants.append("\n"); + final List results = new ArrayList<>(); + + // Querying the descendants of projects requires a CTE (Common Table Expression), which needs to be at the top-level of the query for Microsoft SQL Server. + // Because of JDO, queries are only allowed to start with "SELECT", so the "WITH" clause for the CTE in MSSQL cannot be at top level. + // Activating the JDO property that queries don't have to start with "SELECT" does not help in this case, because JDO queries that do not start with "SELECT" only return "true", so no data can be fetched this way. + // To circumvent this problem, the query is executed via the direct connection to the database and not via JDO. + Connection connection = null; + PreparedStatement preparedStatement = null; + ResultSet rs = null; + try { + connection = (Connection) pm.getDataStoreConnection(); + if (DbUtil.isMssql() || DbUtil.isOracle()) { // Microsoft SQL Server and Oracle DB already imply the "RECURSIVE" keyword in the "WITH" clause, therefore it is not needed in the query + preparedStatement = connection.prepareStatement("WITH " + QUERY_ACL_1 + stringBuilderDescendants + QUERY_ACL_2); + } else { // Other Databases need the "RECURSIVE" keyword in the "WITH" clause to correctly execute the query + preparedStatement = connection.prepareStatement("WITH RECURSIVE " + QUERY_ACL_1 + stringBuilderDescendants + QUERY_ACL_2); + } + int j = 1; + for (Long id : parameters) { + preparedStatement.setLong(j, id); + j++; + } + preparedStatement.execute(); + rs = preparedStatement.getResultSet(); + while (rs.next()) { + results.add(rs.getLong(1)); + } + } catch (Exception e) { + LOGGER.error(e.getMessage()); + if (inputFilter != null && !inputFilter.isEmpty()) { + query.setFilter(inputFilter + " && :false"); + } else { + query.setFilter(":false"); + } + params.put("false", false); + return; + } finally { + DbUtil.close(rs); + DbUtil.close(preparedStatement); + DbUtil.close(connection); + } + + // Add queried projects and descendants to the input filter of the query + if (results != null && !results.isEmpty()) { + final StringBuilder stringBuilderInputFilter = new StringBuilder(); + int j = 0; + int resultSize = results.size(); + for (Long id : results) { + stringBuilderInputFilter.append(" project.id == :id").append(j); + params.put("id" + j, id); + if (j < resultSize-1) { + stringBuilderInputFilter.append(" || "); + } + j++; + } + if (inputFilter != null && !inputFilter.isEmpty()) { + query.setFilter(inputFilter + " && (" + stringBuilderInputFilter.toString() + ")"); + } else { + query.setFilter(stringBuilderInputFilter.toString()); + } + } + } else { + if (inputFilter != null && !inputFilter.isEmpty()) { + query.setFilter(inputFilter + " && :false"); + } else { + query.setFilter(":false"); + } + params.put("false", false); + } + } else if (StringUtils.trimToNull(inputFilter) != null) { + query.setFilter(inputFilter); + } + } + /** * clones a ViolationAnalysis * @param destinationComponent the destinationComponent @@ -611,4 +783,73 @@ public long getAuditedCount(final Component component, final PolicyViolation.Typ return getCount(query, component, type, ViolationAnalysisState.NOT_SET); } + private void processViolationsFilters(Map filters, Map params, List filterCriteria) { + for (Map.Entry 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 params, List 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 params, List 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 params, List 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"); + 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()); + } + } + } diff --git a/src/main/java/org/dependencytrack/persistence/QueryManager.java b/src/main/java/org/dependencytrack/persistence/QueryManager.java index 4c129e5875..12a9fcd20d 100644 --- a/src/main/java/org/dependencytrack/persistence/QueryManager.java +++ b/src/main/java/org/dependencytrack/persistence/QueryManager.java @@ -655,8 +655,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 filters) { + return getPolicyQueryManager().getPolicyViolations(includeSuppressed, showInactive, filters); } public ViolationAnalysis getViolationAnalysis(Component component, PolicyViolation policyViolation) { diff --git a/src/main/java/org/dependencytrack/resources/v1/PolicyViolationResource.java b/src/main/java/org/dependencytrack/resources/v1/PolicyViolationResource.java index 84d03a10d1..69bb9eeb20 100644 --- a/src/main/java/org/dependencytrack/resources/v1/PolicyViolationResource.java +++ b/src/main/java/org/dependencytrack/resources/v1/PolicyViolationResource.java @@ -44,6 +44,8 @@ import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import java.util.Collection; +import java.util.HashMap; +import java.util.Map; /** * JAX-RS resources for processing policy violations. @@ -68,9 +70,36 @@ public class PolicyViolationResource extends AlpineResource { }) @PermissionRequired(Permissions.Constants.VIEW_POLICY_VIOLATION) public Response getViolations(@ApiParam(value = "Optionally includes suppressed violations") - @QueryParam("suppressed") boolean suppressed) { + @QueryParam("suppressed") boolean suppressed, + @ApiParam(value = "Optionally includes inactive projects") + @QueryParam("showInactive") boolean showInactive, + @ApiParam(value = "Filter by violation state") + @QueryParam("violationState") String violationState, + @ApiParam(value = "Filter by risk type") + @QueryParam("riskType") String riskType, + @ApiParam(value = "Filter by policy") + @QueryParam("policy") String policy, + @ApiParam(value = "Filter by analysis state") + @QueryParam("analysisState") String analysisState, + @ApiParam(value = "Filter occurred on from") + @QueryParam("occurredOnDateFrom") String occurredOnDateFrom, + @ApiParam(value = "Filter occurred on to") + @QueryParam("occurredOnDateTo") String occurredOnDateTo, + @ApiParam(value = "Filter the text input in these fields") + @QueryParam("textSearchField") String textSearchField, + @ApiParam(value = "Filter by this text input") + @QueryParam("textSearchInput") String textSearchInput) { try (QueryManager qm = new QueryManager(getAlpineRequest())) { - final PaginatedResult result = qm.getPolicyViolations(suppressed); + Map 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(); From dcf10006a751e8c02a3051f4099191c8733f1a0d Mon Sep 17 00:00:00 2001 From: RBickert Date: Tue, 14 Feb 2023 10:13:03 +0100 Subject: [PATCH 2/9] Add test for PolicyViolationResource Adds a new test for `PolicyViolationResource` which tests the filtering by ACL of the newly enhanced `getViolations` method Signed-off-by: RBickert --- .../persistence/ComponentQueryManager.java | 1 + .../persistence/PolicyQueryManager.java | 263 +++++++++--------- .../persistence/ProjectQueryManager.java | 1 + .../v1/PolicyViolationResourceTest.java | 136 +++++++++ 4 files changed, 270 insertions(+), 131 deletions(-) diff --git a/src/main/java/org/dependencytrack/persistence/ComponentQueryManager.java b/src/main/java/org/dependencytrack/persistence/ComponentQueryManager.java index 0abc1b08c8..6e2f4e2273 100644 --- a/src/main/java/org/dependencytrack/persistence/ComponentQueryManager.java +++ b/src/main/java/org/dependencytrack/persistence/ComponentQueryManager.java @@ -688,6 +688,7 @@ public void reconcileComponents(Project project, List existingProject /** * A similar method exists in ProjectQueryManager */ + // TODO: Move redundant method `preprocessACLs` into own utility class (AclUtil) once #2407 and #2472 are merged. private void preprocessACLs(final Query query, final String inputFilter, final Map params, final boolean bypass) { if (super.principal != null && isEnabled(ConfigPropertyConstants.ACCESS_MANAGEMENT_ACL_ENABLED) && !bypass) { final List teams; diff --git a/src/main/java/org/dependencytrack/persistence/PolicyQueryManager.java b/src/main/java/org/dependencytrack/persistence/PolicyQueryManager.java index a11096a851..6c6ec0c5cc 100644 --- a/src/main/java/org/dependencytrack/persistence/PolicyQueryManager.java +++ b/src/main/java/org/dependencytrack/persistence/PolicyQueryManager.java @@ -408,137 +408,6 @@ public PaginatedResult getPolicyViolations(boolean includeSuppressed, boolean sh return result; } - private void preprocessACLs(final Query query, final String inputFilter, final Map params, final boolean bypass) { - if (super.principal != null && isEnabled(ConfigPropertyConstants.ACCESS_MANAGEMENT_ACL_ENABLED) && !bypass) { - final List 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; - } - } - - // Query every project that the teams have access to - final Map tempParams = new HashMap<>(); - final Query queryAclProjects = pm.newQuery(Project.class); - if (teams != null && teams.size() > 0) { - final StringBuilder stringBuilderAclProjects = new StringBuilder(); - for (int i = 0, teamsSize = teams.size(); i < teamsSize; i++) { - final Team team = super.getObjectById(Team.class, teams.get(i).getId()); - stringBuilderAclProjects.append(" accessTeams.contains(:team").append(i).append(") "); - tempParams.put("team" + i, team); - if (i < teamsSize-1) { - stringBuilderAclProjects.append(" || "); - } - } - queryAclProjects.setFilter(stringBuilderAclProjects.toString()); - } else { - if (inputFilter != null && !inputFilter.isEmpty()) { - query.setFilter(inputFilter + " && :false"); - } else { - query.setFilter(":false"); - } - params.put("false", false); - return; - } - List result = (List) queryAclProjects.executeWithMap(tempParams); - // Query the descendants of the projects that the teams have access to - if (result != null && !result.isEmpty()) { - final StringBuilder stringBuilderDescendants = new StringBuilder(); - final List parameters = new ArrayList<>(); - stringBuilderDescendants.append("WHERE"); - int i = 0, teamSize = result.size(); - for (Project project : result) { - stringBuilderDescendants.append(" \"ID\" = ?").append(" "); - parameters.add(project.getId()); - if (i < teamSize-1) { - stringBuilderDescendants.append(" OR"); - } - i++; - } - stringBuilderDescendants.append("\n"); - final List results = new ArrayList<>(); - - // Querying the descendants of projects requires a CTE (Common Table Expression), which needs to be at the top-level of the query for Microsoft SQL Server. - // Because of JDO, queries are only allowed to start with "SELECT", so the "WITH" clause for the CTE in MSSQL cannot be at top level. - // Activating the JDO property that queries don't have to start with "SELECT" does not help in this case, because JDO queries that do not start with "SELECT" only return "true", so no data can be fetched this way. - // To circumvent this problem, the query is executed via the direct connection to the database and not via JDO. - Connection connection = null; - PreparedStatement preparedStatement = null; - ResultSet rs = null; - try { - connection = (Connection) pm.getDataStoreConnection(); - if (DbUtil.isMssql() || DbUtil.isOracle()) { // Microsoft SQL Server and Oracle DB already imply the "RECURSIVE" keyword in the "WITH" clause, therefore it is not needed in the query - preparedStatement = connection.prepareStatement("WITH " + QUERY_ACL_1 + stringBuilderDescendants + QUERY_ACL_2); - } else { // Other Databases need the "RECURSIVE" keyword in the "WITH" clause to correctly execute the query - preparedStatement = connection.prepareStatement("WITH RECURSIVE " + QUERY_ACL_1 + stringBuilderDescendants + QUERY_ACL_2); - } - int j = 1; - for (Long id : parameters) { - preparedStatement.setLong(j, id); - j++; - } - preparedStatement.execute(); - rs = preparedStatement.getResultSet(); - while (rs.next()) { - results.add(rs.getLong(1)); - } - } catch (Exception e) { - LOGGER.error(e.getMessage()); - if (inputFilter != null && !inputFilter.isEmpty()) { - query.setFilter(inputFilter + " && :false"); - } else { - query.setFilter(":false"); - } - params.put("false", false); - return; - } finally { - DbUtil.close(rs); - DbUtil.close(preparedStatement); - DbUtil.close(connection); - } - - // Add queried projects and descendants to the input filter of the query - if (results != null && !results.isEmpty()) { - final StringBuilder stringBuilderInputFilter = new StringBuilder(); - int j = 0; - int resultSize = results.size(); - for (Long id : results) { - stringBuilderInputFilter.append(" project.id == :id").append(j); - params.put("id" + j, id); - if (j < resultSize-1) { - stringBuilderInputFilter.append(" || "); - } - j++; - } - if (inputFilter != null && !inputFilter.isEmpty()) { - query.setFilter(inputFilter + " && (" + stringBuilderInputFilter.toString() + ")"); - } else { - query.setFilter(stringBuilderInputFilter.toString()); - } - } - } else { - if (inputFilter != null && !inputFilter.isEmpty()) { - query.setFilter(inputFilter + " && :false"); - } else { - query.setFilter(":false"); - } - params.put("false", false); - } - } else if (StringUtils.trimToNull(inputFilter) != null) { - query.setFilter(inputFilter); - } - } - /** * clones a ViolationAnalysis * @param destinationComponent the destinationComponent @@ -852,4 +721,136 @@ private void processInputFilter(Map params, List filterC } } + // TODO: Move redundant method `preprocessACLs` into own utility class (AclUtil) once #2407 and #2472 are merged. + private void preprocessACLs(final Query query, final String inputFilter, final Map params, final boolean bypass) { + if (super.principal != null && isEnabled(ConfigPropertyConstants.ACCESS_MANAGEMENT_ACL_ENABLED) && !bypass) { + final List 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; + } + } + + // Query every project that the teams have access to + final Map tempParams = new HashMap<>(); + final Query queryAclProjects = pm.newQuery(Project.class); + if (teams != null && teams.size() > 0) { + final StringBuilder stringBuilderAclProjects = new StringBuilder(); + for (int i = 0, teamsSize = teams.size(); i < teamsSize; i++) { + final Team team = super.getObjectById(Team.class, teams.get(i).getId()); + stringBuilderAclProjects.append(" accessTeams.contains(:team").append(i).append(") "); + tempParams.put("team" + i, team); + if (i < teamsSize-1) { + stringBuilderAclProjects.append(" || "); + } + } + queryAclProjects.setFilter(stringBuilderAclProjects.toString()); + } else { + if (inputFilter != null && !inputFilter.isEmpty()) { + query.setFilter(inputFilter + " && :false"); + } else { + query.setFilter(":false"); + } + params.put("false", false); + return; + } + List result = (List) queryAclProjects.executeWithMap(tempParams); + // Query the descendants of the projects that the teams have access to + if (result != null && !result.isEmpty()) { + final StringBuilder stringBuilderDescendants = new StringBuilder(); + final List parameters = new ArrayList<>(); + stringBuilderDescendants.append("WHERE"); + int i = 0, teamSize = result.size(); + for (Project project : result) { + stringBuilderDescendants.append(" \"ID\" = ?").append(" "); + parameters.add(project.getId()); + if (i < teamSize-1) { + stringBuilderDescendants.append(" OR"); + } + i++; + } + stringBuilderDescendants.append("\n"); + final List results = new ArrayList<>(); + + // Querying the descendants of projects requires a CTE (Common Table Expression), which needs to be at the top-level of the query for Microsoft SQL Server. + // Because of JDO, queries are only allowed to start with "SELECT", so the "WITH" clause for the CTE in MSSQL cannot be at top level. + // Activating the JDO property that queries don't have to start with "SELECT" does not help in this case, because JDO queries that do not start with "SELECT" only return "true", so no data can be fetched this way. + // To circumvent this problem, the query is executed via the direct connection to the database and not via JDO. + Connection connection = null; + PreparedStatement preparedStatement = null; + ResultSet rs = null; + try { + connection = (Connection) pm.getDataStoreConnection(); + if (DbUtil.isMssql() || DbUtil.isOracle()) { // Microsoft SQL Server and Oracle DB already imply the "RECURSIVE" keyword in the "WITH" clause, therefore it is not needed in the query + preparedStatement = connection.prepareStatement("WITH " + QUERY_ACL_1 + stringBuilderDescendants + QUERY_ACL_2); + } else { // Other Databases need the "RECURSIVE" keyword in the "WITH" clause to correctly execute the query + preparedStatement = connection.prepareStatement("WITH RECURSIVE " + QUERY_ACL_1 + stringBuilderDescendants + QUERY_ACL_2); + } + int j = 1; + for (Long id : parameters) { + preparedStatement.setLong(j, id); + j++; + } + preparedStatement.execute(); + rs = preparedStatement.getResultSet(); + while (rs.next()) { + results.add(rs.getLong(1)); + } + } catch (Exception e) { + LOGGER.error(e.getMessage()); + if (inputFilter != null && !inputFilter.isEmpty()) { + query.setFilter(inputFilter + " && :false"); + } else { + query.setFilter(":false"); + } + params.put("false", false); + return; + } finally { + DbUtil.close(rs); + DbUtil.close(preparedStatement); + DbUtil.close(connection); + } + + // Add queried projects and descendants to the input filter of the query + if (results != null && !results.isEmpty()) { + final StringBuilder stringBuilderInputFilter = new StringBuilder(); + int j = 0; + int resultSize = results.size(); + for (Long id : results) { + stringBuilderInputFilter.append(" project.id == :id").append(j); + params.put("id" + j, id); + if (j < resultSize-1) { + stringBuilderInputFilter.append(" || "); + } + j++; + } + if (inputFilter != null && !inputFilter.isEmpty()) { + query.setFilter(inputFilter + " && (" + stringBuilderInputFilter.toString() + ")"); + } else { + query.setFilter(stringBuilderInputFilter.toString()); + } + } + } else { + if (inputFilter != null && !inputFilter.isEmpty()) { + query.setFilter(inputFilter + " && :false"); + } else { + query.setFilter(":false"); + } + params.put("false", false); + } + } else if (StringUtils.trimToNull(inputFilter) != null) { + query.setFilter(inputFilter); + } + } + } diff --git a/src/main/java/org/dependencytrack/persistence/ProjectQueryManager.java b/src/main/java/org/dependencytrack/persistence/ProjectQueryManager.java index 1cfbfaf7c8..563a616811 100644 --- a/src/main/java/org/dependencytrack/persistence/ProjectQueryManager.java +++ b/src/main/java/org/dependencytrack/persistence/ProjectQueryManager.java @@ -893,6 +893,7 @@ public boolean hasAccess(final Principal principal, final Project project) { /** * A similar method exists in ComponentQueryManager */ + // TODO: Move redundant method `preprocessACLs` into own utility class (AclUtil) once #2407 and #2472 are merged. private void preprocessACLs(final Query query, final String inputFilter, final Map params, final boolean bypass) { if (super.principal != null && isEnabled(ConfigPropertyConstants.ACCESS_MANAGEMENT_ACL_ENABLED) && !bypass) { final List teams; diff --git a/src/test/java/org/dependencytrack/resources/v1/PolicyViolationResourceTest.java b/src/test/java/org/dependencytrack/resources/v1/PolicyViolationResourceTest.java index c5df56ed33..526018c917 100644 --- a/src/test/java/org/dependencytrack/resources/v1/PolicyViolationResourceTest.java +++ b/src/test/java/org/dependencytrack/resources/v1/PolicyViolationResourceTest.java @@ -18,12 +18,14 @@ */ package org.dependencytrack.resources.v1; +import alpine.model.ConfigProperty; import alpine.server.filters.ApiFilter; import alpine.server.filters.AuthenticationFilter; import alpine.server.filters.AuthorizationFilter; import org.dependencytrack.ResourceTest; import org.dependencytrack.auth.Permissions; import org.dependencytrack.model.Component; +import org.dependencytrack.model.ConfigPropertyConstants; import org.dependencytrack.model.Policy; import org.dependencytrack.model.PolicyCondition; import org.dependencytrack.model.PolicyViolation; @@ -311,4 +313,138 @@ public void getViolationsByComponentNotFoundTest() { assertThat(getPlainTextBody(response)).contains("component could not be found"); } + @Test + public void getViolationsWithAclEnabledTest() { + initializeWithPermissions(Permissions.VIEW_POLICY_VIOLATION); + + final Project project = qm.createProject("Acme Example", null, "1.0", null, null, null, true, false); + final Project child = qm.createProject("Acme Example - Child", null, "1.0", null, null, null, true, false); + final Project grandchild = qm.createProject("Acme Example - Grandchild", null, "1.0", null, null, null, true, false); + final Project noAccess = qm.createProject("Acme Example - No Access", 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 component1 = new Component(); + component1.setProject(child); + component1.setName("Acme Component"); + component1.setVersion("1.0"); + component1 = qm.createComponent(component1, false); + + var component2 = new Component(); + component2.setProject(grandchild); + component2.setName("Acme Component"); + component2.setVersion("1.0"); + component2 = qm.createComponent(component2, false); + + var component3 = new Component(); + component3.setProject(noAccess); + component3.setName("Acme Component"); + component3.setVersion("1.0"); + component3 = qm.createComponent(component3, false); + + final Policy policy = qm.createPolicy("Blacklisted Version", Policy.Operator.ALL, Policy.ViolationState.FAIL); + final PolicyCondition condition = qm.createPolicyCondition(policy, PolicyCondition.Subject.VERSION, PolicyCondition.Operator.NUMERIC_EQUAL, "1.0"); + + var violation = new PolicyViolation(); + violation.setType(PolicyViolation.Type.OPERATIONAL); + violation.setComponent(component); + violation.setPolicyCondition(condition); + violation.setTimestamp(new Date()); + violation = qm.persist(violation); + + var violation1 = new PolicyViolation(); + violation1.setType(PolicyViolation.Type.OPERATIONAL); + violation1.setComponent(component1); + violation1.setPolicyCondition(condition); + violation1.setTimestamp(new Date()); + violation1 = qm.persist(violation1); + + var violation2 = new PolicyViolation(); + violation2.setType(PolicyViolation.Type.OPERATIONAL); + violation2.setComponent(component2); + violation2.setPolicyCondition(condition); + violation2.setTimestamp(new Date()); + violation2 = qm.persist(violation2); + + var violation3 = new PolicyViolation(); + violation3.setType(PolicyViolation.Type.OPERATIONAL); + violation3.setComponent(component3); + violation3.setPolicyCondition(condition); + violation3.setTimestamp(new Date()); + violation3 = qm.persist(violation3); + + ConfigProperty aclToggle = qm.getConfigProperty(ConfigPropertyConstants.ACCESS_MANAGEMENT_ACL_ENABLED.getGroupName(), ConfigPropertyConstants.ACCESS_MANAGEMENT_ACL_ENABLED.getPropertyName()); + if (aclToggle == null) { + qm.createConfigProperty(ConfigPropertyConstants.ACCESS_MANAGEMENT_ACL_ENABLED.getGroupName(), ConfigPropertyConstants.ACCESS_MANAGEMENT_ACL_ENABLED.getPropertyName(), "true", ConfigPropertyConstants.ACCESS_MANAGEMENT_ACL_ENABLED.getPropertyType(), ConfigPropertyConstants.ACCESS_MANAGEMENT_ACL_ENABLED.getDescription()); + } else { + aclToggle.setPropertyValue("true"); + qm.persist(aclToggle); + } + + project.addAccessTeam(team); + + final Response response = target(V1_POLICY_VIOLATION) + .request() + .header(X_API_KEY, apiKey) + .get(); + assertThat(response.getStatus()).isEqualTo(Response.Status.OK.getStatusCode()); + assertThat(response.getHeaderString(TOTAL_COUNT_HEADER)).isEqualTo("1"); + + final JsonArray jsonArray = parseJsonArray(response); + assertThat(jsonArray).hasSize(1); + + final JsonObject jsonObject = jsonArray.getJsonObject(0); + assertThat(jsonObject.getString("uuid")).isEqualTo(violation.getUuid().toString()); + assertThat(jsonObject.getString("type")).isEqualTo(PolicyViolation.Type.OPERATIONAL.name()); + assertThat(jsonObject.getJsonObject("policyCondition")).isNotNull(); + assertThat(jsonObject.getJsonObject("policyCondition").getJsonObject("policy")).isNotNull(); + assertThat(jsonObject.getJsonObject("policyCondition").getJsonObject("policy").getString("name")).isEqualTo("Blacklisted Version"); + assertThat(jsonObject.getJsonObject("policyCondition").getJsonObject("policy").getString("violationState")).isEqualTo("FAIL"); + assertThat(jsonObject.getJsonObject("project").getString("uuid")).isEqualTo(project.getUuid().toString()); + + child.setParent(project); + grandchild.setParent(child); + + final Response response1 = target(V1_POLICY_VIOLATION) + .request() + .header(X_API_KEY, apiKey) + .get(); + assertThat(response1.getStatus()).isEqualTo(200); + assertThat(response1.getHeaderString(TOTAL_COUNT_HEADER)).isEqualTo("3"); + + final JsonArray jsonArray1 = parseJsonArray(response1); + assertThat(jsonArray1).hasSize(3); + + final JsonObject jsonObject1 = jsonArray1.getJsonObject(0); + assertThat(jsonObject1.getString("uuid")).isEqualTo(violation2.getUuid().toString()); + assertThat(jsonObject1.getString("type")).isEqualTo(PolicyViolation.Type.OPERATIONAL.name()); + assertThat(jsonObject1.getJsonObject("policyCondition")).isNotNull(); + assertThat(jsonObject1.getJsonObject("policyCondition").getJsonObject("policy")).isNotNull(); + assertThat(jsonObject1.getJsonObject("policyCondition").getJsonObject("policy").getString("name")).isEqualTo("Blacklisted Version"); + assertThat(jsonObject1.getJsonObject("policyCondition").getJsonObject("policy").getString("violationState")).isEqualTo("FAIL"); + assertThat(jsonObject1.getJsonObject("project").getString("uuid")).isEqualTo(grandchild.getUuid().toString()); + + final JsonObject jsonObject2 = jsonArray1.getJsonObject(1); + assertThat(jsonObject2.getString("uuid")).isEqualTo(violation1.getUuid().toString()); + assertThat(jsonObject2.getString("type")).isEqualTo(PolicyViolation.Type.OPERATIONAL.name()); + assertThat(jsonObject2.getJsonObject("policyCondition")).isNotNull(); + assertThat(jsonObject2.getJsonObject("policyCondition").getJsonObject("policy")).isNotNull(); + assertThat(jsonObject2.getJsonObject("policyCondition").getJsonObject("policy").getString("name")).isEqualTo("Blacklisted Version"); + assertThat(jsonObject2.getJsonObject("policyCondition").getJsonObject("policy").getString("violationState")).isEqualTo("FAIL"); + assertThat(jsonObject2.getJsonObject("project").getString("uuid")).isEqualTo(child.getUuid().toString()); + + final JsonObject jsonObject3 = jsonArray1.getJsonObject(2); + assertThat(jsonObject3.getString("uuid")).isEqualTo(violation.getUuid().toString()); + assertThat(jsonObject3.getString("type")).isEqualTo(PolicyViolation.Type.OPERATIONAL.name()); + assertThat(jsonObject3.getJsonObject("policyCondition")).isNotNull(); + assertThat(jsonObject3.getJsonObject("policyCondition").getJsonObject("policy")).isNotNull(); + assertThat(jsonObject3.getJsonObject("policyCondition").getJsonObject("policy").getString("name")).isEqualTo("Blacklisted Version"); + assertThat(jsonObject3.getJsonObject("policyCondition").getJsonObject("policy").getString("violationState")).isEqualTo("FAIL"); + assertThat(jsonObject3.getJsonObject("project").getString("uuid")).isEqualTo(project.getUuid().toString()); + } + } \ No newline at end of file From 9269a993161ebe64a01a23a74f6dcd43f2c1a528 Mon Sep 17 00:00:00 2001 From: RBickert Date: Tue, 14 Feb 2023 14:59:01 +0100 Subject: [PATCH 3/9] Include unresolved license in filter Signed-off-by: RBickert --- .../org/dependencytrack/persistence/PolicyQueryManager.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/dependencytrack/persistence/PolicyQueryManager.java b/src/main/java/org/dependencytrack/persistence/PolicyQueryManager.java index 6c6ec0c5cc..a6ca8e1368 100644 --- a/src/main/java/org/dependencytrack/persistence/PolicyQueryManager.java +++ b/src/main/java/org/dependencytrack/persistence/PolicyQueryManager.java @@ -707,7 +707,7 @@ private void processInputFilter(Map params, List filterC 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"); + 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(")"); From 57c785b248e2d0142a9454fdcfffad9aaca5cb7d Mon Sep 17 00:00:00 2001 From: RBickert Date: Tue, 12 Mar 2024 14:13:38 +0100 Subject: [PATCH 4/9] Revert changes to `preprocessACLs` Signed-off-by: RBickert --- .../persistence/ComponentQueryManager.java | 1 - .../persistence/PolicyQueryManager.java | 133 +---------- .../v1/PolicyViolationResourceTest.java | 206 ++++++++---------- 3 files changed, 99 insertions(+), 241 deletions(-) diff --git a/src/main/java/org/dependencytrack/persistence/ComponentQueryManager.java b/src/main/java/org/dependencytrack/persistence/ComponentQueryManager.java index 6e2f4e2273..0abc1b08c8 100644 --- a/src/main/java/org/dependencytrack/persistence/ComponentQueryManager.java +++ b/src/main/java/org/dependencytrack/persistence/ComponentQueryManager.java @@ -688,7 +688,6 @@ public void reconcileComponents(Project project, List existingProject /** * A similar method exists in ProjectQueryManager */ - // TODO: Move redundant method `preprocessACLs` into own utility class (AclUtil) once #2407 and #2472 are merged. private void preprocessACLs(final Query query, final String inputFilter, final Map params, final boolean bypass) { if (super.principal != null && isEnabled(ConfigPropertyConstants.ACCESS_MANAGEMENT_ACL_ENABLED) && !bypass) { final List teams; diff --git a/src/main/java/org/dependencytrack/persistence/PolicyQueryManager.java b/src/main/java/org/dependencytrack/persistence/PolicyQueryManager.java index a6ca8e1368..6ea6ed6f16 100644 --- a/src/main/java/org/dependencytrack/persistence/PolicyQueryManager.java +++ b/src/main/java/org/dependencytrack/persistence/PolicyQueryManager.java @@ -18,14 +18,11 @@ */ package org.dependencytrack.persistence; -import alpine.common.logging.Logger; import alpine.model.ApiKey; import alpine.model.Team; import alpine.model.UserPrincipal; import alpine.persistence.PaginatedResult; import alpine.resources.AlpineRequest; -import alpine.server.util.DbUtil; -import org.apache.commons.lang3.StringUtils; import org.dependencytrack.model.Component; import org.dependencytrack.model.ConfigPropertyConstants; import org.dependencytrack.model.License; @@ -41,9 +38,6 @@ import javax.jdo.PersistenceManager; import javax.jdo.Query; -import java.sql.Connection; -import java.sql.PreparedStatement; -import java.sql.ResultSet; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; @@ -53,24 +47,6 @@ final class PolicyQueryManager extends QueryManager implements IQueryManager { - public static final String QUERY_ACL_1 = """ - "DESCENDANTS" ("ID", "NAME") AS - (SELECT "PROJECT"."ID", - "PROJECT"."NAME" - FROM "PROJECT" - """; - - public static final String QUERY_ACL_2 = """ - UNION ALL - SELECT "CHILD"."ID", - "CHILD"."NAME" - FROM "PROJECT" "CHILD" - JOIN "DESCENDANTS" - ON "DESCENDANTS"."ID" = "CHILD"."PARENT_PROJECT_ID") - SELECT "DESCENDANTS"."ID", "DESCENDANTS"."NAME" FROM "DESCENDANTS" - """; - private static final Logger LOGGER = Logger.getLogger(PolicyQueryManager.class); - /** * Constructs a new QueryManager. * @param pm a PersistenceManager object @@ -721,7 +697,6 @@ private void processInputFilter(Map params, List filterC } } - // TODO: Move redundant method `preprocessACLs` into own utility class (AclUtil) once #2407 and #2472 are merged. private void preprocessACLs(final Query query, final String inputFilter, final Map params, final boolean bypass) { if (super.principal != null && isEnabled(ConfigPropertyConstants.ACCESS_MANAGEMENT_ACL_ENABLED) && !bypass) { final List teams; @@ -740,115 +715,23 @@ private void preprocessACLs(final Query query, final String inp return; } } - - // Query every project that the teams have access to - final Map tempParams = new HashMap<>(); - final Query queryAclProjects = pm.newQuery(Project.class); if (teams != null && teams.size() > 0) { - final StringBuilder stringBuilderAclProjects = new StringBuilder(); + 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()); - stringBuilderAclProjects.append(" accessTeams.contains(:team").append(i).append(") "); - tempParams.put("team" + i, team); + sb.append(" project.accessTeams.contains(:team").append(i).append(") "); + params.put("team" + i, team); if (i < teamsSize-1) { - stringBuilderAclProjects.append(" || "); + sb.append(" || "); } } - queryAclProjects.setFilter(stringBuilderAclProjects.toString()); - } else { - if (inputFilter != null && !inputFilter.isEmpty()) { - query.setFilter(inputFilter + " && :false"); + if (inputFilter != null) { + query.setFilter(inputFilter + " && (" + sb.toString() + ")"); } else { - query.setFilter(":false"); + query.setFilter(sb.toString()); } - params.put("false", false); - return; } - List result = (List) queryAclProjects.executeWithMap(tempParams); - // Query the descendants of the projects that the teams have access to - if (result != null && !result.isEmpty()) { - final StringBuilder stringBuilderDescendants = new StringBuilder(); - final List parameters = new ArrayList<>(); - stringBuilderDescendants.append("WHERE"); - int i = 0, teamSize = result.size(); - for (Project project : result) { - stringBuilderDescendants.append(" \"ID\" = ?").append(" "); - parameters.add(project.getId()); - if (i < teamSize-1) { - stringBuilderDescendants.append(" OR"); - } - i++; - } - stringBuilderDescendants.append("\n"); - final List results = new ArrayList<>(); - - // Querying the descendants of projects requires a CTE (Common Table Expression), which needs to be at the top-level of the query for Microsoft SQL Server. - // Because of JDO, queries are only allowed to start with "SELECT", so the "WITH" clause for the CTE in MSSQL cannot be at top level. - // Activating the JDO property that queries don't have to start with "SELECT" does not help in this case, because JDO queries that do not start with "SELECT" only return "true", so no data can be fetched this way. - // To circumvent this problem, the query is executed via the direct connection to the database and not via JDO. - Connection connection = null; - PreparedStatement preparedStatement = null; - ResultSet rs = null; - try { - connection = (Connection) pm.getDataStoreConnection(); - if (DbUtil.isMssql() || DbUtil.isOracle()) { // Microsoft SQL Server and Oracle DB already imply the "RECURSIVE" keyword in the "WITH" clause, therefore it is not needed in the query - preparedStatement = connection.prepareStatement("WITH " + QUERY_ACL_1 + stringBuilderDescendants + QUERY_ACL_2); - } else { // Other Databases need the "RECURSIVE" keyword in the "WITH" clause to correctly execute the query - preparedStatement = connection.prepareStatement("WITH RECURSIVE " + QUERY_ACL_1 + stringBuilderDescendants + QUERY_ACL_2); - } - int j = 1; - for (Long id : parameters) { - preparedStatement.setLong(j, id); - j++; - } - preparedStatement.execute(); - rs = preparedStatement.getResultSet(); - while (rs.next()) { - results.add(rs.getLong(1)); - } - } catch (Exception e) { - LOGGER.error(e.getMessage()); - if (inputFilter != null && !inputFilter.isEmpty()) { - query.setFilter(inputFilter + " && :false"); - } else { - query.setFilter(":false"); - } - params.put("false", false); - return; - } finally { - DbUtil.close(rs); - DbUtil.close(preparedStatement); - DbUtil.close(connection); - } - - // Add queried projects and descendants to the input filter of the query - if (results != null && !results.isEmpty()) { - final StringBuilder stringBuilderInputFilter = new StringBuilder(); - int j = 0; - int resultSize = results.size(); - for (Long id : results) { - stringBuilderInputFilter.append(" project.id == :id").append(j); - params.put("id" + j, id); - if (j < resultSize-1) { - stringBuilderInputFilter.append(" || "); - } - j++; - } - if (inputFilter != null && !inputFilter.isEmpty()) { - query.setFilter(inputFilter + " && (" + stringBuilderInputFilter.toString() + ")"); - } else { - query.setFilter(stringBuilderInputFilter.toString()); - } - } - } else { - if (inputFilter != null && !inputFilter.isEmpty()) { - query.setFilter(inputFilter + " && :false"); - } else { - query.setFilter(":false"); - } - params.put("false", false); - } - } else if (StringUtils.trimToNull(inputFilter) != null) { + } else { query.setFilter(inputFilter); } } diff --git a/src/test/java/org/dependencytrack/resources/v1/PolicyViolationResourceTest.java b/src/test/java/org/dependencytrack/resources/v1/PolicyViolationResourceTest.java index 526018c917..09dc157775 100644 --- a/src/test/java/org/dependencytrack/resources/v1/PolicyViolationResourceTest.java +++ b/src/test/java/org/dependencytrack/resources/v1/PolicyViolationResourceTest.java @@ -121,11 +121,11 @@ public void getViolationsByProjectTest() { component0.setVersion("1.0"); component0 = qm.createComponent(component0, false); - var component1 = new Component(); - component1.setProject(project); - component1.setName("Acme Component 1"); - component1.setVersion("1.0"); - component1 = qm.createComponent(component1, false); + var componentA = new Component(); + componentA.setProject(project); + componentA.setName("Acme Component 1"); + componentA.setVersion("1.0"); + componentA = qm.createComponent(componentA, false); final Policy policy0 = qm.createPolicy("Blacklisted Version 0", Policy.Operator.ALL, Policy.ViolationState.FAIL); final PolicyCondition condition0 = qm.createPolicyCondition(policy0, PolicyCondition.Subject.VERSION, PolicyCondition.Operator.NUMERIC_EQUAL, "1.0"); @@ -140,7 +140,7 @@ public void getViolationsByProjectTest() { var violation = new PolicyViolation(); violation.setType(PolicyViolation.Type.OPERATIONAL); - violation.setComponent(componentFilter ? component0 : component1); + violation.setComponent(componentFilter ? component0 : componentA); violation.setPolicyCondition(conditionFilter ? condition0 : condition1); violation.setTimestamp(new Date()); violation = qm.persist(violation); @@ -317,65 +317,74 @@ public void getViolationsByComponentNotFoundTest() { public void getViolationsWithAclEnabledTest() { initializeWithPermissions(Permissions.VIEW_POLICY_VIOLATION); - final Project project = qm.createProject("Acme Example", null, "1.0", null, null, null, true, false); - final Project child = qm.createProject("Acme Example - Child", null, "1.0", null, null, null, true, false); - final Project grandchild = qm.createProject("Acme Example - Grandchild", null, "1.0", null, null, null, true, false); - final Project noAccess = qm.createProject("Acme Example - No Access", 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 component1 = new Component(); - component1.setProject(child); - component1.setName("Acme Component"); - component1.setVersion("1.0"); - component1 = qm.createComponent(component1, false); + final Project projectA = qm.createProject("Acme Example", null, "1.0", null, null, null, true, false); + final Project projectA_child = qm.createProject("Acme Example - Child", null, "1.0", null, projectA, null, true, false); + final Project projectB = qm.createProject("Acme Example - Grandchild", null, "1.0", null, null, null, true, false); - var component2 = new Component(); - component2.setProject(grandchild); - component2.setName("Acme Component"); - component2.setVersion("1.0"); - component2 = qm.createComponent(component2, false); + projectA.addAccessTeam(team); - var component3 = new Component(); - component3.setProject(noAccess); - component3.setName("Acme Component"); - component3.setVersion("1.0"); - component3 = qm.createComponent(component3, false); + var componentA = new Component(); + componentA.setProject(projectA); + componentA.setName("Acme Component"); + componentA.setVersion("1.0"); + componentA = qm.createComponent(componentA, false); + + var componentB = new Component(); + componentB.setProject(projectA_child); + componentB.setName("Acme Component"); + componentB.setVersion("1.0"); + componentB = qm.createComponent(componentB, false); + + var componentC = new Component(); + componentC.setProject(projectB); + componentC.setName("Acme Component"); + componentC.setVersion("1.0"); + componentC = qm.createComponent(componentC, false); + + var componentD = new Component(); + componentD.setProject(projectA); + componentD.setName("Acme Component"); + componentD.setVersion("1.0"); + componentD = qm.createComponent(componentA, false); final Policy policy = qm.createPolicy("Blacklisted Version", Policy.Operator.ALL, Policy.ViolationState.FAIL); final PolicyCondition condition = qm.createPolicyCondition(policy, PolicyCondition.Subject.VERSION, PolicyCondition.Operator.NUMERIC_EQUAL, "1.0"); - var violation = new PolicyViolation(); - violation.setType(PolicyViolation.Type.OPERATIONAL); - violation.setComponent(component); - violation.setPolicyCondition(condition); - violation.setTimestamp(new Date()); - violation = qm.persist(violation); - - var violation1 = new PolicyViolation(); - violation1.setType(PolicyViolation.Type.OPERATIONAL); - violation1.setComponent(component1); - violation1.setPolicyCondition(condition); - violation1.setTimestamp(new Date()); - violation1 = qm.persist(violation1); - - var violation2 = new PolicyViolation(); - violation2.setType(PolicyViolation.Type.OPERATIONAL); - violation2.setComponent(component2); - violation2.setPolicyCondition(condition); - violation2.setTimestamp(new Date()); - violation2 = qm.persist(violation2); - - var violation3 = new PolicyViolation(); - violation3.setType(PolicyViolation.Type.OPERATIONAL); - violation3.setComponent(component3); - violation3.setPolicyCondition(condition); - violation3.setTimestamp(new Date()); - violation3 = qm.persist(violation3); + var violationA = new PolicyViolation(); + violationA.setType(PolicyViolation.Type.OPERATIONAL); + violationA.setComponent(componentA); + violationA.setPolicyCondition(condition); + violationA.setTimestamp(new Date()); + violationA = qm.persist(violationA); + + var violationB = new PolicyViolation(); + violationB.setType(PolicyViolation.Type.OPERATIONAL); + violationB.setComponent(componentB); + violationB.setPolicyCondition(condition); + violationB.setTimestamp(new Date()); + violationB = qm.persist(violationB); + + var violationC = new PolicyViolation(); + violationC.setType(PolicyViolation.Type.OPERATIONAL); + violationC.setComponent(componentC); + violationC.setPolicyCondition(condition); + violationC.setTimestamp(new Date()); + violationC = qm.persist(violationC); + + var violationD = new PolicyViolation(); + violationD.setType(PolicyViolation.Type.OPERATIONAL); + violationD.setComponent(componentD); + violationD.setPolicyCondition(condition); + violationD.setTimestamp(new Date()); + violationD = qm.persist(violationD); + + final Response responseA = target(V1_POLICY_VIOLATION) + .request() + .header(X_API_KEY, apiKey) + .get(); + assertThat(responseA.getStatus()).isEqualTo(Response.Status.OK.getStatusCode()); + assertThat(responseA.getHeaderString(TOTAL_COUNT_HEADER)).isEqualTo("4"); + assertThat(parseJsonArray(responseA)).hasSize(4); ConfigProperty aclToggle = qm.getConfigProperty(ConfigPropertyConstants.ACCESS_MANAGEMENT_ACL_ENABLED.getGroupName(), ConfigPropertyConstants.ACCESS_MANAGEMENT_ACL_ENABLED.getPropertyName()); if (aclToggle == null) { @@ -385,66 +394,33 @@ public void getViolationsWithAclEnabledTest() { qm.persist(aclToggle); } - project.addAccessTeam(team); - - final Response response = target(V1_POLICY_VIOLATION) + final Response responseB = target(V1_POLICY_VIOLATION) .request() - .header(X_API_KEY, apiKey) + .header(X_API_KEY, team.getApiKeys().get(0).getKey()) .get(); - assertThat(response.getStatus()).isEqualTo(Response.Status.OK.getStatusCode()); - assertThat(response.getHeaderString(TOTAL_COUNT_HEADER)).isEqualTo("1"); - - final JsonArray jsonArray = parseJsonArray(response); - assertThat(jsonArray).hasSize(1); - - final JsonObject jsonObject = jsonArray.getJsonObject(0); - assertThat(jsonObject.getString("uuid")).isEqualTo(violation.getUuid().toString()); - assertThat(jsonObject.getString("type")).isEqualTo(PolicyViolation.Type.OPERATIONAL.name()); - assertThat(jsonObject.getJsonObject("policyCondition")).isNotNull(); - assertThat(jsonObject.getJsonObject("policyCondition").getJsonObject("policy")).isNotNull(); - assertThat(jsonObject.getJsonObject("policyCondition").getJsonObject("policy").getString("name")).isEqualTo("Blacklisted Version"); - assertThat(jsonObject.getJsonObject("policyCondition").getJsonObject("policy").getString("violationState")).isEqualTo("FAIL"); - assertThat(jsonObject.getJsonObject("project").getString("uuid")).isEqualTo(project.getUuid().toString()); - - child.setParent(project); - grandchild.setParent(child); - - final Response response1 = target(V1_POLICY_VIOLATION) - .request() - .header(X_API_KEY, apiKey) - .get(); - assertThat(response1.getStatus()).isEqualTo(200); - assertThat(response1.getHeaderString(TOTAL_COUNT_HEADER)).isEqualTo("3"); + assertThat(responseB.getStatus()).isEqualTo(Response.Status.OK.getStatusCode()); + assertThat(responseB.getHeaderString(TOTAL_COUNT_HEADER)).isEqualTo("2"); - final JsonArray jsonArray1 = parseJsonArray(response1); - assertThat(jsonArray1).hasSize(3); + final JsonArray jsonArray = parseJsonArray(responseB); + assertThat(jsonArray).hasSize(2); - final JsonObject jsonObject1 = jsonArray1.getJsonObject(0); - assertThat(jsonObject1.getString("uuid")).isEqualTo(violation2.getUuid().toString()); - assertThat(jsonObject1.getString("type")).isEqualTo(PolicyViolation.Type.OPERATIONAL.name()); - assertThat(jsonObject1.getJsonObject("policyCondition")).isNotNull(); - assertThat(jsonObject1.getJsonObject("policyCondition").getJsonObject("policy")).isNotNull(); - assertThat(jsonObject1.getJsonObject("policyCondition").getJsonObject("policy").getString("name")).isEqualTo("Blacklisted Version"); - assertThat(jsonObject1.getJsonObject("policyCondition").getJsonObject("policy").getString("violationState")).isEqualTo("FAIL"); - assertThat(jsonObject1.getJsonObject("project").getString("uuid")).isEqualTo(grandchild.getUuid().toString()); - - final JsonObject jsonObject2 = jsonArray1.getJsonObject(1); - assertThat(jsonObject2.getString("uuid")).isEqualTo(violation1.getUuid().toString()); - assertThat(jsonObject2.getString("type")).isEqualTo(PolicyViolation.Type.OPERATIONAL.name()); - assertThat(jsonObject2.getJsonObject("policyCondition")).isNotNull(); - assertThat(jsonObject2.getJsonObject("policyCondition").getJsonObject("policy")).isNotNull(); - assertThat(jsonObject2.getJsonObject("policyCondition").getJsonObject("policy").getString("name")).isEqualTo("Blacklisted Version"); - assertThat(jsonObject2.getJsonObject("policyCondition").getJsonObject("policy").getString("violationState")).isEqualTo("FAIL"); - assertThat(jsonObject2.getJsonObject("project").getString("uuid")).isEqualTo(child.getUuid().toString()); - - final JsonObject jsonObject3 = jsonArray1.getJsonObject(2); - assertThat(jsonObject3.getString("uuid")).isEqualTo(violation.getUuid().toString()); - assertThat(jsonObject3.getString("type")).isEqualTo(PolicyViolation.Type.OPERATIONAL.name()); - assertThat(jsonObject3.getJsonObject("policyCondition")).isNotNull(); - assertThat(jsonObject3.getJsonObject("policyCondition").getJsonObject("policy")).isNotNull(); - assertThat(jsonObject3.getJsonObject("policyCondition").getJsonObject("policy").getString("name")).isEqualTo("Blacklisted Version"); - assertThat(jsonObject3.getJsonObject("policyCondition").getJsonObject("policy").getString("violationState")).isEqualTo("FAIL"); - assertThat(jsonObject3.getJsonObject("project").getString("uuid")).isEqualTo(project.getUuid().toString()); + final JsonObject jsonObjectA = jsonArray.getJsonObject(0); + assertThat(jsonObjectA.getString("uuid")).isEqualTo(violationD.getUuid().toString()); + assertThat(jsonObjectA.getString("type")).isEqualTo(PolicyViolation.Type.OPERATIONAL.name()); + assertThat(jsonObjectA.getJsonObject("policyCondition")).isNotNull(); + assertThat(jsonObjectA.getJsonObject("policyCondition").getJsonObject("policy")).isNotNull(); + assertThat(jsonObjectA.getJsonObject("policyCondition").getJsonObject("policy").getString("name")).isEqualTo("Blacklisted Version"); + assertThat(jsonObjectA.getJsonObject("policyCondition").getJsonObject("policy").getString("violationState")).isEqualTo("FAIL"); + assertThat(jsonObjectA.getJsonObject("project").getString("uuid")).isEqualTo(projectA.getUuid().toString()); + + final JsonObject jsonObjectB = jsonArray.getJsonObject(1); + assertThat(jsonObjectB.getString("uuid")).isEqualTo(violationA.getUuid().toString()); + assertThat(jsonObjectB.getString("type")).isEqualTo(PolicyViolation.Type.OPERATIONAL.name()); + assertThat(jsonObjectB.getJsonObject("policyCondition")).isNotNull(); + assertThat(jsonObjectB.getJsonObject("policyCondition").getJsonObject("policy")).isNotNull(); + assertThat(jsonObjectB.getJsonObject("policyCondition").getJsonObject("policy").getString("name")).isEqualTo("Blacklisted Version"); + assertThat(jsonObjectB.getJsonObject("policyCondition").getJsonObject("policy").getString("violationState")).isEqualTo("FAIL"); + assertThat(jsonObjectB.getJsonObject("project").getString("uuid")).isEqualTo(projectA.getUuid().toString()); } } \ No newline at end of file From ee69982b99657da2b6f7c97a680209eb99cd637a Mon Sep 17 00:00:00 2001 From: RBickert Date: Tue, 12 Mar 2024 16:53:46 +0100 Subject: [PATCH 5/9] Remove leftover comment Signed-off-by: RBickert --- .../org/dependencytrack/persistence/ProjectQueryManager.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/org/dependencytrack/persistence/ProjectQueryManager.java b/src/main/java/org/dependencytrack/persistence/ProjectQueryManager.java index 563a616811..1cfbfaf7c8 100644 --- a/src/main/java/org/dependencytrack/persistence/ProjectQueryManager.java +++ b/src/main/java/org/dependencytrack/persistence/ProjectQueryManager.java @@ -893,7 +893,6 @@ public boolean hasAccess(final Principal principal, final Project project) { /** * A similar method exists in ComponentQueryManager */ - // TODO: Move redundant method `preprocessACLs` into own utility class (AclUtil) once #2407 and #2472 are merged. private void preprocessACLs(final Query query, final String inputFilter, final Map params, final boolean bypass) { if (super.principal != null && isEnabled(ConfigPropertyConstants.ACCESS_MANAGEMENT_ACL_ENABLED) && !bypass) { final List teams; From 51382764c8b1ef6204df989565889546dfa1d160 Mon Sep 17 00:00:00 2001 From: RBickert Date: Wed, 13 Mar 2024 11:22:08 +0100 Subject: [PATCH 6/9] Add tests for policy violation filters Signed-off-by: RBickert --- .../v1/PolicyViolationResourceTest.java | 238 ++++++++++++++++++ 1 file changed, 238 insertions(+) diff --git a/src/test/java/org/dependencytrack/resources/v1/PolicyViolationResourceTest.java b/src/test/java/org/dependencytrack/resources/v1/PolicyViolationResourceTest.java index 09dc157775..c3c7ac10a2 100644 --- a/src/test/java/org/dependencytrack/resources/v1/PolicyViolationResourceTest.java +++ b/src/test/java/org/dependencytrack/resources/v1/PolicyViolationResourceTest.java @@ -22,6 +22,7 @@ import alpine.server.filters.ApiFilter; import alpine.server.filters.AuthenticationFilter; import alpine.server.filters.AuthorizationFilter; + import org.dependencytrack.ResourceTest; import org.dependencytrack.auth.Permissions; import org.dependencytrack.model.Component; @@ -30,6 +31,8 @@ import org.dependencytrack.model.PolicyCondition; import org.dependencytrack.model.PolicyViolation; import org.dependencytrack.model.Project; +import org.dependencytrack.model.ViolationAnalysis; +import org.dependencytrack.model.ViolationAnalysisState; import org.glassfish.jersey.server.ResourceConfig; import org.glassfish.jersey.servlet.ServletContainer; import org.glassfish.jersey.test.DeploymentContext; @@ -39,6 +42,7 @@ import javax.json.JsonArray; import javax.json.JsonObject; import javax.ws.rs.core.Response; +import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.UUID; @@ -423,4 +427,238 @@ public void getViolationsWithAclEnabledTest() { assertThat(jsonObjectB.getJsonObject("project").getString("uuid")).isEqualTo(projectA.getUuid().toString()); } + @Test + public void getViolationsWithArrayFilter() { + initializeWithPermissions(Permissions.VIEW_POLICY_VIOLATION); + + 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); + + final Policy policyA = qm.createPolicy("Policy A", Policy.Operator.ALL, Policy.ViolationState.FAIL); + final PolicyCondition conditionA = qm.createPolicyCondition(policyA, PolicyCondition.Subject.VERSION, PolicyCondition.Operator.NUMERIC_EQUAL, "1.0"); + var violationA = new PolicyViolation(); + violationA.setType(PolicyViolation.Type.OPERATIONAL); + violationA.setComponent(component); + violationA.setPolicyCondition(conditionA); + violationA.setTimestamp(new Date()); + violationA = qm.persist(violationA); + + final Policy policyB = qm.createPolicy("Policy B", Policy.Operator.ALL, Policy.ViolationState.INFO); + final PolicyCondition conditionB = qm.createPolicyCondition(policyB, PolicyCondition.Subject.LICENSE, PolicyCondition.Operator.IS, "unresolved"); + var violationB = new PolicyViolation(); + violationB.setType(PolicyViolation.Type.LICENSE); + violationB.setComponent(component); + violationB.setPolicyCondition(conditionB); + violationB.setTimestamp(new Date()); + violationB = qm.persist(violationB); + + final Policy policyC = qm.createPolicy("Policy C", Policy.Operator.ALL, Policy.ViolationState.INFO); + final PolicyCondition conditionC = qm.createPolicyCondition(policyC, PolicyCondition.Subject.VERSION, PolicyCondition.Operator.NUMERIC_EQUAL, "1.0"); + ViolationAnalysis violationAnalysis = new ViolationAnalysis(); + violationAnalysis.setViolationAnalysisState(ViolationAnalysisState.REJECTED); + var violationC = new PolicyViolation(); + violationC.setType(PolicyViolation.Type.OPERATIONAL); + violationC.setComponent(component); + violationC.setPolicyCondition(conditionC); + violationC.setTimestamp(new Date()); + violationC.setAnalysis(violationAnalysis); + violationAnalysis.setPolicyViolation(violationC); + violationC = qm.persist(violationC); + + final Policy policyD = qm.createPolicy("Policy D", Policy.Operator.ALL, Policy.ViolationState.INFO); + final PolicyCondition conditionD = qm.createPolicyCondition(policyD, PolicyCondition.Subject.VERSION, PolicyCondition.Operator.NUMERIC_EQUAL, "1.0"); + var violationD = new PolicyViolation(); + violationD.setType(PolicyViolation.Type.OPERATIONAL); + violationD.setComponent(component); + violationD.setPolicyCondition(conditionD); + violationD.setTimestamp(new Date()); + violationD = qm.persist(violationD); + + final Response response = target(V1_POLICY_VIOLATION) + .request() + .header(X_API_KEY, apiKey) + .get(); + assertThat(response.getStatus()).isEqualTo(Response.Status.OK.getStatusCode()); + assertThat(response.getHeaderString(TOTAL_COUNT_HEADER)).isEqualTo("4"); + assertThat(parseJsonArray(response)).hasSize(4); + + final Response responseA = target(V1_POLICY_VIOLATION).queryParam("violationState", "FAIL") + .request() + .header(X_API_KEY, apiKey) + .get(); + assertThat(responseA.getStatus()).isEqualTo(Response.Status.OK.getStatusCode()); + assertThat(responseA.getHeaderString(TOTAL_COUNT_HEADER)).isEqualTo("1"); + final JsonArray jsonArrayA = parseJsonArray(responseA); + assertThat(jsonArrayA).hasSize(1); + assertThat(jsonArrayA.getJsonObject(0).getString("uuid")).isEqualTo(violationA.getUuid().toString()); + + + final Response responseB = target(V1_POLICY_VIOLATION).queryParam("riskType", "LICENSE") + .request() + .header(X_API_KEY, apiKey) + .get(); + assertThat(responseB.getStatus()).isEqualTo(Response.Status.OK.getStatusCode()); + assertThat(responseB.getHeaderString(TOTAL_COUNT_HEADER)).isEqualTo("1"); + final JsonArray jsonArrayB = parseJsonArray(responseB); + assertThat(jsonArrayB).hasSize(1); + assertThat(jsonArrayB.getJsonObject(0).getString("uuid")).isEqualTo(violationB.getUuid().toString()); + assertThat(jsonArrayB.getJsonObject(0).getString("uuid")).isEqualTo(violationB.getUuid().toString()); + + final Response responseC = target(V1_POLICY_VIOLATION).queryParam("analysisState", "REJECTED") + .request() + .header(X_API_KEY, apiKey) + .get(); + assertThat(responseC.getStatus()).isEqualTo(Response.Status.OK.getStatusCode()); + assertThat(responseC.getHeaderString(TOTAL_COUNT_HEADER)).isEqualTo("1"); + final JsonArray jsonArrayC = parseJsonArray(responseC); + assertThat(jsonArrayC).hasSize(1); + assertThat(jsonArrayC.getJsonObject(0).getString("uuid")).isEqualTo(violationC.getUuid().toString()); + assertThat(jsonArrayC.getJsonObject(0).getString("uuid")).isEqualTo(violationC.getUuid().toString()); + + final Response responseD = target(V1_POLICY_VIOLATION).queryParam("policy", policyD.getUuid().toString()) + .request() + .header(X_API_KEY, apiKey) + .get(); + assertThat(responseD.getStatus()).isEqualTo(Response.Status.OK.getStatusCode()); + assertThat(responseD.getHeaderString(TOTAL_COUNT_HEADER)).isEqualTo("1"); + final JsonArray jsonArrayD = parseJsonArray(responseD); + assertThat(jsonArrayD).hasSize(1); + assertThat(jsonArrayD.getJsonObject(0).getString("uuid")).isEqualTo(violationD.getUuid().toString()); + assertThat(jsonArrayD.getJsonObject(0).getString("uuid")).isEqualTo(violationD.getUuid().toString()); + } + + @Test + public void getViolationsWithInputFilter() { + initializeWithPermissions(Permissions.VIEW_POLICY_VIOLATION); + + final Project projectA = qm.createProject("Project A", null, "1.0", null, null, null, true, false); + final Project projectB = qm.createProject("Project B", null, "1.0", null, null, null, true, false); + final Project projectC = qm.createProject("Project C", null, "1.0", null, null, null, true, false); + final Project projectD = qm.createProject("Project D", null, "1.0", null, null, null, true, false); + + var componentA = new Component(); + componentA.setProject(projectA); + componentA.setName("Component A"); + componentA.setVersion("1.0"); + componentA.setLicense("License A"); + componentA = qm.createComponent(componentA, false); + + var componentB = new Component(); + componentB.setProject(projectB); + componentB.setName("Component B"); + componentB.setVersion("1.0"); + componentB.setLicense("License B"); + componentB = qm.createComponent(componentB, false); + + var componentC = new Component(); + componentC.setProject(projectC); + componentC.setName("Component C"); + componentC.setVersion("1.0"); + componentC.setLicense("License C"); + componentC = qm.createComponent(componentC, false); + + var componentD = new Component(); + componentD.setProject(projectD); + componentD.setName("Component D"); + componentD.setVersion("1.0"); + componentD.setLicense("License D"); + componentD = qm.createComponent(componentD, false); + + final Policy policyA = qm.createPolicy("Policy A", Policy.Operator.ALL, Policy.ViolationState.FAIL); + final PolicyCondition conditionA = qm.createPolicyCondition(policyA, PolicyCondition.Subject.VERSION, PolicyCondition.Operator.NUMERIC_EQUAL, "1.0"); + var violationA = new PolicyViolation(); + violationA.setType(PolicyViolation.Type.OPERATIONAL); + violationA.setComponent(componentA); + violationA.setPolicyCondition(conditionA); + violationA.setTimestamp(new Date()); + violationA = qm.persist(violationA); + + final Policy policyB = qm.createPolicy("Policy B", Policy.Operator.ALL, Policy.ViolationState.FAIL); + final PolicyCondition conditionB = qm.createPolicyCondition(policyB, PolicyCondition.Subject.VERSION, PolicyCondition.Operator.NUMERIC_EQUAL, "1.0"); + var violationB = new PolicyViolation(); + violationB.setType(PolicyViolation.Type.OPERATIONAL); + violationB.setComponent(componentB); + violationB.setPolicyCondition(conditionB); + violationB.setTimestamp(new Date()); + violationB = qm.persist(violationB); + + final Policy policyC = qm.createPolicy("Policy C", Policy.Operator.ALL, Policy.ViolationState.FAIL); + final PolicyCondition conditionC = qm.createPolicyCondition(policyC, PolicyCondition.Subject.VERSION, PolicyCondition.Operator.NUMERIC_EQUAL, "1.0"); + var violationC = new PolicyViolation(); + violationC.setType(PolicyViolation.Type.OPERATIONAL); + violationC.setComponent(componentC); + violationC.setPolicyCondition(conditionC); + violationC.setTimestamp(new Date()); + violationC = qm.persist(violationC); + + final Policy policyD = qm.createPolicy("Policy D", Policy.Operator.ALL, Policy.ViolationState.FAIL); + final PolicyCondition conditionD = qm.createPolicyCondition(policyD, PolicyCondition.Subject.VERSION, PolicyCondition.Operator.NUMERIC_EQUAL, "1.0"); + var violationD = new PolicyViolation(); + violationD.setType(PolicyViolation.Type.OPERATIONAL); + violationD.setComponent(componentD); + violationD.setPolicyCondition(conditionD); + violationD.setTimestamp(new Date()); + violationD = qm.persist(violationD); + + final Response response = target(V1_POLICY_VIOLATION) + .request() + .header(X_API_KEY, apiKey) + .get(); + assertThat(response.getStatus()).isEqualTo(Response.Status.OK.getStatusCode()); + assertThat(response.getHeaderString(TOTAL_COUNT_HEADER)).isEqualTo("4"); + assertThat(parseJsonArray(response)).hasSize(4); + + final Response responseA = target(V1_POLICY_VIOLATION) + .queryParam("textSearchField", "policy_name") + .queryParam("textSearchInput", "Policy A") + .request() + .header(X_API_KEY, apiKey) + .get(); + assertThat(responseA.getStatus()).isEqualTo(Response.Status.OK.getStatusCode()); + assertThat(responseA.getHeaderString(TOTAL_COUNT_HEADER)).isEqualTo("1"); + final JsonArray jsonArrayA = parseJsonArray(responseA); + assertThat(jsonArrayA).hasSize(1); + assertThat(jsonArrayA.getJsonObject(0).getString("uuid")).isEqualTo(violationA.getUuid().toString()); + + final Response responseB = target(V1_POLICY_VIOLATION) + .queryParam("textSearchField", "component") + .queryParam("textSearchInput", "Component B") + .request() + .header(X_API_KEY, apiKey) + .get(); + assertThat(responseB.getStatus()).isEqualTo(Response.Status.OK.getStatusCode()); + assertThat(responseB.getHeaderString(TOTAL_COUNT_HEADER)).isEqualTo("1"); + final JsonArray jsonArrayB = parseJsonArray(responseB); + assertThat(jsonArrayB).hasSize(1); + assertThat(jsonArrayB.getJsonObject(0).getString("uuid")).isEqualTo(violationB.getUuid().toString()); + + final Response responseC = target(V1_POLICY_VIOLATION) + .queryParam("textSearchField", "license") + .queryParam("textSearchInput", "License C") + .request() + .header(X_API_KEY, apiKey) + .get(); + assertThat(responseC.getStatus()).isEqualTo(Response.Status.OK.getStatusCode()); + assertThat(responseC.getHeaderString(TOTAL_COUNT_HEADER)).isEqualTo("1"); + final JsonArray jsonArrayC = parseJsonArray(responseC); + assertThat(jsonArrayC).hasSize(1); + assertThat(jsonArrayC.getJsonObject(0).getString("uuid")).isEqualTo(violationC.getUuid().toString()); + + final Response responseD = target(V1_POLICY_VIOLATION) + .queryParam("textSearchField", "project_name") + .queryParam("textSearchInput", "Project D") + .request() + .header(X_API_KEY, apiKey) + .get(); + assertThat(responseD.getStatus()).isEqualTo(Response.Status.OK.getStatusCode()); + assertThat(responseD.getHeaderString(TOTAL_COUNT_HEADER)).isEqualTo("1"); + final JsonArray jsonArrayD = parseJsonArray(responseD); + assertThat(jsonArrayD).hasSize(1); + assertThat(jsonArrayD.getJsonObject(0).getString("uuid")).isEqualTo(violationD.getUuid().toString()); + } } \ No newline at end of file From 4064fafaf6623e6dca5ff43854637e96fa3c8374 Mon Sep 17 00:00:00 2001 From: RBickert Date: Wed, 13 Mar 2024 11:28:25 +0100 Subject: [PATCH 7/9] Remove unused import Signed-off-by: RBickert --- .../resources/v1/PolicyViolationResourceTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/test/java/org/dependencytrack/resources/v1/PolicyViolationResourceTest.java b/src/test/java/org/dependencytrack/resources/v1/PolicyViolationResourceTest.java index c3c7ac10a2..b5d091138d 100644 --- a/src/test/java/org/dependencytrack/resources/v1/PolicyViolationResourceTest.java +++ b/src/test/java/org/dependencytrack/resources/v1/PolicyViolationResourceTest.java @@ -42,7 +42,6 @@ import javax.json.JsonArray; import javax.json.JsonObject; import javax.ws.rs.core.Response; -import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.UUID; From 5bd46d605b2efe632d01c928a37a16b03e48beb6 Mon Sep 17 00:00:00 2001 From: Richard Bickert Date: Tue, 17 Sep 2024 16:47:57 +0200 Subject: [PATCH 8/9] Fix merge errors Signed-off-by: Richard Bickert --- .../persistence/PolicyQueryManager.java | 3 ++- .../resources/v1/PolicyViolationResource.java | 18 +++++++------- .../v1/PolicyViolationResourceTest.java | 24 +++++++++---------- 3 files changed, 23 insertions(+), 22 deletions(-) diff --git a/src/main/java/org/dependencytrack/persistence/PolicyQueryManager.java b/src/main/java/org/dependencytrack/persistence/PolicyQueryManager.java index 995dd89056..653b5aa8be 100644 --- a/src/main/java/org/dependencytrack/persistence/PolicyQueryManager.java +++ b/src/main/java/org/dependencytrack/persistence/PolicyQueryManager.java @@ -702,7 +702,8 @@ private void processInputFilter(Map params, List filterC } } - private void preprocessACLs(final Query query, final String inputFilter, final Map params, final boolean bypass) { + @Override + void preprocessACLs(final Query query, final String inputFilter, final Map params, final boolean bypass) { if (super.principal != null && isEnabled(ConfigPropertyConstants.ACCESS_MANAGEMENT_ACL_ENABLED) && !bypass) { final List teams; if (super.principal instanceof UserPrincipal) { diff --git a/src/main/java/org/dependencytrack/resources/v1/PolicyViolationResource.java b/src/main/java/org/dependencytrack/resources/v1/PolicyViolationResource.java index 0e05026c08..efc4be5fb5 100644 --- a/src/main/java/org/dependencytrack/resources/v1/PolicyViolationResource.java +++ b/src/main/java/org/dependencytrack/resources/v1/PolicyViolationResource.java @@ -86,23 +86,23 @@ public class PolicyViolationResource extends AlpineResource { @PermissionRequired(Permissions.Constants.VIEW_POLICY_VIOLATION) public Response getViolations(@Parameter(description = "Optionally includes suppressed violations") @QueryParam("suppressed") boolean suppressed, - @ApiParam(value = "Optionally includes inactive projects") + @PathParam(value = "Optionally includes inactive projects") @QueryParam("showInactive") boolean showInactive, - @ApiParam(value = "Filter by violation state") + @PathParam(value = "Filter by violation state") @QueryParam("violationState") String violationState, - @ApiParam(value = "Filter by risk type") + @PathParam(value = "Filter by risk type") @QueryParam("riskType") String riskType, - @ApiParam(value = "Filter by policy") + @PathParam(value = "Filter by policy") @QueryParam("policy") String policy, - @ApiParam(value = "Filter by analysis state") + @PathParam(value = "Filter by analysis state") @QueryParam("analysisState") String analysisState, - @ApiParam(value = "Filter occurred on from") + @PathParam(value = "Filter occurred on from") @QueryParam("occurredOnDateFrom") String occurredOnDateFrom, - @ApiParam(value = "Filter occurred on to") + @PathParam(value = "Filter occurred on to") @QueryParam("occurredOnDateTo") String occurredOnDateTo, - @ApiParam(value = "Filter the text input in these fields") + @PathParam(value = "Filter the text input in these fields") @QueryParam("textSearchField") String textSearchField, - @ApiParam(value = "Filter by this text input") + @PathParam(value = "Filter by this text input") @QueryParam("textSearchInput") String textSearchInput) { try (QueryManager qm = new QueryManager(getAlpineRequest())) { Map filters = new HashMap<>(); diff --git a/src/test/java/org/dependencytrack/resources/v1/PolicyViolationResourceTest.java b/src/test/java/org/dependencytrack/resources/v1/PolicyViolationResourceTest.java index 4f701631bc..8c2c130099 100644 --- a/src/test/java/org/dependencytrack/resources/v1/PolicyViolationResourceTest.java +++ b/src/test/java/org/dependencytrack/resources/v1/PolicyViolationResourceTest.java @@ -377,7 +377,7 @@ public void getViolationsWithAclEnabledTest() { violationD.setTimestamp(new Date()); violationD = qm.persist(violationD); - final Response responseA = target(V1_POLICY_VIOLATION) + final Response responseA = jersey.target(V1_POLICY_VIOLATION) .request() .header(X_API_KEY, apiKey) .get(); @@ -393,7 +393,7 @@ public void getViolationsWithAclEnabledTest() { qm.persist(aclToggle); } - final Response responseB = target(V1_POLICY_VIOLATION) + final Response responseB = jersey.target(V1_POLICY_VIOLATION) .request() .header(X_API_KEY, team.getApiKeys().get(0).getKey()) .get(); @@ -474,7 +474,7 @@ public void getViolationsWithArrayFilter() { violationD.setTimestamp(new Date()); violationD = qm.persist(violationD); - final Response response = target(V1_POLICY_VIOLATION) + final Response response = jersey.target(V1_POLICY_VIOLATION) .request() .header(X_API_KEY, apiKey) .get(); @@ -482,7 +482,7 @@ public void getViolationsWithArrayFilter() { assertThat(response.getHeaderString(TOTAL_COUNT_HEADER)).isEqualTo("4"); assertThat(parseJsonArray(response)).hasSize(4); - final Response responseA = target(V1_POLICY_VIOLATION).queryParam("violationState", "FAIL") + final Response responseA = jersey.target(V1_POLICY_VIOLATION).queryParam("violationState", "FAIL") .request() .header(X_API_KEY, apiKey) .get(); @@ -493,7 +493,7 @@ public void getViolationsWithArrayFilter() { assertThat(jsonArrayA.getJsonObject(0).getString("uuid")).isEqualTo(violationA.getUuid().toString()); - final Response responseB = target(V1_POLICY_VIOLATION).queryParam("riskType", "LICENSE") + final Response responseB = jersey.target(V1_POLICY_VIOLATION).queryParam("riskType", "LICENSE") .request() .header(X_API_KEY, apiKey) .get(); @@ -504,7 +504,7 @@ public void getViolationsWithArrayFilter() { assertThat(jsonArrayB.getJsonObject(0).getString("uuid")).isEqualTo(violationB.getUuid().toString()); assertThat(jsonArrayB.getJsonObject(0).getString("uuid")).isEqualTo(violationB.getUuid().toString()); - final Response responseC = target(V1_POLICY_VIOLATION).queryParam("analysisState", "REJECTED") + final Response responseC = jersey.target(V1_POLICY_VIOLATION).queryParam("analysisState", "REJECTED") .request() .header(X_API_KEY, apiKey) .get(); @@ -515,7 +515,7 @@ public void getViolationsWithArrayFilter() { assertThat(jsonArrayC.getJsonObject(0).getString("uuid")).isEqualTo(violationC.getUuid().toString()); assertThat(jsonArrayC.getJsonObject(0).getString("uuid")).isEqualTo(violationC.getUuid().toString()); - final Response responseD = target(V1_POLICY_VIOLATION).queryParam("policy", policyD.getUuid().toString()) + final Response responseD = jersey.target(V1_POLICY_VIOLATION).queryParam("policy", policyD.getUuid().toString()) .request() .header(X_API_KEY, apiKey) .get(); @@ -600,7 +600,7 @@ public void getViolationsWithInputFilter() { violationD.setTimestamp(new Date()); violationD = qm.persist(violationD); - final Response response = target(V1_POLICY_VIOLATION) + final Response response = jersey.target(V1_POLICY_VIOLATION) .request() .header(X_API_KEY, apiKey) .get(); @@ -608,7 +608,7 @@ public void getViolationsWithInputFilter() { assertThat(response.getHeaderString(TOTAL_COUNT_HEADER)).isEqualTo("4"); assertThat(parseJsonArray(response)).hasSize(4); - final Response responseA = target(V1_POLICY_VIOLATION) + final Response responseA = jersey.target(V1_POLICY_VIOLATION) .queryParam("textSearchField", "policy_name") .queryParam("textSearchInput", "Policy A") .request() @@ -620,7 +620,7 @@ public void getViolationsWithInputFilter() { assertThat(jsonArrayA).hasSize(1); assertThat(jsonArrayA.getJsonObject(0).getString("uuid")).isEqualTo(violationA.getUuid().toString()); - final Response responseB = target(V1_POLICY_VIOLATION) + final Response responseB = jersey.target(V1_POLICY_VIOLATION) .queryParam("textSearchField", "component") .queryParam("textSearchInput", "Component B") .request() @@ -632,7 +632,7 @@ public void getViolationsWithInputFilter() { assertThat(jsonArrayB).hasSize(1); assertThat(jsonArrayB.getJsonObject(0).getString("uuid")).isEqualTo(violationB.getUuid().toString()); - final Response responseC = target(V1_POLICY_VIOLATION) + final Response responseC = jersey.target(V1_POLICY_VIOLATION) .queryParam("textSearchField", "license") .queryParam("textSearchInput", "License C") .request() @@ -644,7 +644,7 @@ public void getViolationsWithInputFilter() { assertThat(jsonArrayC).hasSize(1); assertThat(jsonArrayC.getJsonObject(0).getString("uuid")).isEqualTo(violationC.getUuid().toString()); - final Response responseD = target(V1_POLICY_VIOLATION) + final Response responseD = jersey.target(V1_POLICY_VIOLATION) .queryParam("textSearchField", "project_name") .queryParam("textSearchInput", "Project D") .request() From 1caf0d498e5df769f5cd3197abf3fbc4b72edac3 Mon Sep 17 00:00:00 2001 From: Richard Bickert Date: Mon, 23 Sep 2024 08:57:19 +0200 Subject: [PATCH 9/9] Fix parameters in PolicyViolationResource Signed-off-by: Richard Bickert --- .../resources/v1/PolicyViolationResource.java | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/main/java/org/dependencytrack/resources/v1/PolicyViolationResource.java b/src/main/java/org/dependencytrack/resources/v1/PolicyViolationResource.java index efc4be5fb5..d7dc7e284c 100644 --- a/src/main/java/org/dependencytrack/resources/v1/PolicyViolationResource.java +++ b/src/main/java/org/dependencytrack/resources/v1/PolicyViolationResource.java @@ -86,23 +86,23 @@ public class PolicyViolationResource extends AlpineResource { @PermissionRequired(Permissions.Constants.VIEW_POLICY_VIOLATION) public Response getViolations(@Parameter(description = "Optionally includes suppressed violations") @QueryParam("suppressed") boolean suppressed, - @PathParam(value = "Optionally includes inactive projects") + @Parameter(description = "Optionally includes inactive projects") @QueryParam("showInactive") boolean showInactive, - @PathParam(value = "Filter by violation state") + @Parameter(description = "Filter by violation state") @QueryParam("violationState") String violationState, - @PathParam(value = "Filter by risk type") + @Parameter(description = "Filter by risk type") @QueryParam("riskType") String riskType, - @PathParam(value = "Filter by policy") + @Parameter(description = "Filter by policy") @QueryParam("policy") String policy, - @PathParam(value = "Filter by analysis state") + @Parameter(description = "Filter by analysis state") @QueryParam("analysisState") String analysisState, - @PathParam(value = "Filter occurred on from") + @Parameter(description = "Filter occurred on from") @QueryParam("occurredOnDateFrom") String occurredOnDateFrom, - @PathParam(value = "Filter occurred on to") + @Parameter(description = "Filter occurred on to") @QueryParam("occurredOnDateTo") String occurredOnDateTo, - @PathParam(value = "Filter the text input in these fields") + @Parameter(description = "Filter the text input in these fields") @QueryParam("textSearchField") String textSearchField, - @PathParam(value = "Filter by this text input") + @Parameter(description = "Filter by this text input") @QueryParam("textSearchInput") String textSearchInput) { try (QueryManager qm = new QueryManager(getAlpineRequest())) { Map filters = new HashMap<>();