Skip to content

Commit

Permalink
Port notification publisher fixes and tests
Browse files Browse the repository at this point in the history
Ports the following PRs:

* DependencyTrack/dependency-track#3198

Closes #1071

Further fixes:

* Wrong base URL being set for `JiraPublisher`
* Bearer token authentication with Jira not working (DependencyTrack/dependency-track#2642 was not ported for some reason)
* Notification template using values that no longer exist
  * Observed differences between Hyades and DT v4.x have been documented here: #927 (comment)

Signed-off-by: nscuro <nscuro@protonmail.com>
  • Loading branch information
nscuro committed Feb 12, 2024
1 parent e8fea59 commit b79c557
Show file tree
Hide file tree
Showing 30 changed files with 2,788 additions and 517 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ public static class Title {
public static final String POLICY_VIOLATION = "Policy Violation";
public static final String BOM_CONSUMED = "Bill of Materials Consumed";
public static final String BOM_PROCESSED = "Bill of Materials Processed";
public static final String BOM_PROCESSING_FAILED = "Bill of Materials Processing Failed";
public static final String VEX_CONSUMED = "Vulnerability Exploitability Exchange (VEX) Consumed";
public static final String VEX_PROCESSED = "Vulnerability Exploitability Exchange (VEX) Processed";
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package org.dependencytrack.notification.config;

import io.pebbletemplates.pebble.PebbleEngine;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Named;
import jakarta.ws.rs.Produces;
import org.dependencytrack.notification.template.extension.CustomExtension;

class PebbleConfiguration {

@Produces
@ApplicationScoped
@Named("pebbleEngineJson")
PebbleEngine pebbleEngineJson(final CustomExtension customExtension) {
return new PebbleEngine.Builder()
.extension(customExtension)
.defaultEscapingStrategy("json")
.build();
}

@Produces
@ApplicationScoped
@Named("pebbleEnginePlainText")
PebbleEngine pebbleEnginePlainText(final CustomExtension customExtension) {
return new PebbleEngine.Builder()
.extension(customExtension)
.newLineTrimming(false)
.build();
}

@Produces
@ApplicationScoped
CustomExtension pebbleEngineExtension() {
return new CustomExtension();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,13 @@
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;
import org.dependencytrack.commonutil.HttpUtil;
import org.dependencytrack.persistence.repository.ConfigPropertyRepository;
import org.dependencytrack.proto.notification.v1.Notification;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.util.Base64;

public abstract class AbstractWebhookPublisher implements Publisher {

Expand All @@ -53,9 +53,9 @@ public void publish(final PublishContext ctx, final PebbleTemplate template, fin
return;
}

final BasicAuthCredentials credentials;
final AuthCredentials credentials;
try {
credentials = getBasicAuthCredentials();
credentials = getAuthCredentials();
} catch (RuntimeException e) {
logger.warn("""
An error occurred during the retrieval of credentials needed for notification \
Expand All @@ -74,10 +74,14 @@ public void publish(final PublishContext ctx, final PebbleTemplate template, fin
final String mimeType = getTemplateMimeType(config);
request.addHeader("content-type", mimeType);
request.addHeader("accept", mimeType);

if (credentials != null) {
request.addHeader("Authorization", getBasicAuthenticationHeader(credentials.user(), credentials.password()));
if(credentials.user() != null) {
request.addHeader("Authorization", HttpUtil.basicAuthHeaderValue(credentials.user(), credentials.password()));
} else {
request.addHeader("Authorization", "Bearer " + credentials.password);
}
}

StringEntity entity = new StringEntity(content);
request.setEntity(entity);
try (final CloseableHttpResponse response = httpClient.execute(request);) {
Expand All @@ -96,16 +100,11 @@ public void publish(final PublishContext ctx, final PebbleTemplate template, fin
}
}

private static String getBasicAuthenticationHeader(String username, String password) {
String valueToEncode = username + ":" + password;
return "Basic " + Base64.getEncoder().encodeToString(valueToEncode.getBytes());
}

protected BasicAuthCredentials getBasicAuthCredentials() throws Exception {
protected AuthCredentials getAuthCredentials() throws Exception {
return null;
}

protected record BasicAuthCredentials(String user, String password) {
protected record AuthCredentials(String user, String password) {
}

protected String getDestinationUrl(final JsonObject config) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import io.quarkus.runtime.Startup;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.inject.Named;
import jakarta.json.JsonObject;
import org.dependencytrack.persistence.repository.ConfigPropertyRepository;
import org.dependencytrack.proto.notification.v1.Level;
Expand All @@ -37,12 +38,14 @@
public class ConsolePublisher implements Publisher {

private static final Logger LOGGER = LoggerFactory.getLogger(ConsolePublisher.class);
private static final PebbleEngine ENGINE = new PebbleEngine.Builder().newLineTrimming(false).build();

final ConfigPropertyRepository configPropertyRepository;
private final PebbleEngine pebbleEngine;
private final ConfigPropertyRepository configPropertyRepository;

@Inject
public ConsolePublisher(final ConfigPropertyRepository configPropertyRepository){
public ConsolePublisher(@Named("pebbleEnginePlainText") final PebbleEngine pebbleEngine,
final ConfigPropertyRepository configPropertyRepository){
this.pebbleEngine = pebbleEngine;
this.configPropertyRepository = configPropertyRepository;
}

Expand All @@ -65,6 +68,6 @@ public void inform(final PublishContext ctx, final Notification notification, fi

@Override
public PebbleEngine getTemplateEngine() {
return ENGINE;
return pebbleEngine;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import io.quarkus.runtime.Startup;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.inject.Named;
import jakarta.json.JsonObject;
import org.dependencytrack.persistence.repository.ConfigPropertyRepository;
import org.dependencytrack.proto.notification.v1.Notification;
Expand All @@ -30,22 +31,23 @@
@Startup // Force bean creation even though no direct injection points exist
public class CsWebexPublisher extends AbstractWebhookPublisher implements Publisher {

private final PebbleEngine pebbleEngine;
private final ConfigPropertyRepository configPropertyRepository;

@Inject
public CsWebexPublisher(final ConfigPropertyRepository configPropertyRepository){
public CsWebexPublisher(@Named("pebbleEngineJson") final PebbleEngine pebbleEngine,
final ConfigPropertyRepository configPropertyRepository) {
this.pebbleEngine = pebbleEngine;
this.configPropertyRepository = configPropertyRepository;
}

private static final PebbleEngine ENGINE = new PebbleEngine.Builder().defaultEscapingStrategy("json").build();

public void inform(final PublishContext ctx, final Notification notification, final JsonObject config) throws Exception {
publish(ctx, getTemplate(config), notification, config, configPropertyRepository);
}

@Override
public PebbleEngine getTemplateEngine() {
return ENGINE;
return pebbleEngine;
}

}
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
package org.dependencytrack.notification.publisher;

import io.pebbletemplates.pebble.PebbleEngine;
import io.quarkus.narayana.jta.QuarkusTransaction;
import io.quarkus.runtime.Startup;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.inject.Named;
import jakarta.json.JsonObject;
import org.dependencytrack.common.SecretDecryptor;
import org.dependencytrack.persistence.model.ConfigProperty;
import org.dependencytrack.persistence.model.ConfigPropertyConstants;
import org.dependencytrack.persistence.repository.ConfigPropertyRepository;
import org.dependencytrack.proto.notification.v1.Notification;
import org.slf4j.Logger;
Expand All @@ -20,29 +25,43 @@
public class JiraPublisher extends AbstractWebhookPublisher implements Publisher {

private static final Logger LOGGER = LoggerFactory.getLogger(JiraPublisher.class);

private final PebbleEngine pebbleEngine;
private final ConfigPropertyRepository configPropertyRepository;
private final SecretDecryptor secretDecryptor;
private static final PebbleEngine ENGINE = new PebbleEngine.Builder().defaultEscapingStrategy("json").build();
private String jiraProjectKey;
private String jiraTicketType;

public JiraPublisher(ConfigPropertyRepository configPropertyRepository, SecretDecryptor secretDecryptor) {
@Inject
public JiraPublisher(@Named("pebbleEngineJson") final PebbleEngine pebbleEngine,
final ConfigPropertyRepository configPropertyRepository,
final SecretDecryptor secretDecryptor) {
this.pebbleEngine = pebbleEngine;
this.configPropertyRepository = configPropertyRepository;
this.secretDecryptor = secretDecryptor;
}

@Override
public String getDestinationUrl(final JsonObject config) {
final String baseUrl = config.getString(CONFIG_DESTINATION);
final ConfigProperty baseUrlProperty = QuarkusTransaction.joiningExisting()
.call(() -> configPropertyRepository.findByGroupAndName(
ConfigPropertyConstants.JIRA_URL.getGroupName(),
ConfigPropertyConstants.JIRA_URL.getPropertyName()
));
if (baseUrlProperty == null) {
return null;
}

final String baseUrl = baseUrlProperty.getPropertyValue();
return (baseUrl.endsWith("/") ? baseUrl : baseUrl + '/') + "rest/api/2/issue";
}

@Override
protected BasicAuthCredentials getBasicAuthCredentials() throws Exception {
final String jiraUsername = configPropertyRepository.findByGroupAndName(JIRA_USERNAME.getGroupName(), JIRA_USERNAME.getPropertyName()).getPropertyValue();
final String encryptedPassword = configPropertyRepository.findByGroupAndName(JIRA_PASSWORD.getGroupName(), JIRA_PASSWORD.getPropertyName()).getPropertyValue();
final String jiraPassword = (encryptedPassword == null) ? null : secretDecryptor.decryptAsString(encryptedPassword);
return new BasicAuthCredentials(jiraUsername, jiraPassword);
protected AuthCredentials getAuthCredentials() throws Exception {
final String jiraUsername = configPropertyRepository.findByGroupAndName(JIRA_USERNAME.getGroupName(), JIRA_USERNAME.getPropertyName()).getPropertyValue();
final String encryptedPassword = configPropertyRepository.findByGroupAndName(JIRA_PASSWORD.getGroupName(), JIRA_PASSWORD.getPropertyName()).getPropertyValue();
final String jiraPassword = (encryptedPassword == null) ? null : secretDecryptor.decryptAsString(encryptedPassword);
return new AuthCredentials(jiraUsername, jiraPassword);
}

@Override
Expand All @@ -69,7 +88,7 @@ public void inform(final PublishContext ctx, final Notification notification, fi

@Override
public PebbleEngine getTemplateEngine() {
return ENGINE;
return pebbleEngine;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import io.quarkus.runtime.Startup;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.inject.Named;
import jakarta.json.JsonObject;
import org.dependencytrack.persistence.repository.ConfigPropertyRepository;
import org.dependencytrack.proto.notification.v1.Notification;
Expand All @@ -30,21 +31,22 @@
@Startup // Force bean creation even though no direct injection points exist
public class MattermostPublisher extends AbstractWebhookPublisher implements Publisher {

private final PebbleEngine pebbleEngine;
private final ConfigPropertyRepository configPropertyRepository;

@Inject
public MattermostPublisher(final ConfigPropertyRepository configPropertyRepository) {
public MattermostPublisher(@Named("pebbleEngineJson") final PebbleEngine pebbleEngine,
final ConfigPropertyRepository configPropertyRepository) {
this.pebbleEngine = pebbleEngine;
this.configPropertyRepository = configPropertyRepository;
}

private static final PebbleEngine ENGINE = new PebbleEngine.Builder().defaultEscapingStrategy("json").build();

public void inform(final PublishContext ctx, final Notification notification, final JsonObject config) throws Exception {
publish(ctx, getTemplate(config), notification, config, configPropertyRepository);
}

@Override
public PebbleEngine getTemplateEngine() {
return ENGINE;
return pebbleEngine;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import io.quarkus.runtime.Startup;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.inject.Named;
import jakarta.json.JsonObject;
import org.dependencytrack.persistence.repository.ConfigPropertyRepository;
import org.dependencytrack.proto.notification.v1.Notification;
Expand All @@ -30,22 +31,23 @@
@Startup // Force bean creation even though no direct injection points exist
public class MsTeamsPublisher extends AbstractWebhookPublisher implements Publisher {

private final PebbleEngine pebbleEngine;
private final ConfigPropertyRepository configPropertyRepository;

@Inject
public MsTeamsPublisher(final ConfigPropertyRepository configPropertyRepository) {
public MsTeamsPublisher(@Named("pebbleEngineJson") final PebbleEngine pebbleEngine,
final ConfigPropertyRepository configPropertyRepository) {
this.pebbleEngine = pebbleEngine;
this.configPropertyRepository = configPropertyRepository;
}

private static final PebbleEngine ENGINE = new PebbleEngine.Builder().defaultEscapingStrategy("json").build();

public void inform(final PublishContext ctx, final Notification notification, final JsonObject config) throws Exception {
publish(ctx, getTemplate(config), notification, config, configPropertyRepository);
}

@Override
public PebbleEngine getTemplateEngine() {
return ENGINE;
return pebbleEngine;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import org.dependencytrack.persistence.repository.ConfigPropertyRepository;
import org.dependencytrack.proto.ProtobufUtil;
import org.dependencytrack.proto.notification.v1.BomConsumedOrProcessedSubject;
import org.dependencytrack.proto.notification.v1.BomProcessingFailedSubject;
import org.dependencytrack.proto.notification.v1.NewVulnerabilitySubject;
import org.dependencytrack.proto.notification.v1.NewVulnerableDependencySubject;
import org.dependencytrack.proto.notification.v1.Notification;
Expand Down Expand Up @@ -113,6 +114,10 @@ default String prepareTemplate(final Notification notification, final PebbleTemp
final var subject = notification.getSubject().unpack(BomConsumedOrProcessedSubject.class);
context.put("subject", subject);
context.put("subjectJson", JsonFormat.printer().print(subject));
} else if (notification.getSubject().is(BomProcessingFailedSubject.class)) {
final var subject = notification.getSubject().unpack(BomProcessingFailedSubject.class);
context.put("subject", subject);
context.put("subjectJson", JsonFormat.printer().print(subject));
} else if (notification.getSubject().is(VexConsumedOrProcessedSubject.class)) {
final var subject = notification.getSubject().unpack(VexConsumedOrProcessedSubject.class);
context.put("subject", subject);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import io.quarkus.runtime.Startup;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.inject.Named;
import jakarta.json.JsonObject;
import jakarta.json.JsonString;
import org.apache.commons.lang3.BooleanUtils;
Expand All @@ -50,17 +51,21 @@
public class SendMailPublisher implements Publisher {

private static final Logger LOGGER = LoggerFactory.getLogger(SendMailPublisher.class);
private static final PebbleEngine ENGINE = new PebbleEngine.Builder().newLineTrimming(false).build();

private final PebbleEngine pebbleEngine;
private final UserRepository userRepository;
private final ConfigPropertyRepository configPropertyRepository;
private final Mailer mailer;

@Inject
Mailer mailer;

public SendMailPublisher(final UserRepository userRepository, final ConfigPropertyRepository configPropertyRepository) {
public SendMailPublisher(@Named("pebbleEnginePlainText") final PebbleEngine pebbleEngine,
final UserRepository userRepository,
final ConfigPropertyRepository configPropertyRepository,
final Mailer mailer) {
this.pebbleEngine = pebbleEngine;
this.userRepository = userRepository;
this.configPropertyRepository = configPropertyRepository;
this.mailer = mailer;
}

public void inform(final PublishContext ctx, final Notification notification, final JsonObject config) throws Exception {
Expand Down Expand Up @@ -118,7 +123,7 @@ private void sendNotification(final PublishContext ctx, Notification notificatio

@Override
public PebbleEngine getTemplateEngine() {
return ENGINE;
return pebbleEngine;
}

public static String[] parseDestination(final JsonObject config) {
Expand Down
Loading

0 comments on commit b79c557

Please sign in to comment.