Repository: DependencyTrack/dependency-track (java)

Issue:
Set Jira URL for Jira notifications globally rather than in alerts
### Current Behavior

The Jira URL is set in the destination field, for each alert.

However the Jira credentials are set globally, which is incoherent (different Jira instances would need to have the same user and password).

### Proposed Behavior

The Jira URL should be set globally.

It is expected that users will only ever need to send Dependency-Track alert notifications to a single Jira instance, hence it makes sense to configure the Jira URL globally rather than having to fill it in each alert.

The destination field then becomes the Jira project key.

### Checklist

- [X] I have read and understand the [contributing guidelines](https://github.com/DependencyTrack/dependency-track/blob/master/CONTRIBUTING.md#filing-issues)
- [X] I have checked the [existing issues](https://github.com/DependencyTrack/dependency-track/issues) for whether this enhancement was already requested

Pull request:
Set Jira URL for Jira notifications globally rather than in alerts
Signed-off-by: Mvld3r <Mvld3r@users.noreply.github.com>

### Description

Set Jira URL for Jira notifications globally rather than in alerts

### Addressed Issue

Fixes #2297.


### Checklist

<!-- REQUIRED
    Mark items in this list as done by adding a `x` between the square brackets.
    Non-applicable items may be marked as such by surrounding their text with tildes (`~`).

    This is not meant to be a strict to-do list. If you're unsure about anything,
    just leave it empty for now. The maintainers are happy to assist you in figuring it out!
-->

- [x] I have read and understand the [contributing guidelines](https://github.com/DependencyTrack/dependency-track/blob/github-templates/CONTRIBUTING.md#pull-requests)
- [ ] This PR fixes a defect, and I have provided tests to verify that the fix is effective
- [x] This PR implements an enhancement, and I have provided tests to verify that it works as intended
- [ ] This PR introduces changes to the database model, and I have added corresponding [update logic](https://github.com/DependencyTrack/dependency-track/tree/master/src/main/java/org/dependencytrack/upgrade)
- [x] This PR introduces new or alters existing behavior, and I have updated the [documentation](https://github.com/DependencyTrack/dependency-track/tree/master/docs/_docs) accordingly


Patch:
diff --git a/docs/_docs/integrations/notifications.md b/docs/_docs/integrations/notifications.md
index a2ff165f5..040f32d64 100644
--- a/docs/_docs/integrations/notifications.md
+++ b/docs/_docs/integrations/notifications.md
@@ -406,7 +406,7 @@ Once the alert is created it can be configured. Start with selecting from the li
 to notify on. Then specify the destination:
 - For the Email publisher: it's a comma separated list of email addresses
 - For Slack, Mattermost and Microsoft Teams: it's the incoming webhook URL generated from each respective
-- For Jira: it's the URL for the Jira instance. The Jira alert also asks for the Jira ticket type ('Task', 'Story', etc., refer to the Jira documentation for more details) to be created, along with the project key where the issue will be created
+- For Jira: it's the project key where the issue will be created. The Jira alert also asks for the Jira ticket type ('Task', 'Story', etc., refer to the Jira documentation for more details) to be created
 - For the Outbound Webhook publisher: it's a URL to which to publish the notification
 
 ![configure notification](/images/screenshots/notifications-configure.png)
diff --git a/src/main/java/org/dependencytrack/model/ConfigPropertyConstants.java b/src/main/java/org/dependencytrack/model/ConfigPropertyConstants.java
index 5941988c7..2e9bbe3f3 100644
--- a/src/main/java/org/dependencytrack/model/ConfigPropertyConstants.java
+++ b/src/main/java/org/dependencytrack/model/ConfigPropertyConstants.java
@@ -35,6 +35,7 @@ public enum ConfigPropertyConstants {
     EMAIL_SMTP_TRUSTCERT("email", "smtp.trustcert", "false", PropertyType.BOOLEAN, "Flag to enable/disable the trust of the certificate presented by the SMTP server"),
     INTERNAL_COMPONENTS_GROUPS_REGEX("internal-components", "groups.regex", null, PropertyType.STRING, "Regex that matches groups of internal components"),
     INTERNAL_COMPONENTS_NAMES_REGEX("internal-components", "names.regex", null, PropertyType.STRING, "Regex that matches names of internal components"),
+    JIRA_URL("jira", "jira.url", null, PropertyType.URL, "The base URL of the JIRA instance"),
     JIRA_USERNAME("jira", "jira.username", null, PropertyType.STRING, "The optional username to authenticate with when creating an Jira issue"),
     JIRA_PASSWORD("jira", "jira.password", null, PropertyType.ENCRYPTEDSTRING, "The optional password for the username used for authentication"),
     SCANNER_INTERNAL_ENABLED("scanner", "internal.enabled", "true", PropertyType.BOOLEAN, "Flag to enable/disable the internal analyzer"),
diff --git a/src/main/java/org/dependencytrack/notification/publisher/AbstractWebhookPublisher.java b/src/main/java/org/dependencytrack/notification/publisher/AbstractWebhookPublisher.java
index ecdfff223..b865e156f 100644
--- a/src/main/java/org/dependencytrack/notification/publisher/AbstractWebhookPublisher.java
+++ b/src/main/java/org/dependencytrack/notification/publisher/AbstractWebhookPublisher.java
@@ -55,7 +55,7 @@ public abstract class AbstractWebhookPublisher implements Publisher {
         try {
             credentials = getBasicAuthCredentials();
         } catch (PublisherException e) {
-            logger.warn("An error occurred during the retrieval of credentials needed for notification publication. Skipping notification");
+            logger.warn("An error occurred during the retrieval of credentials needed for notification publication. Skipping notification", e);
             return;
         }
         if (credentials != null) {
diff --git a/src/main/java/org/dependencytrack/notification/publisher/JiraPublisher.java b/src/main/java/org/dependencytrack/notification/publisher/JiraPublisher.java
index 14228e0af..636977fc1 100644
--- a/src/main/java/org/dependencytrack/notification/publisher/JiraPublisher.java
+++ b/src/main/java/org/dependencytrack/notification/publisher/JiraPublisher.java
@@ -29,6 +29,7 @@ import javax.json.JsonObject;
 import java.util.Map;
 
 import static org.dependencytrack.model.ConfigPropertyConstants.JIRA_PASSWORD;
+import static org.dependencytrack.model.ConfigPropertyConstants.JIRA_URL;
 import static org.dependencytrack.model.ConfigPropertyConstants.JIRA_USERNAME;
 
 /**
@@ -46,7 +47,7 @@ public class JiraPublisher extends AbstractWebhookPublisher implements Publisher
     @Override
     public void inform(final Notification notification, final JsonObject config) {
         jiraTicketType = config.getString("jiraTicketType");
-        jiraProjectKey = config.getString("jiraProjectKey");
+        jiraProjectKey = config.getString(CONFIG_DESTINATION);
         publish(DefaultNotificationPublishers.JIRA.getPublisherName(), getTemplate(config), notification, config);
     }
 
@@ -57,8 +58,13 @@ public class JiraPublisher extends AbstractWebhookPublisher implements Publisher
 
     @Override
     public String getDestinationUrl(final JsonObject config) {
-        final String baseUrl = super.getDestinationUrl(config);
-        return (baseUrl.endsWith("/") ? baseUrl : baseUrl + '/') + "rest/api/2/issue";
+        try (final QueryManager qm = new QueryManager()) {
+            final String baseUrl = qm.getConfigProperty(JIRA_URL.getGroupName(), JIRA_URL.getPropertyName()).getPropertyValue();
+            return (baseUrl.endsWith("/") ? baseUrl : baseUrl + '/') + "rest/api/2/issue";
+        } catch (final Exception e) {
+            throw new PublisherException("An error occurred during the retrieval of the Jira URL", e);
+        }
+
     }
 
     @Override


Test patch:
diff --git a/src/test/java/org/dependencytrack/notification/publisher/JiraPublisherTest.java b/src/test/java/org/dependencytrack/notification/publisher/JiraPublisherTest.java
index 231f892bc..1d2bdbd9a 100644
--- a/src/test/java/org/dependencytrack/notification/publisher/JiraPublisherTest.java
+++ b/src/test/java/org/dependencytrack/notification/publisher/JiraPublisherTest.java
@@ -20,26 +20,29 @@ package org.dependencytrack.notification.publisher;
 
 import alpine.notification.Notification;
 import alpine.notification.NotificationLevel;
-import org.apache.http.HttpHeaders;
+import alpine.security.crypto.DataEncryption;
 import org.dependencytrack.PersistenceCapableTest;
 import org.dependencytrack.notification.NotificationGroup;
 import org.dependencytrack.notification.NotificationScope;
 import org.junit.AfterClass;
 import org.junit.BeforeClass;
 import org.junit.Test;
-import org.mockserver.client.MockServerClient;
 import org.mockserver.integration.ClientAndServer;
 
 import javax.json.Json;
 import javax.json.JsonObject;
 import javax.json.JsonObjectBuilder;
-import java.io.IOException;
+import javax.ws.rs.core.HttpHeaders;
+import java.util.Base64;
 
+import static org.dependencytrack.model.ConfigPropertyConstants.JIRA_PASSWORD;
+import static org.dependencytrack.model.ConfigPropertyConstants.JIRA_URL;
+import static org.dependencytrack.model.ConfigPropertyConstants.JIRA_USERNAME;
 import static org.mockserver.integration.ClientAndServer.startClientAndServer;
 import static org.mockserver.model.HttpRequest.request;
 import static org.mockserver.model.HttpResponse.response;
 
-public class JiraPublisherTest extends PersistenceCapableTest implements NotificationTestConfigProvider  {
+public class JiraPublisherTest extends PersistenceCapableTest implements NotificationTestConfigProvider {
 
     private static ClientAndServer mockServer;
 
@@ -55,33 +58,53 @@ public class JiraPublisherTest extends PersistenceCapableTest implements Notific
     }
 
     @Test
-    public void testPublish() throws IOException {
-        new MockServerClient("localhost", 1080)
-                .when(
-                        request()
-                                .withMethod("POST")
-                )
+    public void testPublish() throws Exception {
+        final var jiraUser = "jiraUser";
+        final var jiraPassword = "jiraPassword";
+
+        final var request = request()
+                .withMethod("POST")
+                .withHeader(HttpHeaders.AUTHORIZATION, "Basic " + Base64.getEncoder().encodeToString((jiraUser + ":" + jiraPassword).getBytes()));
+        mockServer.when(request)
                 .respond(
                         response()
                                 .withStatusCode(200)
                                 .withHeader(HttpHeaders.CONTENT_TYPE, "application/json")
                 );
-        JsonObject config = getConfig(DefaultNotificationPublishers.JIRA, "http://localhost:1080");
-        Notification notification = new Notification();
+        final JsonObject config = getConfig(DefaultNotificationPublishers.JIRA, "MyProjectKey");
+        final Notification notification = new Notification();
         notification.setScope(NotificationScope.PORTFOLIO.name());
         notification.setGroup(NotificationGroup.NEW_VULNERABILITY.name());
         notification.setLevel(NotificationLevel.INFORMATIONAL);
         notification.setTitle("Test Notification");
         notification.setContent("This is only a test");
-        JiraPublisher publisher = new JiraPublisher();
+        final JiraPublisher publisher = new JiraPublisher();
+
+
+        qm.createConfigProperty(JIRA_URL.getGroupName(),
+                JIRA_URL.getPropertyName(),
+                "http://localhost:1080",
+                JIRA_URL.getPropertyType(), JIRA_URL.getDescription());
+
+        qm.createConfigProperty(JIRA_USERNAME.getGroupName(),
+                JIRA_USERNAME.getPropertyName(),
+                jiraUser,
+                JIRA_USERNAME.getPropertyType(), JIRA_USERNAME.getDescription());
+
+        qm.createConfigProperty(JIRA_PASSWORD.getGroupName(),
+                JIRA_PASSWORD.getPropertyName(),
+                DataEncryption.encryptAsString(jiraPassword),
+                JIRA_PASSWORD.getPropertyType(), JIRA_PASSWORD.getDescription());
+
         publisher.inform(notification, config);
+        mockServer.verify(request);
+
     }
 
     @Override
     public JsonObjectBuilder getExtraConfig() {
         return Json.createObjectBuilder()
-                .add("jiraTicketType", "Task")
-                .add("jiraProjectKey", "MyProject");
+                .add("jiraTicketType", "Task");
     }
 }