Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Port : Add REST endpoints for tag retrieval #819

Merged
merged 4 commits into from
Aug 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@
import com.github.packageurl.PackageURL;
import org.dependencytrack.model.Analysis;
import org.dependencytrack.model.Component;
import org.dependencytrack.model.ConfigPropertyConstants;
import org.dependencytrack.model.Finding;
import org.dependencytrack.model.GroupedFinding;
import org.dependencytrack.model.RepositoryMetaComponent;
Expand All @@ -41,8 +40,6 @@
import java.util.Map;
import java.util.UUID;

import static org.dependencytrack.util.PrincipalUtil.getPrincipalTeamIds;

public class FindingsSearchQueryManager extends QueryManager implements IQueryManager {

private static final Map<String, String> sortingAttributes = Map.ofEntries(
Expand Down Expand Up @@ -345,31 +342,13 @@ private void processInputFilter(StringBuilder queryFilter, Map<String, Object> p
}

private void preprocessACLs(StringBuilder queryFilter, final Map<String, Object> params) {
if (!isEnabled(ConfigPropertyConstants.ACCESS_MANAGEMENT_ACL_ENABLED)
|| hasAccessManagementPermission(this.principal)) {
return;
}

if (queryFilter.isEmpty()) {
queryFilter.append(" WHERE ");
} else {
queryFilter.append(" AND ");
}

final var teamIds = new ArrayList<>(getPrincipalTeamIds(principal));
if (teamIds.isEmpty()) {
queryFilter.append(":false");
params.put("false", false);
return;
}

queryFilter.append("""
EXISTS (
SELECT 1
FROM "PROJECT_ACCESS_TEAMS"
WHERE "PROJECT_ACCESS_TEAMS"."PROJECT_ID" = "PROJECT"."ID"
AND "PROJECT_ACCESS_TEAMS"."TEAM_ID" = ANY(:teamIds)
)""");
params.put("teamIds", teamIds.toArray(new Long[0]));
final Map.Entry<String, Map<String, Object>> projectAclConditionAndParams = getProjectAclSqlCondition();
queryFilter.append(projectAclConditionAndParams.getKey()).append(" ");
params.putAll(projectAclConditionAndParams.getValue());
}
}
108 changes: 106 additions & 2 deletions src/main/java/org/dependencytrack/persistence/QueryManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,10 @@
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
Expand All @@ -122,6 +125,7 @@
import java.util.function.Predicate;

import static org.datanucleus.PropertyNames.PROPERTY_QUERY_SQL_ALLOWALL;
import static org.dependencytrack.model.ConfigPropertyConstants.ACCESS_MANAGEMENT_ACL_ENABLED;
import static org.dependencytrack.proto.vulnanalysis.v1.ScanStatus.SCAN_STATUS_FAILED;

/**
Expand Down Expand Up @@ -456,6 +460,27 @@ public QueryManager withL2CacheDisabled() {
return this;
}

/**
* Get the IDs of the {@link Team}s a given {@link Principal} is a member of.
*
* @return A {@link Set} of {@link Team} IDs
*/
protected Set<Long> getTeamIds(final Principal principal) {
final var principalTeamIds = new HashSet<Long>();
if (principal instanceof final UserPrincipal userPrincipal
&& userPrincipal.getTeams() != null) {
for (final Team userInTeam : userPrincipal.getTeams()) {
principalTeamIds.add(userInTeam.getId());
}
} else if (principal instanceof final ApiKey apiKey
&& apiKey.getTeams() != null) {
for (final Team userInTeam : apiKey.getTeams()) {
principalTeamIds.add(userInTeam.getId());
}
}
return principalTeamIds;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//// BEGIN WRAPPER METHODS ////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
Expand Down Expand Up @@ -1370,8 +1395,20 @@ public boolean hasAccessManagementPermission(final ApiKey apiKey) {
return getProjectQueryManager().hasAccessManagementPermission(apiKey);
}

public PaginatedResult getTags(String policyUuid) {
return getTagQueryManager().getTags(policyUuid);
public List<TagQueryManager.TagListRow> getTags() {
return getTagQueryManager().getTags();
}

public List<TagQueryManager.TaggedProjectRow> getTaggedProjects(final String tagName) {
return getTagQueryManager().getTaggedProjects(tagName);
}

public List<TagQueryManager.TaggedPolicyRow> getTaggedPolicies(final String tagName) {
return getTagQueryManager().getTaggedPolicies(tagName);
}

public PaginatedResult getTagsForPolicy(String policyUuid) {
return getTagQueryManager().getTagsForPolicy(policyUuid);
}

/**
Expand Down Expand Up @@ -1945,4 +1982,71 @@ public long deleteComponentPropertyByUuid(final Component component, final UUID
public void synchronizeComponentProperties(final Component component, final List<ComponentProperty> properties) {
getComponentQueryManager().synchronizeComponentProperties(component, properties);
}

/**
* @see #getProjectAclSqlCondition(String)
* @since 4.12.0
*/
public Map.Entry<String, Map<String, Object>> getProjectAclSqlCondition() {
return getProjectAclSqlCondition("PROJECT");
}

/**
* @param projectTableAlias Name or alias of the {@code PROJECT} table to use in the condition.
* @return A SQL condition that may be used to check if the {@link #principal} has access to a project
* @since 4.12.0
*/
public Map.Entry<String, Map<String, Object>> getProjectAclSqlCondition(final String projectTableAlias) {
if (request == null) {
return Map.entry(/* true */ "1=1", Collections.emptyMap());
}

if (principal == null || !isEnabled(ACCESS_MANAGEMENT_ACL_ENABLED) || hasAccessManagementPermission(principal)) {
return Map.entry(/* true */ "1=1", Collections.emptyMap());
}

final var teamIds = new ArrayList<>(getTeamIds(principal));
if (teamIds.isEmpty()) {
return Map.entry(/* false */ "1=2", Collections.emptyMap());
}


// NB: Need to work around the fact that the RDBMSes can't agree on how to do member checks. Oh joy! :)))
final var params = new HashMap<String, Object>();
final var teamIdChecks = new ArrayList<String>();
for (int i = 0; i < teamIds.size(); i++) {
teamIdChecks.add("\"PROJECT_ACCESS_TEAMS\".\"TEAM_ID\" = :teamId" + i);
params.put("teamId" + i, teamIds.get(i));
}

return Map.entry("""
EXISTS (
SELECT 1
FROM "PROJECT_ACCESS_TEAMS"
WHERE "PROJECT_ACCESS_TEAMS"."PROJECT_ID" = "%s"."ID"
AND (%s)
)""".formatted(projectTableAlias, String.join(" OR ", teamIdChecks)), params);
}

/**
* @since 4.12.0
* @return A SQL {@code OFFSET ... LIMIT ...} clause if pagination is requested, otherwise an empty string
*/
public String getOffsetLimitSqlClause() {
if (pagination == null || !pagination.isPaginated()) {
return "";
}

final String clauseTemplate;
if (DbUtil.isMssql()) {
clauseTemplate = "OFFSET %d ROWS FETCH NEXT %d ROWS ONLY";
} else if (DbUtil.isMysql()) {
// NB: Order of limit and offset is different for MySQL...
return "LIMIT %s OFFSET %s".formatted(pagination.getLimit(), pagination.getOffset());
} else {
clauseTemplate = "OFFSET %d FETCH NEXT %d ROWS ONLY";
}

return clauseTemplate.formatted(pagination.getOffset(), pagination.getLimit());
}
}
Loading
Loading