Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Global Audit View: Policy Violations #3544

Merged
Prev Previous commit
Next Next commit
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 <[email protected]>
  • Loading branch information
rbt-mm committed Mar 11, 2024
commit dcf10006a751e8c02a3051f4099191c8733f1a0d
Original file line number Diff line number Diff line change
Expand Up @@ -688,6 +688,7 @@ public void reconcileComponents(Project project, List<Component> 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<Component> 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;
Expand Down
263 changes: 132 additions & 131 deletions src/main/java/org/dependencytrack/persistence/PolicyQueryManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -408,137 +408,6 @@ public PaginatedResult getPolicyViolations(boolean includeSuppressed, boolean sh
return result;
}

private void preprocessACLs(final Query<PolicyViolation> 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;
}
}

// Query every project that the teams have access to
final Map<String, Object> tempParams = new HashMap<>();
final Query<Project> 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<Project> result = (List<Project>) 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<Long> 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<Long> 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
Expand Down Expand Up @@ -852,4 +721,136 @@ private void processInputFilter(Map<String, Object> params, List<String> filterC
}
}

// TODO: Move redundant method `preprocessACLs` into own utility class (AclUtil) once #2407 and #2472 are merged.
private void preprocessACLs(final Query<PolicyViolation> 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;
}
}

// Query every project that the teams have access to
final Map<String, Object> tempParams = new HashMap<>();
final Query<Project> 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<Project> result = (List<Project>) 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<Long> 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<Long> 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);
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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<Project> 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;
Expand Down
Loading