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

Issue/2323: Filter portfolio metrics in portfolio access control mode to match team's accesses #2326

Open
wants to merge 74 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
74 commits
Select commit Hold shift + click to select a range
c641ca7
Updating integrations diagram
stevespringett Dec 16, 2022
e4c4695
Bump dessant/lock-threads from 3 to 4
dependabot[bot] Dec 5, 2022
0a905f0
Bump actions/setup-java from 3.6.0 to 3.7.0
dependabot[bot] Dec 5, 2022
3a1830a
Bump alpine to 2.2.0-SNAPSHOT
nscuro Dec 5, 2022
5bf7e1a
Revert "Bump actions/setup-java from 3.6.0 to 3.7.0"
nscuro Dec 5, 2022
b7bb02d
Add swagger types for BOM operations (#2230)
omerlh Dec 6, 2022
64f71da
Delete associated policy conditions when deleting custom licenses (#2…
rbt-mm Dec 6, 2022
5d11de3
Add commenter to `PROJECT_AUDIT_CHANGE` emails (#2227)
rbt-mm Dec 6, 2022
22d4b7d
Enable to set unresolved license for license policy (#2214)
rbt-mm Dec 6, 2022
1ecb3e5
Bump Postgres JDBC driver to 42.5.1
nscuro Dec 6, 2022
13e45f8
Bump resilience4j to 2.0.1
nscuro Dec 6, 2022
6b1600a
Bump pebble to 3.2.0
nscuro Dec 6, 2022
f105c7b
Bump unirest to 3.14.1
nscuro Dec 6, 2022
99aab28
Bump woodstox to 6.4.0
nscuro Dec 6, 2022
2167ae3
Remove deprecated Jetty plugin configs
nscuro Dec 6, 2022
fd582be
Restore lucene index during startup & allow rebuild through UI (#2200)
syalioune Dec 6, 2022
7f64a14
Fix: AffectedComponent mapping from VulnerableSoftware in case of pur…
syalioune Dec 7, 2022
437753a
Feature: Add External References from BOM Metadata to Project
syalioune Dec 7, 2022
f07c365
Add use case example documentation (#2211)
JoergBruenner Dec 7, 2022
a8e9c69
Add total heap size and 100% cpu lines for Grafana dashboard (#2256)
valentijnscholten Dec 8, 2022
d8cb80c
Add Azure DevOps Extension to community integrations
Dec 8, 2022
ba4ef14
Various improvements for Snyk analyzer (#2246)
nscuro Dec 9, 2022
d372ce9
Fix: Exact match between CPE with NA (-) update part in vulnerability…
syalioune Dec 5, 2022
63a5241
Focus on components in dependency graph and hide other nodes (#2189)
rbt-mm Dec 9, 2022
0d13055
Do not create connection pools for upgrades
nscuro Dec 4, 2022
52b6291
Update sample Grafana dashboard to include database metrics
nscuro Dec 9, 2022
5ed069e
Add support for filtering by project in component identity lookups
nscuro Dec 4, 2022
e599198
Bump jetty-maven-plugin from 10.0.12 to 10.0.13
dependabot[bot] Dec 12, 2022
edde60c
Bump actions/setup-java from 3.6.0 to 3.8.0
dependabot[bot] Dec 12, 2022
ab049cf
Bump debian in /src/main/docker
dependabot[bot] Dec 12, 2022
77b9ca5
Display version status in `Audit Vulnerabilities` and `Exploit Predic…
rbt-mm Dec 12, 2022
dcdc4ac
Respect project hierarchy in policy evaluation (#2223)
rbt-mm Dec 13, 2022
52448a8
Prevent `NullPointerException` in `getFindings()`
rbt-mm Dec 14, 2022
54472b4
Added ProjectCreationNotification (#2176)
KramNamez Dec 14, 2022
1cdcabf
Make project search by tags case insensitive (#1723)
Mvld3r Dec 14, 2022
2f24ca9
Workaround for deleting duplicate components when uploading a new BOM…
rbt-mm Dec 14, 2022
1c196d5
Bump Alpine to 2.2.0
nscuro Dec 14, 2022
c813682
Merge generated BOM with services BOM (#2175)
nscuro Dec 15, 2022
a119362
Add OWASP Risk Rating support (#2275)
syalioune Dec 15, 2022
d569539
doc: Adding forgotten IndexTask
syalioune Dec 15, 2022
78dbe21
Fix deleting a team with existing ACL (#2287)
syalioune Dec 15, 2022
3e27831
Bump temurin base image to 17.0.5_8
nscuro Dec 15, 2022
6f649ba
Add VDR as CycloneDX export variant (#2277)
nscuro Dec 16, 2022
c9d7445
Bump mssql-jdbc from 11.2.1.jre17 to 11.2.2.jre17
dependabot[bot] Dec 16, 2022
ce9eb1e
Set Jira URL for Jira notifications globally rather than in alerts
Mvld3r Dec 16, 2022
1057dae
Improve focusing on components in dependency graph (#2296)
rbt-mm Dec 16, 2022
641ee96
Add changelog for 4.7.0 (#2276)
nscuro Dec 16, 2022
c6e57d5
Bump frontend to 4.7.0
nscuro Dec 16, 2022
e8bd95b
prepare-release: set version to 4.7.0
dependencytrack-bot Dec 16, 2022
2408364
Bump versions in issue template
nscuro Dec 16, 2022
e1c3fce
Update release artifact hashes in changelog
nscuro Dec 16, 2022
1a7e59b
Updating integrations diagram
stevespringett Dec 16, 2022
489aaae
Bump actions/setup-java from 3.8.0 to 3.9.0
dependabot[bot] Dec 19, 2022
44805f0
Bump actions/checkout from 3.1.0 to 3.2.0
dependabot[bot] Dec 19, 2022
764b68a
Bump version to 4.8.0-SNAPSHOT
nscuro Dec 19, 2022
71c9d29
Added missing support for BOMs with byte order markers on HTTP PUT. R…
stevespringett Dec 20, 2022
b898e96
Fix `NullPointerException` when updating a project (#2319)
rbt-mm Dec 21, 2022
190ec58
Refactor common code in QueryManager
mulder999 Dec 22, 2022
235f14c
Fix when string is empty
mulder999 Dec 22, 2022
77a17f1
Cleanup preprocessACLs
mulder999 Dec 22, 2022
9f5e59a
Add ACL project sum
mulder999 Dec 23, 2022
f8c16bb
Refactoring to keep additions together
mulder999 Dec 23, 2022
1e9b23f
Ensure principal gets loaded
mulder999 Dec 23, 2022
693b1f2
Also adapt getMostRecentPortfolioMetrics
mulder999 Dec 23, 2022
5cd8c6d
Remove sorting assumption
mulder999 Dec 23, 2022
3136cbc
Merge branch 'master' of github.com:mulder999/dependency-track into b…
mulder999 Dec 23, 2022
05e90c9
Complete method documentation
mulder999 Dec 23, 2022
f3d6b0f
Resolve merge issue
mulder999 Dec 23, 2022
8f566a7
Avoid multiple calls to DB
mulder999 Dec 24, 2022
95f945e
Use fetch group to retrieve only project.id for portfolio with ACL
mulder999 Jan 4, 2023
0d93835
Add configuration option for portfolio ACL
mulder999 Jan 4, 2023
7cfd3f9
Merge branch 'master' of github.com:mulder999/dependency-track into b…
mulder999 Sep 8, 2023
61a6aaa
Fix according to code conventions
mulder999 Sep 8, 2023
f037017
Merge remote-tracking branch 'upstream/master' into bug/2323_filterMe…
mulder999 Feb 18, 2024
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 @@ -89,6 +89,7 @@ public enum ConfigPropertyConstants {
KENNA_TOKEN("integrations", "kenna.token", null, PropertyType.ENCRYPTEDSTRING, "The token to use when authenticating to Kenna Security"),
KENNA_CONNECTOR_ID("integrations", "kenna.connector.id", null, PropertyType.STRING, "The Kenna Security connector identifier to upload to"),
ACCESS_MANAGEMENT_ACL_ENABLED("access-management", "acl.enabled", "false", PropertyType.BOOLEAN, "Flag to enable/disable access control to projects in the portfolio"),
ACCESS_MANAGEMENT_ACL_PORTFOLIOMETRICS_ENABLED("access-management", "acl.portfoliometrics.enabled", "false", PropertyType.BOOLEAN, "Flag to enable/disable use of access control for portfolio metrics"),
NOTIFICATION_TEMPLATE_BASE_DIR("notification", "template.baseDir", SystemUtils.getEnvironmentVariable("DEFAULT_TEMPLATES_OVERRIDE_BASE_DIRECTORY", System.getProperty("user.home")), PropertyType.STRING, "The base directory to use when searching for notification templates"),
NOTIFICATION_TEMPLATE_DEFAULT_OVERRIDE_ENABLED("notification", "template.default.override.enabled", SystemUtils.getEnvironmentVariable("DEFAULT_TEMPLATES_OVERRIDE_ENABLED", "false"), PropertyType.BOOLEAN, "Flag to enable/disable override of default notification templates"),
TASK_SCHEDULER_LDAP_SYNC_CADENCE("task-scheduler", "ldap.sync.cadence", "6", PropertyType.INTEGER, "Sync cadence (in hours) for LDAP"),
Expand Down
4 changes: 4 additions & 0 deletions src/main/java/org/dependencytrack/model/Project.java
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,9 @@
@Persistent(name = "accessTeams"),
@Persistent(name = "metadata")
}),
@FetchGroup(name = "ID", members = {
@Persistent(name = "id")
}),
@FetchGroup(name = "METADATA", members = {
@Persistent(name = "metadata")
}),
Expand All @@ -115,6 +118,7 @@ public class Project implements Serializable {
*/
public enum FetchGroup {
ALL,
ID,
METADATA,
METRICS_UPDATE,
PARENT
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,6 @@

import alpine.common.logging.Logger;
import alpine.event.framework.Event;
import alpine.model.ApiKey;
import alpine.model.Team;
import alpine.model.UserPrincipal;
import alpine.persistence.PaginatedResult;
import alpine.resources.AlpineRequest;
import com.github.packageurl.MalformedPackageURLException;
Expand All @@ -31,7 +28,6 @@
import org.dependencytrack.event.IndexEvent;
import org.dependencytrack.model.Component;
import org.dependencytrack.model.ComponentIdentity;
import org.dependencytrack.model.ConfigPropertyConstants;
import org.dependencytrack.model.Project;
import org.dependencytrack.model.RepositoryMetaComponent;
import org.dependencytrack.model.RepositoryType;
Expand Down Expand Up @@ -588,45 +584,10 @@ public void reconcileComponents(Project project, List<Component> existingProject
}

/**
* A similar method exists in ProjectQueryManager
* Extra team filter when ACL management is enable
*/
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;
if (super.principal instanceof UserPrincipal) {
final UserPrincipal userPrincipal = ((UserPrincipal) super.principal);
teams = userPrincipal.getTeams();
if (super.hasAccessManagementPermission(userPrincipal)) {
query.setFilter(inputFilter);
return;
}
} else {
final ApiKey apiKey = ((ApiKey) super.principal);
teams = apiKey.getTeams();
if (super.hasAccessManagementPermission(apiKey)) {
query.setFilter(inputFilter);
return;
}
}
if (teams != null && teams.size() > 0) {
final StringBuilder sb = new StringBuilder();
for (int i = 0, teamsSize = teams.size(); i < teamsSize; i++) {
final Team team = super.getObjectById(Team.class, teams.get(i).getId());
sb.append(" project.accessTeams.contains(:team").append(i).append(") ");
params.put("team" + i, team);
if (i < teamsSize-1) {
sb.append(" || ");
}
}
if (inputFilter != null) {
query.setFilter(inputFilter + " && (" + sb.toString() + ")");
} else {
query.setFilter(sb.toString());
}
}
} else {
query.setFilter(inputFilter);
}
preprocessACLs(query, inputFilter, params, bypass, "project.accessTeams");
}

public Map<String, Component> getDependencyGraphForComponents(Project project, List<Component> components) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,22 +18,27 @@
*/
package org.dependencytrack.persistence;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import javax.jdo.PersistenceManager;
import javax.jdo.Query;

import org.apache.commons.lang3.time.DateUtils;
import org.dependencytrack.model.Component;
import org.dependencytrack.model.ConfigPropertyConstants;
import org.dependencytrack.model.DependencyMetrics;
import org.dependencytrack.model.PortfolioMetrics;
import org.dependencytrack.model.Project;
import org.dependencytrack.model.ProjectMetrics;
import org.dependencytrack.model.VulnerabilityMetrics;
import org.dependencytrack.util.MetricsUtils;

import alpine.persistence.PaginatedResult;
import alpine.resources.AlpineRequest;


public class MetricsQueryManager extends QueryManager implements IQueryManager {

/**
Expand Down Expand Up @@ -63,33 +68,56 @@ public List<VulnerabilityMetrics> getVulnerabilityMetrics() {
return execute(query).getList(VulnerabilityMetrics.class);
}

/**
* Retrieve all the projects suitable for portfolio
*/
private List<Project> getProjectsForPortfolio() {
mulder999 marked this conversation as resolved.
Show resolved Hide resolved
if (principal != null
&& isEnabled(ConfigPropertyConstants.ACCESS_MANAGEMENT_ACL_ENABLED)
&& isEnabled(ConfigPropertyConstants.ACCESS_MANAGEMENT_ACL_PORTFOLIOMETRICS_ENABLED)) {
if (hasAccessManagementPermission(principal)) {
return null;
}

return getAllProjects(true, true);
}
return null;
}

/**
* Retrieves the most recent PortfolioMetrics.
* @return a PortfolioMetrics object
*/
public PortfolioMetrics getMostRecentPortfolioMetrics() {
final List<Project> projects = getProjectsForPortfolio();
if (projects != null) {
// Fetch up to 2 days to ensure metrics have been computed
final List<ProjectMetrics> lastMetrics = getProjectMetricsSince(projects, DateUtils.addDays(new Date(), -2));
mulder999 marked this conversation as resolved.
Show resolved Hide resolved

final List<PortfolioMetrics> portfolioMetrics = MetricsUtils.sum(lastMetrics, true);
return portfolioMetrics.stream()
.reduce((i, j) -> j)
.orElse(null);
}

final Query<PortfolioMetrics> query = pm.newQuery(PortfolioMetrics.class);
query.setOrdering("lastOccurrence desc");
query.setRange(0, 1);
return singleResult(query.execute());
}

/**
* Retrieves PortfolioMetrics in descending order starting with the most recent.
* @return a PaginatedResult object
*/
public PaginatedResult getPortfolioMetrics() {
final Query<PortfolioMetrics> query = pm.newQuery(PortfolioMetrics.class);
query.setOrdering("lastOccurrence desc");
return execute(query);
}

/**
* Retrieves PortfolioMetrics in ascending order starting with the oldest since the date specified.
* @return a List of metrics
*/
@SuppressWarnings("unchecked")
public List<PortfolioMetrics> getPortfolioMetricsSince(Date since) {
final List<Project> projects = getProjectsForPortfolio();
if (projects != null) {
final var metrics = getProjectMetricsSince(projects, since);
return MetricsUtils.sum(metrics, true);
}

final Query<PortfolioMetrics> query = pm.newQuery(PortfolioMetrics.class, "lastOccurrence >= :since");
query.setOrdering("lastOccurrence asc");
return (List<PortfolioMetrics>)query.execute(since);
Expand All @@ -113,20 +141,45 @@ public ProjectMetrics getMostRecentProjectMetrics(Project project) {
* @return a PaginatedResult object
*/
public PaginatedResult getProjectMetrics(Project project) {
final Query<ProjectMetrics> query = pm.newQuery(ProjectMetrics.class, "project == :project");
final List<Project> projects = new ArrayList<>();
projects.add(project);
return getProjectMetrics(projects);
}

/**
* Retrieves ProjectMetrics in descending order starting with the most recent.
* @param projects the Projects to retrieve metrics for
* @return a PaginatedResult object
*/
public PaginatedResult getProjectMetrics(List<Project> projects) {
final Query<ProjectMetrics> query = pm.newQuery(ProjectMetrics.class, ":project.contains(project)");
query.setOrdering("lastOccurrence desc");
return execute(query, project);
return execute(query, projects);
}

/**
* Retrieves ProjectMetrics in ascending order starting with the oldest since the date specified.
* @param project the Project to retrieve metrics for
* @since first date for lookup
* @return a List of metrics
*/
@SuppressWarnings("unchecked")
public List<ProjectMetrics> getProjectMetricsSince(Project project, Date since) {
final Query<ProjectMetrics> query = pm.newQuery(ProjectMetrics.class, "project == :project && lastOccurrence >= :since");
final List<Project> projects = new ArrayList<>();
projects.add(project);
return getProjectMetricsSince(projects, since);
}

/**
* Retrieves ProjectMetrics in ascending order starting with the oldest since the date specified.
* @param projects the Projects to retrieve metrics for
* @since first date for lookup
* @return a List of metrics
*/
@SuppressWarnings("unchecked")
public List<ProjectMetrics> getProjectMetricsSince(List<Project> projects, Date since) {
final Query<ProjectMetrics> query = pm.newQuery(ProjectMetrics.class, ":project.contains(project) && lastOccurrence >= :since");
query.setOrdering("lastOccurrence asc");
return (List<ProjectMetrics>)query.execute(project, since);
return (List<ProjectMetrics>)query.execute(projects, since);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -170,11 +170,26 @@ public List<Project> getAllProjects() {
*/
@Override
public List<Project> getAllProjects(boolean excludeInactive) {
return getAllProjects(excludeInactive, false);
}

/**
* Returns a list of all projects.
* This method if designed NOT to provide paginated results.
* @return a List of Projects
*/
public List<Project> getAllProjects(boolean excludeInactive, boolean onlyId) {
final Query<Project> query = pm.newQuery(Project.class);
String queryFilter = null;
if (excludeInactive) {
query.setFilter("active == true || active == null");
queryFilter = "active == true || active == null";
}
query.setOrdering("id asc");

preprocessACLs(query, queryFilter);
if (onlyId) {
query.getFetchPlan().setGroup(Project.FetchGroup.ID.name());
}
return query.executeList();
}

Expand Down Expand Up @@ -246,7 +261,6 @@ public Project getProject(final String name, final String version) {
final Map<String, Object> params = filterBuilder.getParams();

preprocessACLs(query, queryFilter, params, false);
query.setFilter(queryFilter);
query.setRange(0, 1);
final Project project = singleResult(query.executeWithMap(params));
if (project != null) {
Expand Down Expand Up @@ -891,44 +905,19 @@ public boolean hasAccess(final Principal principal, final Project project) {
}

/**
* A similar method exists in ComponentQueryManager
* Extra team filter when ACL management is enable
*/
private void preprocessACLs(final Query<Project> query, final String inputFilter) {
final var params = new HashMap<String, Object>();
preprocessACLs(query, inputFilter, params, false);
query.setNamedParameters(params);
}

/**
* Extra team filter when ACL management is enable
*/
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;
if (super.principal instanceof final UserPrincipal userPrincipal) {
teams = userPrincipal.getTeams();
if (super.hasAccessManagementPermission(userPrincipal)) {
query.setFilter(inputFilter);
return;
}
} else {
final ApiKey apiKey = ((ApiKey) super.principal);
teams = apiKey.getTeams();
if (super.hasAccessManagementPermission(apiKey)) {
query.setFilter(inputFilter);
return;
}
}
if (teams != null && teams.size() > 0) {
final StringBuilder sb = new StringBuilder();
for (int i = 0, teamsSize = teams.size(); i < teamsSize; i++) {
final Team team = super.getObjectById(Team.class, teams.get(i).getId());
sb.append(" accessTeams.contains(:team").append(i).append(") ");
params.put("team" + i, team);
if (i < teamsSize-1) {
sb.append(" || ");
}
}
if (inputFilter != null && !inputFilter.isBlank()) {
query.setFilter(inputFilter + " && (" + sb.toString() + ")");
} else {
query.setFilter(sb.toString());
}
}
} else if (StringUtils.trimToNull(inputFilter) != null) {
query.setFilter(inputFilter);
}
preprocessACLs(query, inputFilter, params, bypass, "accessTeams");
}

/**
Expand Down
Loading