Skip to content

Commit

Permalink
Pull config via ConfigProperty in notification-publisher
Browse files Browse the repository at this point in the history
This replaces manual usage of `ConfigPropertyRepository` with more Quarkus-idiomatic `@ConfigProperty` injection. It leverages the new `quarkus-config-dependencytrack` extension.

Another unforeseen benefit is that it's now possible to simply provide property overrides for tests, instead of having to persist anything in a database.

Relates to #929

Signed-off-by: nscuro <nscuro@protonmail.com>
  • Loading branch information
nscuro committed Apr 30, 2024
1 parent 03562a8 commit 28581c9
Show file tree
Hide file tree
Showing 21 changed files with 180 additions and 136 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@
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;
Expand All @@ -41,7 +40,7 @@ public abstract class AbstractWebhookPublisher implements Publisher {
@Named("httpClient")
CloseableHttpClient httpClient;

public void publish(final PublishContext ctx, final PebbleTemplate template, final Notification notification, final JsonObject config, final ConfigPropertyRepository configPropertyRepository) throws Exception {
public void publish(final PublishContext ctx, final PebbleTemplate template, final Notification notification, final JsonObject config) throws Exception {
final Logger logger = LoggerFactory.getLogger(this.getClass());
if (config == null) {
logger.warn("No publisher configuration found; Skipping notification (%s)".formatted(ctx));
Expand All @@ -64,7 +63,7 @@ public void publish(final PublishContext ctx, final PebbleTemplate template, fin
}
final String content;
try {
content = prepareTemplate(notification, template, configPropertyRepository, config);
content = prepareTemplate(notification, template, config);
} catch (IOException | RuntimeException e) {
logger.error("Failed to prepare notification content (%s)".formatted(ctx), e);
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
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;
import org.dependencytrack.proto.notification.v1.Notification;
import org.slf4j.Logger;
Expand All @@ -40,19 +39,16 @@ public class ConsolePublisher implements Publisher {
private static final Logger LOGGER = LoggerFactory.getLogger(ConsolePublisher.class);

private final PebbleEngine pebbleEngine;
private final ConfigPropertyRepository configPropertyRepository;

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

public void inform(final PublishContext ctx, final Notification notification, final JsonObject config) throws Exception {
final String content;
try {
content = prepareTemplate(notification, getTemplate(config), configPropertyRepository, config);
content = prepareTemplate(notification, getTemplate(config), config);
} catch (IOException | RuntimeException e) {
LOGGER.error("Failed to prepare notification content (%s)".formatted(ctx), e);
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,25 +24,21 @@
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;

@ApplicationScoped
@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(@Named("pebbleEngineJson") final PebbleEngine pebbleEngine,
final ConfigPropertyRepository configPropertyRepository) {
public CsWebexPublisher(@Named("pebbleEngineJson") final PebbleEngine pebbleEngine) {
this.pebbleEngine = pebbleEngine;
this.configPropertyRepository = configPropertyRepository;
}

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

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,65 +19,53 @@
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;
import org.slf4j.LoggerFactory;

import java.util.Map;

import static org.dependencytrack.persistence.model.ConfigPropertyConstants.JIRA_PASSWORD;
import static org.dependencytrack.persistence.model.ConfigPropertyConstants.JIRA_USERNAME;

@ApplicationScoped
@Startup // Force bean creation even though no direct injection points exist
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 JiraPublisherConfig publisherConfig;
private final SecretDecryptor secretDecryptor;
private String jiraProjectKey;
private String jiraTicketType;

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

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

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

@Override
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 jiraUsername = publisherConfig.username().orElse(null);
final String encryptedPassword = publisherConfig.password().orElse(null);
final String jiraPassword = (encryptedPassword == null) ? null : secretDecryptor.decryptAsString(encryptedPassword);
return new AuthCredentials(jiraUsername, jiraPassword);
}
Expand All @@ -101,7 +89,7 @@ public void inform(final PublishContext ctx, final Notification notification, fi
return;
}

publish(ctx, getTemplate(config), notification, config, configPropertyRepository);
publish(ctx, getTemplate(config), notification, config);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* This file is part of Dependency-Track.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
* Copyright (c) OWASP Foundation. All Rights Reserved.
*/
package org.dependencytrack.notification.publisher;

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Provider;
import org.eclipse.microprofile.config.inject.ConfigProperty;

import java.util.Optional;

@ApplicationScoped
class JiraPublisherConfig {

private final Provider<Optional<String>> baseUrlProvider;
private final Provider<Optional<String>> usernameProvider;
private final Provider<Optional<String>> passwordProvider;

JiraPublisherConfig(
@ConfigProperty(name = "dtrack.integrations.jira.url") final Provider<Optional<String>> baseUrlProvider,
@ConfigProperty(name = "dtrack.integrations.jira.username") final Provider<Optional<String>> usernameProvider,
@ConfigProperty(name = "dtrack.integrations.jira.password") final Provider<Optional<String>> passwordProvider
) {
this.baseUrlProvider = baseUrlProvider;
this.usernameProvider = usernameProvider;
this.passwordProvider = passwordProvider;
}

Optional<String> baseUrl() {
return baseUrlProvider.get();
}

Optional<String> username() {
return usernameProvider.get();
}

Optional<String> password() {
return passwordProvider.get();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -24,25 +24,21 @@
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;

@ApplicationScoped
@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(@Named("pebbleEngineJson") final PebbleEngine pebbleEngine,
final ConfigPropertyRepository configPropertyRepository) {
public MattermostPublisher(@Named("pebbleEngineJson") final PebbleEngine pebbleEngine) {
this.pebbleEngine = pebbleEngine;
this.configPropertyRepository = configPropertyRepository;
}

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

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,25 +24,21 @@
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;

@ApplicationScoped
@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(@Named("pebbleEngineJson") final PebbleEngine pebbleEngine,
final ConfigPropertyRepository configPropertyRepository) {
public MsTeamsPublisher(@Named("pebbleEngineJson") final PebbleEngine pebbleEngine) {
this.pebbleEngine = pebbleEngine;
this.configPropertyRepository = configPropertyRepository;
}

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

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,6 @@
import io.pebbletemplates.pebble.PebbleEngine;
import io.pebbletemplates.pebble.template.PebbleTemplate;
import jakarta.json.JsonObject;
import org.dependencytrack.persistence.model.ConfigProperty;
import org.dependencytrack.persistence.model.ConfigPropertyConstants;
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;
Expand All @@ -36,6 +33,7 @@
import org.dependencytrack.proto.notification.v1.ProjectVulnAnalysisCompleteSubject;
import org.dependencytrack.proto.notification.v1.VexConsumedOrProcessedSubject;
import org.dependencytrack.proto.notification.v1.VulnerabilityAnalysisDecisionChangeSubject;
import org.eclipse.microprofile.config.ConfigProvider;

import java.io.IOException;
import java.io.StringWriter;
Expand Down Expand Up @@ -75,23 +73,18 @@ default String getTemplateMimeType(JsonObject config) {
}
}

default String prepareTemplate(final Notification notification, final PebbleTemplate template, final ConfigPropertyRepository configPropertyRepository, JsonObject config) throws IOException {

final ConfigProperty baseUrlProperty = configPropertyRepository.findByGroupAndName(
ConfigPropertyConstants.GENERAL_BASE_URL.getGroupName(),
ConfigPropertyConstants.GENERAL_BASE_URL.getPropertyName()
);
default String prepareTemplate(final Notification notification, final PebbleTemplate template, JsonObject config) throws IOException {
final String baseUrl = ConfigProvider.getConfig()
.getOptionalValue("dtrack.general.base.url", String.class)
.map(value -> value.replaceAll("/$", ""))
.orElse("");

final Map<String, Object> context = new HashMap<>();
final long epochSecond = notification.getTimestamp().getSeconds();
context.put("timestampEpochSecond", epochSecond);
context.put("timestamp", ProtobufUtil.formatTimestamp(notification.getTimestamp()));
context.put("notification", notification);
if (baseUrlProperty != null && baseUrlProperty.getPropertyValue() != null) {
context.put("baseUrl", baseUrlProperty.getPropertyValue().replaceAll("/$", ""));
} else {
context.put("baseUrl", "");
}
context.put("baseUrl", baseUrl);

if (notification.getScope() == SCOPE_PORTFOLIO) {
if (notification.getSubject().is(NewVulnerabilitySubject.class)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,7 @@
import jakarta.inject.Named;
import jakarta.json.JsonObject;
import jakarta.json.JsonString;
import org.apache.commons.lang3.BooleanUtils;
import org.dependencytrack.persistence.model.ConfigProperty;
import org.dependencytrack.persistence.model.ConfigPropertyConstants;
import org.dependencytrack.persistence.model.Team;
import org.dependencytrack.persistence.repository.ConfigPropertyRepository;
import org.dependencytrack.persistence.repository.UserRepository;
import org.dependencytrack.proto.notification.v1.Notification;
import org.slf4j.Logger;
Expand All @@ -54,17 +50,17 @@ public class SendMailPublisher implements Publisher {

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

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

Expand Down Expand Up @@ -99,15 +95,14 @@ private void sendNotification(final PublishContext ctx, Notification notificatio
final String content;
try {
final PebbleTemplate template = getTemplate(config);
content = prepareTemplate(notification, template, configPropertyRepository, config);
content = prepareTemplate(notification, template, config);
} catch (IOException | RuntimeException e) {
LOGGER.error("Failed to prepare notification content (%s)".formatted(ctx), e);
return;
}

try {
ConfigProperty smtpEnabledConfig = configPropertyRepository.findByGroupAndName(ConfigPropertyConstants.EMAIL_SMTP_ENABLED.getGroupName(), ConfigPropertyConstants.EMAIL_SMTP_ENABLED.getPropertyName());
boolean smtpEnabled = BooleanUtils.toBoolean(smtpEnabledConfig.getPropertyValue());
boolean smtpEnabled = publisherConfig.isSmtpEnabled().orElse(false);
if (!smtpEnabled) {
LOGGER.warn("SMTP is not enabled; Skipping notification (%s)".formatted(ctx));
return; // smtp is not enabled
Expand Down
Loading

0 comments on commit 28581c9

Please sign in to comment.