Skip to content

Commit

Permalink
Reduce code duplication by migrating more code to use JDBI `Vulnerabi…
Browse files Browse the repository at this point in the history
…lityPolicyDao`

Signed-off-by: nscuro <[email protected]>
  • Loading branch information
nscuro committed Jan 23, 2024
1 parent 65db2b8 commit 6f3afa2
Show file tree
Hide file tree
Showing 13 changed files with 504 additions and 779 deletions.
24 changes: 0 additions & 24 deletions src/main/java/org/dependencytrack/persistence/QueryManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -1943,34 +1943,10 @@ public List<Component> getComponentsByPurl(String purl) {
return getComponentQueryManager().getComponentsByPurl(purl);
}

public int createVulnerabilityPolicy(VulnerabilityPolicy vulnerabilityPolicy, Connection connection) {
return getVulnerabilityPolicyQueryManager().createVulnerabilityPolicy(vulnerabilityPolicy, connection);
}

public List<VulnerabilityPolicy> getAllVulnerabilityPolicies() {
return getVulnerabilityPolicyQueryManager().getAllVulnerabilityPolicies();
}

public List<VulnerabilityPolicy> getAllValidVulnerabilityPolicies() {
return getVulnerabilityPolicyQueryManager().getAllValidVulnerabilityPolicies();
}

public PaginatedResult getAllVulnerabilityPolicies(String name, Date validFrom, Date validUntil) {
return getVulnerabilityPolicyQueryManager().getAllVulnerabilityPolicies(name, validFrom, validUntil);
}

public int deleteVulnerabilityPolicyByName(String vulnerabilityPolicyName, Connection connection) {
return getVulnerabilityPolicyQueryManager().deleteVulnerabilityPolicyByName(vulnerabilityPolicyName,connection);
}

public int updateVulnerablePolicyByName(VulnerabilityPolicy vulnerabilityPolicy, Connection connection) {
return getVulnerabilityPolicyQueryManager().updateVulnerabilityPolicyByName(vulnerabilityPolicy, connection);
}

public VulnerabilityPolicy getVulnerabilityPolicyByName(String vulnerabilityPolicyName) {
return getVulnerabilityPolicyQueryManager().getVulnerabilityPolicyByName(vulnerabilityPolicyName);
}

public VulnerabilityPolicyBundle getVulnerabilityPolicyBundle() {
final Query<VulnerabilityPolicyBundle> query = pm.newQuery(VulnerabilityPolicyBundle.class);
query.setRange(0, 1);
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package org.dependencytrack.persistence.jdbi;

import org.jdbi.v3.sqlobject.customizer.Bind;
import org.jdbi.v3.sqlobject.statement.SqlBatch;

import java.util.List;

public interface AnalysisDao {

@SqlBatch("""
INSERT INTO "ANALYSISCOMMENT"
("ANALYSIS_ID", "COMMENT", "COMMENTER", "TIMESTAMP")
VALUES
(:analysisId, :comment, :commenter, NOW())
""")
void createComments(@Bind List<Long> analysisId, @Bind String commenter, @Bind List<String> comment);

}
Original file line number Diff line number Diff line change
@@ -1,62 +1,222 @@
package org.dependencytrack.persistence.jdbi;

import org.dependencytrack.model.Analysis;
import org.dependencytrack.persistence.jdbi.mapping.AnalysisRowMapper;
import org.dependencytrack.persistence.jdbi.mapping.VulnPolicyAnalysisArgumentFactory;
import org.dependencytrack.persistence.jdbi.mapping.VulnPolicyRatingsArgumentFactory;
import org.dependencytrack.persistence.jdbi.mapping.VulnerabilityPolicyRowMapper;
import org.dependencytrack.policy.vulnerability.VulnerabilityPolicy;
import org.dependencytrack.util.AnalysisCommentFormatter.AnalysisCommentField;
import org.jdbi.v3.sqlobject.SqlObject;
import org.jdbi.v3.sqlobject.config.RegisterArgumentFactories;
import org.jdbi.v3.sqlobject.config.RegisterArgumentFactory;
import org.jdbi.v3.sqlobject.config.RegisterRowMapper;
import org.jdbi.v3.sqlobject.customizer.Bind;
import org.jdbi.v3.sqlobject.customizer.BindBean;
import org.jdbi.v3.sqlobject.statement.GetGeneratedKeys;
import org.jdbi.v3.sqlobject.statement.SqlQuery;
import org.jdbi.v3.sqlobject.statement.SqlUpdate;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

import static org.dependencytrack.util.AnalysisCommentFormatter.formatComment;

@RegisterArgumentFactories({
@RegisterArgumentFactory(VulnPolicyAnalysisArgumentFactory.class),
@RegisterArgumentFactory(VulnPolicyRatingsArgumentFactory.class)
})
@RegisterRowMapper(VulnerabilityPolicyRowMapper.class)
public interface VulnerabilityPolicyDao {
public interface VulnerabilityPolicyDao extends SqlObject {

@SqlQuery("""
SELECT * FROM "VULNERABILITY_POLICY";
SELECT * FROM "VULNERABILITY_POLICY"
""")
List<VulnerabilityPolicy> getAll();

@SqlQuery("""
SELECT
*
FROM
"VULNERABILITY_POLICY"
WHERE
("VALID_FROM" IS NULL OR "VALID_FROM" <= NOW())
AND ("VALID_UNTIL" IS NULL OR "VALID_UNTIL" >= NOW())
""")
List<VulnerabilityPolicy> getAllVulnerabilityPolicies();
List<VulnerabilityPolicy> getAllValid();

@SqlQuery("""
SELECT * FROM "VULNERABILITY_POLICY" WHERE "NAME" = ?;
SELECT * FROM "VULNERABILITY_POLICY" WHERE "NAME" = ?
""")
VulnerabilityPolicy getVulnerabilityPolicyByName(@Bind String name);
VulnerabilityPolicy getByName(@Bind String name);

@SqlUpdate("""
INSERT INTO "VULNERABILITY_POLICY"
("ANALYSIS", "AUTHOR", "CONDITIONS", "CREATED", "DESCRIPTION", "NAME", "RATINGS", "UPDATED", "VALID_FROM", "VALID_UNTIL")
("ANALYSIS", "AUTHOR", "CONDITIONS", "CREATED", "DESCRIPTION", "NAME", "RATINGS", "VALID_FROM", "VALID_UNTIL")
VALUES
((:analysis)::JSON, :author, :conditions, :created, :description, :name, (:ratings)::JSON, :updated, :validFrom, :validUntil)
((:analysis)::JSONB, :author, :conditions, NOW(), :description, :name, (:ratings)::JSONB, :validFrom, :validUntil)
RETURNING *
""")
int createVulnerabilityPolicy(@BindBean VulnerabilityPolicy vulnerabilityPolicy);
@GetGeneratedKeys("*")
VulnerabilityPolicy create(@BindBean VulnerabilityPolicy vulnerabilityPolicy);

@SqlUpdate("""
DELETE FROM "VULNERABILITY_POLICY" WHERE "NAME" = ?
""")
int deleteVulnerabilityPolicyByName(@Bind String name);
int deleteByName(@Bind String name);

@SqlUpdate("""
UPDATE "VULNERABILITY_POLICY"
SET
"ANALYSIS" = :analysis::JSON,
"AUTHOR" = :author,
"CONDITIONS" = :conditions,
"ANALYSIS" = (:analysis)::JSONB,
"AUTHOR" = :author,
"CONDITIONS" = :conditions,
"DESCRIPTION" = :description,
"RATINGS" = :ratings::JSON,
"UPDATED" = :updated,
"VALID_FROM" = :validFrom,
"RATINGS" = (:ratings)::JSONB,
"UPDATED" = NOW(),
"VALID_FROM" = :validFrom,
"VALID_UNTIL" = :validUntil
WHERE "NAME" = :name
WHERE
"NAME" = :name AND (
-- Using IS DISTINCT FROM instead of != for nullable columns
-- because != does not handle NULL.
"ANALYSIS" != (:analysis)::JSONB
OR "AUTHOR" IS DISTINCT FROM :author
OR "CONDITIONS" != (:conditions)::TEXT[]
OR "DESCRIPTION" IS DISTINCT FROM :description
OR "RATINGS" IS DISTINCT FROM (:ratings)::JSONB
OR "VALID_FROM" IS DISTINCT FROM :validFrom
OR "VALID_UNTIL" IS DISTINCT FROM :validUntil
)
RETURNING *
""")
int updateVulnerabilityPolicyByName(@BindBean VulnerabilityPolicy vulnerabilityPolicy);
@GetGeneratedKeys("*")
VulnerabilityPolicy update(@BindBean VulnerabilityPolicy vulnerabilityPolicy);

default List<Analysis> unassignFromAnalysesByName(final String name) {
// NB: Can't use interface method here due to https://github.com/jdbi/jdbi/issues/1807.
return getHandle().createUpdate("""
WITH "CTE_VULN_POLICY" AS (
SELECT
"ID"
FROM
"VULNERABILITY_POLICY"
WHERE
"NAME" = :name
)
UPDATE
"ANALYSIS" AS "NEW"
SET
"STATE" = 'NOT_SET', -- Must be non-null
"JUSTIFICATION" = NULL,
"RESPONSE" = NULL,
"DETAILS" = NULL,
"SUPPRESSED" = FALSE,
"SEVERITY" = NULL,
"CVSSV2VECTOR" = NULL,
"CVSSV2SCORE" = NULL,
"CVSSV3VECTOR" = NULL,
"CVSSV3SCORE" = NULL,
"OWASPVECTOR" = NULL,
"OWASPSCORE" = NULL,
"VULNERABILITY_POLICY_ID" = NULL
FROM
"ANALYSIS" AS "OLD" -- Self-join to get access to pre-update values
WHERE
"NEW"."ID" = "OLD"."ID"
AND "NEW"."VULNERABILITY_POLICY_ID" IS NOT NULL
AND "NEW"."VULNERABILITY_POLICY_ID" = (SELECT "ID" FROM "CTE_VULN_POLICY")
RETURNING
"OLD"."ID",
"OLD"."STATE",
"OLD"."JUSTIFICATION",
"OLD"."RESPONSE",
"OLD"."DETAILS",
"OLD"."SUPPRESSED",
"OLD"."SEVERITY",
"OLD"."CVSSV2VECTOR",
"OLD"."CVSSV2SCORE",
"OLD"."CVSSV3VECTOR",
"OLD"."CVSSV3SCORE",
"OLD"."OWASPVECTOR",
"OLD"."OWASPSCORE"
""")
.bind("name", name)
.executeAndReturnGeneratedKeys()
.map(new AnalysisRowMapper())
.list();
}

/**
* Un-assign a given {@link VulnerabilityPolicy} from any associated {@link Analysis}
* records, reset the analyses' states, and populate the audit trail accordingly.
*
* @param name Name of the {@link VulnerabilityPolicy} to un-assign
*/
default void unassignAndDeleteByName(String name) {
final List<Analysis> unassignedAnalyses = unassignFromAnalysesByName(name);

final var analysisIds = new ArrayList<Long>();
final var comments = new ArrayList<String>();
for (final Analysis analysis : unassignedAnalyses) {
analysisIds.add(analysis.getId());
comments.add("Policy removed");

Optional.ofNullable(analysis.getAnalysisState()).ifPresent(oldState -> {
analysisIds.add(analysis.getId());
comments.add(formatComment(AnalysisCommentField.STATE, oldState, null));
});
Optional.ofNullable(analysis.getAnalysisJustification()).ifPresent(oldJustification -> {
analysisIds.add(analysis.getId());
comments.add(formatComment(AnalysisCommentField.JUSTIFICATION, oldJustification, null));
});
Optional.ofNullable(analysis.getAnalysisResponse()).ifPresent(oldResponse -> {
analysisIds.add(analysis.getId());
comments.add(formatComment(AnalysisCommentField.RESPONSE, oldResponse, null));
});
Optional.ofNullable(analysis.getAnalysisDetails()).ifPresent(oldDetails -> {
analysisIds.add(analysis.getId());
comments.add(formatComment(AnalysisCommentField.DETAILS, oldDetails, null));
});
// SUPPRESSED is not nullable; Can only be changed if it was previously true
Optional.of(analysis.isSuppressed()).filter(Boolean.TRUE::equals).ifPresent(oldSuppressed -> {
analysisIds.add(analysis.getId());
comments.add(formatComment(AnalysisCommentField.SUPPRESSED, oldSuppressed, false));
});
Optional.ofNullable(analysis.getSeverity()).ifPresent(oldSeverity -> {
analysisIds.add(analysis.getId());
comments.add(formatComment(AnalysisCommentField.SEVERITY, oldSeverity, null));
});
Optional.ofNullable(analysis.getCvssV2Vector()).ifPresent(oldVector -> {
analysisIds.add(analysis.getId());
comments.add(formatComment(AnalysisCommentField.CVSSV2_VECTOR, oldVector, null));
});
Optional.ofNullable(analysis.getCvssV2Score()).ifPresent(oldScore -> {
analysisIds.add(analysis.getId());
comments.add(formatComment(AnalysisCommentField.CVSSV2_SCORE, oldScore, null));
});
Optional.ofNullable(analysis.getCvssV3Vector()).ifPresent(oldVector -> {
analysisIds.add(analysis.getId());
comments.add(formatComment(AnalysisCommentField.CVSSV3_VECTOR, oldVector, null));
});
Optional.ofNullable(analysis.getCvssV3Score()).ifPresent(oldScore -> {
analysisIds.add(analysis.getId());
comments.add(formatComment(AnalysisCommentField.CVSSV3_SCORE, oldScore, null));
});
Optional.ofNullable(analysis.getOwaspVector()).ifPresent(oldVector -> {
analysisIds.add(analysis.getId());
comments.add(formatComment(AnalysisCommentField.OWASP_VECTOR, oldVector, null));
});
Optional.ofNullable(analysis.getOwaspScore()).ifPresent(oldScore -> {
analysisIds.add(analysis.getId());
comments.add(formatComment(AnalysisCommentField.OWASP_SCORE, oldScore, null));
});
}

final var commenter = "[Policy{Name=%s}]".formatted(name);
final var analysisDao = getHandle().attach(AnalysisDao.class);
analysisDao.createComments(analysisIds, commenter, comments);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package org.dependencytrack.persistence.jdbi.mapping;

import org.dependencytrack.model.Analysis;
import org.dependencytrack.model.AnalysisJustification;
import org.dependencytrack.model.AnalysisResponse;
import org.dependencytrack.model.AnalysisState;
import org.dependencytrack.model.Component;
import org.dependencytrack.model.Vulnerability;
import org.jdbi.v3.core.mapper.RowMapper;
import org.jdbi.v3.core.statement.StatementContext;

import java.sql.ResultSet;
import java.sql.SQLException;

import static org.dependencytrack.persistence.jdbi.mapping.RowMapperUtil.maybeSet;

public class AnalysisRowMapper implements RowMapper<Analysis> {

@Override
public Analysis map(final ResultSet rs, final StatementContext ctx) throws SQLException {
final var analysis = new Analysis();
maybeSet(rs, "ID", ResultSet::getLong, analysis::setId);
maybeSet(rs, "COMPONENT_ID", ResultSet::getLong, value -> {
final var component = new Component();
component.setId(value);
analysis.setComponent(component);
});
maybeSet(rs, "VULNERABILITY_ID", ResultSet::getLong, value -> {
final var vuln = new Vulnerability();
vuln.setId(value);
analysis.setVulnerability(vuln);
});
maybeSet(rs, "STATE", ResultSet::getString, value -> analysis.setAnalysisState(AnalysisState.valueOf(value)));
maybeSet(rs, "JUSTIFICATION", ResultSet::getString, value -> analysis.setAnalysisJustification(AnalysisJustification.valueOf(value)));
maybeSet(rs, "RESPONSE", ResultSet::getString, value -> analysis.setAnalysisResponse(AnalysisResponse.valueOf(value)));
maybeSet(rs, "DETAILS", ResultSet::getString, analysis::setAnalysisDetails);
maybeSet(rs, "SUPPRESSED", ResultSet::getBoolean, analysis::setSuppressed);
return analysis;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,22 @@
import alpine.persistence.PaginatedResult;
import alpine.resources.AlpineRequest;
import org.dependencytrack.persistence.QueryManager;
import org.dependencytrack.persistence.jdbi.VulnerabilityPolicyDao;
import org.dependencytrack.proto.policy.v1.Project;

import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.util.Arrays;
import java.util.List;

import static org.dependencytrack.persistence.jdbi.JdbiFactory.jdbi;

public class DatabaseVulnerabilityPolicyProvider implements VulnerabilityPolicyProvider {

@Override
public List<VulnerabilityPolicy> getApplicablePolicies(final Project project) {
try (final var qm = new QueryManager()) {
return qm.getAllValidVulnerabilityPolicies().stream()
.map(DatabaseVulnerabilityPolicyProvider::convert)
.toList();
return jdbi(qm).withExtension(VulnerabilityPolicyDao.class, VulnerabilityPolicyDao::getAllValid);
}
}

Expand Down
Loading

0 comments on commit 6f3afa2

Please sign in to comment.