diff --git a/commons-persistence/src/main/java/org/dependencytrack/persistence/converter/NotificationScopeConverter.java b/commons-persistence/src/main/java/org/dependencytrack/persistence/converter/NotificationScopeConverter.java deleted file mode 100644 index 5a44232ec9..0000000000 --- a/commons-persistence/src/main/java/org/dependencytrack/persistence/converter/NotificationScopeConverter.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * 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.persistence.converter; - -import jakarta.persistence.AttributeConverter; -import jakarta.persistence.Converter; -import org.dependencytrack.persistence.model.NotificationScope; - -import static java.util.Optional.ofNullable; - -@Converter(autoApply = true) -public class NotificationScopeConverter implements AttributeConverter { - - @Override - public String convertToDatabaseColumn(final NotificationScope entityValue) { - return ofNullable(entityValue) - .map(notificationScope -> notificationScope.toString()) - .orElse(null); - } - - @Override - public NotificationScope convertToEntityAttribute(final String databaseValue) { - return ofNullable(databaseValue) - .map(databaseNotificationScope -> NotificationScope.valueOf(databaseNotificationScope)) - .orElse(null); - } -} - diff --git a/commons-persistence/src/main/java/org/dependencytrack/persistence/model/Component.java b/commons-persistence/src/main/java/org/dependencytrack/persistence/model/Component.java index 6069524903..b9dc62ee8e 100644 --- a/commons-persistence/src/main/java/org/dependencytrack/persistence/model/Component.java +++ b/commons-persistence/src/main/java/org/dependencytrack/persistence/model/Component.java @@ -25,8 +25,6 @@ import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.Id; -import jakarta.persistence.JoinColumn; -import jakarta.persistence.ManyToOne; import jakarta.persistence.Table; import org.apache.commons.lang3.StringUtils; import org.hibernate.annotations.JdbcType; @@ -79,10 +77,6 @@ public class Component extends PanacheEntityBase { @Column(name = "LICENSE", columnDefinition = "VARCHAR") private String license; - @ManyToOne - @JoinColumn(name = "PROJECT_ID", nullable = false) - private Project project; - @Column(name = "UUID", nullable = false) @JdbcType(UUIDJdbcType.class) private UUID uuid; @@ -268,14 +262,6 @@ public void setLicense(String license) { this.license = StringUtils.abbreviate(license, 255); } - public Project getProject() { - return project; - } - - public void setProject(Project project) { - this.project = project; - } - public UUID getUuid() { return uuid; } diff --git a/commons-persistence/src/main/java/org/dependencytrack/persistence/model/NotificationGroup.java b/commons-persistence/src/main/java/org/dependencytrack/persistence/model/NotificationGroup.java deleted file mode 100644 index cddabb0993..0000000000 --- a/commons-persistence/src/main/java/org/dependencytrack/persistence/model/NotificationGroup.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * 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.persistence.model; - -public enum NotificationGroup { - - // System Groups - CONFIGURATION, - DATASOURCE_MIRRORING, - REPOSITORY, - INTEGRATION, - FILE_SYSTEM, - ANALYZER, - - // Portfolio Groups - NEW_VULNERABILITY, - NEW_VULNERABLE_DEPENDENCY, - //NEW_OUTDATED_COMPONENT, - //FIXED_VULNERABILITY, - //FIXED_OUTDATED, - //GLOBAL_AUDIT_CHANGE, - PROJECT_AUDIT_CHANGE, - BOM_CONSUMED, - BOM_PROCESSED, - BOM_PROCESSING_FAILED, - BOM_VALIDATION_FAILED, - VEX_CONSUMED, - VEX_PROCESSED, - POLICY_VIOLATION, - PROJECT_CREATED, - PROJECT_VULN_ANALYSIS_COMPLETE, - USER_CREATED, - USER_DELETED -} diff --git a/commons-persistence/src/main/java/org/dependencytrack/persistence/model/NotificationLevel.java b/commons-persistence/src/main/java/org/dependencytrack/persistence/model/NotificationLevel.java deleted file mode 100644 index 4b2dde9de9..0000000000 --- a/commons-persistence/src/main/java/org/dependencytrack/persistence/model/NotificationLevel.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * 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.persistence.model; - -public enum NotificationLevel { - - INFORMATIONAL(2), - WARNING(1), - ERROR(0); - - private int level; - - private NotificationLevel(int level) { - this.level = level; - } - - public int asNumericLevel() { - return this.level; - } - -} diff --git a/commons-persistence/src/main/java/org/dependencytrack/persistence/model/NotificationPublisher.java b/commons-persistence/src/main/java/org/dependencytrack/persistence/model/NotificationPublisher.java deleted file mode 100644 index d32c0349df..0000000000 --- a/commons-persistence/src/main/java/org/dependencytrack/persistence/model/NotificationPublisher.java +++ /dev/null @@ -1,126 +0,0 @@ -/* - * 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.persistence.model; - -import io.quarkus.hibernate.orm.panache.PanacheEntityBase; -import io.quarkus.runtime.annotations.RegisterForReflection; -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.Id; -import jakarta.persistence.Table; -import org.hibernate.annotations.JdbcType; -import org.hibernate.type.descriptor.jdbc.UUIDJdbcType; - -import java.util.UUID; - -@Entity -@RegisterForReflection -@Table(name = "NOTIFICATIONPUBLISHER") -public class NotificationPublisher extends PanacheEntityBase { - - @Id - @Column(name = "ID") - private long id; - - @Column(name = "NAME", nullable = false) - private String name; - - @Column(name = "DESCRIPTION") - private String description; - - @Column(name = "PUBLISHER_CLASS", nullable = false) - private String publisherClass; - - @Column(name = "TEMPLATE") - private String template; - - @Column(name = "TEMPLATE_MIME_TYPE", nullable = false) - private String templateMimeType; - - @Column(name = "DEFAULT_PUBLISHER", nullable = false) - private boolean defaultPublisher; - - @Column(name = "UUID", nullable = false) - @JdbcType(UUIDJdbcType.class) - private UUID uuid; - - public long getId() { - return id; - } - - public void setId(long id) { - this.id = id; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public String getDescription() { - return description; - } - - public void setDescription(String description) { - this.description = description; - } - - public String getPublisherClass() { - return publisherClass; - } - - public void setPublisherClass(String publisherClass) { - this.publisherClass = publisherClass; - } - - public String getTemplate() { - return template; - } - - public void setTemplate(String template) { - this.template = template; - } - - public String getTemplateMimeType() { - return templateMimeType; - } - - public void setTemplateMimeType(String templateMimeType) { - this.templateMimeType = templateMimeType; - } - - public boolean isDefaultPublisher() { - return defaultPublisher; - } - - public void setDefaultPublisher(boolean defaultPublisher) { - this.defaultPublisher = defaultPublisher; - } - - public UUID getUuid() { - return uuid; - } - - public void setUuid(UUID uuid) { - this.uuid = uuid; - } -} \ No newline at end of file diff --git a/commons-persistence/src/main/java/org/dependencytrack/persistence/model/NotificationRule.java b/commons-persistence/src/main/java/org/dependencytrack/persistence/model/NotificationRule.java deleted file mode 100644 index 1c674be893..0000000000 --- a/commons-persistence/src/main/java/org/dependencytrack/persistence/model/NotificationRule.java +++ /dev/null @@ -1,268 +0,0 @@ -/* - * 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.persistence.model; - -import io.quarkus.hibernate.orm.panache.PanacheEntityBase; -import jakarta.persistence.CascadeType; -import jakarta.persistence.Column; -import jakarta.persistence.Convert; -import jakarta.persistence.Entity; -import jakarta.persistence.Id; -import jakarta.persistence.JoinColumn; -import jakarta.persistence.JoinTable; -import jakarta.persistence.ManyToOne; -import jakarta.persistence.OneToMany; -import jakarta.persistence.OrderBy; -import jakarta.persistence.Table; -import org.dependencytrack.persistence.converter.NotificationScopeConverter; -import org.hibernate.annotations.JdbcType; -import org.hibernate.annotations.JdbcTypeCode; -import org.hibernate.type.SqlTypes; -import org.hibernate.type.descriptor.jdbc.UUIDJdbcType; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Set; -import java.util.TreeSet; -import java.util.UUID; - -@Entity -@Table(name = "NOTIFICATIONRULE") -public class NotificationRule extends PanacheEntityBase { - - @Id - @Column(name = "ID") - private long id; - - @Column(name = "NAME", nullable = false) - private String name; - - @Column(name = "ENABLED", nullable = false) - private boolean enabled; - - @Column(name = "SCOPE", nullable = false, columnDefinition = "varchar") - @Convert(converter = NotificationScopeConverter.class) - private NotificationScope scope; - - @JdbcTypeCode(SqlTypes.NAMED_ENUM) - @Column(name = "NOTIFICATION_LEVEL", columnDefinition = "notification_level", nullable = false) - private NotificationLevel notificationLevel; - - @OneToMany - @JoinTable( - name = "NOTIFICATIONRULE_PROJECTS", - joinColumns = @JoinColumn(name = "NOTIFICATIONRULE_ID", referencedColumnName = "ID"), - inverseJoinColumns = @JoinColumn(name = "PROJECT_ID", referencedColumnName = "ID") - ) - @OrderBy("name ASC, version ASC") - private List projects; - - @OneToMany - @JoinTable( - name = "NOTIFICATIONRULE_TAGS", - joinColumns = @JoinColumn(name = "NOTIFICATIONRULE_ID", referencedColumnName = "ID"), - inverseJoinColumns = @JoinColumn(name = "TAG_ID", referencedColumnName = "ID") - ) - @OrderBy("name ASC") - private List tags; - -// @Join(column = "NOTIFICATIONRULE_ID") -// @Element(column = "TEAM_ID") -// @Order(extensions = @Extension(vendorName = "datanucleus", key = "list-ordering", value = "name ASC")) -// @OneToMany(cascade = CascadeType.ALL) -// @JoinTable( -// name = "Team", -// joinColumns = @JoinColumn(name = "TEAM_ID") -// ) -// @OrderBy("name ASC") -// private List teams; - - @Column(name = "NOTIFY_ON") - private String notifyOn; - - @Column(name = "MESSAGE") - private String message; - - @JoinColumn(name = "PUBLISHER", columnDefinition = "bigint") - @ManyToOne(cascade = CascadeType.ALL) - private NotificationPublisher publisher; - - @Column(name = "PUBLISHER_CONFIG") - private String publisherConfig; - - @Column(name = "UUID", nullable = false) - @JdbcType(UUIDJdbcType.class) - private UUID uuid; - - @Column(name = "NOTIFY_CHILDREN") - private boolean notifyChildren; - - - /** - * In addition to warnings and errors, also emit a log message upon successful publishing. - *

- * Intended to aid in debugging of missing notifications, or environments where notification - * delivery is critical and subject to auditing. - * - * @since 4.10.0 - */ - @Column(name = "LOG_SUCCESSFUL_PUBLISH") - private Boolean logSuccessfulPublish; - - public long getId() { - return id; - } - - public void setId(long id) { - this.id = id; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public boolean isEnabled() { - return enabled; - } - - public void setEnabled(boolean enabled) { - this.enabled = enabled; - } - - public boolean isLogSuccessfulPublish() { - return logSuccessfulPublish != null ? logSuccessfulPublish : false; - } - - public void setLogSuccessfulPublish(final boolean logSuccessfulPublish) { - this.logSuccessfulPublish = logSuccessfulPublish; - } - - public NotificationScope getScope() { - return scope; - } - - public void setScope(NotificationScope scope) { - this.scope = scope; - } - - public NotificationLevel getNotificationLevel() { - return notificationLevel; - } - - public void setNotificationLevel(NotificationLevel notificationLevel) { - this.notificationLevel = notificationLevel; - } - - public boolean isNotifyChildren() { - return notifyChildren; - } - - public void setNotifyChildren(boolean notifyChildren) { - this.notifyChildren = notifyChildren; - } - - public List getProjects() { - return projects; - } - - public void setProjects(List projects) { - this.projects = projects; - } - - public List getTags() { - return tags; - } - - public void setTags(List tags) { - this.tags = tags; - } - -// public List getTeams() { -// return teams; -// } -// -// public void setTeams(List teams) { -// this.teams = teams; -// } - - public String getMessage() { - return message; - } - - public void setMessage(String message) { - this.message = message; - } - - public Set getNotifyOn() { - Set result = new TreeSet<>(); - if (notifyOn != null) { - String[] groups = notifyOn.split(","); - for (String s : groups) { - result.add(NotificationGroup.valueOf(s.trim())); - } - } - return result; - } - - public void setNotifyOn(Set groups) { - if (groups.isEmpty()) { - this.notifyOn = null; - return; - } - StringBuilder sb = new StringBuilder(); - List list = new ArrayList<>(groups); - Collections.sort(list); - for (int i = 0; i < list.size(); i++) { - sb.append(list.get(i)); - if (i + 1 < list.size()) { - sb.append(","); - } - } - this.notifyOn = sb.toString(); - } - - public NotificationPublisher getPublisher() { - return publisher; - } - - public void setPublisher(NotificationPublisher publisher) { - this.publisher = publisher; - } - - public String getPublisherConfig() { - return publisherConfig; - } - - public void setPublisherConfig(String publisherConfig) { - this.publisherConfig = publisherConfig; - } - - public UUID getUuid() { - return uuid; - } - - public void setUuid(UUID uuid) { - this.uuid = uuid; - } -} diff --git a/commons-persistence/src/main/java/org/dependencytrack/persistence/model/Project.java b/commons-persistence/src/main/java/org/dependencytrack/persistence/model/Project.java deleted file mode 100644 index 1cba2bcef5..0000000000 --- a/commons-persistence/src/main/java/org/dependencytrack/persistence/model/Project.java +++ /dev/null @@ -1,288 +0,0 @@ -/* - * 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.persistence.model; - -import com.github.packageurl.MalformedPackageURLException; -import com.github.packageurl.PackageURL; -import io.quarkus.runtime.annotations.RegisterForReflection; -import jakarta.persistence.Column; -import jakarta.persistence.Convert; -import jakarta.persistence.Entity; -import jakarta.persistence.Id; -import jakarta.persistence.JoinColumn; -import jakarta.persistence.JoinTable; -import jakarta.persistence.ManyToOne; -import jakarta.persistence.OneToMany; -import jakarta.persistence.OrderBy; -import jakarta.persistence.Table; -import org.dependencytrack.persistence.converter.ClassifierToStringConverter; -import org.hibernate.annotations.JdbcType; -import org.hibernate.type.descriptor.jdbc.UUIDJdbcType; - -import java.io.Serializable; -import java.util.Collection; -import java.util.Date; -import java.util.List; -import java.util.UUID; - -@Entity -@Table(name = "PROJECT") -@RegisterForReflection -public class Project implements Serializable { - - @Id - @Column(name = "ID") - private long id; - - @Column(name = "\"GROUP\"", columnDefinition = "VARCHAR") - private String group; - - @Column(name = "NAME", columnDefinition = "VARCHAR", nullable = false) - private String name; - - @Column(name = "DESCRIPTION", columnDefinition = "VARCHAR") - private String description; - - @Column(name = "VERSION", columnDefinition = "VARCHAR") - private String version; - - @ManyToOne - @JoinColumn(name = "PARENT_PROJECT_ID") - private Project parent; - - @OneToMany(mappedBy = "parent") - private Collection children; - - @Column(name = "CLASSIFIER", columnDefinition = "VARCHAR") - @Convert(converter = ClassifierToStringConverter.class) - private Classifier classifier; - - @Column(name = "CPE") - private String cpe; - - @Column(name = "PURL") - private String purl; - - @Column(name = "SWIDTAGID") - private String swidTagId; - - @OneToMany - @JoinTable( - name = "PROJECTS_TAGS", - joinColumns = @JoinColumn(name = "PROJECT_ID", referencedColumnName = "ID"), - inverseJoinColumns = @JoinColumn(name = "TAG_ID", referencedColumnName = "ID") - ) - @OrderBy("name ASC") - private List tags; - - @Column(name = "UUID", nullable = false) - @JdbcType(UUIDJdbcType.class) - private UUID uuid; - - /** - * Convenience field which will contain the date of the last entry in the BOM table - */ - @Column(name = "LAST_BOM_IMPORTED") - private Date lastBomImport; - - /** - * Convenience field which will contain the format of the last entry in the BOM table - */ - @Column(name = "LAST_BOM_IMPORTED_FORMAT") - private String lastBomImportFormat; - - /** - * Convenience field which stores the Inherited Risk Score (IRS) of the last metric in the ProjectMetrics table - */ - @Column(name = "LAST_RISKSCORE") - private Double lastInheritedRiskScore; - - @Column(name = "INACTIVE_SINCE") - private Date inactiveSince; - - public long getId() { - return id; - } - - public void setId(long id) { - this.id = id; - } - - public String getGroup() { - return group; - } - - public void setGroup(String group) { - this.group = group; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public String getDescription() { - return description; - } - - public void setDescription(String description) { - this.description = description; - } - - public String getVersion() { - return version; - } - - public void setVersion(String version) { - this.version = version; - } - - public Classifier getClassifier() { - return classifier; - } - - public void setClassifier(Classifier classifier) { - this.classifier = classifier; - } - - public String getCpe() { - return cpe; - } - - public void setCpe(String cpe) { - this.cpe = cpe; - } - - public PackageURL getPurl() { - try { - return new PackageURL(purl); - } catch (MalformedPackageURLException e) { - return null; - } - } - - public void setPurl(PackageURL purl) { - if (purl != null) { - this.purl = purl.canonicalize(); - } else { - this.purl = null; - } - } - - public void setPurl(String purl) { - this.purl = purl; - } - - public String getSwidTagId() { - return swidTagId; - } - - public void setSwidTagId(String swidTagId) { - this.swidTagId = swidTagId; - } - - public UUID getUuid() { - return uuid; - } - - public void setUuid(UUID uuid) { - this.uuid = uuid; - } - - public Date getLastBomImport() { - return lastBomImport; - } - - public void setLastBomImport(Date lastBomImport) { - this.lastBomImport = lastBomImport; - } - - public String getLastBomImportFormat() { - return lastBomImportFormat; - } - - public void setLastBomImportFormat(String lastBomImportFormat) { - this.lastBomImportFormat = lastBomImportFormat; - } - - public Double getLastInheritedRiskScore() { - return lastInheritedRiskScore; - } - - public List getTags() { - return tags; - } - - public void setTags(List tags) { - this.tags = tags; - } - - public void setLastInheritedRiskScore(Double lastInheritedRiskScore) { - this.lastInheritedRiskScore = lastInheritedRiskScore; - } - - public boolean isActive() { - return inactiveSince == null; - } - - public Date getInactiveSince() { - return inactiveSince; - } - - public void setInactiveSince(Date inactiveSince) { - this.inactiveSince = inactiveSince; - } - - public Project getParent() { - return parent; - } - - public void setParent(Project parent) { - this.parent = parent; - } - - public Collection getChildren() { - return children; - } - - public void setChildren(Collection children) { - this.children = children; - } - - @Override - public String toString() { - if (getPurl() != null) { - return getPurl().canonicalize(); - } else { - StringBuilder sb = new StringBuilder(); - if (getGroup() != null) { - sb.append(getGroup()).append(" : "); - } - sb.append(getName()); - if (getVersion() != null) { - sb.append(" : ").append(getVersion()); - } - return sb.toString(); - } - } - -} diff --git a/commons-persistence/src/main/java/org/dependencytrack/persistence/model/Tag.java b/commons-persistence/src/main/java/org/dependencytrack/persistence/model/Tag.java deleted file mode 100644 index 530b761118..0000000000 --- a/commons-persistence/src/main/java/org/dependencytrack/persistence/model/Tag.java +++ /dev/null @@ -1,115 +0,0 @@ -/* - * 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.persistence.model; - -import io.quarkus.runtime.annotations.RegisterForReflection; - -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.Id; -import jakarta.persistence.JoinColumn; -import jakarta.persistence.JoinTable; -import jakarta.persistence.ManyToMany; -import jakarta.persistence.OrderBy; -import jakarta.persistence.Table; -import java.util.List; -import java.util.Objects; - -/** - * Model for assigning tags to specific objects. - * - * @author Steve Springett - * @since 3.0.0 - */ -@Entity -@Table(name = "TAG") -@RegisterForReflection -public class Tag { - - @Id - @Column(name = "ID") - private long id; - - @Column(name = "NAME", nullable = false) - private String name; - - @OrderBy("name ASC") - @ManyToMany - @JoinTable( - name = "PROJECTS_TAGS", - joinColumns = @JoinColumn(name = "TAG_ID", referencedColumnName = "ID"), - inverseJoinColumns = @JoinColumn(name = "PROJECT_ID", referencedColumnName = "ID") - ) - private List projects; - - @OrderBy("vulnId ASC") - @ManyToMany - @JoinTable( - name = "VULNERABILITIES_TAGS", - joinColumns = @JoinColumn(name = "TAG_ID", referencedColumnName = "ID"), - inverseJoinColumns = @JoinColumn(name = "VULNERABILITY_ID", referencedColumnName = "ID") - ) - private List vulnerabilities; - - public long getId() { - return id; - } - - public void setId(long id) { - this.id = id; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public List getProjects() { - return projects; - } - - public void setProjects(List projects) { - this.projects = projects; - } - - public List getVulnerabilities() { - return vulnerabilities; - } - - public void setVulnerabilities(List vulnerabilities) { - this.vulnerabilities = vulnerabilities; - } - - @Override - public boolean equals(Object object) { - if (object instanceof Tag) { - return this.id == ((Tag) object).id; - } - return false; - } - - @Override - public int hashCode() { - return Objects.hash(id); - } - -} diff --git a/commons-persistence/src/main/java/org/dependencytrack/persistence/model/Team.java b/commons-persistence/src/main/java/org/dependencytrack/persistence/model/Team.java deleted file mode 100644 index 88a07b0b4b..0000000000 --- a/commons-persistence/src/main/java/org/dependencytrack/persistence/model/Team.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * 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.persistence.model; - -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.Id; -import jakarta.persistence.Table; - -@Entity -@Table(name = "TEAM") -public class Team { - - @Id - @Column(name = "ID") - private long id; - - @Column(name = "NAME", nullable = false) - private String name; - - public long getId() { - return id; - } - - public void setId(final long id) { - this.id = id; - } - - public String getName() { - return name; - } - - public void setName(final String name) { - this.name = name; - } - -} diff --git a/commons-persistence/src/main/java/org/dependencytrack/persistence/model/Vulnerability.java b/commons-persistence/src/main/java/org/dependencytrack/persistence/model/Vulnerability.java index d011e9f4b4..ac9e1ef5e4 100644 --- a/commons-persistence/src/main/java/org/dependencytrack/persistence/model/Vulnerability.java +++ b/commons-persistence/src/main/java/org/dependencytrack/persistence/model/Vulnerability.java @@ -29,7 +29,6 @@ import jakarta.persistence.JoinColumn; import jakarta.persistence.JoinTable; import jakarta.persistence.ManyToMany; -import jakarta.persistence.OneToMany; import jakarta.persistence.OrderBy; import jakarta.persistence.Table; import org.dependencytrack.common.cwe.Cwe; @@ -169,15 +168,6 @@ public enum Source { @JdbcType(UUIDJdbcType.class) private UUID uuid; - @OneToMany - @JoinTable( - name = "VULNERABILITIES_TAGS", - joinColumns = @JoinColumn(name = "VULNERABILITY_ID", referencedColumnName = "ID"), - inverseJoinColumns = @JoinColumn(name = "TAG_ID", referencedColumnName = "ID") - ) - @OrderBy("name ASC") - private List tags; - private transient int affectedProjectCount; private transient List aliases; @@ -496,11 +486,4 @@ public void setAliases(List aliases) { this.aliases = aliases; } - public List getTags() { - return tags; - } - - public void setTags(List tags) { - this.tags = tags; - } } diff --git a/commons-persistence/src/main/java/org/dependencytrack/persistence/repository/NotificationRuleRepository.java b/commons-persistence/src/main/java/org/dependencytrack/persistence/repository/NotificationRuleRepository.java deleted file mode 100644 index 099e5b67f0..0000000000 --- a/commons-persistence/src/main/java/org/dependencytrack/persistence/repository/NotificationRuleRepository.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * 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.persistence.repository; - -import io.quarkus.hibernate.orm.panache.PanacheRepository; -import io.quarkus.panache.common.Parameters; -import jakarta.enterprise.context.ApplicationScoped; -import org.dependencytrack.persistence.model.NotificationLevel; -import org.dependencytrack.persistence.model.NotificationRule; -import org.dependencytrack.persistence.model.NotificationScope; - -import java.util.List; - -import static org.hibernate.jpa.HibernateHints.HINT_READ_ONLY; - -@ApplicationScoped -public class NotificationRuleRepository implements PanacheRepository { - - public List findEnabledByScopeAndForLevel( - final NotificationScope scope, - final NotificationLevel level) { - return find( - "enabled and scope = :scope and notificationLevel <= :level", - Parameters.with("scope", scope).and("level", level)) - .withHint(HINT_READ_ONLY, true) - .list(); - } - -} diff --git a/commons-persistence/src/main/java/org/dependencytrack/persistence/repository/ProjectRepository.java b/commons-persistence/src/main/java/org/dependencytrack/persistence/repository/ProjectRepository.java deleted file mode 100644 index 344e4e6a7d..0000000000 --- a/commons-persistence/src/main/java/org/dependencytrack/persistence/repository/ProjectRepository.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * 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.persistence.repository; - -import io.quarkus.hibernate.orm.panache.PanacheRepository; -import org.dependencytrack.persistence.model.Project; - -import jakarta.enterprise.context.ApplicationScoped; -import jakarta.persistence.Query; -import java.util.UUID; - -import static org.hibernate.jpa.HibernateHints.HINT_READ_ONLY; - -/** - * @since 0.6.0 - */ -@ApplicationScoped -public class ProjectRepository implements PanacheRepository { - - public boolean isParentOfActiveChild(final Project parent, final UUID childUuid) { - final Query query = getEntityManager().createNativeQuery(""" - SELECT EXISTS( - SELECT 1 - FROM "PROJECT_HIERARCHY" AS hierarchy - INNER JOIN "PROJECT" AS child_project - ON child_project."ID" = hierarchy."CHILD_PROJECT_ID" - WHERE hierarchy."PARENT_PROJECT_ID" = :parentId - AND hierarchy."DEPTH" > 0 - AND child_project."ID" = (SELECT "ID" FROM "PROJECT" WHERE "UUID" = :childUuid) - AND child_project."INACTIVE_SINCE" IS NULL - ) - """); - - return (boolean) query - .setParameter("parentId", parent.getId()) - .setParameter("childUuid", childUuid) - .setHint(HINT_READ_ONLY, true) - .getSingleResult(); - } - -} diff --git a/commons-persistence/src/main/java/org/dependencytrack/persistence/repository/TeamRepository.java b/commons-persistence/src/main/java/org/dependencytrack/persistence/repository/TeamRepository.java deleted file mode 100644 index 996e56ca29..0000000000 --- a/commons-persistence/src/main/java/org/dependencytrack/persistence/repository/TeamRepository.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * 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.persistence.repository; - -import io.quarkus.hibernate.orm.panache.PanacheRepository; -import jakarta.enterprise.context.ApplicationScoped; -import jakarta.inject.Inject; -import jakarta.persistence.EntityManager; -import org.dependencytrack.persistence.model.Team; - -import java.util.List; - -import static org.hibernate.jpa.HibernateHints.HINT_READ_ONLY; - -@ApplicationScoped -public class TeamRepository implements PanacheRepository { - - private final EntityManager entityManager; - - @Inject - TeamRepository(final EntityManager entityManager) { - this.entityManager = entityManager; - } - - @SuppressWarnings("unchecked") - public List findByNotificationRule(final long notificationRuleId) { - return entityManager - .createNativeQuery(""" - SELECT * FROM "TEAM" AS "T" - INNER JOIN "NOTIFICATIONRULE_TEAMS" AS "NT" ON "NT"."TEAM_ID" = "T"."ID" - WHERE "NT"."NOTIFICATIONRULE_ID" = :ruleId - """, Team.class) - .setParameter("ruleId", notificationRuleId) - .setHint(HINT_READ_ONLY, true) - .getResultList(); - } - -} diff --git a/commons-persistence/src/main/java/org/dependencytrack/persistence/repository/UserRepository.java b/commons-persistence/src/main/java/org/dependencytrack/persistence/repository/UserRepository.java deleted file mode 100644 index d379673a18..0000000000 --- a/commons-persistence/src/main/java/org/dependencytrack/persistence/repository/UserRepository.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * 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.persistence.repository; - -import jakarta.enterprise.context.ApplicationScoped; -import jakarta.inject.Inject; -import jakarta.persistence.EntityManager; - -import java.util.List; - -import static org.hibernate.jpa.HibernateHints.HINT_READ_ONLY; - -@ApplicationScoped -public class UserRepository { - - private final EntityManager entityManager; - - @Inject - UserRepository(final EntityManager entityManager) { - this.entityManager = entityManager; - } - - @SuppressWarnings("unchecked") - public List findEmailsByTeam(final long teamId) { - return entityManager.createNativeQuery(""" - SELECT DISTINCT "USER"."EMAIL" - FROM "USERS_TEAMS" - INNER JOIN "USER" - ON "USER"."ID" = "USERS_TEAMS"."USER_ID" - WHERE "TEAM_ID" = :teamId - AND "USER"."EMAIL" IS NOT NULL - """) - .setParameter("teamId", teamId) - .setHint(HINT_READ_ONLY, true) - .getResultList(); - } - -} diff --git a/commons-persistence/src/test/java/org/dependencytrack/persistence/converter/NotificationScopeConverterTest.java b/commons-persistence/src/test/java/org/dependencytrack/persistence/converter/NotificationScopeConverterTest.java deleted file mode 100644 index 39b58870b6..0000000000 --- a/commons-persistence/src/test/java/org/dependencytrack/persistence/converter/NotificationScopeConverterTest.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * 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.persistence.converter; - -import org.dependencytrack.persistence.model.NotificationScope; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; - -public class NotificationScopeConverterTest { - - @Test - public void convertToDatastoreTest() { - Assertions.assertNull((new NotificationScopeConverter().convertToDatabaseColumn(null))); - Assertions.assertTrue((new NotificationScopeConverter().convertToDatabaseColumn(NotificationScope.PORTFOLIO).equals("PORTFOLIO"))); - Assertions.assertTrue((new NotificationScopeConverter().convertToDatabaseColumn(NotificationScope.SYSTEM).equals("SYSTEM"))); - } - - @Test - public void convertToAttributeTest() { - Assertions.assertEquals(new NotificationScopeConverter().convertToEntityAttribute("PORTFOLIO"), NotificationScope.PORTFOLIO); - Assertions.assertEquals(new NotificationScopeConverter().convertToEntityAttribute("SYSTEM"), NotificationScope.SYSTEM); - - } - -} diff --git a/commons-persistence/src/test/java/org/dependencytrack/persistence/model/NotificationGroupTest.java b/commons-persistence/src/test/java/org/dependencytrack/persistence/model/NotificationGroupTest.java deleted file mode 100644 index e748e2c269..0000000000 --- a/commons-persistence/src/test/java/org/dependencytrack/persistence/model/NotificationGroupTest.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * 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.persistence.model; - -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; - -public class NotificationGroupTest { - - @Test - public void testEnums() { - // System Groups - Assertions.assertEquals("CONFIGURATION", NotificationGroup.CONFIGURATION.name()); - Assertions.assertEquals("DATASOURCE_MIRRORING", NotificationGroup.DATASOURCE_MIRRORING.name()); - Assertions.assertEquals("REPOSITORY", NotificationGroup.REPOSITORY.name()); - Assertions.assertEquals("ANALYZER", NotificationGroup.ANALYZER.name()); - Assertions.assertEquals("INTEGRATION", NotificationGroup.INTEGRATION.name()); - // Portfolio Groups - Assertions.assertEquals("NEW_VULNERABILITY", NotificationGroup.NEW_VULNERABILITY.name()); - Assertions.assertEquals("NEW_VULNERABLE_DEPENDENCY", NotificationGroup.NEW_VULNERABLE_DEPENDENCY.name()); - Assertions.assertEquals("PROJECT_AUDIT_CHANGE", NotificationGroup.PROJECT_AUDIT_CHANGE.name()); - } -} diff --git a/commons-persistence/src/test/java/org/dependencytrack/persistence/model/NotificationPublisherTest.java b/commons-persistence/src/test/java/org/dependencytrack/persistence/model/NotificationPublisherTest.java deleted file mode 100644 index 602c2538a5..0000000000 --- a/commons-persistence/src/test/java/org/dependencytrack/persistence/model/NotificationPublisherTest.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * 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.persistence.model; - -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; - -import java.util.UUID; - -public class NotificationPublisherTest { - - @Test - public void testId() { - NotificationPublisher publisher = new NotificationPublisher(); - publisher.setId(111); - Assertions.assertEquals(111L, publisher.getId()); - } - - @Test - public void testName() { - NotificationPublisher publisher = new NotificationPublisher(); - publisher.setName("My Publisher"); - Assertions.assertEquals("My Publisher", publisher.getName()); - } - - @Test - public void testDescription() { - NotificationPublisher publisher = new NotificationPublisher(); - publisher.setDescription("My description"); - Assertions.assertEquals("My description", publisher.getDescription()); - } - - @Test - public void testPublisherClass() { - NotificationPublisher publisher = new NotificationPublisher(); - publisher.setPublisherClass("org.dependencytrack.publisher"); - Assertions.assertEquals("org.dependencytrack.publisher", publisher.getPublisherClass()); - } - - @Test - public void testTemplate() { - NotificationPublisher publisher = new NotificationPublisher(); - publisher.setTemplate("{ \"config\": \"configured\" }"); - Assertions.assertEquals("{ \"config\": \"configured\" }", publisher.getTemplate()); - } - - @Test - public void testTemplateMimeType() { - NotificationPublisher publisher = new NotificationPublisher(); - publisher.setTemplateMimeType("application/json"); - Assertions.assertEquals("application/json", publisher.getTemplateMimeType()); - } - - @Test - public void testDefaultPublisher() { - NotificationPublisher publisher = new NotificationPublisher(); - publisher.setDefaultPublisher(true); - Assertions.assertTrue(publisher.isDefaultPublisher()); - } - - @Test - public void testUuid() { - UUID uuid = UUID.randomUUID(); - NotificationPublisher publisher = new NotificationPublisher(); - publisher.setUuid(uuid); - Assertions.assertEquals(uuid.toString(), publisher.getUuid().toString()); - } -} diff --git a/commons-persistence/src/test/java/org/dependencytrack/persistence/model/NotificationRuleTest.java b/commons-persistence/src/test/java/org/dependencytrack/persistence/model/NotificationRuleTest.java deleted file mode 100644 index be36320eef..0000000000 --- a/commons-persistence/src/test/java/org/dependencytrack/persistence/model/NotificationRuleTest.java +++ /dev/null @@ -1,128 +0,0 @@ -/* - * 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.persistence.model; - -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; - -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import java.util.UUID; - -public class NotificationRuleTest { - - @Test - public void testId() { - NotificationRule rule = new NotificationRule(); - rule.setId(111); - Assertions.assertEquals(111L, rule.getId()); - } - - @Test - public void testName() { - NotificationRule rule = new NotificationRule(); - rule.setName("Test Name"); - Assertions.assertEquals("Test Name", rule.getName()); - } - - @Test - public void testEnabled() { - NotificationRule rule = new NotificationRule(); - rule.setEnabled(true); - Assertions.assertTrue(rule.isEnabled()); - } - - @Test - public void testScope() { - NotificationRule rule = new NotificationRule(); - rule.setScope(NotificationScope.PORTFOLIO); - Assertions.assertEquals(NotificationScope.PORTFOLIO, rule.getScope()); - } - - @Test - public void testNotificationLevel() { - NotificationRule rule = new NotificationRule(); - rule.setNotificationLevel(NotificationLevel.INFORMATIONAL); - Assertions.assertEquals(NotificationLevel.INFORMATIONAL, rule.getNotificationLevel()); - } - - @Test - public void testProjects() { - List projects = new ArrayList<>(); - Project project = new Project(); - projects.add(project); - NotificationRule rule = new NotificationRule(); - rule.setProjects(projects); - Assertions.assertEquals(1, rule.getProjects().size()); - Assertions.assertEquals(project, rule.getProjects().get(0)); - } - - @Test - public void testTags() { - List tags = new ArrayList<>(); - Tag tag = new Tag(); - tags.add(tag); - NotificationRule rule = new NotificationRule(); - rule.setTags(tags); - Assertions.assertEquals(1, rule.getTags().size()); - Assertions.assertEquals(tag, rule.getTags().get(0)); - } - - @Test - public void testMessage() { - NotificationRule rule = new NotificationRule(); - rule.setMessage("Test Message"); - Assertions.assertEquals("Test Message", rule.getMessage()); - } - - @Test - public void testNotifyOn() { - Set groups = new HashSet<>(); - groups.add(NotificationGroup.NEW_VULNERABLE_DEPENDENCY); - groups.add(NotificationGroup.NEW_VULNERABILITY); - NotificationRule rule = new NotificationRule(); - rule.setNotifyOn(groups); - Assertions.assertEquals(2, rule.getNotifyOn().size()); - } - - @Test - public void testPublisher() { - NotificationPublisher publisher = new NotificationPublisher(); - NotificationRule rule = new NotificationRule(); - rule.setPublisher(publisher); - Assertions.assertEquals(publisher, rule.getPublisher()); - } - - @Test - public void testPublisherConfig() { - NotificationRule rule = new NotificationRule(); - rule.setPublisherConfig("{ \"config\": \"configured\" }"); - Assertions.assertEquals("{ \"config\": \"configured\" }", rule.getPublisherConfig()); - } - - @Test - public void testUuid() { - UUID uuid = UUID.randomUUID(); - NotificationRule rule = new NotificationRule(); - rule.setUuid(uuid); - Assertions.assertEquals(uuid.toString(), rule.getUuid().toString()); - } -} diff --git a/commons-persistence/src/test/java/org/dependencytrack/persistence/model/NotificationScopeTest.java b/commons-persistence/src/test/java/org/dependencytrack/persistence/model/NotificationScopeTest.java deleted file mode 100644 index bac2b3f251..0000000000 --- a/commons-persistence/src/test/java/org/dependencytrack/persistence/model/NotificationScopeTest.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * 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.persistence.model; - -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; - -public class NotificationScopeTest { - - @Test - public void testEnums() { - Assertions.assertEquals("SYSTEM", NotificationScope.SYSTEM.name()); - Assertions.assertEquals("PORTFOLIO", NotificationScope.PORTFOLIO.name()); - } -} diff --git a/commons-persistence/src/test/java/org/dependencytrack/persistence/model/TagTest.java b/commons-persistence/src/test/java/org/dependencytrack/persistence/model/TagTest.java deleted file mode 100644 index 4dfa195f56..0000000000 --- a/commons-persistence/src/test/java/org/dependencytrack/persistence/model/TagTest.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * 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.persistence.model; - -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; - -import java.util.ArrayList; -import java.util.List; - -public class TagTest { - - @Test - public void testId() { - Tag tag = new Tag(); - tag.setId(111); - Assertions.assertEquals(111L, tag.getId()); - } - - @Test - public void testName() { - Tag tag = new Tag(); - tag.setName("java"); - Assertions.assertEquals("java", tag.getName()); - } - - @Test - public void testProjects() { - List projects = new ArrayList<>(); - Project project = new Project(); - projects.add(project); - Tag tag = new Tag(); - tag.setProjects(projects); - Assertions.assertEquals(1, tag.getProjects().size()); - Assertions.assertEquals(project, tag.getProjects().get(0)); - } - - @Test - public void testVulnerabilities() { - List vulnerabilities = new ArrayList<>(); - Vulnerability vulnerability = new Vulnerability(); - vulnerabilities.add(vulnerability); - Tag tag = new Tag(); - tag.setVulnerabilities(vulnerabilities); - Assertions.assertEquals(1, tag.getVulnerabilities().size()); - Assertions.assertEquals(vulnerability, tag.getVulnerabilities().get(0)); - } - - @Test - public void testEquals() { - Tag t1 = new Tag(); - t1.setId(111); - Tag t2 = new Tag(); - t2.setId(222); - Tag t3 = new Tag(); - t3.setId(111); - Assertions.assertFalse(t1.equals(t2)); - Assertions.assertTrue(t1.equals(t3)); - } -} diff --git a/commons-persistence/src/test/java/org/dependencytrack/persistence/model/VulnerabilityTest.java b/commons-persistence/src/test/java/org/dependencytrack/persistence/model/VulnerabilityTest.java index 245eefb9df..52e5bdee9c 100644 --- a/commons-persistence/src/test/java/org/dependencytrack/persistence/model/VulnerabilityTest.java +++ b/commons-persistence/src/test/java/org/dependencytrack/persistence/model/VulnerabilityTest.java @@ -240,14 +240,4 @@ public void testUuid() { Assertions.assertEquals(uuid.toString(), vuln.getUuid().toString()); } - @Test - public void testVulnerabilityTags() { - List tags = new ArrayList<>(); - Tag tag = new Tag(); - tags.add(tag); - Vulnerability vuln = new Vulnerability(); - vuln.setTags(tags); - Assertions.assertEquals(1, vuln.getTags().size()); - Assertions.assertEquals(tag, vuln.getTags().get(0)); - } } diff --git a/commons-persistence/src/test/java/org/dependencytrack/persistence/repository/NotificationRuleRepositoryTest.java b/commons-persistence/src/test/java/org/dependencytrack/persistence/repository/NotificationRuleRepositoryTest.java deleted file mode 100644 index f6da088618..0000000000 --- a/commons-persistence/src/test/java/org/dependencytrack/persistence/repository/NotificationRuleRepositoryTest.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * 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.persistence.repository; - -import io.quarkus.test.TestTransaction; -import io.quarkus.test.junit.QuarkusTest; -import jakarta.inject.Inject; -import jakarta.persistence.EntityManager; -import org.dependencytrack.persistence.model.NotificationLevel; -import org.dependencytrack.persistence.model.NotificationRule; -import org.dependencytrack.persistence.model.NotificationScope; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; - -import java.util.List; - -@QuarkusTest -class NotificationRuleRepositoryTest { - - @Inject - EntityManager entityManager; - - @Inject - NotificationRuleRepository repository; - - @Test - @TestTransaction - public void testRuleLevelEqual() { - entityManager.createNativeQuery(""" - INSERT INTO "NOTIFICATIONRULE" ("ENABLED", "NAME", "NOTIFY_ON", "NOTIFY_CHILDREN", "LOG_SUCCESSFUL_PUBLISH", "NOTIFICATION_LEVEL", "SCOPE", "UUID") VALUES - (true, 'foo', 'NEW_VULNERABILITY', false, true, 'WARNING', 'PORTFOLIO', '6b1fee41-4178-4a23-9d1b-e9df79de8e62'); - """).executeUpdate(); - - final List rules = repository - .findEnabledByScopeAndForLevel(NotificationScope.PORTFOLIO, NotificationLevel.WARNING); - Assertions.assertEquals(1, rules.size()); - } - - @Test - @TestTransaction - public void testRuleLevelBelow() { - entityManager.createNativeQuery(""" - INSERT INTO "NOTIFICATIONRULE" ("ENABLED", "NAME", "NOTIFY_ON", "NOTIFY_CHILDREN", "LOG_SUCCESSFUL_PUBLISH", "NOTIFICATION_LEVEL", "SCOPE", "UUID") VALUES - (true, 'foo', 'NEW_VULNERABILITY', false, true, 'WARNING', 'PORTFOLIO', '6b1fee41-4178-4a23-9d1b-e9df79de8e62'); - """).executeUpdate(); - - final List rules = repository - .findEnabledByScopeAndForLevel(NotificationScope.PORTFOLIO, NotificationLevel.ERROR); - Assertions.assertEquals(1, rules.size()); - } - - @Test - @TestTransaction - public void testRuleLevelAbove() { - entityManager.createNativeQuery(""" - INSERT INTO "NOTIFICATIONRULE" ("ENABLED", "NAME", "NOTIFY_ON", "NOTIFY_CHILDREN", "LOG_SUCCESSFUL_PUBLISH", "NOTIFICATION_LEVEL", "SCOPE", "UUID") VALUES - (true, 'foo', 'NEW_VULNERABILITY', false, true, 'WARNING', 'PORTFOLIO', '6b1fee41-4178-4a23-9d1b-e9df79de8e62'); - """).executeUpdate(); - - final List rules = repository - .findEnabledByScopeAndForLevel(NotificationScope.PORTFOLIO, NotificationLevel.INFORMATIONAL); - Assertions.assertEquals(0, rules.size()); - } - -} \ No newline at end of file diff --git a/commons-persistence/src/test/java/org/dependencytrack/persistence/repository/ProjectRepositoryTest.java b/commons-persistence/src/test/java/org/dependencytrack/persistence/repository/ProjectRepositoryTest.java deleted file mode 100644 index 018147f5c1..0000000000 --- a/commons-persistence/src/test/java/org/dependencytrack/persistence/repository/ProjectRepositoryTest.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * 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.persistence.repository; - -import io.quarkus.test.TestTransaction; -import io.quarkus.test.junit.QuarkusTest; -import org.dependencytrack.persistence.model.Project; -import org.junit.jupiter.api.Test; - -import jakarta.inject.Inject; -import java.util.Date; -import java.util.UUID; - -import static org.assertj.core.api.Assertions.assertThat; - -@QuarkusTest -class ProjectRepositoryTest { - - @Inject - ProjectRepository projectRepository; - - @Test - @TestTransaction - void testIsParentOfActiveChild() { - final var parentProject = new Project(); - parentProject.setId(1); - parentProject.setUuid(UUID.fromString("adbf4d72-6ebc-429a-955b-265a8b8ba997")); - parentProject.setName("acme-app-parent"); - projectRepository.persist(parentProject); - - final var childProjectA = new Project(); - childProjectA.setId(2); - childProjectA.setUuid(UUID.fromString("970829b1-3112-42db-a5ab-73391463e349")); - childProjectA.setParent(parentProject); - childProjectA.setName("acme-app-child-a"); - projectRepository.persist(childProjectA); - - final var childProjectB = new Project(); - childProjectB.setId(3); - childProjectB.setUuid(UUID.fromString("bbf9e846-cc5a-493b-bc9a-ce944795a5ad")); - childProjectB.setParent(parentProject); - childProjectB.setName("acme-app-child-b"); - childProjectB.setInactiveSince(new Date()); - projectRepository.persist(childProjectB); - - assertThat(projectRepository.isParentOfActiveChild(parentProject, childProjectA.getUuid())).isTrue(); - assertThat(projectRepository.isParentOfActiveChild(parentProject, childProjectB.getUuid())).isFalse(); - assertThat(projectRepository.isParentOfActiveChild(parentProject, parentProject.getUuid())).isFalse(); - } - -} \ No newline at end of file diff --git a/commons-persistence/src/test/java/org/dependencytrack/persistence/repository/TeamRepositoryTest.java b/commons-persistence/src/test/java/org/dependencytrack/persistence/repository/TeamRepositoryTest.java deleted file mode 100644 index c79f625794..0000000000 --- a/commons-persistence/src/test/java/org/dependencytrack/persistence/repository/TeamRepositoryTest.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * 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.persistence.repository; - -import io.quarkus.test.TestTransaction; -import io.quarkus.test.junit.QuarkusTest; -import jakarta.inject.Inject; -import jakarta.persistence.EntityManager; -import org.dependencytrack.persistence.model.Team; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; - -import java.util.List; - -@QuarkusTest -class TeamRepositoryTest { - - @Inject - EntityManager entityManager; - - @Inject - TeamRepository repository; - - @Test - @TestTransaction - @SuppressWarnings("unchecked") - void testFindByNotificationRule() { - final var teamIds = (List) entityManager.createNativeQuery(""" - INSERT INTO "TEAM" ("NAME", "UUID") VALUES - ('foo', 'fa26b29f-e106-4d62-b1a3-2073b63c9dd0'), - ('bar', 'c18d0094-f161-4581-96fa-bfc7e413c78d'), - ('baz', '6db9c0cb-9c84-440a-89a8-9bbed5d028d9') - RETURNING "ID"; - """).getResultList(); - final Long teamFooId = teamIds.get(0); - final Long teamBarId = teamIds.get(1); - - final var ruleIds = (List) entityManager.createNativeQuery(""" - INSERT INTO "NOTIFICATIONRULE" ("ENABLED", "NAME", "NOTIFY_CHILDREN", "SCOPE", "UUID") VALUES - (true, 'foo', false, 'PORTFOLIO', '6b1fee41-4178-4a23-9d1b-e9df79de8e62'), - (true, 'bar', false, 'PORTFOLIO', 'ee74dc70-cd8e-41df-ae6a-1093d5f7b608') - RETURNING "ID"; - """).getResultList(); - final Long ruleFooId = ruleIds.get(0); - - entityManager.createNativeQuery(""" - INSERT INTO "NOTIFICATIONRULE_TEAMS" ("NOTIFICATIONRULE_ID", "TEAM_ID") VALUES - (:ruleFooId, :teamFooId), - (:ruleFooId, :teamBarId); - """) - .setParameter("ruleFooId", ruleFooId) - .setParameter("teamFooId", teamFooId) - .setParameter("teamBarId", teamBarId) - .executeUpdate(); - - final List teams = repository.findByNotificationRule(ruleFooId); - Assertions.assertEquals(2, teams.size()); - Assertions.assertEquals("foo", teams.get(0).getName()); - Assertions.assertEquals("bar", teams.get(1).getName()); - - Assertions.assertEquals(0, repository.findByNotificationRule(2).size()); - } - -} \ No newline at end of file diff --git a/commons-persistence/src/test/java/org/dependencytrack/persistence/repository/UserRepositoryTest.java b/commons-persistence/src/test/java/org/dependencytrack/persistence/repository/UserRepositoryTest.java deleted file mode 100644 index 12e61f7b44..0000000000 --- a/commons-persistence/src/test/java/org/dependencytrack/persistence/repository/UserRepositoryTest.java +++ /dev/null @@ -1,162 +0,0 @@ -/* - * 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.persistence.repository; - -import io.quarkus.test.TestTransaction; -import io.quarkus.test.junit.QuarkusTest; -import jakarta.inject.Inject; -import jakarta.persistence.EntityManager; -import org.junit.jupiter.api.Test; - -import java.util.List; - -import static org.assertj.core.api.Assertions.assertThat; - -@QuarkusTest -class UserRepositoryTest { - - @Inject - EntityManager entityManager; - - @Inject - UserRepository repository; - - @Test - @TestTransaction - @SuppressWarnings("unchecked") - void testFindEmailsByTeam() { - final var teamIds = (List) entityManager.createNativeQuery(""" - INSERT INTO "TEAM" ("NAME", "UUID") VALUES - ('foo', 'ba38e779-e252-4033-8e76-156dc46cc7a6'), - ('bar', '507d8f3c-431d-47aa-929e-7647746d07a9') - RETURNING "ID"; - """).getResultList(); - final Long teamFooId = teamIds.get(0); - final Long teamBarId = teamIds.get(1); - - final var managedUserIds = (List) entityManager.createNativeQuery(""" - INSERT INTO "USER" ("TYPE", "USERNAME", "EMAIL", "PASSWORD", "FORCE_PASSWORD_CHANGE", "LAST_PASSWORD_CHANGE", "NON_EXPIRY_PASSWORD", "SUSPENDED") VALUES - ('MANAGED', 'foo-managed', 'foo@managed.example.com', 'foo', false, NOW(), true, false), - ('MANAGED', 'bar-managed', 'bar@managed.example.com', 'bar', false, NOW(), true, false), - ('MANAGED', 'baz-managed', 'baz@managed.example.com', 'baz', false, NOW(), true, false), - ('MANAGED', 'qux-managed', NULL, 'qux', false, NOW(), true, false), - ('MANAGED', 'quux-managed', 'quux@example.com', 'quux', false, NOW(), true, false) - RETURNING "ID"; - """).getResultList(); - final Long managedUserFooId = managedUserIds.get(0); - final Long managedUserBarId = managedUserIds.get(1); - final Long managedUserQuxId = managedUserIds.get(3); - final Long managedUserQuuxId = managedUserIds.get(4); - - entityManager.createNativeQuery(""" - INSERT INTO "USERS_TEAMS" ("USER_ID", "TEAM_ID") VALUES - (:userFooId, :teamFooId), - (:userBarId, :teamFooId), - (:userQuxId, :teamFooId), - (:userBarId, :teamBarId), - (:userQuuxId, :teamBarId); - """) - .setParameter("userFooId", managedUserFooId) - .setParameter("userBarId", managedUserBarId) - .setParameter("userQuxId", managedUserQuxId) - .setParameter("userQuuxId", managedUserQuuxId) - .setParameter("teamFooId", teamFooId) - .setParameter("teamBarId", teamBarId) - .executeUpdate(); - - final var ldapUserIds = (List) entityManager.createNativeQuery(""" - INSERT INTO "USER" ("TYPE", "USERNAME", "EMAIL", "DN") VALUES - ('LDAP', 'foo-ldap', 'foo@ldap.example.com', 'foo'), - ('LDAP', 'bar-ldap', 'bar@ldap.example.com', 'bar'), - ('LDAP', 'baz-ldap', 'baz@ldap.example.com', 'baz'), - ('LDAP', 'qux-ldap', NULL, 'qux'), - ('LDAP', 'quux-ldap', 'quux@example.com', 'quux') - RETURNING "ID"; - """).getResultList(); - final Long ldapUserFooId = ldapUserIds.get(0); - final Long ldapUserBarId = ldapUserIds.get(1); - final Long ldapUserQuxId = ldapUserIds.get(3); - final Long ldapUserQuuxId = ldapUserIds.get(4); - - entityManager.createNativeQuery(""" - INSERT INTO "USERS_TEAMS" ("USER_ID", "TEAM_ID") VALUES - (:userFooId, :teamFooId), - (:userBarId, :teamFooId), - (:userQuxId, :teamFooId), - (:userBarId, :teamBarId), - (:userQuuxId, :teamBarId); - """) - .setParameter("userFooId", ldapUserFooId) - .setParameter("userBarId", ldapUserBarId) - .setParameter("userQuxId", ldapUserQuxId) - .setParameter("userQuuxId", ldapUserQuuxId) - .setParameter("teamFooId", teamFooId) - .setParameter("teamBarId", teamBarId) - .executeUpdate(); - - final var oidcUserIds = (List) entityManager.createNativeQuery(""" - INSERT INTO "USER" ("TYPE", "EMAIL", "USERNAME") VALUES - ('OIDC', 'foo@oidc.example.com', 'foo-oidc'), - ('OIDC', 'bar@oidc.example.com', 'bar-oidc'), - ('OIDC', 'baz@oidc.example.com', 'baz-oidc'), - ('OIDC', NULL, 'qux-oidc'), - ('OIDC', 'quux@example.com', 'quux-oidc') - RETURNING "ID"; - """).getResultList(); - final Long oidcUserFooId = oidcUserIds.get(0); - final Long oidcUserBarId = oidcUserIds.get(1); - final Long oidcUserQuxId = oidcUserIds.get(3); - final Long oidcUserQuuxId = oidcUserIds.get(4); - - entityManager.createNativeQuery(""" - INSERT INTO "USERS_TEAMS" ("USER_ID", "TEAM_ID") VALUES - (:userFooId, :teamFooId), - (:userBarId, :teamFooId), - (:userQuxId, :teamFooId), - (:userBarId, :teamBarId), - (:userQuuxId, :teamBarId); - """) - .setParameter("userFooId", oidcUserFooId) - .setParameter("userBarId", oidcUserBarId) - .setParameter("userQuxId", oidcUserQuxId) - .setParameter("userQuuxId", oidcUserQuuxId) - .setParameter("teamFooId", teamFooId) - .setParameter("teamBarId", teamBarId) - .executeUpdate(); - - assertThat(repository.findEmailsByTeam(teamFooId)).containsExactlyInAnyOrder( - "foo@managed.example.com", - "bar@managed.example.com", - "foo@ldap.example.com", - "bar@ldap.example.com", - "foo@oidc.example.com", - "bar@oidc.example.com" - ); - - assertThat(repository.findEmailsByTeam(teamBarId)).containsExactlyInAnyOrder( - "bar@managed.example.com", - "bar@ldap.example.com", - "bar@oidc.example.com", - "quux@example.com" - // Results are de-duplicated, thus quux@example.com must not appear more than - // once, despite multiple users having that email. - ); - } - -} \ No newline at end of file diff --git a/commons/src/main/java/org/dependencytrack/common/KafkaTopic.java b/commons/src/main/java/org/dependencytrack/common/KafkaTopic.java index d2e7894326..8a290c9ec9 100644 --- a/commons/src/main/java/org/dependencytrack/common/KafkaTopic.java +++ b/commons/src/main/java/org/dependencytrack/common/KafkaTopic.java @@ -23,19 +23,6 @@ public enum KafkaTopic { - NOTIFICATION_ANALYZER("dtrack.notification.analyzer"), - NOTIFICATION_BOM("dtrack.notification.bom"), - NOTIFICATION_CONFIGURATION("dtrack.notification.configuration"), - NOTIFICATION_DATASOURCE_MIRRORING("dtrack.notification.datasource-mirroring"), - NOTIFICATION_FILE_SYSTEM("dtrack.notification.file-system"), - NOTIFICATION_INTEGRATION("dtrack.notification.integration"), - NOTIFICATION_NEW_VULNERABILITY("dtrack.notification.new-vulnerability"), - NOTIFICATION_NEW_VULNERABLE_DEPENDENCY("dtrack.notification.new-vulnerable-dependency"), - NOTIFICATION_POLICY_VIOLATION("dtrack.notification.policy-violation"), - NOTIFICATION_PROJECT_AUDIT_CHANGE("dtrack.notification.project-audit-change"), - NOTIFICATION_REPOSITORY("dtrack.notification.repository"), - NOTIFICATION_VEX("dtrack.notification.vex"), - NOTIFICATION_USER("dtrack.notification.user"), REPO_META_ANALYSIS_COMMAND("dtrack.repo-meta-analysis.component"), REPO_META_ANALYSIS_RESULT("dtrack.repo-meta-analysis.result"), VULN_ANALYSIS_SCANNER_RESULT("dtrack.vuln-analysis.scanner.result"), diff --git a/commons/src/main/java/org/dependencytrack/commonutil/DateUtil.java b/commons/src/main/java/org/dependencytrack/commonutil/DateUtil.java index bf4bf2350b..496376bf6e 100644 --- a/commons/src/main/java/org/dependencytrack/commonutil/DateUtil.java +++ b/commons/src/main/java/org/dependencytrack/commonutil/DateUtil.java @@ -18,11 +18,9 @@ */ package org.dependencytrack.commonutil; -import java.text.DateFormat; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; -import java.util.TimeZone; public final class DateUtil { @@ -45,19 +43,6 @@ public static Date parseDate(final String date, final String dateFormat) { } } - /** - * Formats a Date object into ISO 8601 format. - * @param date the Date object to convert - * @return a String representation of an ISO 8601 date - * @since 3.4.0 - */ - public static String toISO8601(final Date date) { - final TimeZone tz = TimeZone.getTimeZone("UTC"); - final DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); // Quoted "Z" to indicate UTC, no timezone offset - df.setTimeZone(tz); - return df.format(date); - } - public static Date fromISO8601(final String dateString) { if (dateString == null) { return null; diff --git a/commons/src/main/java/org/dependencytrack/commonutil/JsonUtil.java b/commons/src/main/java/org/dependencytrack/commonutil/JsonUtil.java index d19e367c59..26fbb3f235 100644 --- a/commons/src/main/java/org/dependencytrack/commonutil/JsonUtil.java +++ b/commons/src/main/java/org/dependencytrack/commonutil/JsonUtil.java @@ -18,10 +18,6 @@ */ package org.dependencytrack.commonutil; -import jakarta.json.JsonObjectBuilder; - -import java.math.BigDecimal; -import java.math.BigInteger; import java.time.ZonedDateTime; import java.time.format.DateTimeParseException; @@ -32,34 +28,6 @@ public final class JsonUtil { */ private JsonUtil() { } - public static JsonObjectBuilder add(final JsonObjectBuilder builder, final String key, final String value) { - if (value != null) { - builder.add(key, value); - } - return builder; - } - - public static JsonObjectBuilder add(final JsonObjectBuilder builder, final String key, final BigInteger value) { - if (value != null) { - builder.add(key, value); - } - return builder; - } - - public static JsonObjectBuilder add(final JsonObjectBuilder builder, final String key, final BigDecimal value) { - if (value != null) { - builder.add(key, value); - } - return builder; - } - - public static JsonObjectBuilder add(final JsonObjectBuilder builder, final String key, final Enum value) { - if (value != null) { - builder.add(key, value.name()); - } - return builder; - } - public static ZonedDateTime jsonStringToTimestamp(final String s) { if (s == null) { return null; diff --git a/commons/src/main/java/org/dependencytrack/commonutil/VulnerabilityUtil.java b/commons/src/main/java/org/dependencytrack/commonutil/VulnerabilityUtil.java index 5cff5d12c2..fbf9e0a036 100644 --- a/commons/src/main/java/org/dependencytrack/commonutil/VulnerabilityUtil.java +++ b/commons/src/main/java/org/dependencytrack/commonutil/VulnerabilityUtil.java @@ -22,68 +22,11 @@ import org.dependencytrack.common.model.Severity; import java.math.BigDecimal; -import java.security.SecureRandom; -import java.util.Random; public final class VulnerabilityUtil { - public static final SecureRandom DEFAULT_NUMBER_GENERATOR = new SecureRandom(); - public static final char[] DEFAULT_ALPHABET = "0123456789abcdefghijklmnopqrstuvwxyz".toCharArray(); - private VulnerabilityUtil() { } - /** - * Returns the severity based on the numerical CVSS score. CVSSv2 and CVSSv3 have - * slightly different ranges with CVSSv3 introducing critical severity whereas - * CVSSv2 only has high, medium, and low. - * - * This method properly accounts for vulnerabilities that may have only a CVSSv2 - * score. If both scores are available, it will return the CVSSv3 severity. - * @return the severity of the vulnerability - * @since 3.1.0 - */ - public static Severity getSeverity(final Object cvssV2BaseScore, final Object cvssV3BaseScore) { - if (cvssV3BaseScore instanceof BigDecimal) { - return normalizedCvssV3Score(((BigDecimal)cvssV3BaseScore).doubleValue()); - } else if (cvssV2BaseScore instanceof BigDecimal) { - return normalizedCvssV2Score(((BigDecimal)cvssV2BaseScore).doubleValue()); - } else { - return Severity.UNASSIGNED; - } - } - - /** - * Returns the severity based on the numerical CVSS score. CVSSv2 and CVSSv3 have - * slightly different ranges with CVSSv3 introducing critical severity whereas - * CVSSv2 only has high, medium, and low. - * - * This method properly accounts for vulnerabilities that may have only a CVSSv2 - * score. If both scores are available, it will return the CVSSv3 severity. - * @return the severity of the vulnerability - * @since 3.2.1 - */ - public static Severity getSeverity(final Object severity, final Object cvssV2BaseScore, final Object cvssV3BaseScore) { - if (severity instanceof String) { - final String s = (String)severity; - if (s.equalsIgnoreCase(Severity.CRITICAL.name())) { - return Severity.CRITICAL; - } else if (s.equalsIgnoreCase(Severity.HIGH.name())) { - return Severity.HIGH; - } else if (s.equalsIgnoreCase(Severity.MEDIUM.name())) { - return Severity.MEDIUM; - } else if (s.equalsIgnoreCase(Severity.LOW.name())) { - return Severity.LOW; - } else if (s.equalsIgnoreCase(Severity.INFO.name())) { - return Severity.INFO; - } - } else if (severity instanceof Severity) { - return (Severity)severity; - } else { - return getSeverity(cvssV2BaseScore, cvssV3BaseScore); - } - return Severity.UNASSIGNED; - } - /** * Returns the severity based on the numerical CVSS score. CVSSv2 and CVSSv3 have * slightly different ranges with CVSSv3 introducing critical severity whereas @@ -140,36 +83,6 @@ public static Severity normalizedCvssV3Score(final double score) { } } - /** - * Generates a random ODT vulnerability identifier, based on the NanoId library - * - * @return A randomly generated NanoId String. - */ - public static String randomInternalId() { - final Random random = DEFAULT_NUMBER_GENERATOR; - final char[] alphabet = DEFAULT_ALPHABET; - int size = 12; - double d = 1; - - final int mask = (2 << (int) Math.floor(Math.log(alphabet.length - d) / Math.log(2))) - 1; - int step = (int) Math.ceil(1.6 * mask * size / alphabet.length); - - StringBuilder idBuilder = new StringBuilder(); - while (true) { - final byte[] bytes = new byte[step]; - random.nextBytes(bytes); - for (int i = 0; i < step; i++) { - final int alphabetIndex = bytes[i] & mask; - if (alphabetIndex < alphabet.length) { - idBuilder.append(alphabet[alphabetIndex]); - if (idBuilder.length() == size) { - return idBuilder.toString().replaceFirst("(\\p{Alnum}{4})(\\p{Alnum}{4})(\\p{Alnum}+)", "INT-$1-$2-$3"); - } - } - } - } - } - public static String trimSummary(String summary) { int MAX_LEN = 255; if (summary != null && summary.length() > 255) { @@ -178,16 +91,4 @@ public static String trimSummary(String summary) { return summary; } - public static Severity fromString(String severity) { - if (severity == null) { - return Severity.UNASSIGNED; - } - return switch (severity.toUpperCase()) { - case "CRITICAL" -> Severity.CRITICAL; - case "HIGH" -> Severity.HIGH; - case "MODERATE", "MEDIUM" -> Severity.MEDIUM; - case "LOW" -> Severity.LOW; - default -> Severity.UNASSIGNED; - }; - } } diff --git a/commons/src/test/java/org/dependencytrack/commonutil/DateUtilTest.java b/commons/src/test/java/org/dependencytrack/commonutil/DateUtilTest.java index 021270dcd9..bd1347d82d 100644 --- a/commons/src/test/java/org/dependencytrack/commonutil/DateUtilTest.java +++ b/commons/src/test/java/org/dependencytrack/commonutil/DateUtilTest.java @@ -24,7 +24,6 @@ import java.time.LocalDateTime; import java.time.Month; import java.time.ZoneId; -import java.time.ZoneOffset; import java.util.Date; public class DateUtilTest { @@ -50,9 +49,4 @@ public void testParseGMTDate() { Assertions.assertEquals(2022, localDateTime.getYear()); } - @Test - public void testToISO8601() { - Date date = Date.from(LocalDateTime.of(2019, Month.JANUARY, 31, 15, 30, 12).toInstant(ZoneOffset.UTC)); - Assertions.assertEquals("2019-01-31T15:30:12Z", DateUtil.toISO8601(date)); - } } diff --git a/docker-compose.yml b/docker-compose.yml index 187110b0b4..331e19befa 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -15,33 +15,6 @@ # SPDX-License-Identifier: Apache-2.0 # Copyright (c) OWASP Foundation. All Rights Reserved. services: - notification-publisher: - image: ghcr.io/dependencytrack/hyades-notification-publisher:snapshot - depends_on: - postgres: - condition: service_healthy - redpanda: - condition: service_healthy - initializer: - condition: service_completed_successfully - secret-init: - condition: service_completed_successfully - environment: - KAFKA_BOOTSTRAP_SERVERS: "dt-redpanda:29092" - QUARKUS_DATASOURCE_JDBC_URL: "jdbc:postgresql://dt-postgres:5432/dtrack" - QUARKUS_DATASOURCE_USERNAME: "dtrack" - QUARKUS_DATASOURCE_PASSWORD: "dtrack" - SECRET_KEY_PATH: "/var/run/secrets/secret.key" - ports: - # Dynamic host port binding to allow for scaling of the service. - # Scaling with Compose doesn't work when assigning static host ports. - - "8090" - profiles: - - demo - volumes: - - "secret-data:/var/run/secrets:ro" - restart: unless-stopped - repo-meta-analyzer: image: ghcr.io/dependencytrack/hyades-repository-meta-analyzer:snapshot depends_on: @@ -234,8 +207,6 @@ services: environment: REDPANDA_BROKERS: "dt-redpanda:29092" # KAFKA_TOPIC_PREFIX: "" - # NOTIFICATION_TOPICS_PARTITIONS: "3" - # NOTIFICATION_TOPICS_RETENTION_MS: "43200000" # 12h # REPO_META_ANALYSIS_TOPICS_PARTITIONS: "3" # REPO_META_ANALYSIS_TOPICS_RETENTION_MS: "43200000" # 12h # VULN_ANALYSIS_TOPICS_PARTITIONS: "3" @@ -262,34 +233,6 @@ services: protobuf: enabled: true mappings: - - topicName: dtrack.notification.analyzer - valueProtoType: org.dependencytrack.notification.v1.Notification - - topicName: dtrack.notification.bom - valueProtoType: org.dependencytrack.notification.v1.Notification - - topicName: dtrack.notification.configuration - valueProtoType: org.dependencytrack.notification.v1.Notification - - topicName: dtrack.notification.datasource-mirroring - valueProtoType: org.dependencytrack.notification.v1.Notification - - topicName: dtrack.notification.file-system - valueProtoType: org.dependencytrack.notification.v1.Notification - - topicName: dtrack.notification.integration - valueProtoType: org.dependencytrack.notification.v1.Notification - - topicName: dtrack.notification.new-vulnerability - valueProtoType: org.dependencytrack.notification.v1.Notification - - topicName: dtrack.notification.new-vulnerable-dependency - valueProtoType: org.dependencytrack.notification.v1.Notification - - topicName: dtrack.notification.policy-violation - valueProtoType: org.dependencytrack.notification.v1.Notification - - topicName: dtrack.notification.project-audit-change - valueProtoType: org.dependencytrack.notification.v1.Notification - - topicName: dtrack.notification.project-created - valueProtoType: org.dependencytrack.notification.v1.Notification - - topicName: dtrack.notification.project-vuln-analysis-complete - valueProtoType: org.dependencytrack.notification.v1.Notification - - topicName: dtrack.notification.repository - valueProtoType: org.dependencytrack.notification.v1.Notification - - topicName: dtrack.notification.vex - valueProtoType: org.dependencytrack.notification.v1.Notification - topicName: dtrack.repo-meta-analysis.component valueProtoType: org.dependencytrack.repometaanalysis.v1.AnalysisCommand - topicName: dtrack.repo-meta-analysis.result @@ -305,10 +248,6 @@ services: valueProtoType: org.dependencytrack.vulnanalysis.v1.ScanResult - topicName: dtrack.vuln-analysis.result.processed valueProtoType: org.dependencytrack.vulnanalysis.v1.ScanResult - - topicName: dtrack.vulnerability - valueProtoType: org.cyclonedx.v1_6.Bom - - topicName: dtrack.notification.user - valueProtoType: org.dependencytrack.notification.v1.Notification fileSystem: enabled: true paths: ["/etc/protos"] diff --git a/docs/architecture/index.md b/docs/architecture/index.md index 048f638213..87b4f29249 100644 --- a/docs/architecture/index.md +++ b/docs/architecture/index.md @@ -21,7 +21,3 @@ TBD ### Repository Metadata Analyzer TBD - -### Notification Publisher - -TBD diff --git a/docs/getting-started/upgrading.md b/docs/getting-started/upgrading.md index c5419a9321..4671944936 100644 --- a/docs/getting-started/upgrading.md +++ b/docs/getting-started/upgrading.md @@ -1,6 +1,21 @@ ### Upgrading to 0.7.0 * The minimum supported PostgreSQL version has been raised from 13 to 14 ([hyades/#1910]). +* **The notification-publisher service has been removed**. Publishing of notifications + is now performed by the apiserver. +* The way notification publishers and alerts are configured behind the scenes has changed. + * Existing configuration is migrated during the upgrade on a best-effort basis. + * To prevent partially migrated alert configurations from taking effect, + **all alerts are disabled during the upgrade**. + * You must manually review and re-enable them after the upgrade. + * Refer to the [notification publishers documentation](../usage/notifications/publishers.md) for guidance. +* **Notifications are no longer published to Kafka by default**. Going forward, you must configure + alerts explicitly, and use the new [Kafka publisher](../usage/notifications/publishers.md#kafka) + if you want to receive notifications via Kafka. +* Email server configuration in the UI has moved from `Administration → General → Email` to + `Administration → Notifications → Publishers → Email`. **Previously configured email settings + are discarded** during the upgrade, and you'll need to reconfigure it if you rely on notifications + being sent via email. * Various database configurations in the API server have been deprecated: | Before | After | @@ -15,9 +30,11 @@ | `alpine.database.pool.idle.timeout` | `dt.datasource.pool.idle-timeout-ms` | | `alpine.database.pool.max.lifetime` | `dt.datasource.pool.max-lifetime-ms` | -* For this version, the `dt.datasource.*` configurations default to their `alpine.database.*` - counterparts. Existing deployments should continue to function without changes. - However, support for `alpine.database.*` configs will be removed prior to the GA release. + * For this version, the `dt.datasource.*` configurations default to their `alpine.database.*` + counterparts. Existing deployments should continue to function without changes. + However, support for `alpine.database.*` configs will be removed prior to the GA release. + * The new datasource configuration mechanism is documented [here](../operations/configuration/datasources.md). + * The following init task configurations have been removed and replaced with `init.tasks.datasource.name`: * `init.tasks.database.url` * `init.tasks.database.username` diff --git a/docs/usage/notifications/images/email-publisher-alert-config.png b/docs/usage/notifications/images/email-publisher-alert-config.png new file mode 100644 index 0000000000..1766f98323 Binary files /dev/null and b/docs/usage/notifications/images/email-publisher-alert-config.png differ diff --git a/docs/usage/notifications/images/email-publisher-global-config.png b/docs/usage/notifications/images/email-publisher-global-config.png new file mode 100644 index 0000000000..818a7a8b59 Binary files /dev/null and b/docs/usage/notifications/images/email-publisher-global-config.png differ diff --git a/docs/usage/notifications/images/jira-publisher-alert-config.png b/docs/usage/notifications/images/jira-publisher-alert-config.png new file mode 100644 index 0000000000..dfa96daf38 Binary files /dev/null and b/docs/usage/notifications/images/jira-publisher-alert-config.png differ diff --git a/docs/usage/notifications/images/jira-publisher-global-config.png b/docs/usage/notifications/images/jira-publisher-global-config.png new file mode 100644 index 0000000000..4c54124c6d Binary files /dev/null and b/docs/usage/notifications/images/jira-publisher-global-config.png differ diff --git a/docs/usage/notifications/images/kafka-publisher-alert-config.png b/docs/usage/notifications/images/kafka-publisher-alert-config.png new file mode 100644 index 0000000000..ecad8b1368 Binary files /dev/null and b/docs/usage/notifications/images/kafka-publisher-alert-config.png differ diff --git a/docs/usage/notifications/images/kafka-publisher-global-config.png b/docs/usage/notifications/images/kafka-publisher-global-config.png new file mode 100644 index 0000000000..dfeb67c5bf Binary files /dev/null and b/docs/usage/notifications/images/kafka-publisher-global-config.png differ diff --git a/docs/usage/notifications/overview.md b/docs/usage/notifications/overview.md new file mode 100644 index 0000000000..b9148a6e13 --- /dev/null +++ b/docs/usage/notifications/overview.md @@ -0,0 +1,71 @@ +## Introduction + +Dependency-Track includes a robust and configurable notification framework, +capable of alerting users or systems about the occurrences of various events +in the platform. + +## Concepts + +### Alerts + +Alerts, a.k.a. *notification rules*, are configurations that specify + +### Publishers + +Publishers are software components that send notifications emitted by the platform +to a destination system. Dependency-Track supports multiple publishers, ranging from +email to Webhook. Refer to [Publishers](publishers.md) for details. + +### Templates + +Templates define how the platform-internal representation of notifications +(see [Notification Schema](../../reference/schemas/notification.md)) is transformed +to match the expectation of notification recipients. + +While each [publisher](#publishers) ships with a default template, administrators +can also configure custom templates. Refer to [Templating](templating.md) for details. + +### Levels + +Notifications can have one of three possible levels: + +* Informational +* Warning +* Error + +These levels behave similar to logging levels, in that they allow [alerts](#alerts) +to define the verbosity of notifications being sent: + +* Configuring an alert for level *Informational* will match notifications of level *Informational*, *Warning*, and *Error*. +* Configuring an alert for level *Warning* will match notifications of level *Warning* and *Error*. +* Configuring an alert for level *Error* will only match notifications of level *Error*. + +### Scopes + +Notifications are emitted for different *scopes*. A scope broadly categorises the *subject* +of a notification. + +* **SYSTEM**: Informs about system-level events, such as users being created, or integrations failing. +* **PORTFOLIO**: Informs about portfolio-level events, such as BOM uploads, or newly identified vulnerabilities. + +### Groups + +A group is a granular classification of notification subjects within a [scope](#scopes). + +| Scope | Group | Level(s) | Description | +|-----------|-------------------------------|---------------|-----------------------------------------------------------------------------------------------------------------------------------| +| SYSTEM | ANALYZER | (Any) | Notifications generated as a result of interacting with an external source of vulnerability intelligence | +| SYSTEM | DATASOURCE_MIRRORING | (Any) | Notifications generated when performing mirroring of one of the supported datasources such as the NVD | +| SYSTEM | FILE_SYSTEM | (Any) | Notifications generated as a result of a file system operation. These are typically only generated on error conditions | +| SYSTEM | REPOSITORY | (Any) | Notifications generated as a result of interacting with one of the supported repositories such as Maven Central, RubyGems, or NPM | +| SYSTEM | USER_CREATED | Informational | Notifications generated as a result of a user creation | +| SYSTEM | USER_DELETED | Informational | Notifications generated as a result of a user deletion | +| PORTFOLIO | NEW_VULNERABILITY | Informational | Notifications generated whenever a new vulnerability is identified | +| PORTFOLIO | NEW_VULNERABLE_DEPENDENCY | Informational | Notifications generated as a result of a vulnerable component becoming a dependency of a project | +| PORTFOLIO | GLOBAL_AUDIT_CHANGE | Informational | Notifications generated whenever an analysis or suppression state has changed on a finding from a component (global) | +| PORTFOLIO | PROJECT_AUDIT_CHANGE | Informational | Notifications generated whenever an analysis or suppression state has changed on a finding from a project | +| PORTFOLIO | BOM_CONSUMED | Informational | Notifications generated whenever a supported BOM is ingested and identified | +| PORTFOLIO | BOM_PROCESSED | Informational | Notifications generated after a supported BOM is ingested, identified, and successfully processed | +| PORTFOLIO | BOM_PROCESSING_FAILED | Error | Notifications generated whenever a BOM upload process fails | +| PORTFOLIO | BOM_VALIDATION_FAILED | Error | Notifications generated whenever an invalid BOM is uploaded | +| PORTFOLIO | POLICY_VIOLATION | Informational | Notifications generated whenever a policy violation is identified | diff --git a/docs/usage/notifications/publishers.md b/docs/usage/notifications/publishers.md new file mode 100644 index 0000000000..62a75c068b --- /dev/null +++ b/docs/usage/notifications/publishers.md @@ -0,0 +1,164 @@ +## Console + +Publishes notifications by writing them to standard output. + +This publisher is intended for testing. It does not provide any configuration options. + +## Email + +Publishes notifications as emails. [SMTP] and [SMTPS] protocols are supported. + +**Global Config** + +The global configuration defines how Dependency-Track connects to your email server. + +![Email publisher global config](images/email-publisher-global-config.png) + +**Alert Config** + +The alert configuration defines the recipients of email notifications, +as well as an optional subject prefix. + +![Email publisher alert config](images/email-publisher-alert-config.png) + +In addition to listing recipient addresses explicitly, you can also specify +one or more teams as recipients. When teams are specified, emails will be sent +to all members of those teams. + +You can mix explicit recipient addresses and teams, but you must configure +*at least one* of the two. + +## Jira + +Publishes notifications by creating issues in an Atlassian Jira instance. + +**Global Config** + +The global configuration defines how Dependency-Track connects to your Jira server. + +![Jira publisher global config](images/jira-publisher-global-config.png) + +**Alert Config** + +The alert configuration defines properties of the issues to create. + +![Jira publisher alert config](images/jira-publisher-alert-config.png) + +!!! note + Selecting teams as recipients has no effect for this publisher. + +## Kafka + +Publishes notifications by emitting records to an Apache Kafka cluster. + +**Global Config** + +The global configuration defines how Dependency-Track connects to your Kafka cluster. + +![Kafka publisher global config](images/kafka-publisher-global-config.png) + +!!! note "Configuring TLS" + When enabling TLS, you **must** provide the certificate of the certificate authority (CA) + that signed the certificate used by your Kafka brokers. It **must** be provided in + [PEM] format and **must not** be encrypted, i.e. **not** password-protected. + +!!! note "Configuring mTLS" + When enabling [mutual TLS], you **must** provide a client certificate and key in [PEM] format. + Both **must not** be encrypted. The client key **must** be a [managed secret](../secret-management/overview.md). + +!!! note "Default Producer Configs" + Dependency-Track applies the following [configs](https://kafka.apache.org/41/configuration/producer-configs/) + to the underlying Kafka producer by default: + + * [`compression.type`](https://kafka.apache.org/41/configuration/producer-configs/#producerconfigs_compression.type): `snappy` + * [`enable.idempotence`](https://kafka.apache.org/41/configuration/producer-configs/#producerconfigs_enable.idempotence): `true` + +**Alert Config** + +The alert config defines the destination and format of Kafka records emitted by the publisher. + +![Kafka publisher alert config](images/kafka-publisher-alert-config.png) + +!!! note + Selecting teams as recipients has no effect for this publisher. + +!!! tip "Protobuf" + You are strongly encouraged to publish notifications in Protobuf format. + Dependency-Track ensures that changes to the Protobuf schema are backward-compatible, + which is crucial when notifications are stored in a durable log like Kafka. + +!!! warning "Templating" + The Kafka publisher does not ship with a default template, since it's meant to + be used with Protobuf. If you prefer a different payload format, you must configure + a custom template first. Refer to [Templating](templating.md) for details. + +!!! tip "Record Keys" + If a notification's subject is a project, as is the case for groups like `BOM_CONSUMED`, + `NEW_VULNERABILITY` etc., then the Kafka record key will be the project's UUID. + If the notification's subject is *not* a project, the key will be `null`. + +## Mattermost + +Publishes notifications as Mattermost messages. + +**Alert Config** + +The alert config defines the destination of Mattermost messages. + +This should be the URL of an [incoming Webhook](https://docs.mattermost.com/integrations-guide/incoming-webhooks.html). + +!!! note + Selecting teams as recipients has no effect for this publisher. + +## Microsoft Teams + +Publishes notifications as Microsoft Teams messages. + +**Alert Config** + +The alert config defines the destination of Microsoft Teams messages. + +This should be the URL of an [incoming Webhook](https://learn.microsoft.com/en-us/microsoftteams/platform/webhooks-and-connectors/how-to/add-incoming-webhook). + +!!! note + Selecting teams as recipients has no effect for this publisher. + +## Slack + +Publishes notifications as Slack messages. + +**Alert Config** + +The alert config defines the destination of Microsoft Teams messages. + +This should be the URL of an [incoming Webhook](https://docs.slack.dev/messaging/sending-messages-using-incoming-webhooks/). + +!!! note + Selecting teams as recipients has no effect for this publisher. + +## Webex + +Publishes notifications as Cisco Webex messages. + +**Alert Config** + +The alert config defines the destination of Microsoft Teams messages. + +This should be the URL of an [incoming Webhook](https://apphub.webex.com/applications/incoming-webhooks-cisco-systems-38054-23307-75252). + +!!! note + Selecting teams as recipients has no effect for this publisher. + +## Webhook + +Publishes notifications as Webhooks. + +**Alert Config** + +!!! note + Selecting teams as recipients has no effect for this publisher. + +[PEM]: https://en.wikipedia.org/wiki/Privacy-Enhanced_Mail +[SMTP]: https://en.wikipedia.org/wiki/Simple_Mail_Transfer_Protocol +[SMTPS]: https://en.wikipedia.org/wiki/SMTPS +[mutual TLS]: https://en.wikipedia.org/wiki/Mutual_authentication#mTLS \ No newline at end of file diff --git a/docs/usage/notifications/templating.md b/docs/usage/notifications/templating.md new file mode 100644 index 0000000000..30404ce4c5 --- /dev/null +++ b/docs/usage/notifications/templating.md @@ -0,0 +1 @@ +TODO \ No newline at end of file diff --git a/e2e/src/main/java/org/dependencytrack/apiserver/ApiServerClient.java b/e2e/src/main/java/org/dependencytrack/apiserver/ApiServerClient.java index dab3373b7c..3376ba39e3 100644 --- a/e2e/src/main/java/org/dependencytrack/apiserver/ApiServerClient.java +++ b/e2e/src/main/java/org/dependencytrack/apiserver/ApiServerClient.java @@ -30,151 +30,156 @@ import jakarta.ws.rs.core.MediaType; import org.dependencytrack.apiserver.model.Analysis; import org.dependencytrack.apiserver.model.ApiKey; -import org.dependencytrack.apiserver.model.EventProcessingResponse; import org.dependencytrack.apiserver.model.BomUploadRequest; -import org.dependencytrack.apiserver.model.ConfigProperty; import org.dependencytrack.apiserver.model.CreateNotificationRuleRequest; import org.dependencytrack.apiserver.model.CreateTeamRequest; import org.dependencytrack.apiserver.model.CreateVulnerabilityRequest; +import org.dependencytrack.apiserver.model.EventProcessingResponse; import org.dependencytrack.apiserver.model.Finding; import org.dependencytrack.apiserver.model.NotificationPublisher; import org.dependencytrack.apiserver.model.NotificationRule; import org.dependencytrack.apiserver.model.Project; import org.dependencytrack.apiserver.model.Team; +import org.dependencytrack.apiserver.model.UpdateExtensionConfigRequest; import org.dependencytrack.apiserver.model.UpdateNotificationRuleRequest; import org.dependencytrack.apiserver.model.VulnerabilityPolicy; import org.dependencytrack.apiserver.model.WorkflowState; import org.dependencytrack.apiserver.model.WorkflowTokenResponse; -import java.util.Collection; import java.util.List; import java.util.UUID; -@Path("/api/v1") +@Path("/api") public interface ApiServerClient { @POST - @Path("/user/forceChangePassword") + @Path("/v1/user/forceChangePassword") @Produces(MediaType.WILDCARD) @Consumes(MediaType.APPLICATION_FORM_URLENCODED) - void forcePasswordChange(@FormParam("username") final String username, - @FormParam("password") final String password, - @FormParam("newPassword") final String newPassword, - @FormParam("confirmPassword") final String confirmPassword); + void forcePasswordChange( + @FormParam("username") String username, + @FormParam("password") String password, + @FormParam("newPassword") String newPassword, + @FormParam("confirmPassword") String confirmPassword); @POST - @Path("/user/login") + @Path("/v1/user/login") @Produces(MediaType.WILDCARD) @Consumes(MediaType.APPLICATION_FORM_URLENCODED) - String login(@FormParam("username") final String username, - @FormParam("password") final String password); + String login( + @FormParam("username") String username, + @FormParam("password") String password); @PUT - @Path("/team") + @Path("/v1/team") @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) - Team createTeam(final CreateTeamRequest request); + Team createTeam(CreateTeamRequest request); @PUT - @Path("/team/{uuid}/key") + @Path("/v1/team/{uuid}/key") @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.WILDCARD) - ApiKey createApiKey(@PathParam("uuid") final UUID teamUuid); + ApiKey createApiKey(@PathParam("uuid") UUID teamUuid); @POST - @Path("/permission/{permission}/team/{uuid}") + @Path("/v1/permission/{permission}/team/{uuid}") @Produces(MediaType.WILDCARD) @Consumes(MediaType.APPLICATION_JSON) - Team addPermissionToTeam(@PathParam("uuid") final UUID teamUuid, - @PathParam("permission") final String permission); + Team addPermissionToTeam( + @PathParam("uuid") UUID teamUuid, + @PathParam("permission") String permission); @PUT - @Path("/bom") + @Path("/v1/bom") @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) - WorkflowTokenResponse uploadBom(final BomUploadRequest request); + WorkflowTokenResponse uploadBom(BomUploadRequest request); @GET - @Path("/event/token/{token}") + @Path("/v1/event/token/{token}") @Produces(MediaType.WILDCARD) @Consumes(MediaType.WILDCARD) - EventProcessingResponse isEventBeingProcessed(@PathParam("token") final String token); + EventProcessingResponse isEventBeingProcessed(@PathParam("token") String token); @PUT - @Path("/vulnerability") + @Path("/v1/vulnerability") @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) - void createVulnerability(final CreateVulnerabilityRequest request); + void createVulnerability(CreateVulnerabilityRequest request); @GET - @Path("/finding/project/{uuid}") + @Path("/v1/finding/project/{uuid}") @Produces(MediaType.WILDCARD) @Consumes(MediaType.APPLICATION_JSON) - List getFindings(@PathParam("uuid") final UUID projectUuid, @QueryParam("suppressed") final boolean includeSuppressed); + List getFindings( + @PathParam("uuid") UUID projectUuid, + @QueryParam("suppressed") boolean includeSuppressed); @GET - @Path("/project/lookup") + @Path("/v1/project/lookup") @Produces(MediaType.WILDCARD) @Consumes(MediaType.APPLICATION_JSON) - Project lookupProject(@QueryParam("name") final String name, @QueryParam("version") final String version); + Project lookupProject( + @QueryParam("name") String name, + @QueryParam("version") String version); @GET - @Path("/notification/publisher") + @Path("/v1/notification/publisher") @Produces(MediaType.WILDCARD) @Consumes(MediaType.APPLICATION_JSON) List getAllNotificationPublishers(); @PUT - @Path("/notification/rule") - @Produces(MediaType.APPLICATION_JSON) - @Consumes(MediaType.APPLICATION_JSON) - NotificationRule createNotificationRule(final CreateNotificationRuleRequest request); - - @POST - @Path("/notification/rule") - @Produces(MediaType.APPLICATION_JSON) - @Consumes(MediaType.APPLICATION_JSON) - NotificationRule updateNotificationRule(final UpdateNotificationRuleRequest request); - - @POST - @Path("/configProperty") + @Path("/v1/notification/rule") @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) - ConfigProperty updateConfigProperty(final ConfigProperty configProperty); + NotificationRule createNotificationRule(CreateNotificationRuleRequest request); @POST - @Path("/configProperty/aggregate") + @Path("/v1/notification/rule") @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) - List updateConfigProperties(final Collection configProperties); + NotificationRule updateNotificationRule(UpdateNotificationRuleRequest request); @GET - @Path("/policy/vulnerability") + @Path("/v1/policy/vulnerability") @Produces(MediaType.WILDCARD) @Consumes(MediaType.APPLICATION_JSON) List getAllVulnerabilityPolicies(); @POST - @Path("/policy/vulnerability/bundle/sync") + @Path("/v1/policy/vulnerability/bundle/sync") @Produces(MediaType.WILDCARD) @Consumes(MediaType.APPLICATION_JSON) WorkflowTokenResponse triggerVulnerabilityPolicyBundleSync(); @GET - @Path("/analysis") + @Path("/v1/analysis") @Produces(MediaType.WILDCARD) @Consumes(MediaType.APPLICATION_JSON) - Analysis getAnalysis(@QueryParam("project") final UUID projectUuid, @QueryParam("component") final UUID componentUuid, - @QueryParam("vulnerability") final UUID vulnUuid); + Analysis getAnalysis( + @QueryParam("project") UUID projectUuid, + @QueryParam("component") UUID componentUuid, + @QueryParam("vulnerability") UUID vulnUuid); @POST - @Path("/finding/project/{uuid}/analyze") + @Path("/v1/finding/project/{uuid}/analyze") @Produces(MediaType.WILDCARD) @Consumes(MediaType.APPLICATION_JSON) - WorkflowTokenResponse analyzeProject(@PathParam("uuid") final UUID projectUuid); + WorkflowTokenResponse analyzeProject(@PathParam("uuid") UUID projectUuid); @GET - @Path("/workflow/token/{token}/status") - List getWorkflowStatus(@PathParam("token") final String token); + @Path("/v1/workflow/token/{token}/status") + List getWorkflowStatus(@PathParam("token") String token); + + @PUT + @Path("/v2/extension-points/{extensionPoint}/extensions/{extension}/config") + @Produces(MediaType.WILDCARD) + @Consumes(MediaType.APPLICATION_JSON) + void updateExtensionConfig( + @PathParam("extensionPoint") String extensionPoint, + @PathParam("extension") String extension, + UpdateExtensionConfigRequest request); } diff --git a/e2e/src/main/java/org/dependencytrack/apiserver/model/ConfigProperty.java b/e2e/src/main/java/org/dependencytrack/apiserver/model/ConfigProperty.java deleted file mode 100644 index 7789525d84..0000000000 --- a/e2e/src/main/java/org/dependencytrack/apiserver/model/ConfigProperty.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * 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.apiserver.model; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; - -@JsonIgnoreProperties(ignoreUnknown = true) -public record ConfigProperty(String groupName, String propertyName, String propertyValue) { -} diff --git a/commons-persistence/src/main/java/org/dependencytrack/persistence/model/NotificationScope.java b/e2e/src/main/java/org/dependencytrack/apiserver/model/UpdateExtensionConfigRequest.java similarity index 83% rename from commons-persistence/src/main/java/org/dependencytrack/persistence/model/NotificationScope.java rename to e2e/src/main/java/org/dependencytrack/apiserver/model/UpdateExtensionConfigRequest.java index 4d50db3b89..f609bf550e 100644 --- a/commons-persistence/src/main/java/org/dependencytrack/persistence/model/NotificationScope.java +++ b/e2e/src/main/java/org/dependencytrack/apiserver/model/UpdateExtensionConfigRequest.java @@ -16,9 +16,9 @@ * SPDX-License-Identifier: Apache-2.0 * Copyright (c) OWASP Foundation. All Rights Reserved. */ -package org.dependencytrack.persistence.model; +package org.dependencytrack.apiserver.model; -public enum NotificationScope { - SYSTEM, - PORTFOLIO -} \ No newline at end of file +import java.util.Map; + +public record UpdateExtensionConfigRequest(Map config) { +} diff --git a/e2e/src/test/java/org/dependencytrack/e2e/AbstractE2ET.java b/e2e/src/test/java/org/dependencytrack/e2e/AbstractE2ET.java index 1d3c4fb8ba..6cdc047086 100644 --- a/e2e/src/test/java/org/dependencytrack/e2e/AbstractE2ET.java +++ b/e2e/src/test/java/org/dependencytrack/e2e/AbstractE2ET.java @@ -58,8 +58,6 @@ public class AbstractE2ET { protected static DockerImageName REDPANDA_IMAGE = DockerImageName.parse("docker.redpanda.com/redpandadata/redpanda:v25.3.4"); protected static DockerImageName API_SERVER_IMAGE = DockerImageName.parse("ghcr.io/dependencytrack/hyades-apiserver") .withTag(Optional.ofNullable(System.getenv("APISERVER_VERSION")).orElse("snapshot")); - protected static DockerImageName NOTIFICATION_PUBLISHER_IMAGE = DockerImageName.parse("ghcr.io/dependencytrack/hyades-notification-publisher") - .withTag(Optional.ofNullable(System.getenv("HYADES_VERSION")).orElse("snapshot")); protected static DockerImageName REPO_META_ANALYZER_IMAGE = DockerImageName.parse("ghcr.io/dependencytrack/hyades-repository-meta-analyzer") .withTag(Optional.ofNullable(System.getenv("HYADES_VERSION")).orElse("snapshot")); protected static DockerImageName VULN_ANALYZER_IMAGE = DockerImageName.parse("ghcr.io/dependencytrack/hyades-vulnerability-analyzer") @@ -70,7 +68,6 @@ public class AbstractE2ET { protected PostgreSQLContainer postgresContainer; protected GenericContainer redpandaContainer; protected GenericContainer apiServerContainer; - protected GenericContainer notificationPublisherContainer; protected GenericContainer repoMetaAnalyzerContainer; protected GenericContainer vulnAnalyzerContainer; protected ApiServerClient apiServerClient; @@ -87,12 +84,10 @@ void beforeEach() throws Exception { runInitializer(); apiServerContainer = createApiServerContainer(); - notificationPublisherContainer = createNotificationPublisherContainer(); repoMetaAnalyzerContainer = createRepoMetaAnalyzerContainer(); vulnAnalyzerContainer = createVulnAnalyzerContainer(); deepStart( apiServerContainer, - notificationPublisherContainer, repoMetaAnalyzerContainer, vulnAnalyzerContainer ).join(); @@ -158,6 +153,7 @@ private void runInitializer() { .withEnv("DT_DATASOURCE_PASSWORD", "dtrack") .withEnv("INIT_TASKS_ENABLED", "true") .withEnv("INIT_AND_EXIT", "true") + .withEnv("ALPINE_BCRYPT_ROUNDS", "4") .withLogConsumer(new Slf4jLogConsumer(LoggerFactory.getLogger("initializer"))) .withStartupCheckStrategy(new OneShotStartupCheckStrategy()) .withNetwork(internalNetwork) @@ -174,6 +170,7 @@ private GenericContainer createApiServerContainer() { .withEnv("DT_DATASOURCE_PASSWORD", "dtrack") .withEnv("KAFKA_BOOTSTRAP_SERVERS", "redpanda:29092") .withEnv("INIT_TASKS_ENABLED", "false") + .withEnv("ALPINE_BCRYPT_ROUNDS", "4") .withEnv("ALPINE_SECRET_KEY_PATH", "/var/run/secrets/secret.key") .withCopyFileToContainer( MountableFile.forHostPath(secretKeyPath, 444), @@ -191,31 +188,6 @@ private GenericContainer createApiServerContainer() { protected void customizeApiServerContainer(final GenericContainer container) { } - @SuppressWarnings("resource") - private GenericContainer createNotificationPublisherContainer() { - final var container = new GenericContainer<>(NOTIFICATION_PUBLISHER_IMAGE) - .withImagePullPolicy("local".equals(NOTIFICATION_PUBLISHER_IMAGE.getVersionPart()) ? PullPolicy.defaultPolicy() : PullPolicy.alwaysPull()) - .withEnv("JAVA_OPTS", "-Xmx256m") - .withEnv("KAFKA_BOOTSTRAP_SERVERS", "redpanda:29092") - .withEnv("QUARKUS_DATASOURCE_JDBC_URL", "jdbc:postgresql://postgres:5432/dtrack") - .withEnv("QUARKUS_DATASOURCE_USERNAME", "dtrack") - .withEnv("QUARKUS_DATASOURCE_PASSWORD", "dtrack") - .withEnv("SECRET_KEY_PATH", "/var/run/secrets/secret.key") - .withCopyFileToContainer( - MountableFile.forHostPath(secretKeyPath, 444), - "/var/run/secrets/secret.key" - ) - .withLogConsumer(new Slf4jLogConsumer(LoggerFactory.getLogger("notification-publisher"))) - .withNetworkAliases("notification-publisher") - .withNetwork(internalNetwork) - .withStartupAttempts(3); - customizeNotificationPublisherContainer(container); - return container; - } - - protected void customizeNotificationPublisherContainer(final GenericContainer container) { - } - @SuppressWarnings("resource") private GenericContainer createRepoMetaAnalyzerContainer() { final var container = new GenericContainer<>(REPO_META_ANALYZER_IMAGE) @@ -289,9 +261,11 @@ private ApiServerClient initializeApiServerClient() { "POLICY_MANAGEMENT", "PORTFOLIO_MANAGEMENT", "PROJECT_CREATION_UPLOAD", + "SECRET_MANAGEMENT_CREATE", "SYSTEM_CONFIGURATION", "VIEW_PORTFOLIO", "VIEW_VULNERABILITY", + "VULNERABILITY_ANALYSIS", "VULNERABILITY_MANAGEMENT" )) { client.addPermissionToTeam(team.uuid(), permission); @@ -309,7 +283,6 @@ void afterEach() { Optional.ofNullable(vulnAnalyzerContainer).ifPresent(GenericContainer::stop); Optional.ofNullable(repoMetaAnalyzerContainer).ifPresent(GenericContainer::stop); - Optional.ofNullable(notificationPublisherContainer).ifPresent(GenericContainer::stop); Optional.ofNullable(apiServerContainer).ifPresent(GenericContainer::stop); Optional.ofNullable(redpandaContainer).ifPresent(GenericContainer::stop); Optional.ofNullable(postgresContainer).ifPresent(GenericContainer::stop); diff --git a/e2e/src/test/java/org/dependencytrack/e2e/BomProcessedNotificationDelayedE2ET.java b/e2e/src/test/java/org/dependencytrack/e2e/BomProcessedNotificationDelayedE2ET.java index fbcd757845..ac1c873f34 100644 --- a/e2e/src/test/java/org/dependencytrack/e2e/BomProcessedNotificationDelayedE2ET.java +++ b/e2e/src/test/java/org/dependencytrack/e2e/BomProcessedNotificationDelayedE2ET.java @@ -84,7 +84,7 @@ void test() throws Exception { // Find the webhook notification publisher. final NotificationPublisher webhookPublisher = publishers.stream() - .filter(publisher -> publisher.name().equals("Outbound Webhook")) + .filter(publisher -> publisher.name().equals("Webhook")) .findAny() .orElseThrow(() -> new AssertionError("Unable to find webhook notification publisher")); @@ -92,9 +92,9 @@ void test() throws Exception { final NotificationRule webhookRule = apiServerClient.createNotificationRule(new CreateNotificationRuleRequest( "foo", "PORTFOLIO", "INFORMATIONAL", new CreateNotificationRuleRequest.Publisher(webhookPublisher.uuid()))); apiServerClient.updateNotificationRule(new UpdateNotificationRuleRequest(webhookRule.uuid(), webhookRule.name(), true, - "INFORMATIONAL", Set.of("BOM_PROCESSED", "PROJECT_VULN_ANALYSIS_COMPLETE"), """ + "INFORMATIONAL", Set.of("BOM_PROCESSED", "PROJECT_VULN_ANALYSIS_COMPLETE"), /* language=JSON */ """ { - "destination": "http://host.testcontainers.internal:%d/notification" + "destinationUrl": "http://host.testcontainers.internal:%d/notification" } """.formatted(wireMock.getPort()))); diff --git a/e2e/src/test/java/org/dependencytrack/e2e/BomUploadProcessingE2ET.java b/e2e/src/test/java/org/dependencytrack/e2e/BomUploadProcessingE2ET.java index a2dbb2f665..fae6f48bf7 100644 --- a/e2e/src/test/java/org/dependencytrack/e2e/BomUploadProcessingE2ET.java +++ b/e2e/src/test/java/org/dependencytrack/e2e/BomUploadProcessingE2ET.java @@ -21,18 +21,19 @@ import com.github.tomakehurst.wiremock.junit5.WireMockExtension; import com.icegreen.greenmail.junit5.GreenMailExtension; import com.icegreen.greenmail.util.ServerSetup; +import jakarta.mail.internet.MimeMessage; import org.apache.commons.io.IOUtils; -import org.dependencytrack.apiserver.model.EventProcessingResponse; import org.dependencytrack.apiserver.model.BomUploadRequest; -import org.dependencytrack.apiserver.model.ConfigProperty; import org.dependencytrack.apiserver.model.CreateNotificationRuleRequest; import org.dependencytrack.apiserver.model.CreateNotificationRuleRequest.Publisher; import org.dependencytrack.apiserver.model.CreateVulnerabilityRequest; import org.dependencytrack.apiserver.model.CreateVulnerabilityRequest.AffectedComponent; +import org.dependencytrack.apiserver.model.EventProcessingResponse; import org.dependencytrack.apiserver.model.Finding; import org.dependencytrack.apiserver.model.NotificationPublisher; import org.dependencytrack.apiserver.model.NotificationRule; import org.dependencytrack.apiserver.model.Project; +import org.dependencytrack.apiserver.model.UpdateExtensionConfigRequest; import org.dependencytrack.apiserver.model.UpdateNotificationRuleRequest; import org.dependencytrack.apiserver.model.WorkflowTokenResponse; import org.junit.jupiter.api.BeforeEach; @@ -41,10 +42,10 @@ import org.testcontainers.Testcontainers; import org.testcontainers.containers.GenericContainer; -import jakarta.mail.internet.MimeMessage; import java.time.Duration; import java.util.Base64; import java.util.List; +import java.util.Map; import java.util.Set; import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; @@ -80,6 +81,14 @@ void beforeEach() throws Exception { super.beforeEach(); } + @Override + protected void customizeApiServerContainer(GenericContainer container) { + container + .withEnv("DT_NOTIFICATION_PUBLISHER_EMAIL_ALLOW_LOCAL_CONNECTIONS", "true") + .withEnv("DT_SECRET_MANAGEMENT_PROVIDER", "env") + .withEnv("DT_SECRET_EMAIL_PASSWORD", "fromPass"); + } + @Override protected void customizeVulnAnalyzerContainer(final GenericContainer container) { // Disable all scanners except the internal one. @@ -91,6 +100,18 @@ protected void customizeVulnAnalyzerContainer(final GenericContainer containe @Test void test() throws Exception { + apiServerClient.updateExtensionConfig( + "notification-publisher", + "email", + new UpdateExtensionConfigRequest( + Map.ofEntries( + Map.entry("enabled", true), + Map.entry("host", "host.testcontainers.internal"), + Map.entry("port", greenMail.getSmtp().getPort()), + Map.entry("username", "from"), + Map.entry("password", "EMAIL_PASSWORD"), + Map.entry("senderAddress", "from@localhost")))); + final List publishers = apiServerClient.getAllNotificationPublishers(); // Find the email notification publisher. @@ -101,7 +122,7 @@ void test() throws Exception { // Find the webhook notification publisher. final NotificationPublisher webhookPublisher = publishers.stream() - .filter(publisher -> publisher.name().equals("Outbound Webhook")) + .filter(publisher -> publisher.name().equals("Webhook")) .findAny() .orElseThrow(() -> new AssertionError("Unable to find webhook notification publisher")); @@ -109,29 +130,21 @@ void test() throws Exception { final NotificationRule emailRule = apiServerClient.createNotificationRule(new CreateNotificationRuleRequest( "email", "PORTFOLIO", "INFORMATIONAL", new Publisher(emailPublisher.uuid()))); apiServerClient.updateNotificationRule(new UpdateNotificationRuleRequest(emailRule.uuid(), emailRule.name(), true, - "INFORMATIONAL", Set.of("NEW_VULNERABILITY"), """ + "INFORMATIONAL", Set.of("NEW_VULNERABILITY"), /* language=JSON */ """ { - "destination": "to@localhost" + "recipientAddresses": [ + "to@localhost" + ] } """)); - // Configure email. - apiServerClient.updateConfigProperties(List.of( - new ConfigProperty("email", "smtp.enabled", "true"), - new ConfigProperty("email", "smtp.from.address", "from@localhost"), - new ConfigProperty("email", "smtp.server.hostname", "host.testcontainers.internal"), - new ConfigProperty("email", "smtp.server.port", Integer.toString(greenMail.getSmtp().getPort())), - new ConfigProperty("email", "smtp.username", "from"), - new ConfigProperty("email", "smtp.password", "fromPass") - )); - // Create a webhook alert for NEW_VULNERABILITY notifications and point it to WireMock. final NotificationRule webhookRule = apiServerClient.createNotificationRule(new CreateNotificationRuleRequest( "foo", "PORTFOLIO", "INFORMATIONAL", new Publisher(webhookPublisher.uuid()))); apiServerClient.updateNotificationRule(new UpdateNotificationRuleRequest(webhookRule.uuid(), webhookRule.name(), true, - "INFORMATIONAL", Set.of("NEW_VULNERABILITY"), """ + "INFORMATIONAL", Set.of("NEW_VULNERABILITY"), /* language=JSON */ """ { - "destination": "http://host.testcontainers.internal:%d/notification" + "destinationUrl": "http://host.testcontainers.internal:%d/notification" } """.formatted(wireMock.getPort()))); @@ -139,9 +152,9 @@ void test() throws Exception { final NotificationRule projectVulnAnalysisCompleteWebhookRule = apiServerClient.createNotificationRule(new CreateNotificationRuleRequest( "projectVulnAnalysisCompleteWebhookRule", "PORTFOLIO", "INFORMATIONAL", new Publisher(webhookPublisher.uuid()))); apiServerClient.updateNotificationRule(new UpdateNotificationRuleRequest(projectVulnAnalysisCompleteWebhookRule.uuid(), projectVulnAnalysisCompleteWebhookRule.name(), true, - "INFORMATIONAL", Set.of("PROJECT_VULN_ANALYSIS_COMPLETE"), """ + "INFORMATIONAL", Set.of("PROJECT_VULN_ANALYSIS_COMPLETE"), /* language=JSON */ """ { - "destination": "http://host.testcontainers.internal:%d/notification" + "destinationUrl": "http://host.testcontainers.internal:%d/notification" } """.formatted(wireMock.getPort()))); wireMock.stubFor(post(urlPathEqualTo("/notification")) diff --git a/e2e/src/test/java/org/dependencytrack/e2e/VulnerabilityPolicyE2ET.java b/e2e/src/test/java/org/dependencytrack/e2e/VulnerabilityPolicyE2ET.java index 17b36dce13..1b4a53ac1a 100644 --- a/e2e/src/test/java/org/dependencytrack/e2e/VulnerabilityPolicyE2ET.java +++ b/e2e/src/test/java/org/dependencytrack/e2e/VulnerabilityPolicyE2ET.java @@ -155,10 +155,7 @@ protected void customizeApiServerContainer(final GenericContainer container) .withEnv("VULNERABILITY_POLICY_S3_SECRET_KEY", minioContainer.getPassword()) .withEnv("VULNERABILITY_POLICY_S3_BUCKET_NAME", BUNDLE_BUCKET_NAME) .withEnv("VULNERABILITY_POLICY_S3_BUNDLE_NAME", BUNDLE_FILE_NAME) - // Configure policy bundle fetching to occur 5s after startup, - // and every minute from then on. - .withEnv("TASK_SCHEDULER_INITIAL_DELAY", "5000") - .withEnv("TASK_CRON_VULNERABILITY_POLICY_BUNDLE_FETCH", "* * * * *"); + .withEnv("TASK_VULNERABILITY_POLICY_FETCH_CRON", "*/10 * * * * *"); } @Override @@ -416,7 +413,7 @@ private void setUpWebhookNotifications() { // Find the webhook notification publisher. final NotificationPublisher webhookPublisher = publishers.stream() - .filter(publisher -> publisher.name().equals("Outbound Webhook")) + .filter(publisher -> publisher.name().equals("Webhook")) .findAny() .orElseThrow(() -> new AssertionError("Unable to find webhook notification publisher")); @@ -424,19 +421,19 @@ private void setUpWebhookNotifications() { final NotificationRule newVulnerabilityRule = apiServerClient.createNotificationRule(new CreateNotificationRuleRequest( "foo", "PORTFOLIO", "INFORMATIONAL", new CreateNotificationRuleRequest.Publisher(webhookPublisher.uuid()))); apiServerClient.updateNotificationRule(new UpdateNotificationRuleRequest(newVulnerabilityRule.uuid(), newVulnerabilityRule.name(), true, - "INFORMATIONAL", Set.of("NEW_VULNERABILITY"), """ + "INFORMATIONAL", Set.of("NEW_VULNERABILITY"), /* language=JSON */ """ { - "destination": "http://host.testcontainers.internal:%d/notification/newVuln" + "destinationUrl": "http://host.testcontainers.internal:%d/notification/newVuln" } """.formatted(wireMock.getPort()))); // ... and NEW_VULNERABLE_DEPENDENCY notifications final NotificationRule newVulnerableDependencyRule = apiServerClient.createNotificationRule(new CreateNotificationRuleRequest( - "foo", "PORTFOLIO", "INFORMATIONAL", new CreateNotificationRuleRequest.Publisher(webhookPublisher.uuid()))); + "bar", "PORTFOLIO", "INFORMATIONAL", new CreateNotificationRuleRequest.Publisher(webhookPublisher.uuid()))); apiServerClient.updateNotificationRule(new UpdateNotificationRuleRequest(newVulnerableDependencyRule.uuid(), newVulnerableDependencyRule.name(), true, - "INFORMATIONAL", Set.of("NEW_VULNERABLE_DEPENDENCY"), """ + "INFORMATIONAL", Set.of("NEW_VULNERABLE_DEPENDENCY"), /* language=JSON */ """ { - "destination": "http://host.testcontainers.internal:%d/notification/newVulnDependency" + "destinationUrl": "http://host.testcontainers.internal:%d/notification/newVulnDependency" } """.formatted(wireMock.getPort()))); diff --git a/mkdocs.yml b/mkdocs.yml index 4448b37209..c876e304c1 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -85,6 +85,10 @@ nav: - Policy Compliance: - Overview: usage/policy-compliance/overview.md - Expressions: usage/policy-compliance/expressions.md + - Notifications: + - Overview: usage/notifications/overview.md + - Publishers: usage/notifications/publishers.md + - Templating: usage/notifications/templating.md - Secret Management: - Overview: usage/secret-management/overview.md - Providers: usage/secret-management/providers.md diff --git a/monitoring/grafana/dashboards/notification-publisher_dashboard.json b/monitoring/grafana/dashboards/notification-publisher_dashboard.json deleted file mode 100755 index b4d408d773..0000000000 --- a/monitoring/grafana/dashboards/notification-publisher_dashboard.json +++ /dev/null @@ -1,1503 +0,0 @@ -{ - "annotations": { - "list": [ - { - "builtIn": 1, - "datasource": { - "type": "grafana", - "uid": "-- Grafana --" - }, - "enable": true, - "hide": true, - "iconColor": "rgba(0, 211, 255, 1)", - "name": "Annotations & Alerts", - "target": { - "limit": 100, - "matchAny": false, - "tags": [], - "type": "dashboard" - }, - "type": "dashboard" - } - ] - }, - "editable": true, - "fiscalYearStartMonth": 0, - "graphTooltip": 0, - "id": 10, - "links": [], - "liveNow": false, - "panels": [ - { - "collapsed": false, - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 0 - }, - "id": 104, - "panels": [], - "title": "System Cpu Usage", - "type": "row" - }, - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 15, - "gradientMode": "opacity", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "max": 1, - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "percentunit" - }, - "overrides": [] - }, - "gridPos": { - "h": 8, - "w": 24, - "x": 0, - "y": 1 - }, - "id": 52, - "options": { - "legend": { - "calcs": [ - "lastNotNull", - "max", - "min", - "mean" - ], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "multi", - "sort": "desc" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "editorMode": "code", - "expr": "system_cpu_usage{instance=~\"$instance\"}", - "legendFormat": "{{instance}}", - "range": true, - "refId": "A" - } - ], - "title": "System CPU Usage", - "type": "timeseries" - }, - { - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 9 - }, - "id": 99, - "title": "JVM($instance)", - "type": "row" - }, - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "bytes" - }, - "overrides": [] - }, - "gridPos": { - "h": 8, - "w": 24, - "x": 0, - "y": 10 - }, - "id": 97, - "options": { - "legend": { - "calcs": [ - "lastNotNull", - "max", - "mean" - ], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "editorMode": "code", - "expr": "sum(jvm_memory_used_bytes{instance=~\"$instance\", area=\"heap\"})", - "legendFormat": "__auto", - "range": true, - "refId": "A" - }, - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "editorMode": "code", - "expr": "sum(jvm_memory_used_bytes{instance=~\"$instance\", area=\"nonheap\"})", - "hide": false, - "legendFormat": "__auto", - "range": true, - "refId": "B" - }, - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "editorMode": "code", - "expr": "sum(jvm_memory_max_bytes{instance=~\"$instance\", area=\"heap\"})", - "hide": false, - "legendFormat": "__auto", - "range": true, - "refId": "C" - }, - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "editorMode": "code", - "expr": "process_memory_rss_bytes{instance=~\"$instance\"}", - "hide": false, - "legendFormat": "__auto", - "range": true, - "refId": "D" - } - ], - "title": "Memory Usage($instance)", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "bytes" - }, - "overrides": [] - }, - "gridPos": { - "h": 8, - "w": 12, - "x": 0, - "y": 18 - }, - "id": 101, - "options": { - "legend": { - "calcs": [ - "lastNotNull", - "max", - "mean" - ], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "editorMode": "code", - "expr": "sum(jvm_memory_used_bytes{instance=~\"$instance\", area=\"nonheap\"}) by (id)", - "legendFormat": "__auto", - "range": true, - "refId": "A" - } - ], - "title": "Non Heap Memory Usage($instance)", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "bytes" - }, - "overrides": [] - }, - "gridPos": { - "h": 8, - "w": 12, - "x": 12, - "y": 18 - }, - "id": 100, - "options": { - "legend": { - "calcs": [ - "lastNotNull", - "max", - "mean" - ], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "editorMode": "code", - "expr": "sum(jvm_memory_used_bytes{instance=~\"$instance\", area=\"heap\"}) by (id)", - "legendFormat": "__auto", - "range": true, - "refId": "A" - } - ], - "title": "Heap Memory Usage($instance)", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "bars", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "s" - }, - "overrides": [] - }, - "gridPos": { - "h": 8, - "w": 12, - "x": 0, - "y": 26 - }, - "id": 102, - "options": { - "legend": { - "calcs": [ - "lastNotNull", - "max", - "mean" - ], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "editorMode": "code", - "expr": "rate(jvm_gc_pause_seconds_sum{instance=~\"$instance\"}[1m]) / rate(jvm_gc_pause_seconds_count{instance=~\"$instance\"}[1m]) > 0", - "legendFormat": "__auto", - "range": true, - "refId": "A" - } - ], - "title": "Average GC Pause Duration($instance)", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "decbytes" - }, - "overrides": [] - }, - "gridPos": { - "h": 8, - "w": 12, - "x": 12, - "y": 26 - }, - "id": 105, - "options": { - "legend": { - "calcs": [ - "lastNotNull", - "max", - "mean" - ], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "editorMode": "code", - "expr": "rate(jvm_gc_memory_allocated_bytes_total{instance=~\"$instance\"}[1m])", - "legendFormat": "__auto", - "range": true, - "refId": "A" - } - ], - "title": "GC Memory Allocation Rate($instance)", - "type": "timeseries" - }, - { - "collapsed": false, - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 34 - }, - "id": 39, - "panels": [], - "title": "Kafka Streams($instance)", - "type": "row" - }, - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 15, - "gradientMode": "opacity", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "ops" - }, - "overrides": [] - }, - "gridPos": { - "h": 11, - "w": 12, - "x": 0, - "y": 35 - }, - "id": 86, - "options": { - "legend": { - "calcs": [ - "lastNotNull", - "max", - "mean" - ], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "multi", - "sort": "desc" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "editorMode": "code", - "expr": "sum(rate(kafka_consumer_fetch_manager_records_consumed_rate{instance=~\"$instance\"}[1m])) by (topic)", - "legendFormat": "__auto", - "range": true, - "refId": "A" - } - ], - "title": "Record Consumption Rate", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "description": "Number of responses received per second", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 15, - "gradientMode": "opacity", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "ops" - }, - "overrides": [] - }, - "gridPos": { - "h": 11, - "w": 12, - "x": 12, - "y": 35 - }, - "id": 110, - "options": { - "legend": { - "calcs": [ - "lastNotNull", - "max", - "mean" - ], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "multi", - "sort": "desc" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "editorMode": "code", - "expr": "sum(kafka_consumer_response_rate{instance=~\"$instance\"}) ", - "legendFormat": "__auto", - "range": true, - "refId": "A" - } - ], - "title": "Consumer Response Rate", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "description": "Total number of records successfully processed", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 15, - "gradientMode": "opacity", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "ops" - }, - "overrides": [] - }, - "gridPos": { - "h": 11, - "w": 12, - "x": 0, - "y": 46 - }, - "id": 111, - "options": { - "legend": { - "calcs": [ - "lastNotNull", - "max", - "mean" - ], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "multi", - "sort": "desc" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "editorMode": "code", - "expr": "sum(rate(pc_processed_records_total{instance=~\"$instance\"}[1m])) by (topic)", - "legendFormat": "__auto", - "range": true, - "refId": "A" - } - ], - "title": "Records successfully processed", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "description": "", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 15, - "gradientMode": "opacity", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "ops" - }, - "overrides": [] - }, - "gridPos": { - "h": 11, - "w": 12, - "x": 12, - "y": 46 - }, - "id": 112, - "options": { - "legend": { - "calcs": [ - "lastNotNull", - "max", - "mean" - ], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "multi", - "sort": "desc" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "editorMode": "code", - "expr": "sum(rate(pc_failed_records_total{instance=~\"$instance\"}[1m])) by (topic)", - "legendFormat": "__auto", - "range": true, - "refId": "A" - } - ], - "title": "Records failed to process", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "ops" - }, - "overrides": [] - }, - "gridPos": { - "h": 9, - "w": 11, - "x": 0, - "y": 57 - }, - "id": 84, - "options": { - "legend": { - "calcs": [ - "lastNotNull", - "max", - "min", - "mean" - ], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "multi", - "sort": "desc" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "editorMode": "code", - "expr": "sum(kafka_consumer_coordinator_heartbeat_rate{instance=~\"$instance\"}) by (instance)", - "legendFormat": "__auto", - "range": true, - "refId": "A" - } - ], - "title": "Client Heartbeat Rate", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "ops" - }, - "overrides": [] - }, - "gridPos": { - "h": 9, - "w": 13, - "x": 11, - "y": 57 - }, - "id": 83, - "options": { - "legend": { - "calcs": [ - "lastNotNull", - "max", - "min", - "mean" - ], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "multi", - "sort": "desc" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "editorMode": "code", - "expr": "sum(rate(kafka_consumer_time_between_poll_avg{instance=~\"$instance\"}[1m])) by (instance)", - "legendFormat": "__auto", - "range": true, - "refId": "A" - } - ], - "title": "Poll Rate", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 15, - "gradientMode": "opacity", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "binBps" - }, - "overrides": [] - }, - "gridPos": { - "h": 11, - "w": 12, - "x": 0, - "y": 66 - }, - "id": 93, - "options": { - "legend": { - "calcs": [ - "lastNotNull", - "max", - "mean" - ], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "multi", - "sort": "desc" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "editorMode": "code", - "expr": "sum(rate(kafka_consumer_outgoing_byte_total{instance=~\"$instance\"}[1m])) by (topic)", - "legendFormat": "__auto", - "range": true, - "refId": "A" - } - ], - "title": "Consumer Outgoing Byte Rate", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 15, - "gradientMode": "opacity", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "none" - }, - "overrides": [] - }, - "gridPos": { - "h": 11, - "w": 12, - "x": 12, - "y": 66 - }, - "id": 92, - "options": { - "legend": { - "calcs": [ - "lastNotNull", - "max", - "mean" - ], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "multi", - "sort": "desc" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "editorMode": "code", - "expr": "rate(kafka_consumer_request_total{instance=~\"$instance\"}[5m]) ", - "legendFormat": "__auto", - "range": true, - "refId": "A" - } - ], - "title": "Consumer Request Rate", - "type": "timeseries" - } - ], - "refresh": "5s", - "schemaVersion": 37, - "style": "dark", - "tags": [], - "templating": { - "list": [ - { - "current": { - "selected": false, - "text": "All", - "value": "$__all" - }, - "datasource": { - "type": "prometheus", - "uid": "Prometheus" - }, - "definition": "label_values(kafka_consumer_node_request_rate{client_id=~\".*hyades-notification-publisher.*\"}, instance)", - "hide": 0, - "includeAll": true, - "label": "Instance", - "multi": true, - "name": "instance", - "options": [], - "query": { - "query": "label_values(kafka_consumer_node_request_rate{client_id=~\".*hyades-notification-publisher.*\"}, instance)", - "refId": "StandardVariableQuery" - }, - "refresh": 1, - "regex": "", - "skipUrlSync": false, - "sort": 1, - "type": "query" - } - ] - }, - "time": { - "from": "now-1h", - "to": "now" - }, - "timepicker": {}, - "timezone": "utc", - "title": "Notification Publisher", - "uid": "ds4y4nW8Urdsaydh", - "version": 3, - "weekStart": "monday" -} \ No newline at end of file diff --git a/monitoring/prometheus.yml b/monitoring/prometheus.yml index 108ba1b543..174b468d6d 100644 --- a/monitoring/prometheus.yml +++ b/monitoring/prometheus.yml @@ -18,17 +18,6 @@ scrape_configs: - names: [ "apiserver" ] type: A port: 8080 -- job_name: notification-publisher - scrape_interval: 15s - scheme: http - metrics_path: /q/metrics -# static config to be used when running services locally and prometheus in a container -# static_configs: -# - targets: [ "host.docker.internal:8090" ] - dns_sd_configs: - - names: [ "notification-publisher" ] - type: A - port: 8090 - job_name: repo-meta-analyzer scrape_interval: 15s scheme: http diff --git a/notification-publisher/.dockerignore b/notification-publisher/.dockerignore deleted file mode 100644 index e87d7210b4..0000000000 --- a/notification-publisher/.dockerignore +++ /dev/null @@ -1,7 +0,0 @@ -* -!target/*-runner -!target/amd64/*-runner -!target/arm64/*-runner -!target/*-runner.jar -!target/lib/* -!target/quarkus-app/* \ No newline at end of file diff --git a/notification-publisher/pom.xml b/notification-publisher/pom.xml deleted file mode 100644 index d09ffaf372..0000000000 --- a/notification-publisher/pom.xml +++ /dev/null @@ -1,166 +0,0 @@ - - - - 4.0.0 - - - org.dependencytrack - hyades - 0.7.0-alpha.1-SNAPSHOT - - notification-publisher - quarkus - - - ${project.basedir}/.. - - - - - org.dependencytrack - commons-persistence - - - org.dependencytrack - proto - - - - org.dependencytrack - quarkus-config-dependencytrack - - - io.quarkus - quarkus-container-image-docker - - - io.quarkus - quarkus-cyclonedx - - - io.quarkus - quarkus-resteasy - - - io.quarkus - quarkus-kafka-client - - - io.confluent.parallelconsumer - parallel-consumer-core - - - io.quarkus - quarkus-jdbc-postgresql - - - io.quarkus - quarkus-mailer - - - io.quarkus - quarkus-smallrye-health - - - io.quarkus - quarkus-logging-json - - - - io.pebbletemplates - pebble - - - - com.google.cloud.sql - postgres-socket-factory - - - - io.github.resilience4j - resilience4j-all - - - - io.quarkus - quarkus-junit - test - - - io.quarkus - quarkus-junit-mockito - test - - - io.quarkiverse.mailpit - quarkus-mailpit - test - - - io.quarkiverse.mailpit - quarkus-mailpit-testing - test - - - org.assertj - assertj-core - test - - - io.quarkus - quarkus-jacoco - test - - - io.quarkiverse.wiremock - quarkus-wiremock - test - - - io.quarkiverse.wiremock - quarkus-wiremock-test - test - - - com.github.tomakehurst - wiremock-jre8-standalone - test - - - io.quarkus - quarkus-test-kafka-companion - test - - - - - - - io.quarkus - quarkus-maven-plugin - - - - org.jacoco - jacoco-maven-plugin - - - - diff --git a/notification-publisher/src/main/docker/Dockerfile.jvm b/notification-publisher/src/main/docker/Dockerfile.jvm deleted file mode 100644 index d4578e510a..0000000000 --- a/notification-publisher/src/main/docker/Dockerfile.jvm +++ /dev/null @@ -1,21 +0,0 @@ -FROM bellsoft/liberica-openjdk-alpine-musl:21.0.2-14 - -ENV JAVA_OPTS="" -ENV LANG='en_US.UTF-8' LANGUAGE='en_US:en' - -# libstdc++ is required for librocksdbjndi (RocksDB) -# gcompat is required for libsnappy (Snappy compression) -RUN apk --no-cache add gcompat libstdc++ - -# We make four distinct layers so if there are application changes the library layers can be re-used -COPY --chown=185 target/quarkus-app/lib/ /deployments/lib/ -COPY --chown=185 target/quarkus-app/*.jar /deployments/ -COPY --chown=185 target/quarkus-app/app/ /deployments/app/ -COPY --chown=185 target/quarkus-app/quarkus/ /deployments/quarkus/ - -EXPOSE 8090 -USER 185 -WORKDIR /deployments -CMD exec java ${JAVA_OPTS} -jar quarkus-run.jar \ - -Dquarkus.http.host=0.0.0.0 \ - -Djava.util.logging.manager=org.jboss.logmanager.LogManager diff --git a/notification-publisher/src/main/docker/Dockerfile.native b/notification-publisher/src/main/docker/Dockerfile.native deleted file mode 100644 index 5ffbd3622a..0000000000 --- a/notification-publisher/src/main/docker/Dockerfile.native +++ /dev/null @@ -1,11 +0,0 @@ -FROM quay.io/quarkus/ubi9-quarkus-micro-image:2.0 -WORKDIR /work/ -RUN chown 1001 /work \ - && chmod "g+rwX" /work \ - && chown 1001:root /work -COPY --chown=1001:root target/*-runner /work/application - -EXPOSE 8090 -USER 1001 - -CMD ["./application", "-Dquarkus.http.host=0.0.0.0"] diff --git a/notification-publisher/src/main/docker/Dockerfile.native-multiarch b/notification-publisher/src/main/docker/Dockerfile.native-multiarch deleted file mode 100644 index 4bc14e924f..0000000000 --- a/notification-publisher/src/main/docker/Dockerfile.native-multiarch +++ /dev/null @@ -1,12 +0,0 @@ -FROM quay.io/quarkus/ubi9-quarkus-micro-image:2.0 -ARG TARGETARCH -WORKDIR /work/ -RUN chown 1001 /work \ - && chmod "g+rwX" /work \ - && chown 1001:root /work -COPY --chown=1001:root --chmod=755 target/$TARGETARCH/*-runner /work/application - -EXPOSE 8090 -USER 1001 - -CMD ["./application", "-Dquarkus.http.host=0.0.0.0"] diff --git a/notification-publisher/src/main/java/org/dependencytrack/notification/NotificationConstants.java b/notification-publisher/src/main/java/org/dependencytrack/notification/NotificationConstants.java deleted file mode 100644 index 124da2be29..0000000000 --- a/notification-publisher/src/main/java/org/dependencytrack/notification/NotificationConstants.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * 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; - -public class NotificationConstants { - - public static class Title { - public static final String NOTIFICATION_TEST = "Notification Test"; - public static final String NVD_MIRROR = "NVD Mirroring"; - public static final String GITHUB_ADVISORY_MIRROR = "GitHub Advisory Mirroring"; - public static final String NPM_ADVISORY_MIRROR = "NPM Advisory Mirroring"; - public static final String VULNDB_MIRROR = "VulnDB Mirroring"; - public static final String FILE_SYSTEM_ERROR = "File System Error"; - public static final String REPO_ERROR = "Repository Error"; - public static final String ANALYZER_ERROR = "Analyzer Error"; - public static final String INTEGRATION_ERROR = "Integration Error"; - public static final String NEW_VULNERABILITY = "New Vulnerability Identified"; - public static final String NEW_VULNERABLE_DEPENDENCY = "Vulnerable Dependency Introduced"; - public static final String ANALYSIS_DECISION_EXPLOITABLE = "Analysis Decision: Exploitable"; - public static final String ANALYSIS_DECISION_IN_TRIAGE = "Analysis Decision: In Triage"; - public static final String ANALYSIS_DECISION_FALSE_POSITIVE = "Analysis Decision: False Positive"; - public static final String ANALYSIS_DECISION_NOT_AFFECTED = "Analysis Decision: Not Affected"; - public static final String ANALYSIS_DECISION_NOT_SET = "Analysis Decision: Marking Finding as NOT SET"; - public static final String ANALYSIS_DECISION_SUPPRESSED = "Analysis Decision: Finding Suppressed"; - public static final String ANALYSIS_DECISION_UNSUPPRESSED = "Analysis Decision: Finding UnSuppressed"; - public static final String ANALYSIS_DECISION_RESOLVED = "Analysis Decision: Finding Resolved"; - public static final String VIOLATIONANALYSIS_DECISION_APPROVED = "Violation Analysis Decision: Approved"; - public static final String VIOLATIONANALYSIS_DECISION_REJECTED = "Violation Analysis Decision: Rejected"; - public static final String VIOLATIONANALYSIS_DECISION_NOT_SET = "Violation Analysis Decision: Marking Finding as NOT SET"; - public static final String VIOLATIONANALYSIS_DECISION_SUPPRESSED = "Violation Analysis Decision: Violation Suppressed"; - public static final String VIOLATIONANALYSIS_DECISION_UNSUPPRESSED = "Violation Analysis Decision: Violation UnSuppressed"; - 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 BOM_VALIDATION_FAILED = "Bill of Materials Validation Failed"; - public static final String VEX_CONSUMED = "Vulnerability Exploitability Exchange (VEX) Consumed"; - public static final String VEX_PROCESSED = "Vulnerability Exploitability Exchange (VEX) Processed"; - public static final String USER_CREATED = "User Created"; - public static final String USER_DELETED = "User Deleted"; - } - -} diff --git a/notification-publisher/src/main/java/org/dependencytrack/notification/NotificationRouter.java b/notification-publisher/src/main/java/org/dependencytrack/notification/NotificationRouter.java deleted file mode 100644 index 0a055c4e37..0000000000 --- a/notification-publisher/src/main/java/org/dependencytrack/notification/NotificationRouter.java +++ /dev/null @@ -1,326 +0,0 @@ -/* - * 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; - -import com.google.protobuf.InvalidProtocolBufferException; -import io.confluent.parallelconsumer.PCRetriableException; -import io.confluent.parallelconsumer.ParallelStreamProcessor; -import io.quarkus.narayana.jta.QuarkusTransaction; -import io.quarkus.runtime.StartupEvent; -import org.apache.commons.lang3.exception.ExceptionUtils; -import org.apache.http.conn.ConnectTimeoutException; -import org.apache.kafka.clients.consumer.ConsumerRecord; -import org.dependencytrack.notification.publisher.PublishContext; -import org.dependencytrack.notification.publisher.Publisher; -import org.dependencytrack.notification.publisher.PublisherException; -import org.dependencytrack.notification.publisher.SendMailPublisher; -import org.dependencytrack.persistence.model.NotificationPublisher; -import org.dependencytrack.persistence.model.NotificationRule; -import org.dependencytrack.persistence.model.NotificationScope; -import org.dependencytrack.persistence.model.Project; -import org.dependencytrack.persistence.model.Tag; -import org.dependencytrack.persistence.model.Team; -import org.dependencytrack.persistence.repository.NotificationRuleRepository; -import org.dependencytrack.persistence.repository.ProjectRepository; -import org.dependencytrack.persistence.repository.TeamRepository; -import org.dependencytrack.proto.notification.v1.BomConsumedOrProcessedSubject; -import org.dependencytrack.proto.notification.v1.BomProcessingFailedSubject; -import org.dependencytrack.proto.notification.v1.BomValidationFailedSubject; -import org.dependencytrack.proto.notification.v1.NewVulnerabilitySubject; -import org.dependencytrack.proto.notification.v1.NewVulnerableDependencySubject; -import org.dependencytrack.proto.notification.v1.Notification; -import org.dependencytrack.proto.notification.v1.PolicyViolationAnalysisDecisionChangeSubject; -import org.dependencytrack.proto.notification.v1.PolicyViolationSubject; -import org.dependencytrack.proto.notification.v1.VexConsumedOrProcessedSubject; -import org.dependencytrack.proto.notification.v1.VulnerabilityAnalysisDecisionChangeSubject; -import org.hibernate.QueryTimeoutException; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import jakarta.enterprise.context.ApplicationScoped; -import jakarta.enterprise.event.Observes; -import jakarta.enterprise.inject.UnsatisfiedResolutionException; -import jakarta.enterprise.inject.spi.CDI; -import jakarta.json.Json; -import jakarta.json.JsonObject; -import jakarta.json.JsonReader; -import java.io.IOException; -import java.io.StringReader; -import java.net.SocketTimeoutException; -import java.util.ArrayList; -import java.util.List; -import java.util.UUID; -import java.util.function.Predicate; -import java.util.stream.Collectors; - -import static org.dependencytrack.notification.publisher.Publisher.CONFIG_TEMPLATE_KEY; -import static org.dependencytrack.notification.publisher.Publisher.CONFIG_TEMPLATE_MIME_TYPE_KEY; -import static org.dependencytrack.notification.util.ModelConverter.convert; -import static org.dependencytrack.proto.notification.v1.Scope.SCOPE_PORTFOLIO; -import static org.dependencytrack.proto.notification.v1.Scope.SCOPE_SYSTEM; - -@ApplicationScoped -public class NotificationRouter { - - private static final Logger LOGGER = LoggerFactory.getLogger(NotificationRouter.class); - - private final ParallelStreamProcessor parallelConsumer; - private final NotificationRuleRepository ruleRepository; - private final ProjectRepository projectRepository; - private final TeamRepository teamRepository; - - NotificationRouter( - final ParallelStreamProcessor parallelConsumer, - final NotificationRuleRepository ruleRepository, - final ProjectRepository projectRepository, - final TeamRepository teamRepository) { - this.parallelConsumer = parallelConsumer; - this.ruleRepository = ruleRepository; - this.projectRepository = projectRepository; - this.teamRepository = teamRepository; - } - - void onStart(@Observes final StartupEvent event) { - parallelConsumer.poll(pollCtx -> { - final ConsumerRecord consumerRecord = pollCtx.getSingleConsumerRecord(); - - final PublishContext publishCtx; - try { - publishCtx = PublishContext.fromRecord(consumerRecord); - } catch (IOException e) { - LOGGER.error("Failed to build context from {}", consumerRecord); - return; - } - - try { - inform(publishCtx, pollCtx.value()); - } catch (RuntimeException e) { - final Throwable rootCause = ExceptionUtils.getRootCause(e); - if (rootCause instanceof ConnectTimeoutException - || rootCause instanceof QueryTimeoutException - || rootCause instanceof SocketTimeoutException) { - LOGGER.warn("Encountered retryable exception ({})", publishCtx,e); - throw new PCRetriableException(e); - } - - LOGGER.error("Encountered non-retryable exception; Skipping ({})", publishCtx, e); - } - }); - } - - public void inform(final PublishContext ctx, final Notification notification) { - // Workaround for the fact that we can't currently use @Transactional. - // Even read-only operations require an active transaction in Quarkus, - // but @Transactional only works when the caller of the method is also - // a CDI-managed bean. Because we invoke the inform method from the - // Parallel Consumer thread pool, the caller is not CDI-managed. - QuarkusTransaction.joiningExisting().run(() -> { - try { - informInternal(ctx, notification); - } catch (Exception e) { - throw new RuntimeException(e); - } - }); - } - - private void informInternal(final PublishContext ctx, final Notification notification) throws Exception { - for (final NotificationRule rule : resolveRules(ctx, notification)) { - - // Not all publishers need configuration (i.e. ConsolePublisher) - JsonObject config = Json.createObjectBuilder().build(); - if (rule.getPublisherConfig() != null) { - try (StringReader stringReader = new StringReader(rule.getPublisherConfig()); - final JsonReader jsonReader = Json.createReader(stringReader)) { - config = jsonReader.readObject(); - } catch (Exception e) { - LOGGER.error("An error occurred while preparing the configuration for the notification publisher ({})", ctx.withRule(rule), e); - continue; - } - } - try { - NotificationPublisher notificationPublisher = rule.getPublisher(); - final Class publisherClass = PublisherClass.getPublisherClass(notificationPublisher.getPublisherClass()); - if (publisherClass != null && Publisher.class.isAssignableFrom(publisherClass)) { - // Instead of instantiating publisher classes ad-hoc, look the up in the CDI context. - // This way publishers can make use of dependency injection. - // TODO: Ensure all publisher implementations are available in CDI - final Publisher publisher = (Publisher) CDI.current().select(publisherClass).get(); - JsonObject notificationPublisherConfig = Json.createObjectBuilder() - .add(CONFIG_TEMPLATE_MIME_TYPE_KEY, notificationPublisher.getTemplateMimeType()) - .add(CONFIG_TEMPLATE_KEY, notificationPublisher.getTemplate()) - .addAll(Json.createObjectBuilder(config)) - .build(); - - final List ruleTeams = teamRepository.findByNotificationRule(rule.getId()); - if (publisherClass != SendMailPublisher.class || ruleTeams.isEmpty()) { - publisher.inform(ctx.withRule(rule), notification, notificationPublisherConfig); - } else { - ((SendMailPublisher) publisher).inform(ctx.withRule(rule), notification, notificationPublisherConfig, ruleTeams); - } - } else { - LOGGER.error("The defined notification publisher is not assignable from {} ({})", - Publisher.class.getCanonicalName(), ctx.withRule(rule)); - } - } catch (ClassNotFoundException | UnsatisfiedResolutionException e) { - LOGGER.error("An error occurred while instantiating a notification publisher ({})", ctx.withRule(rule), e); - } catch (PublisherException publisherException) { - LOGGER.error("An error occurred during the publication of the notification ({})", ctx.withRule(rule), publisherException); - } - } - } - - List resolveRules(final PublishContext ctx, final Notification notification) throws InvalidProtocolBufferException { - // The notification rules to process for this specific notification - final List rules = new ArrayList<>(); - - if (notification == null) { - return rules; - } - - final NotificationScope scope; - if (notification.getScope() == SCOPE_PORTFOLIO) { - scope = NotificationScope.PORTFOLIO; - } else if (notification.getScope() == SCOPE_SYSTEM) { - scope = NotificationScope.SYSTEM; - } else { - LOGGER.error("Invalid notification scope {} ({})", notification.getScope(), ctx); - return rules; - } - - final List result = ruleRepository.findEnabledByScopeAndForLevel(scope, convert(notification.getLevel())); - LOGGER.debug("Matched %d notification rules (%s)".formatted(result.size(), ctx)); - if (notification.getScope() == SCOPE_PORTFOLIO - && notification.getSubject().is(NewVulnerabilitySubject.class)) { - limitToProject(ctx, rules, result, notification, notification.getSubject().unpack(NewVulnerabilitySubject.class).getProject()); - } else if (notification.getScope() == SCOPE_PORTFOLIO - && notification.getSubject().is(NewVulnerableDependencySubject.class)) { - limitToProject(ctx, rules, result, notification, notification.getSubject().unpack(NewVulnerableDependencySubject.class).getProject()); - } else if (notification.getScope() == SCOPE_PORTFOLIO - && notification.getSubject().is(BomConsumedOrProcessedSubject.class)) { - limitToProject(ctx, rules, result, notification, notification.getSubject().unpack(BomConsumedOrProcessedSubject.class).getProject()); - } else if (notification.getScope() == SCOPE_PORTFOLIO - && notification.getSubject().is(BomProcessingFailedSubject.class)) { - limitToProject(ctx, rules, result, notification, notification.getSubject().unpack(BomProcessingFailedSubject.class).getProject()); - } else if (notification.getScope() == SCOPE_PORTFOLIO - && notification.getSubject().is(BomValidationFailedSubject.class)) { - limitToProject(ctx, rules, result, notification, notification.getSubject().unpack(BomValidationFailedSubject.class).getProject()); - } else if (notification.getScope() == SCOPE_PORTFOLIO - && notification.getSubject().is(VexConsumedOrProcessedSubject.class)) { - limitToProject(ctx, rules, result, notification, notification.getSubject().unpack(VexConsumedOrProcessedSubject.class).getProject()); - } else if (notification.getScope() == SCOPE_PORTFOLIO - && notification.getSubject().is(PolicyViolationSubject.class)) { - limitToProject(ctx, rules, result, notification, notification.getSubject().unpack(PolicyViolationSubject.class).getProject()); - } else if (notification.getScope() == SCOPE_PORTFOLIO - && notification.getSubject().is(VulnerabilityAnalysisDecisionChangeSubject.class)) { - limitToProject(ctx, rules, result, notification, notification.getSubject().unpack(VulnerabilityAnalysisDecisionChangeSubject.class).getProject()); - } else if (notification.getScope() == SCOPE_PORTFOLIO - && notification.getSubject().is(PolicyViolationAnalysisDecisionChangeSubject.class)) { - limitToProject(ctx, rules, result, notification, notification.getSubject().unpack(PolicyViolationAnalysisDecisionChangeSubject.class).getProject()); - } else { - for (final NotificationRule rule : result) { - if (rule.getNotifyOn().contains(convert(notification.getGroup()))) { - rules.add(rule); - } - } - } - return rules; - } - - /** - * if the rule specified one or more projects as targets, reduce the execution - * of the notification down to those projects that the rule matches and which - * also match projects affected by the vulnerability. - */ - private void limitToProject( - final PublishContext ctx, - final List applicableRules, - final List rules, - final Notification notification, - final org.dependencytrack.proto.notification.v1.Project limitToProject - ) { - for (final NotificationRule rule : rules) { - final PublishContext ruleCtx = ctx.withRule(rule); - if (!rule.getNotifyOn().contains(convert(notification.getGroup()))) { - continue; - } - - final boolean isLimitedToProjects = rule.getProjects() != null && !rule.getProjects().isEmpty(); - final boolean isLimitedToTags = rule.getTags() != null && !rule.getTags().isEmpty(); - if (!isLimitedToProjects && !isLimitedToTags) { - LOGGER.debug("Rule is not limited to projects or tags; Rule is applicable (%s)".formatted(ruleCtx)); - applicableRules.add(rule); - continue; - } - - if (isLimitedToTags) { - final Predicate tagMatchPredicate = project -> - project.getTagsList() != null - && rule.getTags().stream() - .map(Tag::getName) - .anyMatch(project.getTagsList()::contains); - - if (tagMatchPredicate.test(limitToProject)) { - LOGGER.debug(""" - Project %s is tagged with any of the "limit to" tags; \ - Rule is applicable (%s)""".formatted(limitToProject.getUuid(), ruleCtx)); - applicableRules.add(rule); - continue; - } - } else { - LOGGER.debug("Rule is not limited to tags (%s)".formatted(ruleCtx)); - } - - if (isLimitedToProjects) { - final UUID limitToProjectUuid = UUID.fromString(limitToProject.getUuid()); - - var matched = false; - for (final Project project : rule.getProjects()) { - if (project.getUuid().equals(limitToProjectUuid)) { - LOGGER.debug("Project %s is part of the \"limit to\" list of the rule; Rule is applicable (%s)" - .formatted(limitToProjectUuid, ruleCtx)); - matched = true; - break; - } else if (rule.isNotifyChildren()) { - final boolean isChildOfLimitToProject = projectRepository.isParentOfActiveChild(project, limitToProjectUuid); - if (isChildOfLimitToProject) { - LOGGER.debug("Project %s is child of \"limit to\" project %s; Rule is applicable (%s)" - .formatted(limitToProjectUuid, project.getUuid(), ruleCtx)); - matched = true; - break; - } else { - LOGGER.debug("Project %s is not a child of \"limit to\" project %s (%s)" - .formatted(limitToProjectUuid, project.getUuid(), ruleCtx)); - } - } - } - if (matched) { - applicableRules.add(rule); - } else { - LOGGER.debug("Project %s is not part of the \"limit to\" list of the rule; Rule is not applicable (%s)" - .formatted(limitToProjectUuid, ruleCtx)); - } - } else { - LOGGER.debug("Rule is not limited to projects (%s)".formatted(ruleCtx)); - } - } - LOGGER.debug("Applicable rules: %s (%s)" - .formatted(applicableRules.stream().map(NotificationRule::getName).collect(Collectors.joining(", ")), ctx)); - } - -} diff --git a/notification-publisher/src/main/java/org/dependencytrack/notification/PublisherClass.java b/notification-publisher/src/main/java/org/dependencytrack/notification/PublisherClass.java deleted file mode 100644 index adf27ea00a..0000000000 --- a/notification-publisher/src/main/java/org/dependencytrack/notification/PublisherClass.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * 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; - -import org.apache.commons.lang3.StringUtils; -import org.dependencytrack.notification.publisher.Publisher; - -public enum PublisherClass { - - SlackPublisher, - MsTeamsPublisher, - MattermostPublisher, - SendMailPublisher, - ConsolePublisher, - WebhookPublisher, - CsWebexPublisher, - JiraPublisher; - - public static Class getPublisherClass(String publisherClassName) throws ClassNotFoundException { - final var classPath = Publisher.class.getPackageName() + "."; - if (StringUtils.containsIgnoreCase(publisherClassName, PublisherClass.SlackPublisher.name())){ - return Class.forName(classPath + PublisherClass.SlackPublisher.name()); - } - if (StringUtils.containsIgnoreCase(publisherClassName, PublisherClass.MsTeamsPublisher.name())){ - return Class.forName(classPath + PublisherClass.MsTeamsPublisher.name()); - } - if (StringUtils.containsIgnoreCase(publisherClassName, PublisherClass.MattermostPublisher.name())){ - return Class.forName(classPath + PublisherClass.MattermostPublisher.name()); - } - if (StringUtils.containsIgnoreCase(publisherClassName, PublisherClass.SendMailPublisher.name())){ - return Class.forName(classPath + PublisherClass.SendMailPublisher.name()); - } - if (StringUtils.containsIgnoreCase(publisherClassName, PublisherClass.ConsolePublisher.name())){ - return Class.forName(classPath + PublisherClass.ConsolePublisher.name()); - } - if (StringUtils.containsIgnoreCase(publisherClassName, PublisherClass.WebhookPublisher.name())){ - return Class.forName(classPath + PublisherClass.WebhookPublisher.name()); - } - if (StringUtils.containsIgnoreCase(publisherClassName, PublisherClass.CsWebexPublisher.name())){ - return Class.forName(classPath + PublisherClass.CsWebexPublisher.name()); - } - if (StringUtils.containsIgnoreCase(publisherClassName, PublisherClass.JiraPublisher.name())){ - return Class.forName(classPath + PublisherClass.JiraPublisher.name()); - } - return null; - } -} diff --git a/notification-publisher/src/main/java/org/dependencytrack/notification/config/ParallelConsumerConfig.java b/notification-publisher/src/main/java/org/dependencytrack/notification/config/ParallelConsumerConfig.java deleted file mode 100644 index 435fd2dd61..0000000000 --- a/notification-publisher/src/main/java/org/dependencytrack/notification/config/ParallelConsumerConfig.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * 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.config; - -import io.smallrye.config.ConfigMapping; - -@ConfigMapping(prefix = "parallel-consumer") -public interface ParallelConsumerConfig { - - int maxConcurrency(); - - RetryConfig retry(); - -} diff --git a/notification-publisher/src/main/java/org/dependencytrack/notification/config/ParallelConsumerConfiguration.java b/notification-publisher/src/main/java/org/dependencytrack/notification/config/ParallelConsumerConfiguration.java deleted file mode 100644 index e301c4b97b..0000000000 --- a/notification-publisher/src/main/java/org/dependencytrack/notification/config/ParallelConsumerConfiguration.java +++ /dev/null @@ -1,133 +0,0 @@ -/* - * 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.config; - -import io.confluent.parallelconsumer.ParallelConsumerOptions; -import io.confluent.parallelconsumer.ParallelConsumerOptions.ProcessingOrder; -import io.confluent.parallelconsumer.ParallelStreamProcessor; -import io.micrometer.core.instrument.MeterRegistry; -import io.micrometer.core.instrument.binder.kafka.KafkaClientMetrics; -import io.quarkus.runtime.ShutdownEvent; -import io.quarkus.runtime.annotations.RegisterForReflection; -import io.smallrye.common.annotation.Identifier; -import org.apache.kafka.clients.consumer.ConsumerConfig; -import org.apache.kafka.clients.consumer.KafkaConsumer; -import org.apache.kafka.clients.consumer.internals.AsyncKafkaConsumer; -import org.apache.kafka.clients.consumer.internals.ClassicKafkaConsumer; -import org.apache.kafka.clients.consumer.internals.ConsumerCoordinator; -import org.apache.kafka.common.serialization.StringDeserializer; -import org.dependencytrack.notification.serialization.NotificationKafkaProtobufDeserializer; -import org.dependencytrack.proto.notification.v1.Notification; -import org.eclipse.microprofile.config.ConfigProvider; - -import jakarta.enterprise.context.ApplicationScoped; -import jakarta.enterprise.event.Observes; -import jakarta.enterprise.inject.Produces; -import java.time.Duration; -import java.util.HashMap; -import java.util.Map; -import java.util.Optional; -import java.util.regex.Pattern; - -@ApplicationScoped -@RegisterForReflection(targets = { - // parallel-consumer uses reflection with these classes - // to determine whether auto-commit is enabled. - // https://github.com/confluentinc/parallel-consumer/pull/762 - KafkaConsumer.class, - AsyncKafkaConsumer.class, - ClassicKafkaConsumer.class, - ConsumerCoordinator.class -}) -class ParallelConsumerConfiguration { - - private final ParallelStreamProcessor parallelConsumer; - private final KafkaClientMetrics consumerMetrics; - - ParallelConsumerConfiguration(@Identifier("default-kafka-broker") final Map kafkaConfig, - final ParallelConsumerConfig parallelConsumerConfig, - final MeterRegistry meterRegistry) { - final KafkaConsumer consumer = createConsumer(kafkaConfig); - this.consumerMetrics = new KafkaClientMetrics(consumer); - this.consumerMetrics.bindTo(meterRegistry); - this.parallelConsumer = createParallelConsumer(consumer, parallelConsumerConfig, meterRegistry); - } - - void onStop(@Observes final ShutdownEvent event) { - if (parallelConsumer != null) { - parallelConsumer.closeDrainFirst(Duration.ofSeconds(30)); - } - if (consumerMetrics != null) { - consumerMetrics.close(); - } - } - - @Produces - @ApplicationScoped - ParallelStreamProcessor parallelConsumer() { - return parallelConsumer; - } - - private static ParallelStreamProcessor createParallelConsumer( - final KafkaConsumer consumer, - final ParallelConsumerConfig parallelConsumerConfig, - final MeterRegistry meterRegistry) { - final var parallelConsumerOptions = ParallelConsumerOptions.builder() - .consumer(consumer) - .maxConcurrency(parallelConsumerConfig.maxConcurrency()) - .ignoreReflectiveAccessExceptionsForAutoCommitDisabledCheck(true) - .ordering(ProcessingOrder.KEY) - .retryDelayProvider(recordCtx -> { - final long delayMillis = RetryConfig - .toIntervalFunction(parallelConsumerConfig.retry()) - .apply(recordCtx.getNumberOfFailedAttempts()); - return Duration.ofMillis(delayMillis); - }) - .meterRegistry(meterRegistry) - .build(); - - final ParallelStreamProcessor parallelConsumer = ParallelStreamProcessor - .createEosStreamProcessor(parallelConsumerOptions); - - final Optional optionalPrefix = ConfigProvider.getConfig() - .getOptionalValue("dt.kafka.topic.prefix", String.class) - .map(Pattern::quote); - final var topicPattern = Pattern.compile(optionalPrefix.orElse("") + "dtrack\\.notification\\..+"); - parallelConsumer.subscribe(topicPattern); - - return parallelConsumer; - } - - private static KafkaConsumer createConsumer(final Map kafkaConfig) { - final var consumerConfig = new HashMap(); - - for (final Map.Entry entry : kafkaConfig.entrySet()) { - if (ConsumerConfig.configNames().contains(entry.getKey())) { - consumerConfig.put(entry.getKey(), entry.getValue()); - } - } - - consumerConfig.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, "false"); // Parallel consumer will handle commits - consumerConfig.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class); - consumerConfig.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, NotificationKafkaProtobufDeserializer.class); - - return new KafkaConsumer<>(consumerConfig); - } - -} diff --git a/notification-publisher/src/main/java/org/dependencytrack/notification/config/PebbleConfiguration.java b/notification-publisher/src/main/java/org/dependencytrack/notification/config/PebbleConfiguration.java deleted file mode 100644 index b8e0fce9e5..0000000000 --- a/notification-publisher/src/main/java/org/dependencytrack/notification/config/PebbleConfiguration.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * 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.config; - -import io.pebbletemplates.pebble.PebbleEngine; -import jakarta.enterprise.context.ApplicationScoped; -import jakarta.inject.Named; -import jakarta.ws.rs.Produces; - -import io.pebbletemplates.pebble.extension.core.DisallowExtensionCustomizerBuilder; -import org.dependencytrack.notification.template.extension.CustomExtension; - -import java.util.List; - -class PebbleConfiguration { - - @Produces - @ApplicationScoped - @Named("pebbleEngineJson") - PebbleEngine pebbleEngineJson(final CustomExtension customExtension) { - return new PebbleEngine.Builder() - .registerExtensionCustomizer(new DisallowExtensionCustomizerBuilder() - .disallowedTokenParserTags(List.of("include")) - .build()) - .extension(customExtension) - .defaultEscapingStrategy("json") - .build(); - } - - @Produces - @ApplicationScoped - @Named("pebbleEnginePlainText") - PebbleEngine pebbleEnginePlainText(final CustomExtension customExtension) { - return new PebbleEngine.Builder() - .registerExtensionCustomizer(new DisallowExtensionCustomizerBuilder() - .disallowedTokenParserTags(List.of("include")) - .build()) - .extension(customExtension) - .newLineTrimming(false) - .build(); - } - - @Produces - @ApplicationScoped - CustomExtension pebbleEngineExtension() { - return new CustomExtension(); - } - -} diff --git a/notification-publisher/src/main/java/org/dependencytrack/notification/config/RetryConfig.java b/notification-publisher/src/main/java/org/dependencytrack/notification/config/RetryConfig.java deleted file mode 100644 index 11a8aca41d..0000000000 --- a/notification-publisher/src/main/java/org/dependencytrack/notification/config/RetryConfig.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * 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.config; - -import io.github.resilience4j.core.IntervalFunction; -import jakarta.validation.constraints.Positive; - -import java.time.Duration; -import java.util.OptionalDouble; - -public interface RetryConfig { - - Duration initialDelay(); - - @Positive - int multiplier(); - - OptionalDouble randomizationFactor(); - - Duration maxDuration(); - - static IntervalFunction toIntervalFunction(final RetryConfig config) { - if (config.randomizationFactor().isPresent()) { - return IntervalFunction.ofExponentialRandomBackoff( - config.initialDelay(), - config.multiplier(), - config.randomizationFactor().getAsDouble(), - config.maxDuration() - ); - } - - return IntervalFunction.ofExponentialBackoff( - config.initialDelay(), - config.multiplier(), - config.maxDuration() - ); - } - -} diff --git a/notification-publisher/src/main/java/org/dependencytrack/notification/health/ParallelConsumerHealthCheck.java b/notification-publisher/src/main/java/org/dependencytrack/notification/health/ParallelConsumerHealthCheck.java deleted file mode 100644 index 291b23d178..0000000000 --- a/notification-publisher/src/main/java/org/dependencytrack/notification/health/ParallelConsumerHealthCheck.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * 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.health; - -import io.confluent.parallelconsumer.ParallelEoSStreamProcessor; -import io.confluent.parallelconsumer.ParallelStreamProcessor; -import jakarta.enterprise.context.ApplicationScoped; -import org.dependencytrack.proto.notification.v1.Notification; -import org.eclipse.microprofile.health.HealthCheck; -import org.eclipse.microprofile.health.HealthCheckResponse; -import org.eclipse.microprofile.health.HealthCheckResponseBuilder; -import org.eclipse.microprofile.health.Liveness; - -/** - * Basic liveness check for the Confluent Parallel Consumer. - *

- * To be replaced with official implementation once - * #485 is merged. - */ -@Liveness -@ApplicationScoped -public class ParallelConsumerHealthCheck implements HealthCheck { - - private final ParallelStreamProcessor parallelConsumer; - - ParallelConsumerHealthCheck(final ParallelStreamProcessor parallelConsumer) { - this.parallelConsumer = parallelConsumer; - } - - @Override - public HealthCheckResponse call() { - final HealthCheckResponseBuilder responseBuilder = HealthCheckResponse - .named("parallel_consumer") - .status(!parallelConsumer.isClosedOrFailed()); - - if (parallelConsumer.isClosedOrFailed() - && parallelConsumer instanceof final ParallelEoSStreamProcessor concreteParallelConsumer - && concreteParallelConsumer.getFailureCause() != null) { - responseBuilder.withData("failure_cause", concreteParallelConsumer.getFailureCause().getMessage()); - } - - return responseBuilder.build(); - } - -} diff --git a/notification-publisher/src/main/java/org/dependencytrack/notification/publisher/AbstractWebhookPublisher.java b/notification-publisher/src/main/java/org/dependencytrack/notification/publisher/AbstractWebhookPublisher.java deleted file mode 100644 index a84bfabe52..0000000000 --- a/notification-publisher/src/main/java/org/dependencytrack/notification/publisher/AbstractWebhookPublisher.java +++ /dev/null @@ -1,123 +0,0 @@ -/* - * 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 io.pebbletemplates.pebble.template.PebbleTemplate; -import jakarta.inject.Inject; -import jakarta.inject.Named; -import jakarta.json.JsonObject; -import org.apache.http.client.methods.CloseableHttpResponse; -import org.apache.http.client.methods.HttpPost; -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.proto.notification.v1.Notification; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.IOException; - -public abstract class AbstractWebhookPublisher implements Publisher { - - @Inject - @Named("httpClient") - CloseableHttpClient httpClient; - - 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)); - return; - } - final String destination = getDestinationUrl(config); - if (destination == null) { - logger.warn("No destination configured; Skipping notification (%s)".formatted(ctx)); - return; - } - - final AuthCredentials credentials; - try { - credentials = getAuthCredentials(); - } catch (RuntimeException e) { - logger.warn(""" - An error occurred during the retrieval of credentials needed for notification \ - publication; Skipping notification (%s)""".formatted(ctx), e); - return; - } - final String content; - try { - content = prepareTemplate(notification, template, config); - } catch (IOException | RuntimeException e) { - logger.error("Failed to prepare notification content (%s)".formatted(ctx), e); - return; - } - - final var request = new HttpPost(destination); - final String mimeType = getTemplateMimeType(config); - request.addHeader("content-type", mimeType); - request.addHeader("accept", mimeType); - if (credentials != null) { - if(credentials.user() != null) { - request.addHeader("Authorization", HttpUtil.basicAuthHeaderValue(credentials.user(), credentials.password())); - } else { - request.addHeader("Authorization", "Bearer " + credentials.password); - } - } else if (getToken(config) != null) { - request.addHeader(getTokenHeader(config), getToken(config)); - } - - StringEntity entity = new StringEntity(content); - request.setEntity(entity); - try (final CloseableHttpResponse response = httpClient.execute(request);) { - final int statusCode = response.getStatusLine().getStatusCode(); - if (statusCode < 200 || statusCode >= 300) { - logger.warn("Destination responded with with status code %d, likely indicating a processing failure (%s)" - .formatted(statusCode, ctx)); - if (logger.isDebugEnabled()) { - logger.debug("Response headers: %s".formatted((Object[]) response.getAllHeaders())); - logger.debug("Response body: %s".formatted(EntityUtils.toString(response.getEntity()))); - } - } else if (ctx.shouldLogSuccess()) { - logger.info("Destination acknowledged reception of notification with status code %d (%s)" - .formatted(statusCode, ctx)); - } - } - } - - protected AuthCredentials getAuthCredentials() throws Exception { - return null; - } - - protected record AuthCredentials(String user, String password) { - } - - protected String getDestinationUrl(final JsonObject config) { - return config.getString(CONFIG_DESTINATION, null); - } - - protected String getToken(final JsonObject config) { - return config.getString(CONFIG_TOKEN, null); - } - - protected String getTokenHeader(final JsonObject config) { - return config.getString(CONFIG_TOKEN_HEADER, "X-Api-Key"); - } -} - diff --git a/notification-publisher/src/main/java/org/dependencytrack/notification/publisher/ConsolePublisher.java b/notification-publisher/src/main/java/org/dependencytrack/notification/publisher/ConsolePublisher.java deleted file mode 100644 index bbb278589e..0000000000 --- a/notification-publisher/src/main/java/org/dependencytrack/notification/publisher/ConsolePublisher.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * 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 io.pebbletemplates.pebble.PebbleEngine; -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.proto.notification.v1.Level; -import org.dependencytrack.proto.notification.v1.Notification; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.IOException; -import java.io.PrintStream; - -@ApplicationScoped -@Startup // Force bean creation even though no direct injection points exist -public class ConsolePublisher implements Publisher { - - private static final Logger LOGGER = LoggerFactory.getLogger(ConsolePublisher.class); - - private final PebbleEngine pebbleEngine; - - @Inject - public ConsolePublisher(@Named("pebbleEnginePlainText") final PebbleEngine pebbleEngine) { - this.pebbleEngine = pebbleEngine; - } - - public void inform(final PublishContext ctx, final Notification notification, final JsonObject config) throws Exception { - final String content; - try { - content = prepareTemplate(notification, getTemplate(config), config); - } catch (IOException | RuntimeException e) { - LOGGER.error("Failed to prepare notification content (%s)".formatted(ctx), e); - return; - } - final PrintStream ps; - if (notification.getLevel() == Level.LEVEL_ERROR) { - ps = System.err; - } else { - ps = System.out; - } - ps.println(content); - } - - @Override - public PebbleEngine getTemplateEngine() { - return pebbleEngine; - } -} diff --git a/notification-publisher/src/main/java/org/dependencytrack/notification/publisher/CsWebexPublisher.java b/notification-publisher/src/main/java/org/dependencytrack/notification/publisher/CsWebexPublisher.java deleted file mode 100644 index c6ee0abd40..0000000000 --- a/notification-publisher/src/main/java/org/dependencytrack/notification/publisher/CsWebexPublisher.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * 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 io.pebbletemplates.pebble.PebbleEngine; -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.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; - - @Inject - public CsWebexPublisher(@Named("pebbleEngineJson") final PebbleEngine pebbleEngine) { - this.pebbleEngine = pebbleEngine; - } - - public void inform(final PublishContext ctx, final Notification notification, final JsonObject config) throws Exception { - publish(ctx, getTemplate(config), notification, config); - } - - @Override - public PebbleEngine getTemplateEngine() { - return pebbleEngine; - } - -} \ No newline at end of file diff --git a/notification-publisher/src/main/java/org/dependencytrack/notification/publisher/JiraPublisher.java b/notification-publisher/src/main/java/org/dependencytrack/notification/publisher/JiraPublisher.java deleted file mode 100644 index fb943b7559..0000000000 --- a/notification-publisher/src/main/java/org/dependencytrack/notification/publisher/JiraPublisher.java +++ /dev/null @@ -1,107 +0,0 @@ -/* - * 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 io.pebbletemplates.pebble.PebbleEngine; -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.proto.notification.v1.Notification; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.Map; - -@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 JiraPublisherConfig publisherConfig; - private final SecretDecryptor secretDecryptor; - private String jiraProjectKey; - private String jiraTicketType; - - @Inject - JiraPublisher(@Named("pebbleEngineJson") final PebbleEngine pebbleEngine, - final JiraPublisherConfig publisherConfig, - final SecretDecryptor secretDecryptor) { - this.pebbleEngine = pebbleEngine; - this.publisherConfig = publisherConfig; - this.secretDecryptor = secretDecryptor; - } - - @Override - public String getDestinationUrl(final JsonObject config) { - final String baseUrl = publisherConfig.baseUrl().orElse(null); - if (baseUrl == null) { - return null; - } - - return (baseUrl.endsWith("/") ? baseUrl : baseUrl + '/') + "rest/api/2/issue"; - } - - @Override - protected AuthCredentials getAuthCredentials() throws Exception { - 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); - } - - @Override - public void inform(final PublishContext ctx, final Notification notification, final JsonObject config) throws Exception { - if (config == null) { - LOGGER.warn("No publisher configuration provided; Skipping notification (%s)".formatted(ctx)); - return; - } - - jiraTicketType = config.getString("jiraTicketType", null); - if (jiraTicketType == null) { - LOGGER.warn("No JIRA ticket type configured; Skipping notification (%s)".formatted(ctx)); - return; - } - - jiraProjectKey = config.getString(CONFIG_DESTINATION, null); - if (jiraProjectKey == null) { - LOGGER.warn("No JIRA project key configured; Skipping notification (%s)".formatted(ctx)); - return; - } - - publish(ctx, getTemplate(config), notification, config); - } - - @Override - public PebbleEngine getTemplateEngine() { - return pebbleEngine; - } - - @Override - public void enrichTemplateContext(final Map context, JsonObject config) { - jiraTicketType = config.getString("jiraTicketType"); - jiraProjectKey = config.getString(CONFIG_DESTINATION); - context.put("jiraProjectKey", jiraProjectKey); - context.put("jiraTicketType", jiraTicketType); - } -} diff --git a/notification-publisher/src/main/java/org/dependencytrack/notification/publisher/JiraPublisherConfig.java b/notification-publisher/src/main/java/org/dependencytrack/notification/publisher/JiraPublisherConfig.java deleted file mode 100644 index f7b558103f..0000000000 --- a/notification-publisher/src/main/java/org/dependencytrack/notification/publisher/JiraPublisherConfig.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * 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 io.smallrye.config.ConfigMapping; -import jakarta.enterprise.context.ApplicationScoped; -import jakarta.inject.Provider; -import org.eclipse.microprofile.config.inject.ConfigProperty; - -import java.util.Optional; - -/** - * As of Quarkus 3.9 / smallrye-config 3.7, it is not possible to use {@link ConfigMapping} - * interfaces with {@link Provider} fields. We need {@link Provider} fields in order to support - * configuration changes at runtime. Refer to Injecting Dynamic Values in the - * {@link ConfigProperty} JavaDoc for details. - * - * @see Related smallrye-config issue - */ -@ApplicationScoped -class JiraPublisherConfig { - - private final Provider> baseUrlProvider; - private final Provider> usernameProvider; - private final Provider> passwordProvider; - - JiraPublisherConfig( - @ConfigProperty(name = "dtrack.integrations.jira.url") final Provider> baseUrlProvider, - @ConfigProperty(name = "dtrack.integrations.jira.username") final Provider> usernameProvider, - @ConfigProperty(name = "dtrack.integrations.jira.password") final Provider> passwordProvider - ) { - this.baseUrlProvider = baseUrlProvider; - this.usernameProvider = usernameProvider; - this.passwordProvider = passwordProvider; - } - - Optional baseUrl() { - return baseUrlProvider.get(); - } - - Optional username() { - return usernameProvider.get(); - } - - Optional password() { - return passwordProvider.get(); - } - -} diff --git a/notification-publisher/src/main/java/org/dependencytrack/notification/publisher/MattermostPublisher.java b/notification-publisher/src/main/java/org/dependencytrack/notification/publisher/MattermostPublisher.java deleted file mode 100644 index d1545a4c9c..0000000000 --- a/notification-publisher/src/main/java/org/dependencytrack/notification/publisher/MattermostPublisher.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * 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 io.pebbletemplates.pebble.PebbleEngine; -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.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; - - @Inject - public MattermostPublisher(@Named("pebbleEngineJson") final PebbleEngine pebbleEngine) { - this.pebbleEngine = pebbleEngine; - } - - public void inform(final PublishContext ctx, final Notification notification, final JsonObject config) throws Exception { - publish(ctx, getTemplate(config), notification, config); - } - - @Override - public PebbleEngine getTemplateEngine() { - return pebbleEngine; - } -} diff --git a/notification-publisher/src/main/java/org/dependencytrack/notification/publisher/MsTeamsPublisher.java b/notification-publisher/src/main/java/org/dependencytrack/notification/publisher/MsTeamsPublisher.java deleted file mode 100644 index 938e3b1928..0000000000 --- a/notification-publisher/src/main/java/org/dependencytrack/notification/publisher/MsTeamsPublisher.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * 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 io.pebbletemplates.pebble.PebbleEngine; -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.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; - - @Inject - public MsTeamsPublisher(@Named("pebbleEngineJson") final PebbleEngine pebbleEngine) { - this.pebbleEngine = pebbleEngine; - } - - public void inform(final PublishContext ctx, final Notification notification, final JsonObject config) throws Exception { - publish(ctx, getTemplate(config), notification, config); - } - - @Override - public PebbleEngine getTemplateEngine() { - return pebbleEngine; - } - -} diff --git a/notification-publisher/src/main/java/org/dependencytrack/notification/publisher/PublishContext.java b/notification-publisher/src/main/java/org/dependencytrack/notification/publisher/PublishContext.java deleted file mode 100644 index 81bd8eabd1..0000000000 --- a/notification-publisher/src/main/java/org/dependencytrack/notification/publisher/PublishContext.java +++ /dev/null @@ -1,284 +0,0 @@ -/* - * 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 com.google.common.base.MoreObjects; -import org.apache.kafka.clients.consumer.ConsumerRecord; -import org.dependencytrack.persistence.model.NotificationRule; -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.BomValidationFailedSubject; -import org.dependencytrack.proto.notification.v1.NewVulnerabilitySubject; -import org.dependencytrack.proto.notification.v1.NewVulnerableDependencySubject; -import org.dependencytrack.proto.notification.v1.Notification; -import org.dependencytrack.proto.notification.v1.PolicyViolationAnalysisDecisionChangeSubject; -import org.dependencytrack.proto.notification.v1.PolicyViolationSubject; -import org.dependencytrack.proto.notification.v1.ProjectVulnAnalysisCompleteSubject; -import org.dependencytrack.proto.notification.v1.UserSubject; -import org.dependencytrack.proto.notification.v1.VexConsumedOrProcessedSubject; -import org.dependencytrack.proto.notification.v1.VulnerabilityAnalysisDecisionChangeSubject; - -import java.io.IOException; -import java.util.HashMap; -import java.util.Map; -import java.util.Optional; - -public class PublishContext { - - private static final String SUBJECT_COMPONENT = "component"; - private static final String SUBJECT_PROJECT = "project"; - private static final String SUBJECT_VULNERABILITY = "vulnerability"; - private static final String SUBJECT_VULNERABILITIES = "vulnerabilities"; - private static final String SUBJECT_USER = "user"; - - private final String kafkaTopic; - private final int kafkaTopicPartition; - private final long kafkaPartitionOffset; - private final String notificationGroup; - private final String notificationLevel; - private final String notificationScope; - private final String notificationTimestamp; - private final Map notificationSubjects; - private String ruleName; - private String ruleScope; - private String ruleLevel; - private boolean logSuccess; - - /** - * Create a new {@link PublishContext} instance. - * - * @param kafkaTopic The Kafka topic the {@link Notification} was retrieved from - * @param kafkaTopicPartition The partition of the Kafka topic the {@link Notification} was retrieved from - * @param kafkaPartitionOffset The offset in the partition of the Kafka topic the {@link Notification} was retrieved from - * @param notificationGroup The group of the {@link Notification} - * @param notificationLevel The level of the {@link Notification} - * @param notificationScope The scope of the {@link Notification} - * @param notificationTimestamp The timestamp of the {@link Notification} - * @param notificationSubjects The subjects of the {@link Notification} (can contain either {@link Component}, {@link Project}, or both) - */ - PublishContext(final String kafkaTopic, final int kafkaTopicPartition, final long kafkaPartitionOffset, - final String notificationGroup, final String notificationLevel, final String notificationScope, - final String notificationTimestamp, final Map notificationSubjects) { - this.kafkaTopic = kafkaTopic; - this.kafkaTopicPartition = kafkaTopicPartition; - this.kafkaPartitionOffset = kafkaPartitionOffset; - this.notificationGroup = notificationGroup; - this.notificationLevel = notificationLevel; - this.notificationScope = notificationScope; - this.notificationTimestamp = notificationTimestamp; - this.notificationSubjects = notificationSubjects; - } - - public static PublishContext fromRecord(final ConsumerRecord consumerRecord) throws IOException { - final Notification notification = consumerRecord.value(); - final var notificationSubjects = new HashMap(); - - if (notification.getSubject().is(BomConsumedOrProcessedSubject.class)) { - final BomConsumedOrProcessedSubject subject = notification.getSubject().unpack(BomConsumedOrProcessedSubject.class); - notificationSubjects.put(SUBJECT_PROJECT, Project.convert(subject.getProject())); - } else if (notification.getSubject().is(BomProcessingFailedSubject.class)) { - final BomProcessingFailedSubject subject = notification.getSubject().unpack(BomProcessingFailedSubject.class); - notificationSubjects.put(SUBJECT_PROJECT, Project.convert(subject.getProject())); - } else if (notification.getSubject().is(BomValidationFailedSubject.class)) { - final BomValidationFailedSubject subject = notification.getSubject().unpack(BomValidationFailedSubject.class); - notificationSubjects.put(SUBJECT_PROJECT, Project.convert(subject.getProject())); - } else if (notification.getSubject().is(NewVulnerabilitySubject.class)) { - final NewVulnerabilitySubject subject = notification.getSubject().unpack(NewVulnerabilitySubject.class); - notificationSubjects.put(SUBJECT_COMPONENT, Component.convert(subject.getComponent())); - notificationSubjects.put(SUBJECT_PROJECT, Project.convert(subject.getProject())); - notificationSubjects.put(SUBJECT_VULNERABILITY, Vulnerability.convert(subject.getVulnerability())); - } else if (notification.getSubject().is(NewVulnerableDependencySubject.class)) { - final NewVulnerableDependencySubject subject = notification.getSubject().unpack(NewVulnerableDependencySubject.class); - notificationSubjects.put(SUBJECT_COMPONENT, Component.convert(subject.getComponent())); - notificationSubjects.put(SUBJECT_PROJECT, Project.convert(subject.getProject())); - if (subject.getVulnerabilitiesList() != null) { - notificationSubjects.put(SUBJECT_VULNERABILITIES, subject.getVulnerabilitiesList().stream().map(Vulnerability::convert).toList()); - } else { - notificationSubjects.put(SUBJECT_VULNERABILITIES, null); - } - } else if (notification.getSubject().is(org.dependencytrack.proto.notification.v1.Project.class)) { - final org.dependencytrack.proto.notification.v1.Project subject = notification.getSubject().unpack(org.dependencytrack.proto.notification.v1.Project.class); - notificationSubjects.put(SUBJECT_PROJECT, Project.convert(subject)); - } else if (notification.getSubject().is(ProjectVulnAnalysisCompleteSubject.class)) { - final ProjectVulnAnalysisCompleteSubject subject = notification.getSubject().unpack(ProjectVulnAnalysisCompleteSubject.class); - notificationSubjects.put(SUBJECT_PROJECT, Project.convert(subject.getProject())); - } else if (notification.getSubject().is(PolicyViolationSubject.class)) { - final PolicyViolationSubject subject = notification.getSubject().unpack(PolicyViolationSubject.class); - notificationSubjects.put(SUBJECT_COMPONENT, Component.convert(subject.getComponent())); - notificationSubjects.put(SUBJECT_PROJECT, Project.convert(subject.getProject())); - } else if (notification.getSubject().is(PolicyViolationAnalysisDecisionChangeSubject.class)) { - final PolicyViolationAnalysisDecisionChangeSubject subject = notification.getSubject().unpack(PolicyViolationAnalysisDecisionChangeSubject.class); - notificationSubjects.put(SUBJECT_COMPONENT, Component.convert(subject.getComponent())); - notificationSubjects.put(SUBJECT_PROJECT, Project.convert(subject.getProject())); - } else if (notification.getSubject().is(VulnerabilityAnalysisDecisionChangeSubject.class)) { - final VulnerabilityAnalysisDecisionChangeSubject subject = notification.getSubject().unpack(VulnerabilityAnalysisDecisionChangeSubject.class); - notificationSubjects.put(SUBJECT_COMPONENT, Component.convert(subject.getComponent())); - notificationSubjects.put(SUBJECT_PROJECT, Project.convert(subject.getProject())); - notificationSubjects.put(SUBJECT_VULNERABILITY, Vulnerability.convert(subject.getVulnerability())); - } else if (notification.getSubject().is(VexConsumedOrProcessedSubject.class)) { - final VexConsumedOrProcessedSubject subject = notification.getSubject().unpack(VexConsumedOrProcessedSubject.class); - notificationSubjects.put(SUBJECT_PROJECT, Project.convert(subject.getProject())); - } else if (notification.getSubject().is(UserSubject.class)) { - final UserSubject subject = notification.getSubject().unpack(UserSubject.class); - notificationSubjects.put(SUBJECT_USER, User.convert(subject)); - } - - return new PublishContext(consumerRecord.topic(), consumerRecord.partition(), consumerRecord.offset(), - notification.getGroup().name(), Optional.ofNullable(notification.getLevel()).map(Enum::name).orElse(null), notification.getScope().name(), - ProtobufUtil.formatTimestamp(notification.getTimestamp()), notificationSubjects); - } - - /** - * Enrich the {@link PublishContext} with additional information about the {@link NotificationRule} once known. - * - * @param rule The applicable {@link NotificationRule} - * @return This {@link PublishContext} - */ - public PublishContext withRule(final NotificationRule rule) { - this.ruleName = rule.getName(); - this.ruleLevel = rule.getNotificationLevel().name(); - this.ruleScope = rule.getScope().name(); - this.logSuccess = rule.isLogSuccessfulPublish(); - return this; - } - - public boolean shouldLogSuccess() { - return this.logSuccess; - } - - /** - * {@inheritDoc} - */ - @Override - public String toString() { - return MoreObjects.toStringHelper(this) - .add("kafkaTopic", kafkaTopic) - .add("kafkaTopicPartition", kafkaTopicPartition) - .add("kafkaPartitionOffset", kafkaPartitionOffset) - .add("notificationGroup", notificationGroup) - .add("notificationLevel", notificationLevel) - .add("notificationScope", notificationScope) - .add("notificationTimestamp", notificationTimestamp) - .add("notificationSubjects", notificationSubjects) - .add("ruleName", ruleName) - .add("ruleScope", ruleScope) - .add("ruleLevel", ruleLevel) - .omitNullValues() - .toString(); - } - - public record Component(String uuid, String group, String name, String version) { - - private static Component convert(final org.dependencytrack.proto.notification.v1.Component notificationComponent) { - if (notificationComponent == null) { - return null; - } - return new Component( - notificationComponent.getUuid(), - notificationComponent.getGroup(), - notificationComponent.getName(), - notificationComponent.getVersion() - ); - } - - } - - public record Project(String uuid, String name, String version) { - - private static Project convert(final org.dependencytrack.proto.notification.v1.Project notificationProject) { - if (notificationProject == null) { - return null; - } - return new Project( - notificationProject.getUuid(), - notificationProject.getName(), - notificationProject.getVersion() - ); - } - - } - - public record Vulnerability(String id, String source) { - - private static Vulnerability convert(final org.dependencytrack.proto.notification.v1.Vulnerability notificationVuln) { - if (notificationVuln == null) { - return null; - } - return new Vulnerability(notificationVuln.getVulnId(), notificationVuln.getSource()); - } - - } - - public record User(String username, String email) { - - private static User convert(final UserSubject userSubject) { - if (userSubject == null) { - return null; - } - return new User(userSubject.getUsername(), userSubject.getEmail()); - } - - } - - public String kafkaTopic() { - return kafkaTopic; - } - - public int kafkaTopicPartition() { - return kafkaTopicPartition; - } - - public long kafkaPartitionOffset() { - return kafkaPartitionOffset; - } - - public String notificationGroup() { - return notificationGroup; - } - - public String notificationLevel() { - return notificationLevel; - } - - public String notificationScope() { - return notificationScope; - } - - public String notificationTimestamp() { - return notificationTimestamp; - } - - public Map notificationSubjects() { - return notificationSubjects; - } - - public String ruleName() { - return ruleName; - } - - public String ruleScope() { - return ruleScope; - } - - public String ruleLevel() { - return ruleLevel; - } - -} diff --git a/notification-publisher/src/main/java/org/dependencytrack/notification/publisher/Publisher.java b/notification-publisher/src/main/java/org/dependencytrack/notification/publisher/Publisher.java deleted file mode 100644 index 6bc78723d1..0000000000 --- a/notification-publisher/src/main/java/org/dependencytrack/notification/publisher/Publisher.java +++ /dev/null @@ -1,157 +0,0 @@ -/* - * 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 com.google.protobuf.util.JsonFormat; -import io.pebbletemplates.pebble.PebbleEngine; -import io.pebbletemplates.pebble.template.PebbleTemplate; -import jakarta.json.JsonObject; -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.BomValidationFailedSubject; -import org.dependencytrack.proto.notification.v1.NewVulnerabilitySubject; -import org.dependencytrack.proto.notification.v1.NewVulnerableDependencySubject; -import org.dependencytrack.proto.notification.v1.Notification; -import org.dependencytrack.proto.notification.v1.PolicyViolationAnalysisDecisionChangeSubject; -import org.dependencytrack.proto.notification.v1.PolicyViolationSubject; -import org.dependencytrack.proto.notification.v1.ProjectVulnAnalysisCompleteSubject; -import org.dependencytrack.proto.notification.v1.UserSubject; -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; -import java.io.Writer; -import java.util.HashMap; -import java.util.Map; - -import static org.dependencytrack.proto.notification.v1.Scope.SCOPE_PORTFOLIO; -import static org.dependencytrack.proto.notification.v1.Scope.SCOPE_SYSTEM; - -public interface Publisher { - - String CONFIG_TEMPLATE_KEY = "template"; - - String CONFIG_TEMPLATE_MIME_TYPE_KEY = "mimeType"; - - String CONFIG_DESTINATION = "destination"; - - String CONFIG_TOKEN = "token"; - - String CONFIG_TOKEN_HEADER = "tokenHeader"; - - - void inform(final PublishContext ctx, final Notification notification, final JsonObject config) throws Exception; - - PebbleEngine getTemplateEngine(); - - default PebbleTemplate getTemplate(JsonObject config) { - try { - String literalTemplate = config.getString(CONFIG_TEMPLATE_KEY); - return getTemplateEngine().getLiteralTemplate(literalTemplate); - } catch (NullPointerException | ClassCastException templateException) { - throw new PublisherException(templateException.getMessage(), templateException); - } - } - - default String getTemplateMimeType(JsonObject config) { - try { - return config.getString(CONFIG_TEMPLATE_MIME_TYPE_KEY); - } catch (NullPointerException | ClassCastException templateException) { - throw new PublisherException(templateException.getMessage(), templateException); - } - } - - 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 context = new HashMap<>(); - final long epochSecond = notification.getTimestamp().getSeconds(); - context.put("timestampEpochSecond", epochSecond); - context.put("timestamp", ProtobufUtil.formatTimestamp(notification.getTimestamp())); - context.put("notification", notification); - context.put("baseUrl", baseUrl); - - if (notification.getScope() == SCOPE_PORTFOLIO) { - if (notification.getSubject().is(NewVulnerabilitySubject.class)) { - final var subject = notification.getSubject().unpack(NewVulnerabilitySubject.class); - context.put("subject", subject); - context.put("subjectJson", JsonFormat.printer().print(subject)); - } else if (notification.getSubject().is(NewVulnerableDependencySubject.class)) { - final var subject = notification.getSubject().unpack(NewVulnerableDependencySubject.class); - context.put("subject", subject); - context.put("subjectJson", JsonFormat.printer().print(subject)); - } else if (notification.getSubject().is(VulnerabilityAnalysisDecisionChangeSubject.class)) { - final var subject = notification.getSubject().unpack(VulnerabilityAnalysisDecisionChangeSubject.class); - context.put("subject", subject); - context.put("subjectJson", JsonFormat.printer().print(subject)); - } else if (notification.getSubject().is(PolicyViolationAnalysisDecisionChangeSubject.class)) { - final var subject = notification.getSubject().unpack(PolicyViolationAnalysisDecisionChangeSubject.class); - context.put("subject", subject); - context.put("subjectJson", JsonFormat.printer().print(subject)); - } else if (notification.getSubject().is(BomConsumedOrProcessedSubject.class)) { - 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(BomValidationFailedSubject.class)) { - final var subject = notification.getSubject().unpack(BomValidationFailedSubject.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); - context.put("subjectJson", JsonFormat.printer().print(subject)); - } else if (notification.getSubject().is(PolicyViolationSubject.class)) { - final var subject = notification.getSubject().unpack(PolicyViolationSubject.class); - context.put("subject", subject); - context.put("subjectJson", JsonFormat.printer().print(subject)); - } else if (notification.getSubject().is(ProjectVulnAnalysisCompleteSubject.class)) { - final var subject = notification.getSubject().unpack(ProjectVulnAnalysisCompleteSubject.class); - context.put("subject", subject); - context.put("subjectJson", JsonFormat.printer().print(subject)); - } - } - else if (notification.getScope() == SCOPE_SYSTEM) { - if (notification.getSubject().is(UserSubject.class)) { - final var subject = notification.getSubject().unpack(UserSubject.class); - context.put("subject", subject); - context.put("subjectJson", JsonFormat.printer().print(subject)); - } - } - - enrichTemplateContext(context, config); - - try (Writer writer = new StringWriter()) { - template.evaluate(writer, context); - return writer.toString(); - } - } - - default void enrichTemplateContext(final Map context, JsonObject config) { - } -} diff --git a/notification-publisher/src/main/java/org/dependencytrack/notification/publisher/PublisherException.java b/notification-publisher/src/main/java/org/dependencytrack/notification/publisher/PublisherException.java deleted file mode 100644 index 91b33ee295..0000000000 --- a/notification-publisher/src/main/java/org/dependencytrack/notification/publisher/PublisherException.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * 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; - -public class PublisherException extends RuntimeException { - - public PublisherException(String message, Throwable cause) { - super(message, cause); - } - -} - diff --git a/notification-publisher/src/main/java/org/dependencytrack/notification/publisher/SendMailPublisher.java b/notification-publisher/src/main/java/org/dependencytrack/notification/publisher/SendMailPublisher.java deleted file mode 100644 index c96d0c36ee..0000000000 --- a/notification-publisher/src/main/java/org/dependencytrack/notification/publisher/SendMailPublisher.java +++ /dev/null @@ -1,210 +0,0 @@ -/* - * 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 io.pebbletemplates.pebble.PebbleEngine; -import io.pebbletemplates.pebble.template.PebbleTemplate; -import io.quarkus.runtime.Startup; -import io.vertx.ext.mail.MailConfig; -import io.vertx.ext.mail.MailMessage; -import io.vertx.mutiny.core.Vertx; -import io.vertx.mutiny.ext.mail.MailClient; -import jakarta.enterprise.context.ApplicationScoped; -import jakarta.inject.Inject; -import jakarta.inject.Named; -import jakarta.json.JsonObject; -import jakarta.json.JsonString; -import org.dependencytrack.common.SecretDecryptor; -import org.dependencytrack.persistence.model.Team; -import org.dependencytrack.persistence.repository.UserRepository; -import org.dependencytrack.proto.notification.v1.Notification; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.IOException; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Optional; -import java.util.function.Predicate; -import java.util.stream.Stream; - -@ApplicationScoped -@Startup // Force bean creation even though no direct injection points exist -public class SendMailPublisher implements Publisher { - - private static final Logger LOGGER = LoggerFactory.getLogger(SendMailPublisher.class); - - private final PebbleEngine pebbleEngine; - private final UserRepository userRepository; - private final SendMailPublisherConfig publisherConfig; - private final SecretDecryptor secretDecryptor; - private final Vertx vertx; - - @Inject - SendMailPublisher(@Named("pebbleEnginePlainText") final PebbleEngine pebbleEngine, - final UserRepository userRepository, - final SendMailPublisherConfig publisherConfig, - final SecretDecryptor secretDecryptor, - final Vertx vertx) { - this.pebbleEngine = pebbleEngine; - this.userRepository = userRepository; - this.secretDecryptor = secretDecryptor; - this.publisherConfig = publisherConfig; - this.vertx = vertx; - } - - public void inform(final PublishContext ctx, final Notification notification, final JsonObject config) throws Exception { - if (config == null) { - LOGGER.warn("No configuration found; Skipping notification (%s)".formatted(ctx)); - return; - } - final String[] destinations = parseDestination(config); - sendNotification(ctx, notification, config, destinations); - } - - public void inform(final PublishContext ctx, final Notification notification, final JsonObject config, List teams) throws Exception { - if (config == null) { - LOGGER.warn("No configuration found; Skipping notification (%s)".formatted(ctx)); - return; - } - final String[] destinations = parseDestination(config, teams); - sendNotification(ctx, notification, config, destinations); - } - - private void sendNotification(final PublishContext ctx, Notification notification, JsonObject config, String[] destinations) throws IOException { - if (config == null) { - LOGGER.warn("No publisher configuration found; Skipping notification (%s)".formatted(ctx)); - return; - } - if (destinations == null) { - LOGGER.warn("No destination(s) provided; Skipping notification (%s)".formatted(ctx)); - return; - } - - final String content; - try { - final PebbleTemplate template = getTemplate(config); - content = prepareTemplate(notification, template, config); - } catch (IOException | RuntimeException e) { - LOGGER.error("Failed to prepare notification content (%s)".formatted(ctx), e); - return; - } - - boolean smtpEnabled = publisherConfig.isSmtpEnabled().orElse(false); - if (!smtpEnabled) { - LOGGER.warn("SMTP is not enabled; Skipping notification (%s)".formatted(ctx)); - return; - } - - String emailSubjectPrefix; - emailSubjectPrefix = publisherConfig.emailPrefix().orElse(" "); - - final String fromAddress = publisherConfig.fromAddress().orElse(null); - if (fromAddress == null) { - LOGGER.warn("From address is not configured; Skipping notification (%s)".formatted(ctx)); - return; - } - - final MailClient mailClient; - try { - mailClient = createMailClient(); - } catch (RuntimeException e) { - LOGGER.error("Failed to create mail client; Skipping notification (%s)".formatted(ctx), e); - return; - } - - try { - for (String destination : destinations) { - final var message = new MailMessage(); - message.setFrom(fromAddress); - message.setTo(destination); - message.setSubject(emailSubjectPrefix + " " + notification.getTitle()); - message.setText(content); - - mailClient.sendMailAndAwait(message); - LOGGER.info("Notification successfully sent to destination {} ({})", destination, ctx); - } - } catch (Exception e) { - LOGGER.error("An error occurred sending output email notification ({})", ctx, e); - } finally { - mailClient.closeAndForget(); - } - } - - @Override - public PebbleEngine getTemplateEngine() { - return pebbleEngine; - } - - public static String[] parseDestination(final JsonObject config) { - JsonString destinationString = config.getJsonString("destination"); - if ((destinationString == null) || destinationString.getString().isEmpty()) { - return null; - } - return destinationString.getString().split(","); - } - - String[] parseDestination(final JsonObject config, final List teams) { - String[] destination = teams.stream().flatMap( - team -> Stream.of( - Optional.ofNullable(config.getJsonString("destination")) - .map(JsonString::getString) - .stream() - .flatMap(dest -> Arrays.stream(dest.split(","))) - .filter(Predicate.not(String::isEmpty)), - Optional.ofNullable(userRepository.findEmailsByTeam(team.getId())).orElseGet(Collections::emptyList).stream() - ) - .reduce(Stream::concat) - .orElseGet(Stream::empty) - ) - .distinct() - .toArray(String[]::new); - return destination.length == 0 ? null : destination; - } - - private MailClient createMailClient() { - // TODO: Can we cache clients based on configuration values? - // Ideally we wouldn't create a new client instance if the config did not change. - // Caching needs to accommodate for closing of old instances to prevent resource leakage. - - final var mailConfig = new MailConfig(); - mailConfig.setHostname(publisherConfig.serverHostname() - .orElseThrow(() -> new IllegalArgumentException("No server hostname configured"))); - mailConfig.setPort(publisherConfig.serverPort() - .orElseThrow(() -> new IllegalArgumentException("No server port configured"))); - - publisherConfig.username().ifPresent(mailConfig::setUsername); - publisherConfig.password() - .map(encryptedPassword -> { - try { - return secretDecryptor.decryptAsString(encryptedPassword); - } catch (Exception e) { - throw new RuntimeException("Failed to decrypt password", e); - } - }) - .ifPresent(mailConfig::setPassword); - - publisherConfig.tlsEnabled().ifPresent(mailConfig::setSsl); - publisherConfig.trustCertificate().ifPresent(mailConfig::setTrustAll); - - return MailClient.create(vertx, mailConfig); - } - -} diff --git a/notification-publisher/src/main/java/org/dependencytrack/notification/publisher/SendMailPublisherConfig.java b/notification-publisher/src/main/java/org/dependencytrack/notification/publisher/SendMailPublisherConfig.java deleted file mode 100644 index ba69854f62..0000000000 --- a/notification-publisher/src/main/java/org/dependencytrack/notification/publisher/SendMailPublisherConfig.java +++ /dev/null @@ -1,107 +0,0 @@ -/* - * 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 io.smallrye.config.ConfigMapping; -import jakarta.enterprise.context.ApplicationScoped; -import jakarta.inject.Provider; -import org.eclipse.microprofile.config.inject.ConfigProperty; - -import java.util.Optional; - -/** - * As of Quarkus 3.9 / smallrye-config 3.7, it is not possible to use {@link ConfigMapping} - * interfaces with {@link Provider} fields. We need {@link Provider} fields in order to support - * configuration changes at runtime. Refer to Injecting Dynamic Values in the - * {@link ConfigProperty} JavaDoc for details. - * - * @see Related smallrye-config issue - */ -@ApplicationScoped -class SendMailPublisherConfig { - - private final Provider> smtpEnabledProvider; - private final Provider> fromAddressProvider; - private final Provider> serverHostnameProvider; - private final Provider> serverPortProvider; - private final Provider> usernameProvider; - private final Provider> passwordProvider; - private final Provider> tlsEnabledProvider; - private final Provider> trustCertificateProvider; - private final Provider> emailPrefixProvider; - - SendMailPublisherConfig( - @ConfigProperty(name = "dtrack.email.smtp.enabled") final Provider> smtpEnabledProvider, - @ConfigProperty(name = "dtrack.email.smtp.from.address") final Provider> fromAddressProvider, - @ConfigProperty(name = "dtrack.email.smtp.server.hostname") final Provider> serverHostnameProvider, - @ConfigProperty(name = "dtrack.email.smtp.server.port") final Provider> serverPortProvider, - @ConfigProperty(name = "dtrack.email.smtp.username") final Provider> usernameProvider, - @ConfigProperty(name = "dtrack.email.smtp.password") final Provider> passwordProvider, - @ConfigProperty(name = "dtrack.email.smtp.ssltls") final Provider> tlsEnabledProvider, - @ConfigProperty(name = "dtrack.email.smtp.trustcert") final Provider> trustCertificateProvider, - @ConfigProperty(name = "dtrack.email.subject.prefix") final Provider> emailPrefixProvider - ) { - this.smtpEnabledProvider = smtpEnabledProvider; - this.fromAddressProvider = fromAddressProvider; - this.serverHostnameProvider = serverHostnameProvider; - this.serverPortProvider = serverPortProvider; - this.usernameProvider = usernameProvider; - this.passwordProvider = passwordProvider; - this.tlsEnabledProvider = tlsEnabledProvider; - this.trustCertificateProvider = trustCertificateProvider; - this.emailPrefixProvider = emailPrefixProvider; - } - - Optional isSmtpEnabled() { - return smtpEnabledProvider.get(); - } - - Optional emailPrefix() { - return emailPrefixProvider.get(); - } - - Optional fromAddress() { - return fromAddressProvider.get(); - } - - Optional serverHostname() { - return serverHostnameProvider.get(); - } - - Optional serverPort() { - return serverPortProvider.get(); - } - - Optional username() { - return usernameProvider.get(); - } - - Optional password() { - return passwordProvider.get(); - } - - Optional tlsEnabled() { - return tlsEnabledProvider.get(); - } - - Optional trustCertificate() { - return trustCertificateProvider.get(); - } - -} diff --git a/notification-publisher/src/main/java/org/dependencytrack/notification/publisher/SlackPublisher.java b/notification-publisher/src/main/java/org/dependencytrack/notification/publisher/SlackPublisher.java deleted file mode 100644 index 8c66fa0ce4..0000000000 --- a/notification-publisher/src/main/java/org/dependencytrack/notification/publisher/SlackPublisher.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * 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 io.pebbletemplates.pebble.PebbleEngine; -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.proto.notification.v1.Notification; - -@ApplicationScoped -@Startup // Force bean creation even though no direct injection points exist -public class SlackPublisher extends AbstractWebhookPublisher implements Publisher { - - private final PebbleEngine pebbleEngine; - - @Inject - public SlackPublisher(@Named("pebbleEngineJson") final PebbleEngine pebbleEngine) { - this.pebbleEngine = pebbleEngine; - } - - public void inform(final PublishContext ctx, final Notification notification, final JsonObject config) throws Exception { - publish(ctx, getTemplate(config), notification, config); - } - - @Override - public PebbleEngine getTemplateEngine() { - return pebbleEngine; - } - -} diff --git a/notification-publisher/src/main/java/org/dependencytrack/notification/publisher/WebhookPublisher.java b/notification-publisher/src/main/java/org/dependencytrack/notification/publisher/WebhookPublisher.java deleted file mode 100644 index 54bbf0fb6a..0000000000 --- a/notification-publisher/src/main/java/org/dependencytrack/notification/publisher/WebhookPublisher.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * 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 io.pebbletemplates.pebble.PebbleEngine; -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.proto.notification.v1.Notification; - -@ApplicationScoped -@Startup // Force bean creation even though no direct injection points exist -public class WebhookPublisher extends AbstractWebhookPublisher implements Publisher { - - private final PebbleEngine pebbleEngine; - - @Inject - public WebhookPublisher(@Named("pebbleEngineJson") final PebbleEngine pebbleEngine) { - this.pebbleEngine = pebbleEngine; - } - - public void inform(final PublishContext ctx, final Notification notification, final JsonObject config) throws Exception { - publish(ctx, getTemplate(config), notification, config); - } - - @Override - public PebbleEngine getTemplateEngine() { - return pebbleEngine; - } - -} diff --git a/notification-publisher/src/main/java/org/dependencytrack/notification/serialization/NotificationKafkaProtobufDeserializer.java b/notification-publisher/src/main/java/org/dependencytrack/notification/serialization/NotificationKafkaProtobufDeserializer.java deleted file mode 100644 index e3940670f1..0000000000 --- a/notification-publisher/src/main/java/org/dependencytrack/notification/serialization/NotificationKafkaProtobufDeserializer.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * 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.serialization; - -import io.quarkus.runtime.annotations.RegisterForReflection; -import org.dependencytrack.proto.KafkaProtobufDeserializer; -import org.dependencytrack.proto.notification.v1.Notification; - -@RegisterForReflection -public class NotificationKafkaProtobufDeserializer extends KafkaProtobufDeserializer { - - public NotificationKafkaProtobufDeserializer() { - super(Notification.parser()); - } - -} \ No newline at end of file diff --git a/notification-publisher/src/main/java/org/dependencytrack/notification/template/extension/CustomExtension.java b/notification-publisher/src/main/java/org/dependencytrack/notification/template/extension/CustomExtension.java deleted file mode 100644 index f431a5c5bd..0000000000 --- a/notification-publisher/src/main/java/org/dependencytrack/notification/template/extension/CustomExtension.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * 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.template.extension; - -import io.pebbletemplates.pebble.attributes.AttributeResolver; -import io.pebbletemplates.pebble.extension.Extension; -import io.pebbletemplates.pebble.extension.Filter; -import io.pebbletemplates.pebble.extension.Function; -import io.pebbletemplates.pebble.extension.NodeVisitorFactory; -import io.pebbletemplates.pebble.extension.Test; -import io.pebbletemplates.pebble.operator.BinaryOperator; -import io.pebbletemplates.pebble.operator.UnaryOperator; -import io.pebbletemplates.pebble.tokenParser.TokenParser; - -import java.util.List; -import java.util.Map; - -public class CustomExtension implements Extension { - - @Override - public Map getFilters() { - return Map.of("summarize", new SummarizeFilter()); - } - - @Override - public Map getTests() { - return null; - } - - @Override - public Map getFunctions() { - return null; - } - - @Override - public List getTokenParsers() { - return null; - } - - @Override - public List getBinaryOperators() { - return null; - } - - @Override - public List getUnaryOperators() { - return null; - } - - @Override - public Map getGlobalVariables() { - return null; - } - - @Override - public List getNodeVisitors() { - return null; - } - - @Override - public List getAttributeResolver() { - return null; - } - -} diff --git a/notification-publisher/src/main/java/org/dependencytrack/notification/template/extension/SummarizeFilter.java b/notification-publisher/src/main/java/org/dependencytrack/notification/template/extension/SummarizeFilter.java deleted file mode 100644 index f439de240d..0000000000 --- a/notification-publisher/src/main/java/org/dependencytrack/notification/template/extension/SummarizeFilter.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * 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.template.extension; - -import io.pebbletemplates.pebble.error.PebbleException; -import io.pebbletemplates.pebble.extension.Filter; -import io.pebbletemplates.pebble.template.EvaluationContext; -import io.pebbletemplates.pebble.template.PebbleTemplate; -import org.dependencytrack.proto.notification.v1.Component; -import org.dependencytrack.proto.notification.v1.Project; - -import java.util.List; -import java.util.Map; - -public class SummarizeFilter implements Filter { - - @Override - public Object apply(final Object input, final Map args, final PebbleTemplate self, - final EvaluationContext context, final int lineNumber) throws PebbleException { - if (input instanceof final Project project) { - return summarize(project); - } else if (input instanceof final Component component) { - return summarize(component); - } - - return String.valueOf(input); - } - - @Override - public List getArgumentNames() { - return null; - } - - private static String summarize(final Project project) { - if (!project.getPurl().isBlank()) { - return project.getPurl(); - } else { - final var sb = new StringBuilder(); - sb.append(project.getName()); - if (!project.getVersion().isBlank()) { - sb.append(" : ").append(project.getVersion()); - } - return sb.toString(); - } - } - - private static String summarize(final Component component) { - if (!component.getPurl().isBlank()) { - return component.getPurl(); - } else { - final var sb = new StringBuilder(); - if (!component.getGroup().isBlank()) { - sb.append(component.getGroup()).append(" : "); - } - sb.append(component.getName()); - if (!component.getVersion().isBlank()) { - sb.append(" : ").append(component.getVersion()); - } - return sb.toString(); - } - } - -} diff --git a/notification-publisher/src/main/java/org/dependencytrack/notification/util/ModelConverter.java b/notification-publisher/src/main/java/org/dependencytrack/notification/util/ModelConverter.java deleted file mode 100644 index 3fe0c84c81..0000000000 --- a/notification-publisher/src/main/java/org/dependencytrack/notification/util/ModelConverter.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * 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.util; - -import org.dependencytrack.persistence.model.NotificationGroup; -import org.dependencytrack.persistence.model.NotificationLevel; -import org.dependencytrack.proto.notification.v1.Group; -import org.dependencytrack.proto.notification.v1.Level; - -public final class ModelConverter { - - private ModelConverter() { - } - - public static NotificationLevel convert(final Level level) { - if (level == null) { - throw new IllegalArgumentException("level must not be null"); - } - - return switch (level) { - case LEVEL_ERROR -> NotificationLevel.ERROR; - case LEVEL_WARNING -> NotificationLevel.WARNING; - case LEVEL_INFORMATIONAL -> NotificationLevel.INFORMATIONAL; - default -> throw new IllegalArgumentException("Unknown level: " + level); - }; - } - - public static NotificationGroup convert(final Group group) { - if (group == null) { - throw new IllegalArgumentException("group must not be null"); - } - - return switch (group) { - case GROUP_CONFIGURATION -> NotificationGroup.CONFIGURATION; - case GROUP_DATASOURCE_MIRRORING -> NotificationGroup.DATASOURCE_MIRRORING; - case GROUP_REPOSITORY -> NotificationGroup.REPOSITORY; - case GROUP_INTEGRATION -> NotificationGroup.INTEGRATION; - case GROUP_FILE_SYSTEM -> NotificationGroup.FILE_SYSTEM; - case GROUP_ANALYZER -> NotificationGroup.ANALYZER; - case GROUP_NEW_VULNERABILITY -> NotificationGroup.NEW_VULNERABILITY; - case GROUP_NEW_VULNERABLE_DEPENDENCY -> NotificationGroup.NEW_VULNERABLE_DEPENDENCY; - case GROUP_PROJECT_AUDIT_CHANGE -> NotificationGroup.PROJECT_AUDIT_CHANGE; - case GROUP_BOM_CONSUMED -> NotificationGroup.BOM_CONSUMED; - case GROUP_BOM_PROCESSED -> NotificationGroup.BOM_PROCESSED; - case GROUP_BOM_PROCESSING_FAILED -> NotificationGroup.BOM_PROCESSING_FAILED; - case GROUP_BOM_VALIDATION_FAILED -> NotificationGroup.BOM_VALIDATION_FAILED; - case GROUP_VEX_CONSUMED -> NotificationGroup.VEX_CONSUMED; - case GROUP_VEX_PROCESSED -> NotificationGroup.VEX_PROCESSED; - case GROUP_POLICY_VIOLATION -> NotificationGroup.POLICY_VIOLATION; - case GROUP_PROJECT_CREATED -> NotificationGroup.PROJECT_CREATED; - case GROUP_PROJECT_VULN_ANALYSIS_COMPLETE -> NotificationGroup.PROJECT_VULN_ANALYSIS_COMPLETE; - case GROUP_USER_CREATED -> NotificationGroup.USER_CREATED; - case GROUP_USER_DELETED -> NotificationGroup.USER_DELETED; - default -> throw new IllegalArgumentException("Unknown group: " + group); - }; - } - -} diff --git a/notification-publisher/src/main/resources/application.properties b/notification-publisher/src/main/resources/application.properties deleted file mode 100644 index a7aba87caf..0000000000 --- a/notification-publisher/src/main/resources/application.properties +++ /dev/null @@ -1,72 +0,0 @@ -## Quarkus -# -quarkus.application.name=hyades-notification-publisher -quarkus.http.port=8090 - -## Logging -# -quarkus.log.console.json=false -quarkus.log.category."org.apache.kafka".level=WARN -quarkus.log.category."pl.tlinkowski.unij".level=ERROR - -%test.dtrack.internal.cluster.id=${quarkus.uuid} - -## Kafka -# -%dev.kafka.bootstrap.servers=localhost:9092 -kafka.consumer.group.id=${quarkus.application.name} -quarkus.kafka.snappy.enabled=true - -dt.kafka.topic.prefix= - -# Quarkus' ClassLoader black magic doesn't play well with -# native libraries like the one required by Snappy. -# It's causing failures when multiple tests with different -# TestProfile are executed in the same test run. -%test.quarkus.kafka.snappy.enabled=false -%test.kafka.compression.type=none - -## Dev Services for Kafka -# -quarkus.kafka.devservices.image-name=docker.redpanda.com/redpandadata/redpanda:v25.3.4 -quarkus.kafka.devservices.topic-partitions."dtrack.notification.new-vulnerability"=1 - -##quarkus hibernate properties -quarkus.datasource.db-kind=postgresql - -# Always use quotes for keywords, column- and table names. -# e.g. SELECT "FOO"."BAR" FROM "BAZ". This matches what the API server does, -# and is required for compatibility with its schema. -quarkus.hibernate-orm.quote-identifiers.strategy=all - -# Hibernate should only validate that the existing schema matches our entity classes, -# but it should never generate a schema by itself. -quarkus.hibernate-orm.schema-management.strategy=validate - -# Use external Postgres DB for dev mode (./mvnw quarkus:dev), but let Quarkus -# take care of test container creation in the test profile. -# See https://quarkus.io/guides/databases-dev-services -%dev.quarkus.datasource.username=dtrack -%dev.quarkus.datasource.password=dtrack -%dev.quarkus.datasource.jdbc.url=jdbc:postgresql://localhost:5432/dtrack -quarkus.datasource.devservices.image-name=postgres:14-alpine -quarkus.datasource.devservices.init-script-path=schema.sql - -quarkus.hibernate-orm.active=true - -## Parallel Consumer -# -parallel-consumer.max-concurrency=6 -parallel-consumer.retry.initial-delay=3S -parallel-consumer.retry.multiplier=2 -parallel-consumer.retry.randomization-factor=0.3 -parallel-consumer.retry.max-duration=2M - -## Secret Decryption -# -%test.secret.key.path=src/test/resources/secret.key - -## Container Image -# -quarkus.container-image.registry=ghcr.io -quarkus.container-image.group=dependencytrack diff --git a/notification-publisher/src/test/java/org/dependencytrack/notification/NotificationConstantsTest.java b/notification-publisher/src/test/java/org/dependencytrack/notification/NotificationConstantsTest.java deleted file mode 100644 index 9beed108bd..0000000000 --- a/notification-publisher/src/test/java/org/dependencytrack/notification/NotificationConstantsTest.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * 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; - -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; - -public class NotificationConstantsTest { - - @Test - public void testConstants() { - Assertions.assertEquals("Notification Test", NotificationConstants.Title.NOTIFICATION_TEST); - Assertions.assertEquals("NVD Mirroring", NotificationConstants.Title.NVD_MIRROR); - Assertions.assertEquals("NPM Advisory Mirroring", NotificationConstants.Title.NPM_ADVISORY_MIRROR); - Assertions.assertEquals("VulnDB Mirroring", NotificationConstants.Title.VULNDB_MIRROR); - Assertions.assertEquals("File System Error", NotificationConstants.Title.FILE_SYSTEM_ERROR); - Assertions.assertEquals("Repository Error", NotificationConstants.Title.REPO_ERROR); - Assertions.assertEquals("Integration Error", NotificationConstants.Title.INTEGRATION_ERROR); - Assertions.assertEquals("New Vulnerability Identified", NotificationConstants.Title.NEW_VULNERABILITY); - Assertions.assertEquals("Vulnerable Dependency Introduced", NotificationConstants.Title.NEW_VULNERABLE_DEPENDENCY); - Assertions.assertEquals("Analysis Decision: Exploitable", NotificationConstants.Title.ANALYSIS_DECISION_EXPLOITABLE); - Assertions.assertEquals("Analysis Decision: In Triage", NotificationConstants.Title.ANALYSIS_DECISION_IN_TRIAGE); - Assertions.assertEquals("Analysis Decision: False Positive", NotificationConstants.Title.ANALYSIS_DECISION_FALSE_POSITIVE); - Assertions.assertEquals("Analysis Decision: Not Affected", NotificationConstants.Title.ANALYSIS_DECISION_NOT_AFFECTED); - Assertions.assertEquals("Analysis Decision: Marking Finding as NOT SET", NotificationConstants.Title.ANALYSIS_DECISION_NOT_SET); - Assertions.assertEquals("Analysis Decision: Finding Suppressed", NotificationConstants.Title.ANALYSIS_DECISION_SUPPRESSED); - Assertions.assertEquals("Analysis Decision: Finding UnSuppressed", NotificationConstants.Title.ANALYSIS_DECISION_UNSUPPRESSED); - Assertions.assertEquals("Analysis Decision: Finding Resolved", NotificationConstants.Title.ANALYSIS_DECISION_RESOLVED); - Assertions.assertEquals("User Created", NotificationConstants.Title.USER_CREATED); - Assertions.assertEquals("User Deleted", NotificationConstants.Title.USER_DELETED); - } -} diff --git a/notification-publisher/src/test/java/org/dependencytrack/notification/NotificationRouterIT.java b/notification-publisher/src/test/java/org/dependencytrack/notification/NotificationRouterIT.java deleted file mode 100644 index 49cdae892b..0000000000 --- a/notification-publisher/src/test/java/org/dependencytrack/notification/NotificationRouterIT.java +++ /dev/null @@ -1,192 +0,0 @@ -/* - * 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; - -import com.github.tomakehurst.wiremock.client.WireMock; -import com.google.protobuf.Any; -import com.google.protobuf.util.Timestamps; -import io.quarkiverse.wiremock.devservice.ConnectWireMock; -import io.quarkiverse.wiremock.devservice.WireMockConfigKey; -import io.quarkus.test.common.DevServicesContext; -import io.quarkus.test.common.QuarkusTestResource; -import io.quarkus.test.junit.QuarkusIntegrationTest; -import io.quarkus.test.junit.QuarkusTestProfile; -import io.quarkus.test.junit.TestProfile; -import io.quarkus.test.kafka.InjectKafkaCompanion; -import io.quarkus.test.kafka.KafkaCompanionResource; -import io.smallrye.reactive.messaging.kafka.companion.KafkaCompanion; -import org.apache.kafka.clients.producer.ProducerRecord; -import org.apache.kafka.common.serialization.Serdes; -import org.awaitility.Awaitility; -import org.dependencytrack.common.KafkaTopic; -import org.dependencytrack.notification.publisher.PublisherTestUtil; -import org.dependencytrack.proto.KafkaProtobufSerde; -import org.dependencytrack.proto.notification.v1.Component; -import org.dependencytrack.proto.notification.v1.Group; -import org.dependencytrack.proto.notification.v1.Level; -import org.dependencytrack.proto.notification.v1.NewVulnerabilitySubject; -import org.dependencytrack.proto.notification.v1.Notification; -import org.dependencytrack.proto.notification.v1.Project; -import org.dependencytrack.proto.notification.v1.Scope; -import org.dependencytrack.proto.notification.v1.Vulnerability; -import org.eclipse.microprofile.config.ConfigProvider; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import java.sql.Connection; -import java.sql.DriverManager; -import java.sql.PreparedStatement; -import java.util.Map; -import java.util.concurrent.TimeUnit; - -import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; -import static com.github.tomakehurst.wiremock.client.WireMock.equalTo; -import static com.github.tomakehurst.wiremock.client.WireMock.equalToJson; -import static com.github.tomakehurst.wiremock.client.WireMock.post; -import static com.github.tomakehurst.wiremock.client.WireMock.postRequestedFor; -import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; -import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo; - -@QuarkusIntegrationTest -@TestProfile(NotificationRouterIT.TestProfile.class) -@QuarkusTestResource(KafkaCompanionResource.class) -@ConnectWireMock -class NotificationRouterIT { - - public static class TestProfile implements QuarkusTestProfile { - - @Override - public Map getConfigOverrides() { - return Map.of("client.http.config.proxy-timeout-socket", "2"); - } - - } - - @InjectKafkaCompanion - KafkaCompanion kafkaCompanion; - - WireMock wireMock; - - DevServicesContext devServicesContext; - - @BeforeEach - void beforeEach() throws Exception { - try (final Connection connection = DriverManager.getConnection( - ConfigProvider.getConfig().getValue("quarkus.datasource.jdbc.url", String.class), - ConfigProvider.getConfig().getValue("quarkus.datasource.username", String.class), - ConfigProvider.getConfig().getValue("quarkus.datasource.password", String.class))) { - final PreparedStatement ps = connection.prepareStatement(""" - INSERT INTO "NOTIFICATIONPUBLISHER" - ("ID", "DEFAULT_PUBLISHER", "NAME", "PUBLISHER_CLASS", "TEMPLATE", "TEMPLATE_MIME_TYPE", "UUID") - VALUES - (1, TRUE, 'foo', 'org.dependencytrack.notification.publisher.WebhookPublisher', ?, 'application/json', '1781db56-51a8-462a-858c-6030a2341dfc'); - """); - ps.setString(1, PublisherTestUtil.getTemplateContent("WEBHOOK")); - ps.execute(); - - int port = Integer.parseInt(devServicesContext.devServicesProperties().get(WireMockConfigKey.PORT)); - connection.createStatement().execute(""" - INSERT INTO "NOTIFICATIONRULE" ("ID", "ENABLED", "NAME", "PUBLISHER", "NOTIFY_ON", "NOTIFY_CHILDREN", "LOG_SUCCESSFUL_PUBLISH", "NOTIFICATION_LEVEL", "SCOPE", "UUID", "PUBLISHER_CONFIG") VALUES - (1, true, 'foo', 1, 'NEW_VULNERABILITY', false, false, 'INFORMATIONAL', 'PORTFOLIO', '6b1fee41-4178-4a23-9d1b-e9df79de8e62', '{"destination": "http://localhost:%d/foo"}'); - """.formatted(port)); - } - } - - @Test - void test() { - wireMock.register(post(urlEqualTo("/foo")) - .inScenario("notification-delivery") - .willReturn(aResponse() - .withStatus(204) - .withFixedDelay(5 * 1000)) - .willSetStateTo("first-attempt-timeout")); - - wireMock.register(post(urlEqualTo("/foo")) - .inScenario("notification-delivery") - .whenScenarioStateIs("first-attempt-timeout") - .willReturn(aResponse() - .withStatus(204))); - - final var notification = Notification.newBuilder() - .setScope(Scope.SCOPE_PORTFOLIO) - .setLevel(Level.LEVEL_INFORMATIONAL) - .setGroup(Group.GROUP_NEW_VULNERABILITY) - .setTitle("Test Notification") - .setContent("This is only a test") - .setTimestamp(Timestamps.fromSeconds(666)) - .setSubject(Any.pack(NewVulnerabilitySubject.newBuilder() - .setComponent(Component.newBuilder() - .setUuid("componentUuid") - .setGroup("componentGroup") - .setName("componentName") - .setVersion("componentVersion")) - .setProject(Project.newBuilder() - .setUuid("projectUuid") - .setName("projectName") - .setVersion("projectVersion")) - .setVulnerability(Vulnerability.newBuilder() - .setUuid("vulnUuid") - .setVulnId("vulnId") - .setSource("vulnSource")) - .build())) - .build(); - - kafkaCompanion - .produce(Serdes.String(), new KafkaProtobufSerde<>(Notification.parser())) - .fromRecords(new ProducerRecord<>(KafkaTopic.NOTIFICATION_NEW_VULNERABILITY.getName(), "", notification)); - - Awaitility.await() - .atMost(15, TimeUnit.SECONDS) - .untilAsserted(() -> wireMock.verifyThat(2, postRequestedFor(urlPathEqualTo("/foo")))); - - wireMock.verifyThat(postRequestedFor(urlPathEqualTo("/foo")) - .withHeader("Content-Type", equalTo("application/json")) - .withRequestBody(equalToJson(""" - { - "notification" : { - "level" : "LEVEL_INFORMATIONAL", - "scope" : "SCOPE_PORTFOLIO", - "group" : "GROUP_NEW_VULNERABILITY", - "timestamp" : "1970-01-01T00:11:06.000Z", - "title" : "Test Notification", - "content" : "This is only a test", - "subject" : { - "component" : { - "uuid" : "componentUuid", - "group" : "componentGroup", - "name" : "componentName", - "version" : "componentVersion" - }, - "project" : { - "uuid" : "projectUuid", - "name" : "projectName", - "version" : "projectVersion" - }, - "vulnerability" : { - "uuid" : "vulnUuid", - "vulnId" : "vulnId", - "source" : "vulnSource" - } - } - } - } - """))); - } - -} \ No newline at end of file diff --git a/notification-publisher/src/test/java/org/dependencytrack/notification/NotificationRouterTest.java b/notification-publisher/src/test/java/org/dependencytrack/notification/NotificationRouterTest.java deleted file mode 100644 index 238563d6c0..0000000000 --- a/notification-publisher/src/test/java/org/dependencytrack/notification/NotificationRouterTest.java +++ /dev/null @@ -1,1037 +0,0 @@ -/* - * 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; - -import com.google.protobuf.Any; -import io.quarkus.test.TestTransaction; -import io.quarkus.test.junit.QuarkusMock; -import io.quarkus.test.junit.QuarkusTest; -import jakarta.inject.Inject; -import jakarta.persistence.EntityManager; -import org.assertj.core.api.Assertions; -import org.dependencytrack.notification.publisher.ConsolePublisher; -import org.dependencytrack.notification.publisher.PublisherTestUtil; -import org.dependencytrack.persistence.model.NotificationGroup; -import org.dependencytrack.persistence.model.NotificationLevel; -import org.dependencytrack.persistence.model.NotificationRule; -import org.dependencytrack.persistence.model.NotificationScope; -import org.dependencytrack.proto.notification.v1.BackReference; -import org.dependencytrack.proto.notification.v1.BomConsumedOrProcessedSubject; -import org.dependencytrack.proto.notification.v1.BomProcessingFailedSubject; -import org.dependencytrack.proto.notification.v1.BomValidationFailedSubject; -import org.dependencytrack.proto.notification.v1.Component; -import org.dependencytrack.proto.notification.v1.Level; -import org.dependencytrack.proto.notification.v1.NewVulnerabilitySubject; -import org.dependencytrack.proto.notification.v1.NewVulnerableDependencySubject; -import org.dependencytrack.proto.notification.v1.Notification; -import org.dependencytrack.proto.notification.v1.PolicyViolationAnalysisDecisionChangeSubject; -import org.dependencytrack.proto.notification.v1.PolicyViolationSubject; -import org.dependencytrack.proto.notification.v1.Project; -import org.dependencytrack.proto.notification.v1.UserSubject; -import org.dependencytrack.proto.notification.v1.VexConsumedOrProcessedSubject; -import org.dependencytrack.proto.notification.v1.Vulnerability; -import org.dependencytrack.proto.notification.v1.VulnerabilityAnalysisDecisionChangeSubject; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.CsvSource; -import org.mockito.Mockito; - -import java.util.Date; -import java.util.List; -import java.util.UUID; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.dependencytrack.proto.notification.v1.Group.GROUP_BOM_CONSUMED; -import static org.dependencytrack.proto.notification.v1.Group.GROUP_BOM_PROCESSED; -import static org.dependencytrack.proto.notification.v1.Group.GROUP_BOM_PROCESSING_FAILED; -import static org.dependencytrack.proto.notification.v1.Group.GROUP_BOM_VALIDATION_FAILED; -import static org.dependencytrack.proto.notification.v1.Group.GROUP_NEW_VULNERABILITY; -import static org.dependencytrack.proto.notification.v1.Group.GROUP_NEW_VULNERABLE_DEPENDENCY; -import static org.dependencytrack.proto.notification.v1.Group.GROUP_POLICY_VIOLATION; -import static org.dependencytrack.proto.notification.v1.Group.GROUP_PROJECT_AUDIT_CHANGE; -import static org.dependencytrack.proto.notification.v1.Group.GROUP_USER_CREATED; -import static org.dependencytrack.proto.notification.v1.Group.GROUP_VEX_CONSUMED; -import static org.dependencytrack.proto.notification.v1.Level.LEVEL_ERROR; -import static org.dependencytrack.proto.notification.v1.Level.LEVEL_INFORMATIONAL; -import static org.dependencytrack.proto.notification.v1.Scope.SCOPE_PORTFOLIO; -import static org.dependencytrack.proto.notification.v1.Scope.SCOPE_SYSTEM; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.verify; - -@QuarkusTest -class NotificationRouterTest { - - @Inject - EntityManager entityManager; - - @Inject - NotificationRouter notificationRouter; - - private ConsolePublisher consolePublisherMock; - - @BeforeEach - void setUp() { - consolePublisherMock = Mockito.mock(ConsolePublisher.class); - QuarkusMock.installMockForType(consolePublisherMock, ConsolePublisher.class); - } - - @Test - @TestTransaction - void testResolveRulesWithNullNotification() throws Exception { - Assertions.assertThat(notificationRouter.resolveRules(null, null)).isEmpty(); - } - - @Test - @TestTransaction - void testResolveRulesWithInvalidNotification() throws Exception { - Assertions.assertThat(notificationRouter.resolveRules(null, Notification.newBuilder().build())).isEmpty(); - } - - @Test - @TestTransaction - void testResolveRulesWithNoRules() throws Exception { - final var notification = Notification.newBuilder() - .setScope(SCOPE_PORTFOLIO) - .setLevel(LEVEL_INFORMATIONAL) - .setGroup(GROUP_NEW_VULNERABILITY) - .build(); - Assertions.assertThat(notificationRouter.resolveRules(PublisherTestUtil.createPublisherContext(notification), notification)).isEmpty(); - } - - @Test - @TestTransaction - void testResolveRulesWithValidMatchingRule() throws Exception { - final Long publisherId = createConsolePublisher(); - // Creates a new rule and defines when the rule should be triggered (notifyOn) - createRule("Test Rule", - NotificationScope.PORTFOLIO, NotificationLevel.INFORMATIONAL, - NotificationGroup.NEW_VULNERABILITY, publisherId); - // Creates a new notification - final var notification = Notification.newBuilder() - .setScope(SCOPE_PORTFOLIO) - .setGroup(GROUP_NEW_VULNERABILITY) - .setLevel(LEVEL_INFORMATIONAL) - .setSubject(Any.pack(NewVulnerabilitySubject.newBuilder().build())) - .build(); - // Ok, let's test this - final List rules = notificationRouter.resolveRules(PublisherTestUtil.createPublisherContext(notification), notification); - assertThat(rules).satisfiesExactly( - rule -> assertThat(rule.getName()).isEqualTo("Test Rule") - ); - } - - @Test - @TestTransaction - void testResolveRulesWithValidMatchingProjectLimitRule() throws Exception { - final Long publisherId = createConsolePublisher(); - // Creates a new rule and defines when the rule should be triggered (notifyOn) - final Long ruleId = createRule("Test Rule", - NotificationScope.PORTFOLIO, NotificationLevel.INFORMATIONAL, - NotificationGroup.NEW_VULNERABILITY, publisherId); - // Creates a project which will later be matched on - final UUID projectUuid = UUID.randomUUID(); - final Long projectId = createProject("Test Project", "1.0", null, projectUuid); - addProjectToRule(projectId, ruleId); - // Creates a new notification - final var notification = Notification.newBuilder() - .setScope(SCOPE_PORTFOLIO) - .setGroup(GROUP_NEW_VULNERABILITY) - .setLevel(LEVEL_INFORMATIONAL) - .setSubject(Any.pack(NewVulnerabilitySubject.newBuilder() - .setComponent(Component.newBuilder() - .setUuid(UUID.randomUUID().toString())) - .setProject(Project.newBuilder() - .setUuid(projectUuid.toString())) - .setVulnerability(Vulnerability.newBuilder() - .setUuid(UUID.randomUUID().toString())) - .setAffectedProjectsReference(BackReference.newBuilder() - .setApiUri("foo") - .setFrontendUri("bar")) - .addAffectedProjects(Project.newBuilder() - .setUuid(projectUuid.toString())) - .build())) - .build(); - // Ok, let's test this - final List rules = notificationRouter.resolveRules(PublisherTestUtil.createPublisherContext(notification), notification); - assertThat(rules).satisfiesExactly( - rule -> assertThat(rule.getName()).isEqualTo("Test Rule") - ); - } - - @Test - @TestTransaction - void testResolveRulesWithValidNonMatchingProjectLimitRule() throws Exception { - final Long publisherId = createConsolePublisher(); - // Creates a new rule and defines when the rule should be triggered (notifyOn) - final Long ruleId = createRule("Test Rule", - NotificationScope.PORTFOLIO, NotificationLevel.INFORMATIONAL, - NotificationGroup.NEW_VULNERABILITY, publisherId); - // Creates a project which will later be matched on - final UUID projectUuid = UUID.randomUUID(); - final Long projectId = createProject("Test Project", "1.0", null, projectUuid); - addProjectToRule(projectId, ruleId); - // Creates a new notification - final var notification = Notification.newBuilder() - .setScope(SCOPE_PORTFOLIO) - .setGroup(GROUP_NEW_VULNERABILITY) - .setLevel(LEVEL_INFORMATIONAL) - .setSubject(Any.pack(NewVulnerabilitySubject.newBuilder() - .setComponent(Component.newBuilder() - .setUuid(UUID.randomUUID().toString())) - .setProject(Project.newBuilder() - .setUuid(UUID.randomUUID().toString())) - .setVulnerability(Vulnerability.newBuilder() - .setUuid(UUID.randomUUID().toString())) - .setAffectedProjectsReference(BackReference.newBuilder() - .setApiUri("foo") - .setFrontendUri("bar")) - .addAffectedProjects(Project.newBuilder() - .setUuid(projectUuid.toString())) - .build())) - .build(); - // Ok, let's test this - final List rules = notificationRouter.resolveRules(PublisherTestUtil.createPublisherContext(notification), notification); - assertThat(rules).isEmpty(); - } - - @Test - @TestTransaction - void testResolveRulesWithValidNonMatchingRule() throws Exception { - final Long publisherId = createConsolePublisher(); - // Creates a new rule and defines when the rule should be triggered (notifyOn) - final Long ruleId = createRule("Test Rule", - NotificationScope.PORTFOLIO, NotificationLevel.INFORMATIONAL, - NotificationGroup.PROJECT_AUDIT_CHANGE, publisherId); - // Creates a project which will later be matched on - final UUID projectUuid = UUID.randomUUID(); - final Long projectId = createProject("Test Project", "1.0", null, projectUuid); - addProjectToRule(projectId, ruleId); - // Creates a new notification - final var notification = Notification.newBuilder() - .setScope(SCOPE_PORTFOLIO) - .setGroup(GROUP_NEW_VULNERABILITY) - .setLevel(LEVEL_INFORMATIONAL) - .setSubject(Any.pack(NewVulnerabilitySubject.newBuilder() - .setComponent(Component.newBuilder() - .setUuid(UUID.randomUUID().toString())) - .setProject(Project.newBuilder() - .setUuid(projectUuid.toString())) - .setVulnerability(Vulnerability.newBuilder() - .setUuid(UUID.randomUUID().toString())) - .setAffectedProjectsReference(BackReference.newBuilder() - .setApiUri("foo") - .setFrontendUri("bar")) - .addAffectedProjects(Project.newBuilder() - .setUuid(projectUuid.toString())) - .build())) - .build(); - // Ok, let's test this - final List rules = notificationRouter.resolveRules(PublisherTestUtil.createPublisherContext(notification), notification); - assertThat(rules).isEmpty(); - } - - @TestTransaction - @ParameterizedTest - @CsvSource(value = { - "WARNING, LEVEL_WARNING, true", // Levels are equal - "WARNING, LEVEL_ERROR, true", // Rule level is below - "WARNING, LEVEL_INFORMATIONAL, false" // Rule level is above - }) - void testResolveRulesLevels(final NotificationLevel ruleLevel, final Level notificationLevel, - final boolean expectedMatch) throws Exception { - final Long publisherId = createConsolePublisher(); - createRule("Test Levels Rule", - NotificationScope.PORTFOLIO, ruleLevel, - NotificationGroup.BOM_PROCESSED, publisherId); - - final var notification = Notification.newBuilder() - .setScope(SCOPE_PORTFOLIO) - .setGroup(GROUP_BOM_PROCESSED) - .setLevel(notificationLevel) - .build(); - - if (expectedMatch) { - Assertions.assertThat(notificationRouter.resolveRules(PublisherTestUtil.createPublisherContext(notification), notification)).satisfiesExactly( - rule -> Assertions.assertThat(rule.getName()).isEqualTo("Test Levels Rule") - ); - } else { - Assertions.assertThat(notificationRouter.resolveRules(PublisherTestUtil.createPublisherContext(notification), notification)).isEmpty(); - } - } - - @Test - @TestTransaction - void testResolveRulesWithDisabledRule() throws Exception { - final Long publisherId = createConsolePublisher(); - final Long ruleId = createRule("Test Rule", - NotificationScope.PORTFOLIO, NotificationLevel.INFORMATIONAL, - NotificationGroup.NEW_VULNERABILITY, publisherId); - disableRule(ruleId); - - final var notification = Notification.newBuilder() - .setScope(SCOPE_PORTFOLIO) - .setGroup(GROUP_NEW_VULNERABILITY) - .setLevel(LEVEL_INFORMATIONAL) - .build(); - - Assertions.assertThat(notificationRouter.resolveRules(PublisherTestUtil.createPublisherContext(notification), notification)).isEmpty(); - } - - @Test - @TestTransaction - void testResolveRulesLimitedToProjectForNewVulnerabilityNotification() throws Exception { - final UUID projectUuidA = UUID.randomUUID(); - final Long projectIdA = createProject("Project A", "1.0", null, projectUuidA); - - final UUID projectUuidB = UUID.randomUUID(); - createProject("Project B", "2.0", null, projectUuidB); - - final Long publisherId = createConsolePublisher(); - final Long ruleId = createRule("Limit To Test Rule", - NotificationScope.PORTFOLIO, NotificationLevel.INFORMATIONAL, - NotificationGroup.NEW_VULNERABILITY, publisherId); - addProjectToRule(projectIdA, ruleId); - - final var notificationProjectA = Notification.newBuilder() - .setScope(SCOPE_PORTFOLIO) - .setGroup(GROUP_NEW_VULNERABILITY) - .setLevel(LEVEL_INFORMATIONAL) - .setSubject(Any.pack(NewVulnerabilitySubject.newBuilder() - .setComponent(Component.newBuilder() - .setUuid(UUID.randomUUID().toString())) - .setProject(Project.newBuilder() - .setUuid(projectUuidA.toString())) - .setVulnerability(Vulnerability.newBuilder() - .setUuid(UUID.randomUUID().toString())) - .build())) - .build(); - - Assertions.assertThat(notificationRouter.resolveRules(PublisherTestUtil.createPublisherContext(notificationProjectA), notificationProjectA)).satisfiesExactly( - rule -> Assertions.assertThat(rule.getName()).isEqualTo("Limit To Test Rule") - ); - - final var notificationProjectB = Notification.newBuilder(notificationProjectA) - .setSubject(Any.pack(NewVulnerabilitySubject.newBuilder() - .setComponent(Component.newBuilder() - .setUuid(UUID.randomUUID().toString())) - .setProject(Project.newBuilder() - .setUuid(projectUuidB.toString())) - .setVulnerability(Vulnerability.newBuilder() - .setUuid(UUID.randomUUID().toString())) - .build())) - .build(); - - Assertions.assertThat(notificationRouter.resolveRules(PublisherTestUtil.createPublisherContext(notificationProjectB), notificationProjectB)).isEmpty(); - } - - @Test - @TestTransaction - void testResolveRulesLimitedToProjectForNewVulnerableDependencyNotification() throws Exception { - final UUID projectUuidA = UUID.randomUUID(); - final Long projectIdA = createProject("Project A", "1.0", null, projectUuidA); - - final UUID projectUuidB = UUID.randomUUID(); - createProject("Project B", "2.0", null, projectUuidB); - - final Long publisherId = createConsolePublisher(); - final Long ruleId = createRule("Limit To Test Rule", - NotificationScope.PORTFOLIO, NotificationLevel.INFORMATIONAL, - NotificationGroup.NEW_VULNERABLE_DEPENDENCY, publisherId); - addProjectToRule(projectIdA, ruleId); - - final var notificationProjectA = Notification.newBuilder() - .setScope(SCOPE_PORTFOLIO) - .setGroup(GROUP_NEW_VULNERABLE_DEPENDENCY) - .setLevel(LEVEL_INFORMATIONAL) - .setSubject(Any.pack(NewVulnerableDependencySubject.newBuilder() - .setComponent(Component.newBuilder() - .setUuid(UUID.randomUUID().toString())) - .setProject(Project.newBuilder() - .setUuid(projectUuidA.toString())) - .build())) - .build(); - - Assertions.assertThat(notificationRouter.resolveRules(PublisherTestUtil.createPublisherContext(notificationProjectA), notificationProjectA)).satisfiesExactly( - rule -> Assertions.assertThat(rule.getName()).isEqualTo("Limit To Test Rule") - ); - - final var notificationProjectB = Notification.newBuilder(notificationProjectA) - .setSubject(Any.pack(NewVulnerableDependencySubject.newBuilder() - .setComponent(Component.newBuilder() - .setUuid(UUID.randomUUID().toString())) - .setProject(Project.newBuilder() - .setUuid(projectUuidB.toString())) - .build())) - .build(); - - Assertions.assertThat(notificationRouter.resolveRules(PublisherTestUtil.createPublisherContext(notificationProjectB), notificationProjectB)).isEmpty(); - } - - @Test - @TestTransaction - void testResolveRulesLimitedToProjectForBomConsumedOrProcessedNotification() throws Exception { - final UUID projectUuidA = UUID.randomUUID(); - final Long projectIdA = createProject("Project A", "1.0", null, projectUuidA); - - final UUID projectUuidB = UUID.randomUUID(); - createProject("Project B", "2.0", null, projectUuidB); - - final Long publisherId = createConsolePublisher(); - final Long ruleId = createRule("Limit To Test Rule", - NotificationScope.PORTFOLIO, NotificationLevel.INFORMATIONAL, - NotificationGroup.BOM_CONSUMED, publisherId); - addProjectToRule(projectIdA, ruleId); - - final var notificationProjectA = Notification.newBuilder() - .setScope(SCOPE_PORTFOLIO) - .setGroup(GROUP_BOM_CONSUMED) - .setLevel(LEVEL_INFORMATIONAL) - .setSubject(Any.pack(BomConsumedOrProcessedSubject.newBuilder() - .setProject(Project.newBuilder() - .setUuid(projectUuidA.toString())) - .build())) - .build(); - - Assertions.assertThat(notificationRouter.resolveRules(PublisherTestUtil.createPublisherContext(notificationProjectA), notificationProjectA)).satisfiesExactly( - rule -> Assertions.assertThat(rule.getName()).isEqualTo("Limit To Test Rule") - ); - - final var notificationProjectB = Notification.newBuilder(notificationProjectA) - .setSubject(Any.pack(BomConsumedOrProcessedSubject.newBuilder() - .setProject(Project.newBuilder() - .setUuid(projectUuidB.toString())) - .build())) - .build(); - - Assertions.assertThat(notificationRouter.resolveRules(PublisherTestUtil.createPublisherContext(notificationProjectB), notificationProjectB)).isEmpty(); - } - - @Test - @TestTransaction - void testResolveRulesLimitedToProjectForVexConsumedOrProcessedNotification() throws Exception { - final UUID projectUuidA = UUID.randomUUID(); - final Long projectIdA = createProject("Project A", "1.0", null, projectUuidA); - - final UUID projectUuidB = UUID.randomUUID(); - createProject("Project B", "2.0", null, projectUuidB); - - final Long publisherId = createConsolePublisher(); - final Long ruleId = createRule("Limit To Test Rule", - NotificationScope.PORTFOLIO, NotificationLevel.INFORMATIONAL, - NotificationGroup.VEX_CONSUMED, publisherId); - addProjectToRule(projectIdA, ruleId); - - final var notificationProjectA = Notification.newBuilder() - .setScope(SCOPE_PORTFOLIO) - .setGroup(GROUP_VEX_CONSUMED) - .setLevel(LEVEL_INFORMATIONAL) - .setSubject(Any.pack(VexConsumedOrProcessedSubject.newBuilder() - .setProject(Project.newBuilder() - .setUuid(projectUuidA.toString())) - .build())) - .build(); - - Assertions.assertThat(notificationRouter.resolveRules(PublisherTestUtil.createPublisherContext(notificationProjectA), notificationProjectA)).satisfiesExactly( - rule -> Assertions.assertThat(rule.getName()).isEqualTo("Limit To Test Rule") - ); - - final var notificationProjectB = Notification.newBuilder(notificationProjectA) - .setSubject(Any.pack(VexConsumedOrProcessedSubject.newBuilder() - .setProject(Project.newBuilder() - .setUuid(projectUuidB.toString())) - .build())) - .build(); - - Assertions.assertThat(notificationRouter.resolveRules(PublisherTestUtil.createPublisherContext(notificationProjectB), notificationProjectB)).isEmpty(); - } - - @Test - @TestTransaction - void testResolveRulesLimitedToProjectForPolicyViolationNotification() throws Exception { - final UUID projectUuidA = UUID.randomUUID(); - final Long projectIdA = createProject("Project A", "1.0", null, projectUuidA); - - final UUID projectUuidB = UUID.randomUUID(); - createProject("Project B", "2.0", null, projectUuidB); - - final Long publisherId = createConsolePublisher(); - final Long ruleId = createRule("Limit To Test Rule", - NotificationScope.PORTFOLIO, NotificationLevel.INFORMATIONAL, - NotificationGroup.POLICY_VIOLATION, publisherId); - addProjectToRule(projectIdA, ruleId); - - final var notificationProjectA = Notification.newBuilder() - .setScope(SCOPE_PORTFOLIO) - .setGroup(GROUP_POLICY_VIOLATION) - .setLevel(LEVEL_INFORMATIONAL) - .setSubject(Any.pack(PolicyViolationSubject.newBuilder() - .setProject(Project.newBuilder() - .setUuid(projectUuidA.toString())) - .build())) - .build(); - - Assertions.assertThat(notificationRouter.resolveRules(PublisherTestUtil.createPublisherContext(notificationProjectA), notificationProjectA)).satisfiesExactly( - rule -> Assertions.assertThat(rule.getName()).isEqualTo("Limit To Test Rule") - ); - - final var notificationProjectB = Notification.newBuilder(notificationProjectA) - .setSubject(Any.pack(PolicyViolationSubject.newBuilder() - .setProject(Project.newBuilder() - .setUuid(projectUuidB.toString())) - .build())) - .build(); - - Assertions.assertThat(notificationRouter.resolveRules(PublisherTestUtil.createPublisherContext(notificationProjectB), notificationProjectB)).isEmpty(); - } - - @Test - @TestTransaction - void testResolveRulesLimitedToProjectForVulnerabilityAnalysisDecisionChangeNotification() throws Exception { - final UUID projectUuidA = UUID.randomUUID(); - final Long projectIdA = createProject("Project A", "1.0", null, projectUuidA); - - final UUID projectUuidB = UUID.randomUUID(); - createProject("Project B", "2.0", null, projectUuidB); - - final Long publisherId = createConsolePublisher(); - final Long ruleId = createRule("Limit To Test Rule", - NotificationScope.PORTFOLIO, NotificationLevel.INFORMATIONAL, - NotificationGroup.PROJECT_AUDIT_CHANGE, publisherId); - addProjectToRule(projectIdA, ruleId); - - final var notificationProjectA = Notification.newBuilder() - .setScope(SCOPE_PORTFOLIO) - .setGroup(GROUP_PROJECT_AUDIT_CHANGE) - .setLevel(LEVEL_INFORMATIONAL) - .setSubject(Any.pack(VulnerabilityAnalysisDecisionChangeSubject.newBuilder() - .setProject(Project.newBuilder() - .setUuid(projectUuidA.toString())) - .build())) - .build(); - - Assertions.assertThat(notificationRouter.resolveRules(PublisherTestUtil.createPublisherContext(notificationProjectA), notificationProjectA)).satisfiesExactly( - rule -> Assertions.assertThat(rule.getName()).isEqualTo("Limit To Test Rule") - ); - - final var notificationProjectB = Notification.newBuilder(notificationProjectA) - .setSubject(Any.pack(VulnerabilityAnalysisDecisionChangeSubject.newBuilder() - .setProject(Project.newBuilder() - .setUuid(projectUuidB.toString())) - .build())) - .build(); - - Assertions.assertThat(notificationRouter.resolveRules(PublisherTestUtil.createPublisherContext(notificationProjectB), notificationProjectB)).isEmpty(); - } - - @Test - @TestTransaction - void testResolveRulesLimitedToProjectForPolicyViolationAnalysisDecisionChangeNotification() throws Exception { - final UUID projectUuidA = UUID.randomUUID(); - final Long projectIdA = createProject("Project A", "1.0", null, projectUuidA); - - final UUID projectUuidB = UUID.randomUUID(); - createProject("Project B", "2.0", null, projectUuidB); - - final Long publisherId = createConsolePublisher(); - final Long ruleId = createRule("Limit To Test Rule", - NotificationScope.PORTFOLIO, NotificationLevel.INFORMATIONAL, - NotificationGroup.PROJECT_AUDIT_CHANGE, publisherId); - addProjectToRule(projectIdA, ruleId); - - final var notificationProjectA = Notification.newBuilder() - .setScope(SCOPE_PORTFOLIO) - .setGroup(GROUP_PROJECT_AUDIT_CHANGE) - .setLevel(LEVEL_INFORMATIONAL) - .setSubject(Any.pack(PolicyViolationAnalysisDecisionChangeSubject.newBuilder() - .setProject(Project.newBuilder() - .setUuid(projectUuidA.toString())) - .build())) - .build(); - - Assertions.assertThat(notificationRouter.resolveRules(PublisherTestUtil.createPublisherContext(notificationProjectA), notificationProjectA)).satisfiesExactly( - rule -> Assertions.assertThat(rule.getName()).isEqualTo("Limit To Test Rule") - ); - - final var notificationProjectB = Notification.newBuilder(notificationProjectA) - .setSubject(Any.pack(PolicyViolationAnalysisDecisionChangeSubject.newBuilder() - .setProject(Project.newBuilder() - .setUuid(projectUuidB.toString())) - .build())) - .build(); - - Assertions.assertThat(notificationRouter.resolveRules(PublisherTestUtil.createPublisherContext(notificationProjectB), notificationProjectB)).isEmpty(); - } - - @Test - @TestTransaction - void testResolveRulesLimitedToProjectForBomProcessingFailedNotification() throws Exception { - final UUID projectUuidA = UUID.randomUUID(); - final Long projectIdA = createProject("Project A", "1.0", null, projectUuidA); - - final UUID projectUuidB = UUID.randomUUID(); - createProject("Project B", "2.0", null, projectUuidB); - - final Long publisherId = createConsolePublisher(); - final Long ruleId = createRule("Limit To Test Rule", - NotificationScope.PORTFOLIO, NotificationLevel.INFORMATIONAL, - NotificationGroup.BOM_PROCESSING_FAILED, publisherId); - addProjectToRule(projectIdA, ruleId); - - final var notificationProjectA = Notification.newBuilder() - .setScope(SCOPE_PORTFOLIO) - .setGroup(GROUP_BOM_PROCESSING_FAILED) - .setLevel(LEVEL_ERROR) - .setSubject(Any.pack(BomProcessingFailedSubject.newBuilder() - .setProject(Project.newBuilder() - .setUuid(projectUuidA.toString())) - .build())) - .build(); - - Assertions.assertThat(notificationRouter.resolveRules(PublisherTestUtil.createPublisherContext(notificationProjectA), notificationProjectA)).satisfiesExactly( - rule -> Assertions.assertThat(rule.getName()).isEqualTo("Limit To Test Rule") - ); - - final var notificationProjectB = Notification.newBuilder(notificationProjectA) - .setSubject(Any.pack(BomProcessingFailedSubject.newBuilder() - .setProject(Project.newBuilder() - .setUuid(projectUuidB.toString())) - .build())) - .build(); - - Assertions.assertThat(notificationRouter.resolveRules(PublisherTestUtil.createPublisherContext(notificationProjectB), notificationProjectB)).isEmpty(); - } - - @Test - @TestTransaction - void testResolveRulesLimitedToProjectForBomValidationFailedNotification() throws Exception { - final UUID projectUuidA = UUID.randomUUID(); - final Long projectIdA = createProject("Project A", "1.0", null, projectUuidA); - - final UUID projectUuidB = UUID.randomUUID(); - createProject("Project B", "2.0", null, projectUuidB); - - final Long publisherId = createConsolePublisher(); - final Long ruleId = createRule("Limit To Test Rule", - NotificationScope.PORTFOLIO, NotificationLevel.INFORMATIONAL, - NotificationGroup.BOM_VALIDATION_FAILED, publisherId); - addProjectToRule(projectIdA, ruleId); - - final var notificationProjectA = Notification.newBuilder() - .setScope(SCOPE_PORTFOLIO) - .setGroup(GROUP_BOM_VALIDATION_FAILED) - .setLevel(LEVEL_ERROR) - .setSubject(Any.pack(BomValidationFailedSubject.newBuilder() - .setProject(Project.newBuilder() - .setUuid(projectUuidA.toString())) - .build())) - .build(); - - Assertions.assertThat(notificationRouter.resolveRules(PublisherTestUtil.createPublisherContext(notificationProjectA), notificationProjectA)).satisfiesExactly( - rule -> Assertions.assertThat(rule.getName()).isEqualTo("Limit To Test Rule") - ); - - final var notificationProjectB = Notification.newBuilder(notificationProjectA) - .setSubject(Any.pack(BomValidationFailedSubject.newBuilder() - .setProject(Project.newBuilder() - .setUuid(projectUuidB.toString())) - .build())) - .build(); - - Assertions.assertThat(notificationRouter.resolveRules(PublisherTestUtil.createPublisherContext(notificationProjectB), notificationProjectB)).isEmpty(); - } - - @Test - @TestTransaction - void testResolveRulesWithAffectedChild() throws Exception { - final Long publisherId = createConsolePublisher(); - // Creates a new rule and defines when the rule should be triggered (notifyOn) - final Long ruleId = createRule("Test Rule", - NotificationScope.PORTFOLIO, NotificationLevel.INFORMATIONAL, - NotificationGroup.NEW_VULNERABILITY, publisherId); - setNotifyChildren(ruleId, true); - // Creates a project which will later be matched on - final UUID grandParentUuid = UUID.randomUUID(); - final Long grandParentProjectId = createProject("Test Project Grandparent", "1.0", null, grandParentUuid); - final UUID parentUuid = UUID.randomUUID(); - final Long parentProjectId = createProject("Test Project Parent", "1.0", null, parentUuid); - setProjectParent(parentProjectId, grandParentProjectId); - final UUID childUuid = UUID.randomUUID(); - final Long childProjectId = createProject("Test Project Child", "1.0", null, childUuid); - setProjectParent(childProjectId, parentProjectId); - final UUID grandChildUuid = UUID.randomUUID(); - final Long grandChildProjectId = createProject("Test Project Grandchild", "1.0", null, grandChildUuid); - setProjectParent(grandChildProjectId, childProjectId); - addProjectToRule(grandParentProjectId, ruleId); - // Creates a new notification - final var notification = Notification.newBuilder() - .setScope(SCOPE_PORTFOLIO) - .setGroup(GROUP_NEW_VULNERABILITY) - .setLevel(LEVEL_INFORMATIONAL) - .setSubject(Any.pack(NewVulnerabilitySubject.newBuilder() - .setComponent(Component.newBuilder() - .setUuid(UUID.randomUUID().toString())) - .setProject(Project.newBuilder() - .setUuid(grandChildUuid.toString())) - .setVulnerability(Vulnerability.newBuilder() - .setUuid(UUID.randomUUID().toString())) - .setAffectedProjectsReference(BackReference.newBuilder() - .setApiUri("foo") - .setFrontendUri("bar")) - .addAffectedProjects(Project.newBuilder() - .setUuid(grandChildUuid.toString())) - .build())) - .build(); - // Ok, let's test this - final List rules = notificationRouter.resolveRules(PublisherTestUtil.createPublisherContext(notification), notification); - assertThat(rules).satisfiesExactly( - rule -> assertThat(rule.getName()).isEqualTo("Test Rule") - ); - } - - @Test - @TestTransaction - void testResolveRulesWithAffectedChildAndNotifyChildrenDisabled() throws Exception { - final Long publisherId = createConsolePublisher(); - // Creates a new rule and defines when the rule should be triggered (notifyOn) - final Long ruleId = createRule("Test Rule", - NotificationScope.PORTFOLIO, NotificationLevel.INFORMATIONAL, - NotificationGroup.NEW_VULNERABILITY, publisherId); - setNotifyChildren(ruleId, false); - // Creates a project which will later be matched on - final UUID grandParentUuid = UUID.randomUUID(); - final Long grandParentProjectId = createProject("Test Project Grandparent", "1.0", null, grandParentUuid); - final UUID parentUuid = UUID.randomUUID(); - final Long parentProjectId = createProject("Test Project Parent", "1.0", null, parentUuid); - setProjectParent(parentProjectId, grandParentProjectId); - final UUID childUuid = UUID.randomUUID(); - final Long childProjectId = createProject("Test Project Child", "1.0", null, childUuid); - setProjectParent(childProjectId, parentProjectId); - final UUID grandChildUuid = UUID.randomUUID(); - final Long grandChildProjectId = createProject("Test Project Grandchild", "1.0", null, grandChildUuid); - setProjectParent(grandChildProjectId, childProjectId); - addProjectToRule(grandParentProjectId, ruleId); - // Creates a new notification - final var notification = Notification.newBuilder() - .setScope(SCOPE_PORTFOLIO) - .setGroup(GROUP_NEW_VULNERABILITY) - .setLevel(LEVEL_INFORMATIONAL) - .setSubject(Any.pack(NewVulnerabilitySubject.newBuilder() - .setComponent(Component.newBuilder() - .setUuid(UUID.randomUUID().toString())) - .setProject(Project.newBuilder() - .setUuid(grandChildUuid.toString())) - .setVulnerability(Vulnerability.newBuilder() - .setUuid(UUID.randomUUID().toString())) - .setAffectedProjectsReference(BackReference.newBuilder() - .setApiUri("foo") - .setFrontendUri("bar")) - .addAffectedProjects(Project.newBuilder() - .setUuid(grandChildUuid.toString())) - .build())) - .build(); - // Ok, let's test this - final List rules = notificationRouter.resolveRules(PublisherTestUtil.createPublisherContext(notification), notification); - assertThat(rules).isEmpty(); - } - - @Test - @TestTransaction - void testResolveRulesWithAffectedChildAndInactiveChild() throws Exception { - final Long publisherId = createConsolePublisher(); - // Creates a new rule and defines when the rule should be triggered (notifyOn) - final Long ruleId = createRule("Test Rule", - NotificationScope.PORTFOLIO, NotificationLevel.INFORMATIONAL, - NotificationGroup.NEW_VULNERABILITY, publisherId); - setNotifyChildren(ruleId, true); - // Creates a project which will later be matched on - final UUID grandParentUuid = UUID.randomUUID(); - final Long grandParentProjectId = createProject("Test Project Grandparent", "1.0", null, grandParentUuid); - final UUID parentUuid = UUID.randomUUID(); - final Long parentProjectId = createProject("Test Project Parent", "1.0", null, parentUuid); - setProjectParent(parentProjectId, grandParentProjectId); - final UUID childUuid = UUID.randomUUID(); - final Long childProjectId = createProject("Test Project Child", "1.0", null, childUuid); - setProjectParent(childProjectId, parentProjectId); - final UUID grandChildUuid = UUID.randomUUID(); - final Long grandChildProjectId = createProject("Test Project Grandchild", "1.0", new Date(), grandChildUuid); - setProjectParent(grandChildProjectId, childProjectId); - addProjectToRule(grandParentProjectId, ruleId); - // Creates a new notification - final var notification = Notification.newBuilder() - .setScope(SCOPE_PORTFOLIO) - .setGroup(GROUP_NEW_VULNERABILITY) - .setLevel(LEVEL_INFORMATIONAL) - .setSubject(Any.pack(NewVulnerabilitySubject.newBuilder() - .setComponent(Component.newBuilder() - .setUuid(UUID.randomUUID().toString())) - .setProject(Project.newBuilder() - .setUuid(grandChildUuid.toString())) - .setVulnerability(Vulnerability.newBuilder() - .setUuid(UUID.randomUUID().toString())) - .setAffectedProjectsReference(BackReference.newBuilder() - .setApiUri("foo") - .setFrontendUri("bar")) - .addAffectedProjects(Project.newBuilder() - .setUuid(grandChildUuid.toString())) - .build())) - .build(); - // Ok, let's test this - final List rules = notificationRouter.resolveRules(PublisherTestUtil.createPublisherContext(notification), notification); - assertThat(rules).isEmpty(); - } - - @Test - @TestTransaction - void testInformWithValidMatchingRule() throws Exception { - final Long publisherId = createConsolePublisher(); - // Creates a new rule and defines when the rule should be triggered (notifyOn) - createRule("Test Rule", - NotificationScope.PORTFOLIO, NotificationLevel.INFORMATIONAL, - NotificationGroup.NEW_VULNERABILITY, publisherId); - // Creates a project which will later be matched on - final UUID projectUuid = UUID.randomUUID(); - createProject("Test Project", "1.0", null, projectUuid); - // Creates a new notification - final var notification = Notification.newBuilder() - .setScope(SCOPE_PORTFOLIO) - .setGroup(GROUP_NEW_VULNERABILITY) - .setLevel(LEVEL_INFORMATIONAL) - .setSubject(Any.pack(NewVulnerabilitySubject.newBuilder() - .setComponent(Component.newBuilder() - .setUuid(UUID.randomUUID().toString())) - .setProject(Project.newBuilder() - .setUuid(projectUuid.toString())) - .setVulnerability(Vulnerability.newBuilder() - .setUuid(UUID.randomUUID().toString())) - .setAffectedProjectsReference(BackReference.newBuilder() - .setApiUri("foo") - .setFrontendUri("bar")) - .addAffectedProjects(Project.newBuilder() - .setUuid(projectUuid.toString())) - .build())) - .build(); - // Ok, let's test this - notificationRouter.inform(PublisherTestUtil.createPublisherContext(notification), notification); - verify(consolePublisherMock).inform(any(), eq(notification), any()); - } - - @Test - @TestTransaction - void testResolveRulesUserCreatedNotification() throws Exception { - - final Long publisherId = createConsolePublisher(); - createRule("Limit To Test Rule", - NotificationScope.SYSTEM, NotificationLevel.INFORMATIONAL, - NotificationGroup.USER_CREATED, publisherId); - - final var notificationUser = Notification.newBuilder() - .setScope(SCOPE_SYSTEM) - .setGroup(GROUP_USER_CREATED) - .setLevel(LEVEL_INFORMATIONAL) - .setSubject(Any.pack(UserSubject.newBuilder() - .setUsername("username") - .setEmail("email.com") - .build())) - .build(); - - Assertions.assertThat(notificationRouter.resolveRules(PublisherTestUtil.createPublisherContext(notificationUser), notificationUser)).satisfiesExactly( - rule -> Assertions.assertThat(rule.getName()).isEqualTo("Limit To Test Rule") - ); - } - - @Test - @TestTransaction - void testResolveRulesLimitedToProjectTag() throws Exception { - final UUID projectUuidA = UUID.randomUUID(); - createProject("Project A", "1.0", null, projectUuidA); - - final Long tagId = createTag("test-tag"); - - final Long publisherId = createConsolePublisher(); - final Long ruleId = createRule("Limit To Test Rule", - NotificationScope.PORTFOLIO, NotificationLevel.INFORMATIONAL, - NotificationGroup.NEW_VULNERABILITY, publisherId); - addTagToRule(tagId, ruleId); - - final var notificationProjectA = Notification.newBuilder() - .setScope(SCOPE_PORTFOLIO) - .setGroup(GROUP_NEW_VULNERABILITY) - .setLevel(LEVEL_INFORMATIONAL) - .setSubject(Any.pack(NewVulnerabilitySubject.newBuilder() - .setComponent(Component.newBuilder() - .setUuid(UUID.randomUUID().toString())) - .setProject(Project.newBuilder() - .setUuid(projectUuidA.toString()) - .addTags("test-tag")) - .setVulnerability(Vulnerability.newBuilder() - .setUuid(UUID.randomUUID().toString())) - .build())) - .build(); - - Assertions.assertThat(notificationRouter.resolveRules(PublisherTestUtil.createPublisherContext(notificationProjectA), notificationProjectA)).satisfiesExactly( - rule -> Assertions.assertThat(rule.getName()).isEqualTo("Limit To Test Rule") - ); - } - - @Test - @TestTransaction - void testResolveRulesWithValidNonMatchingTagLimitRule() throws Exception { - final Long publisherId = createConsolePublisher(); - final Long ruleId = createRule("Test Rule", - NotificationScope.PORTFOLIO, NotificationLevel.INFORMATIONAL, - NotificationGroup.NEW_VULNERABILITY, publisherId); - - final UUID projectUuid = UUID.randomUUID(); - createProject("Test Project", "1.0", null, projectUuid); - - final Long tagId = createTag("test-tag"); - addTagToRule(tagId, ruleId); - - final var notification = Notification.newBuilder() - .setScope(SCOPE_PORTFOLIO) - .setGroup(GROUP_NEW_VULNERABILITY) - .setLevel(LEVEL_INFORMATIONAL) - .setSubject(Any.pack(NewVulnerabilitySubject.newBuilder() - .setComponent(Component.newBuilder() - .setUuid(UUID.randomUUID().toString())) - // project is not tagged - .setProject(Project.newBuilder() - .setUuid(UUID.randomUUID().toString())) - .setVulnerability(Vulnerability.newBuilder() - .setUuid(UUID.randomUUID().toString())) - .build())) - .build(); - final List rules = notificationRouter.resolveRules(PublisherTestUtil.createPublisherContext(notification), notification); - assertThat(rules).isEmpty(); - } - - private Long createConsolePublisher() { - return (Long) entityManager.createNativeQuery(""" - INSERT INTO "NOTIFICATIONPUBLISHER" ("DEFAULT_PUBLISHER", "NAME", "PUBLISHER_CLASS", "TEMPLATE", "TEMPLATE_MIME_TYPE", "UUID") VALUES - (true, 'foo', 'org.dependencytrack.notification.publisher.ConsolePublisher', 'template','text/plain', :uuid) - RETURNING "ID"; - """) - .setParameter("uuid", UUID.randomUUID()).getSingleResult(); - } - - private Long createRule(final String name, final NotificationScope scope, final NotificationLevel level, - final NotificationGroup group, final Long publisherId) { - return (Long) entityManager.createNativeQuery(""" - INSERT INTO "NOTIFICATIONRULE" ( - "ENABLED" - , "NAME" - , "PUBLISHER" - , "NOTIFY_ON" - , "NOTIFY_CHILDREN" - , "LOG_SUCCESSFUL_PUBLISH" - , "NOTIFICATION_LEVEL" - , "SCOPE" - , "UUID" - ) VALUES ( - true - , :name - , :publisherId - , :notifyOn - , false - , true - , CAST(:level AS notification_level) - , :scope - , :uuid - ) - RETURNING "ID"; - """) - .setParameter("name", name) - .setParameter("publisherId", publisherId) - .setParameter("notifyOn", group.name()) - .setParameter("level", level.name()) - .setParameter("scope", scope.name()) - .setParameter("uuid", UUID.randomUUID()) - .getSingleResult(); - } - - private void setNotifyChildren(final Long ruleId, final boolean notifyChildren) { - entityManager.createNativeQuery(""" - UPDATE "NOTIFICATIONRULE" SET "NOTIFY_CHILDREN" = :notifyChildren WHERE "ID" = :id - """) - .setParameter("id", ruleId) - .setParameter("notifyChildren", notifyChildren) - .executeUpdate(); - } - - private void disableRule(final Long ruleId) { - entityManager.createNativeQuery(""" - UPDATE "NOTIFICATIONRULE" SET "ENABLED" = false WHERE "ID" = :ruleId - """) - .setParameter("ruleId", ruleId) - .executeUpdate(); - } - - private Long createProject(final String name, final String version, final Date inactiveSince, final UUID uuid) { - return (Long) entityManager.createNativeQuery(""" - INSERT INTO "PROJECT" ("NAME", "VERSION", "INACTIVE_SINCE", "UUID") VALUES - (:name, :version, :inactiveSince, :uuid) - RETURNING "ID"; - """) - .setParameter("name", name) - .setParameter("version", version) - .setParameter("inactiveSince", inactiveSince) - .setParameter("uuid", uuid) - .getSingleResult(); - } - - private void setProjectParent(final Long childId, final Long parentId) { - entityManager.createNativeQuery(""" - UPDATE "PROJECT" SET "PARENT_PROJECT_ID" = :parentId WHERE "ID" = :id - """) - .setParameter("parentId", parentId) - .setParameter("id", childId) - .executeUpdate(); - } - - private void addProjectToRule(final Long projectId, final Long ruleId) { - entityManager.createNativeQuery(""" - INSERT INTO "NOTIFICATIONRULE_PROJECTS" ("PROJECT_ID", "NOTIFICATIONRULE_ID") VALUES - (:projectId, :ruleId); - """) - .setParameter("projectId", projectId) - .setParameter("ruleId", ruleId) - .executeUpdate(); - } - - private Long createTag(final String name) { - return (Long) entityManager.createNativeQuery(""" - INSERT INTO "TAG" ("NAME") VALUES (:name) - RETURNING "ID"; - """) - .setParameter("name", name) - .getSingleResult(); - } - - private void addTagToRule(final Long tagId, final Long ruleId) { - entityManager.createNativeQuery(""" - INSERT INTO "NOTIFICATIONRULE_TAGS" ("TAG_ID", "NOTIFICATIONRULE_ID") VALUES - (:tagId, :ruleId); - """) - .setParameter("tagId", tagId) - .setParameter("ruleId", ruleId) - .executeUpdate(); - } -} \ No newline at end of file diff --git a/notification-publisher/src/test/java/org/dependencytrack/notification/PublisherClassTest.java b/notification-publisher/src/test/java/org/dependencytrack/notification/PublisherClassTest.java deleted file mode 100644 index b486df2478..0000000000 --- a/notification-publisher/src/test/java/org/dependencytrack/notification/PublisherClassTest.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * 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; - -import org.dependencytrack.notification.publisher.SlackPublisher; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; - -public class PublisherClassTest { - - @Test - public void getPublisherClassTest() throws ClassNotFoundException { - - Class publisherClass = PublisherClass.getPublisherClass("org.dependencytrack.SlackPublisher"); - Assertions.assertEquals(SlackPublisher.class, publisherClass); - - publisherClass = PublisherClass.getPublisherClass("org.dependencytrack.InvalidPublisher"); - Assertions.assertNull(publisherClass); - - publisherClass = PublisherClass.getPublisherClass("SlackPublisher"); - Assertions.assertEquals(SlackPublisher.class, publisherClass); - - publisherClass = PublisherClass.getPublisherClass("p1.p2.SlackPublisher"); - Assertions.assertEquals(SlackPublisher.class, publisherClass); - } -} diff --git a/notification-publisher/src/test/java/org/dependencytrack/notification/publisher/AbstractPublisherTest.java b/notification-publisher/src/test/java/org/dependencytrack/notification/publisher/AbstractPublisherTest.java deleted file mode 100644 index c280a5bd76..0000000000 --- a/notification-publisher/src/test/java/org/dependencytrack/notification/publisher/AbstractPublisherTest.java +++ /dev/null @@ -1,416 +0,0 @@ -/* - * 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 com.github.tomakehurst.wiremock.client.WireMock; -import com.google.protobuf.Any; -import com.google.protobuf.util.Timestamps; -import io.pebbletemplates.pebble.error.ParserException; -import io.quarkiverse.wiremock.devservice.ConnectWireMock; -import io.quarkiverse.wiremock.devservice.WireMockConfigKey; -import jakarta.inject.Inject; -import jakarta.json.Json; -import jakarta.json.JsonObject; -import jakarta.json.JsonObjectBuilder; -import jakarta.persistence.EntityManager; -import org.apache.commons.io.IOUtils; -import org.apache.kafka.clients.consumer.ConsumerRecord; -import org.dependencytrack.notification.NotificationConstants; -import org.dependencytrack.proto.notification.v1.BackReference; -import org.dependencytrack.proto.notification.v1.Bom; -import org.dependencytrack.proto.notification.v1.BomConsumedOrProcessedSubject; -import org.dependencytrack.proto.notification.v1.BomProcessingFailedSubject; -import org.dependencytrack.proto.notification.v1.BomValidationFailedSubject; -import org.dependencytrack.proto.notification.v1.Component; -import org.dependencytrack.proto.notification.v1.NewVulnerabilitySubject; -import org.dependencytrack.proto.notification.v1.NewVulnerableDependencySubject; -import org.dependencytrack.proto.notification.v1.Notification; -import org.dependencytrack.proto.notification.v1.Project; -import org.dependencytrack.proto.notification.v1.Vulnerability; -import org.dependencytrack.proto.notification.v1.VulnerabilityAnalysis; -import org.dependencytrack.proto.notification.v1.VulnerabilityAnalysisDecisionChangeSubject; -import org.eclipse.microprofile.config.inject.ConfigProperty; - -import java.util.List; - -import static java.nio.charset.StandardCharsets.UTF_8; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.assertj.core.api.Assertions.assertThatNoException; -import static org.dependencytrack.proto.notification.v1.Group.GROUP_ANALYZER; -import static org.dependencytrack.proto.notification.v1.Group.GROUP_BOM_CONSUMED; -import static org.dependencytrack.proto.notification.v1.Group.GROUP_BOM_PROCESSING_FAILED; -import static org.dependencytrack.proto.notification.v1.Group.GROUP_BOM_VALIDATION_FAILED; -import static org.dependencytrack.proto.notification.v1.Group.GROUP_DATASOURCE_MIRRORING; -import static org.dependencytrack.proto.notification.v1.Group.GROUP_NEW_VULNERABILITY; -import static org.dependencytrack.proto.notification.v1.Group.GROUP_NEW_VULNERABLE_DEPENDENCY; -import static org.dependencytrack.proto.notification.v1.Group.GROUP_PROJECT_AUDIT_CHANGE; -import static org.dependencytrack.proto.notification.v1.Level.LEVEL_ERROR; -import static org.dependencytrack.proto.notification.v1.Level.LEVEL_INFORMATIONAL; -import static org.dependencytrack.proto.notification.v1.Scope.SCOPE_PORTFOLIO; -import static org.dependencytrack.proto.notification.v1.Scope.SCOPE_SYSTEM; - -@ConnectWireMock -abstract class AbstractPublisherTest { - - WireMock wireMock; - - @ConfigProperty(name = WireMockConfigKey.PORT) - Integer wireMockPort; - - @Inject - @SuppressWarnings("CdiInjectionPointsInspection") - T publisherInstance; - - @Inject - EntityManager entityManager; - - void testInformWithBomConsumedNotification() throws Exception { - final var subject = BomConsumedOrProcessedSubject.newBuilder() - .setProject(createProject()) - .setBom(Bom.newBuilder() - .setContent("bomContent") - .setFormat("CycloneDX") - .setSpecVersion("1.5")) - .build(); - - final var notification = Notification.newBuilder() - .setId("019a224d-5b71-778d-953f-594edb4a44e8") - .setScope(SCOPE_PORTFOLIO) - .setGroup(GROUP_BOM_CONSUMED) - .setTitle(NotificationConstants.Title.BOM_CONSUMED) - .setContent("A CycloneDX BOM was consumed and will be processed") - .setLevel(LEVEL_INFORMATIONAL) - .setTimestamp(Timestamps.fromSeconds(66666)) - .setSubject(Any.pack(subject)) - .build(); - - assertThatNoException() - .isThrownBy(() -> publisherInstance.inform(createPublishContext(notification), notification, createConfig())); - } - - void testInformWithBomProcessingFailedNotification() throws Exception { - final var subject = BomProcessingFailedSubject.newBuilder() - .setProject(createProject()) - .setBom(Bom.newBuilder() - .setContent("bomContent") - .setFormat("CycloneDX") - .setSpecVersion("1.5")) - .setCause("cause") - .build(); - - final var notification = Notification.newBuilder() - .setId("019a224d-5b71-778d-953f-594edb4a44e8") - .setScope(SCOPE_PORTFOLIO) - .setGroup(GROUP_BOM_PROCESSING_FAILED) - .setTitle(NotificationConstants.Title.BOM_PROCESSING_FAILED) - .setContent("An error occurred while processing a BOM") - .setLevel(LEVEL_ERROR) - .setTimestamp(Timestamps.fromSeconds(66666)) - .setSubject(Any.pack(subject)) - .build(); - - assertThatNoException() - .isThrownBy(() -> publisherInstance.inform(createPublishContext(notification), notification, createConfig())); - } - - // https://github.com/DependencyTrack/dependency-track/issues/3197 - void testInformWithBomProcessingFailedNotificationAndNoSpecVersionInSubject() throws Exception { - final var subject = BomProcessingFailedSubject.newBuilder() - .setProject(createProject()) - .setBom(Bom.newBuilder() - .setContent("bomContent") - .setFormat("CycloneDX") - /* .setSpecVersion("1.5") */) - .setCause("cause") - .build(); - - final var notification = Notification.newBuilder() - .setId("019a224d-5b71-778d-953f-594edb4a44e8") - .setScope(SCOPE_PORTFOLIO) - .setGroup(GROUP_BOM_PROCESSING_FAILED) - .setTitle(NotificationConstants.Title.BOM_PROCESSING_FAILED) - .setContent("An error occurred while processing a BOM") - .setLevel(LEVEL_ERROR) - .setTimestamp(Timestamps.fromSeconds(66666)) - .setSubject(Any.pack(subject)) - .build(); - - assertThatNoException() - .isThrownBy(() -> publisherInstance.inform(createPublishContext(notification), notification, createConfig())); - } - - void testInformWithBomValidationFailedNotificationSubject() throws Exception { - final var subject = BomValidationFailedSubject.newBuilder() - .setProject(createProject()) - .setBom(Bom.newBuilder() - .setContent("bomContent") - .setFormat("CycloneDX")) - .addErrors("cause 1") - .addErrors("cause 2") - .build(); - - final var notification = Notification.newBuilder() - .setId("019a224d-5b71-778d-953f-594edb4a44e8") - .setScope(SCOPE_PORTFOLIO) - .setGroup(GROUP_BOM_VALIDATION_FAILED) - .setTitle(NotificationConstants.Title.BOM_VALIDATION_FAILED) - .setContent("An error occurred while validating a BOM") - .setLevel(LEVEL_ERROR) - .setTimestamp(Timestamps.fromSeconds(66666)) - .setSubject(Any.pack(subject)) - .build(); - - assertThatNoException() - .isThrownBy(() -> publisherInstance.inform(createPublishContext(notification), notification, createConfig())); - } - - void testInformWithDataSourceMirroringNotification() throws Exception { - final var notification = Notification.newBuilder() - .setId("019a224d-5b71-778d-953f-594edb4a44e8") - .setScope(SCOPE_SYSTEM) - .setGroup(GROUP_DATASOURCE_MIRRORING) - .setTitle(NotificationConstants.Title.GITHUB_ADVISORY_MIRROR) - .setContent("An error occurred mirroring the contents of GitHub Advisories. Check log for details.") - .setLevel(LEVEL_ERROR) - .setTimestamp(Timestamps.fromSeconds(66666)) - .build(); - - assertThatNoException() - .isThrownBy(() -> publisherInstance.inform(createPublishContext(notification), notification, createConfig())); - } - - void testInformWithNewVulnerabilityNotification() throws Exception { - final var project = createProject(); - final var component = createComponent(); - final var vuln = createVulnerability(); - - final var subject = NewVulnerabilitySubject.newBuilder() - .setComponent(component) - .setProject(project) - .setVulnerability(vuln) - .setVulnerabilityAnalysisLevel("BOM_UPLOAD_ANALYSIS") - .addAffectedProjects(project) - .setAffectedProjectsReference(BackReference.newBuilder() - .setApiUri("/api/v1/vulnerability/source/INTERNAL/vuln/INT-001/projects") - .setFrontendUri("/vulnerabilities/INTERNAL/INT-001/affectedProjects")) - .build(); - - final var notification = Notification.newBuilder() - .setId("019a224d-5b71-778d-953f-594edb4a44e8") - .setScope(SCOPE_PORTFOLIO) - .setGroup(GROUP_NEW_VULNERABILITY) - .setTitle(NotificationConstants.Title.NEW_VULNERABILITY) - .setContent("") - .setLevel(LEVEL_INFORMATIONAL) - .setTimestamp(Timestamps.fromSeconds(66666)) - .setSubject(Any.pack(subject)) - .build(); - - assertThatNoException() - .isThrownBy(() -> publisherInstance.inform(createPublishContext(notification), notification, createConfig())); - } - - void testInformWithNewVulnerableDependencyNotification() throws Exception { - final var project = createProject(); - final var component = createComponent(); - final var vuln = createVulnerability(); - - final var subject = NewVulnerableDependencySubject.newBuilder() - .setComponent(component) - .setProject(project) - .addVulnerabilities(vuln).build(); - - final var notification = Notification.newBuilder() - .setId("019a224d-5b71-778d-953f-594edb4a44e8") - .setScope(SCOPE_PORTFOLIO) - .setGroup(GROUP_NEW_VULNERABLE_DEPENDENCY) - .setTitle(NotificationConstants.Title.NEW_VULNERABLE_DEPENDENCY) - .setContent("") - .setLevel(LEVEL_INFORMATIONAL) - .setTimestamp(Timestamps.fromSeconds(66666)) - .setSubject(Any.pack(subject)) - .build(); - - assertThatNoException() - .isThrownBy(() -> publisherInstance.inform(createPublishContext(notification), notification, createConfig())); - } - - void testInformWithProjectAuditChangeNotification() throws Exception { - final var project = createProject(); - final var component = createComponent(); - final var vuln = createVulnerability(); - final var analysis = createAnalysis(component, vuln); - - final var subject = VulnerabilityAnalysisDecisionChangeSubject.newBuilder() - .setComponent(component) - .setProject(project) - .setVulnerability(vuln) - .setAnalysis(analysis) - .build(); - - final var notification = Notification.newBuilder() - .setId("019a224d-5b71-778d-953f-594edb4a44e8") - .setScope(SCOPE_PORTFOLIO) - .setGroup(GROUP_PROJECT_AUDIT_CHANGE) - .setTitle(NotificationConstants.Title.ANALYSIS_DECISION_SUPPRESSED) - .setContent("") - .setLevel(LEVEL_INFORMATIONAL) - .setTimestamp(Timestamps.fromSeconds(66666)) - .setSubject(Any.pack(subject)) - .build(); - - assertThatNoException() - .isThrownBy(() -> publisherInstance.inform(createPublishContext(notification), notification, createConfig())); - } - - void testInformWithTemplateInclude() throws Exception { - final var notification = Notification.newBuilder() - .setId("019a224d-5b71-778d-953f-594edb4a44e8") - .setScope(SCOPE_SYSTEM) - .setGroup(GROUP_ANALYZER) - .setTitle(NotificationConstants.Title.NOTIFICATION_TEST) - .setLevel(LEVEL_ERROR) - .setTimestamp(Timestamps.fromSeconds(66666)) - .build(); - - final JsonObject config = Json.createObjectBuilder(createConfig()) - .add(Publisher.CONFIG_TEMPLATE_KEY, "{% include '/some/path' %}") - .build(); - - assertThatExceptionOfType(ParserException.class) - .isThrownBy(() -> publisherInstance.inform(createPublishContext(notification), notification, config)) - .withMessage("Unexpected tag name \"include\" ({% include '/some/path' %}:1)"); - } - - JsonObject createConfig() throws Exception { - return Json.createObjectBuilder() - .add(Publisher.CONFIG_TEMPLATE_MIME_TYPE_KEY, getTemplateMimeType()) - .add(Publisher.CONFIG_TEMPLATE_KEY, getTemplate()) - .addAll(extraConfig()) - .build(); - } - - JsonObjectBuilder extraConfig() { - return Json.createObjectBuilder(); - } - - private String getTemplateMimeType() { - if (publisherInstance instanceof CsWebexPublisher - || publisherInstance instanceof JiraPublisher - || publisherInstance instanceof MattermostPublisher - || publisherInstance instanceof MsTeamsPublisher - || publisherInstance instanceof SlackPublisher - || publisherInstance instanceof WebhookPublisher) { - return "application/json"; - } else if (publisherInstance instanceof ConsolePublisher - || publisherInstance instanceof SendMailPublisher) { - return "text/plain"; - } - - throw new IllegalStateException(); - } - - private String getTemplate() throws Exception { - final String templateFile; - if (publisherInstance instanceof CsWebexPublisher) { - templateFile = "cswebex.peb"; - } else if (publisherInstance instanceof ConsolePublisher) { - templateFile = "console.peb"; - } else if (publisherInstance instanceof SendMailPublisher) { - templateFile = "email.peb"; - } else if (publisherInstance instanceof JiraPublisher) { - templateFile = "jira.peb"; - } else if (publisherInstance instanceof MattermostPublisher) { - templateFile = "mattermost.peb"; - } else if (publisherInstance instanceof MsTeamsPublisher) { - templateFile = "msteams.peb"; - } else if (publisherInstance instanceof SlackPublisher) { - templateFile = "slack.peb"; - } else if (publisherInstance instanceof WebhookPublisher) { - templateFile = "webhook.peb"; - } else { - throw new IllegalStateException(); - } - - return IOUtils.resourceToString("/templates/" + templateFile, UTF_8); - } - - private static Component createComponent() { - return Component.newBuilder() - .setUuid("94f87321-a5d1-4c2f-b2fe-95165debebc6") - .setName("componentName") - .setVersion("componentVersion") - .build(); - } - - private static Project createProject() { - return Project.newBuilder() - .setUuid("c9c9539a-e381-4b36-ac52-6a7ab83b2c95") - .setName("projectName") - .setVersion("projectVersion") - .setDescription("projectDescription") - .setPurl("pkg:maven/org.acme/projectName@projectVersion") - .addAllTags(List.of("tag1", "tag2")) - .setIsActive(true) - .build(); - } - - private static Vulnerability createVulnerability() { - return Vulnerability.newBuilder() - .setUuid("bccec5d5-ec21-4958-b3e8-22a7a866a05a") - .setVulnId("INT-001") - .setSource("INTERNAL") - .addAliases(Vulnerability.Alias.newBuilder() - .setId("OSV-001") - .setSource("OSV") - .build()) - .setTitle("vulnerabilityTitle") - .setSubTitle("vulnerabilitySubTitle") - .setDescription("vulnerabilityDescription") - .setRecommendation("vulnerabilityRecommendation") - .setCvssV2(5.5) - .setCvssV3(6.6) - .setOwaspRrLikelihood(1.1) - .setOwaspRrTechnicalImpact(2.2) - .setOwaspRrBusinessImpact(3.3) - .setSeverity("MEDIUM") - .addCwes(Vulnerability.Cwe.newBuilder() - .setCweId(666) - .setName("Operation on Resource in Wrong Phase of Lifetime")) - .addCwes(Vulnerability.Cwe.newBuilder() - .setCweId(777) - .setName("Regular Expression without Anchors")) - .build(); - } - - private static VulnerabilityAnalysis createAnalysis(final Component component, final Vulnerability vuln) { - return VulnerabilityAnalysis.newBuilder() - .setComponent(component) - .setVulnerability(vuln) - .setState("FALSE_POSITIVE") - .setSuppressed(true) - .build(); - } - - static PublishContext createPublishContext(final Notification notification) throws Exception { - final var record = new ConsumerRecord<>("topic", 1, 2L, "key", notification); - return PublishContext.fromRecord(record); - } - -} diff --git a/notification-publisher/src/test/java/org/dependencytrack/notification/publisher/AbstractWebhookPublisherTest.java b/notification-publisher/src/test/java/org/dependencytrack/notification/publisher/AbstractWebhookPublisherTest.java deleted file mode 100644 index e3d45e1e9b..0000000000 --- a/notification-publisher/src/test/java/org/dependencytrack/notification/publisher/AbstractWebhookPublisherTest.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * 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.json.JsonObjectBuilder; -import org.junit.jupiter.api.BeforeEach; - -import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; -import static com.github.tomakehurst.wiremock.client.WireMock.anyUrl; -import static com.github.tomakehurst.wiremock.client.WireMock.post; - - -abstract class AbstractWebhookPublisherTest extends AbstractPublisherTest { - - @BeforeEach - void beforeEach() { - wireMock.resetToDefaultMappings(); - wireMock.register(post(anyUrl()) - .willReturn(aResponse() - .withStatus(200))); - } - - @Override - JsonObjectBuilder extraConfig() { - return super.extraConfig() - .add("custom.config.wiremock.url", "http://localhost:${quarkus.wiremock.devservices.port}"); - } - -} diff --git a/notification-publisher/src/test/java/org/dependencytrack/notification/publisher/ConsolePublisherTest.java b/notification-publisher/src/test/java/org/dependencytrack/notification/publisher/ConsolePublisherTest.java deleted file mode 100644 index c2961035ac..0000000000 --- a/notification-publisher/src/test/java/org/dependencytrack/notification/publisher/ConsolePublisherTest.java +++ /dev/null @@ -1,145 +0,0 @@ -/* - * 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 com.google.protobuf.Any; -import io.quarkus.test.TestTransaction; -import io.quarkus.test.junit.QuarkusTest; -import jakarta.inject.Inject; -import jakarta.persistence.EntityManager; -import org.dependencytrack.proto.notification.v1.Bom; -import org.dependencytrack.proto.notification.v1.BomConsumedOrProcessedSubject; -import org.dependencytrack.proto.notification.v1.Group; -import org.dependencytrack.proto.notification.v1.Level; -import org.dependencytrack.proto.notification.v1.Notification; -import org.dependencytrack.proto.notification.v1.Scope; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import java.io.ByteArrayOutputStream; -import java.io.PrintStream; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.dependencytrack.proto.notification.v1.Group.GROUP_FILE_SYSTEM; -import static org.dependencytrack.proto.notification.v1.Level.LEVEL_ERROR; -import static org.dependencytrack.proto.notification.v1.Scope.SCOPE_SYSTEM; - -@QuarkusTest -public class ConsolePublisherTest { - - @Inject - ConsolePublisher publisher; - - private final ByteArrayOutputStream outContent = new ByteArrayOutputStream(); - private final ByteArrayOutputStream errContent = new ByteArrayOutputStream(); - private final PrintStream originalOut = System.out; - private final PrintStream originalErr = System.err; - - @Inject - EntityManager entityManager; - - - @BeforeEach - public void setUpStreams() { - System.setOut(new PrintStream(outContent)); - System.setErr(new PrintStream(errContent)); - } - - @AfterEach - public void restoreStreams() { - System.setOut(originalOut); - System.setErr(originalErr); - } - - @Test - @TestTransaction - public void testOutputStream() throws Exception { - entityManager.createNativeQuery(""" - INSERT INTO "CONFIGPROPERTY" ("DESCRIPTION", "GROUPNAME", "PROPERTYTYPE", "PROPERTYNAME", "PROPERTYVALUE") VALUES - ('console', 'general', 'STRING', 'base.url', ''); - """).executeUpdate(); - - final var notification = Notification.newBuilder() - .setScope(Scope.SCOPE_PORTFOLIO) - .setLevel(Level.LEVEL_INFORMATIONAL) - .setGroup(Group.GROUP_NEW_VULNERABILITY) - .setTitle("Test Notification") - .setContent("This is only a test") - .build(); - publisher.inform(PublisherTestUtil.createPublisherContext(notification), notification, PublisherTestUtil.getConfig("CONSOLE", "")); - System.out.println("outContent: " + outContent); - assertThat(outContent.toString()).contains(expectedResult(notification)); - } - - @Test - @TestTransaction - public void testOutputStreamForBomConsumed() throws Exception { - entityManager.createNativeQuery(""" - INSERT INTO "CONFIGPROPERTY" ("DESCRIPTION", "GROUPNAME", "PROPERTYTYPE", "PROPERTYNAME", "PROPERTYVALUE") VALUES - ('console', 'general', 'STRING', 'base.url', ''); - """).executeUpdate(); - - final var notification = Notification.newBuilder() - .setScope(Scope.SCOPE_PORTFOLIO) - .setLevel(Level.LEVEL_INFORMATIONAL) - .setGroup(Group.GROUP_BOM_CONSUMED) - .setTitle("Test Notification") - .setContent("This is only a test") - .setSubject(Any.pack(BomConsumedOrProcessedSubject.newBuilder() - .setBom(Bom.newBuilder() - .setContent("BOM Content") - .setFormat("CycloneDx") - .setSpecVersion("1.0.0").build()) - .build())) - .build(); - publisher.inform(PublisherTestUtil.createPublisherContext(notification), notification, PublisherTestUtil.getConfig("CONSOLE", "")); - System.out.println("outContent: " + outContent); - assertThat(outContent.toString()).contains(expectedResult(notification)); - } - - @Test - @TestTransaction - public void testErrorStream() throws Exception { - entityManager.createNativeQuery(""" - INSERT INTO "CONFIGPROPERTY" ("DESCRIPTION", "GROUPNAME", "PROPERTYTYPE", "PROPERTYNAME", "PROPERTYVALUE") VALUES - ('console', 'general', 'STRING', 'base.url', ''); - """).executeUpdate(); - - final var notification = Notification.newBuilder() - .setScope(SCOPE_SYSTEM) - .setGroup(GROUP_FILE_SYSTEM) - .setLevel(LEVEL_ERROR) - .setTitle("Test Notification") - .setContent("This is only a test") - .build(); - publisher.inform(PublisherTestUtil.createPublisherContext(notification), notification, PublisherTestUtil.getConfig("CONSOLE", "")); - } - - private String expectedResult(Notification notification) { - return "--------------------------------------------------------------------------------" + System.lineSeparator() + - "Notification" + System.lineSeparator() + - " -- timestamp: 1970-01-01T00:00:00.000Z" + System.lineSeparator() + - " -- level: " + notification.getLevel() + System.lineSeparator() + - " -- scope: " + notification.getScope() + System.lineSeparator() + - " -- group: " + notification.getGroup() + System.lineSeparator() + - " -- title: " + notification.getTitle() + System.lineSeparator() + - " -- content: " + notification.getContent() + System.lineSeparator() + System.lineSeparator(); - } -} diff --git a/notification-publisher/src/test/java/org/dependencytrack/notification/publisher/CsWebexPublisherTest.java b/notification-publisher/src/test/java/org/dependencytrack/notification/publisher/CsWebexPublisherTest.java deleted file mode 100644 index 61fb72909d..0000000000 --- a/notification-publisher/src/test/java/org/dependencytrack/notification/publisher/CsWebexPublisherTest.java +++ /dev/null @@ -1,167 +0,0 @@ -/* - * 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 io.quarkus.test.TestTransaction; -import io.quarkus.test.junit.QuarkusTest; -import io.quarkus.test.junit.QuarkusTestProfile; -import io.quarkus.test.junit.TestProfile; -import jakarta.json.JsonObjectBuilder; -import org.junit.jupiter.api.Test; - -import java.util.Map; - -import static com.github.tomakehurst.wiremock.client.WireMock.anyUrl; -import static com.github.tomakehurst.wiremock.client.WireMock.equalTo; -import static com.github.tomakehurst.wiremock.client.WireMock.equalToJson; -import static com.github.tomakehurst.wiremock.client.WireMock.postRequestedFor; - -@QuarkusTest -@TestProfile(CsWebexPublisherTest.TestProfile.class) -public class CsWebexPublisherTest extends AbstractWebhookPublisherTest { - - public static class TestProfile implements QuarkusTestProfile { - - @Override - public Map getConfigOverrides() { - return Map.ofEntries( - Map.entry("dtrack.general.base.url", "https://example.com") - ); - } - } - - @Override - JsonObjectBuilder extraConfig() { - return super.extraConfig() - .add(Publisher.CONFIG_DESTINATION, "http://localhost:" + wireMockPort); - } - - @Test - @Override - @TestTransaction - void testInformWithBomConsumedNotification() throws Exception { - super.testInformWithBomConsumedNotification(); - - wireMock.verifyThat(postRequestedFor(anyUrl()) - .withHeader("Content-Type", equalTo("application/json")) - .withRequestBody(equalToJson(""" - { - "markdown": "**Bill of Materials Consumed**\\n[View Component](https://example.com/component/?uuid=)\\n**Description:** A CycloneDX BOM was consumed and will be processed" - } - """))); - } - - @Test - @Override - @TestTransaction - void testInformWithBomProcessingFailedNotification() throws Exception { - super.testInformWithBomProcessingFailedNotification(); - - wireMock.verifyThat(postRequestedFor(anyUrl()) - .withHeader("Content-Type", equalTo("application/json")) - .withRequestBody(equalToJson(""" - { - "markdown": "**Bill of Materials Processing Failed**\\n[View Component](https://example.com/component/?uuid=)\\n**Description:** An error occurred while processing a BOM" - } - """))); - } - - @Test - @Override - @TestTransaction - void testInformWithBomProcessingFailedNotificationAndNoSpecVersionInSubject() throws Exception { - super.testInformWithBomProcessingFailedNotificationAndNoSpecVersionInSubject(); - - wireMock.verifyThat(postRequestedFor(anyUrl()) - .withHeader("Content-Type", equalTo("application/json")) - .withRequestBody(equalToJson(""" - { - "markdown": "**Bill of Materials Processing Failed**\\n[View Component](https://example.com/component/?uuid=)\\n**Description:** An error occurred while processing a BOM" - } - """))); - } - - @Test - @Override - @TestTransaction - void testInformWithBomValidationFailedNotificationSubject() throws Exception { - super.testInformWithBomValidationFailedNotificationSubject(); - - wireMock.verifyThat(postRequestedFor(anyUrl()) - .withHeader("Content-Type", equalTo("application/json")) - .withRequestBody(equalToJson(""" - { - "markdown": "**Bill of Materials Validation Failed**\\n[View Component](https://example.com/component/?uuid=)\\n**Description:** An error occurred while validating a BOM" - } - """))); - } - - @Test - @Override - @TestTransaction - void testInformWithDataSourceMirroringNotification() throws Exception { - super.testInformWithDataSourceMirroringNotification(); - - wireMock.verifyThat(postRequestedFor(anyUrl()) - .withHeader("Content-Type", equalTo("application/json")) - .withRequestBody(equalToJson(""" - { - "markdown": "**GitHub Advisory Mirroring**\\n[View Component](https://example.com/component/?uuid=)\\n**Description:** An error occurred mirroring the contents of GitHub Advisories. Check log for details." - } - """))); - } - - @Test - @Override - @TestTransaction - void testInformWithNewVulnerabilityNotification() throws Exception { - super.testInformWithNewVulnerabilityNotification(); - - wireMock.verifyThat(postRequestedFor(anyUrl()) - .withHeader("Content-Type", equalTo("application/json")) - .withRequestBody(equalToJson(""" - { - "markdown": "**New Vulnerability Identified**\\n**VulnID:** INT-001\\n**Severity:** MEDIUM\\n**Source:** INTERNAL\\n**Component:** componentName : componentVersion\\n**Actions:**\\n[View Vulnerability](https://example.com/vulnerability/?source=INTERNAL&vulnId=INT-001)\\n[View Component](https://example.com/component/?uuid=94f87321-a5d1-4c2f-b2fe-95165debebc6)\\n**Description:** " - } - """))); - } - - @Test - @Override - @TestTransaction - void testInformWithProjectAuditChangeNotification() throws Exception { - super.testInformWithProjectAuditChangeNotification(); - - wireMock.verifyThat(postRequestedFor(anyUrl()) - .withHeader("Content-Type", equalTo("application/json")) - .withRequestBody(equalToJson(""" - { - "markdown": "**Analysis Decision: Finding Suppressed**\\n[View Component](https://example.com/component/?uuid=94f87321-a5d1-4c2f-b2fe-95165debebc6)\\n**Description:** " - } - """))); - } - - @Test - @Override - @TestTransaction - void testInformWithTemplateInclude() throws Exception { - super.testInformWithTemplateInclude(); - } - -} diff --git a/notification-publisher/src/test/java/org/dependencytrack/notification/publisher/JiraPublisherBearerTokenTest.java b/notification-publisher/src/test/java/org/dependencytrack/notification/publisher/JiraPublisherBearerTokenTest.java deleted file mode 100644 index e42b0dd085..0000000000 --- a/notification-publisher/src/test/java/org/dependencytrack/notification/publisher/JiraPublisherBearerTokenTest.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * 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 io.quarkus.test.TestTransaction; -import io.quarkus.test.junit.QuarkusTest; -import io.quarkus.test.junit.QuarkusTestProfile; -import io.quarkus.test.junit.TestProfile; -import jakarta.json.JsonObjectBuilder; -import org.junit.jupiter.api.Test; - -import java.util.Map; - -import static com.github.tomakehurst.wiremock.client.WireMock.equalTo; -import static com.github.tomakehurst.wiremock.client.WireMock.equalToJson; -import static com.github.tomakehurst.wiremock.client.WireMock.postRequestedFor; -import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo; - -@QuarkusTest -@TestProfile(JiraPublisherBearerTokenTest.TestProfile.class) -public class JiraPublisherBearerTokenTest extends AbstractWebhookPublisherTest { - - public static class TestProfile implements QuarkusTestProfile { - - @Override - public Map getConfigOverrides() { - return Map.ofEntries( - Map.entry("dtrack.general.base.url", "https://example.com"), - Map.entry("dtrack.integrations.jira.password", "Pk7KKYvayX0yN9ZeIJFXs6jDBxDKszT1Gw9bTKxxZTc="), - Map.entry("dtrack.integrations.jira.url", "http://localhost:${quarkus.wiremock.devservices.port}") - ); - } - } - - @Override - JsonObjectBuilder extraConfig() { - return super.extraConfig() - .add(Publisher.CONFIG_DESTINATION, "PROJECT") - .add("jiraTicketType", "Task"); - } - - @Test - @TestTransaction - void testInformWithBearerToken() throws Exception { - super.testInformWithBomConsumedNotification(); - - wireMock.verifyThat(postRequestedFor(urlPathEqualTo("/rest/api/2/issue")) - .withHeader("Authorization", equalTo("Bearer jiraToken")) - .withHeader("Content-Type", equalTo("application/json")) - .withRequestBody(equalToJson(""" - { - "fields" : { - "project" : { - "key" : "PROJECT" - }, - "issuetype" : { - "name" : "Task" - }, - "summary" : "[Dependency-Track] [GROUP_BOM_CONSUMED] Bill of Materials Consumed", - "description" : "A CycloneDX BOM was consumed and will be processed\\n\\\\\\\\\\n\\\\\\\\\\n*Level*\\nLEVEL_INFORMATIONAL\\n\\n" - } - } - """))); - } - -} diff --git a/notification-publisher/src/test/java/org/dependencytrack/notification/publisher/JiraPublisherTest.java b/notification-publisher/src/test/java/org/dependencytrack/notification/publisher/JiraPublisherTest.java deleted file mode 100644 index 602b74203d..0000000000 --- a/notification-publisher/src/test/java/org/dependencytrack/notification/publisher/JiraPublisherTest.java +++ /dev/null @@ -1,267 +0,0 @@ -/* - * 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 io.quarkus.test.TestTransaction; -import io.quarkus.test.junit.QuarkusTest; -import io.quarkus.test.junit.QuarkusTestProfile; -import io.quarkus.test.junit.TestProfile; -import jakarta.json.JsonObjectBuilder; -import org.junit.jupiter.api.Test; - -import java.util.Map; - -import static com.github.tomakehurst.wiremock.client.WireMock.equalTo; -import static com.github.tomakehurst.wiremock.client.WireMock.equalToJson; -import static com.github.tomakehurst.wiremock.client.WireMock.postRequestedFor; -import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo; - -@QuarkusTest -@TestProfile(JiraPublisherTest.TestProfile.class) -public class JiraPublisherTest extends AbstractWebhookPublisherTest { - - public static class TestProfile implements QuarkusTestProfile { - - @Override - public Map getConfigOverrides() { - - return Map.ofEntries( - Map.entry("dtrack.general.base.url", "https://example.com"), - Map.entry("dtrack.integrations.jira.username", "jiraUser"), - Map.entry("dtrack.integrations.jira.url", "http://localhost:${quarkus.wiremock.devservices.port}"), - Map.entry("dtrack.integrations.jira.password", "7h5IR+TUX22lXLHCv8wJqxKud8NdPrujF4Lnbx+GHgI=") - ); - } - } - - @Override - JsonObjectBuilder extraConfig() { - return super.extraConfig() - .add(Publisher.CONFIG_DESTINATION, "PROJECT") - .add("jiraTicketType", "Task"); - } - - @Test - @Override - @TestTransaction - void testInformWithBomConsumedNotification() throws Exception { - super.testInformWithBomConsumedNotification(); - - wireMock.verifyThat(postRequestedFor(urlPathEqualTo("/rest/api/2/issue")) - .withHeader("Authorization", equalTo("Basic amlyYVVzZXI6amlyYVBhc3N3b3Jk")) - .withHeader("Content-Type", equalTo("application/json")) - .withRequestBody(equalToJson(""" - { - "fields" : { - "project" : { - "key" : "PROJECT" - }, - "issuetype" : { - "name" : "Task" - }, - "summary" : "[Dependency-Track] [GROUP_BOM_CONSUMED] Bill of Materials Consumed", - "description" : "A CycloneDX BOM was consumed and will be processed\\n\\\\\\\\\\n\\\\\\\\\\n*Level*\\nLEVEL_INFORMATIONAL\\n\\n" - } - } - """))); - } - - @Test - @Override - @TestTransaction - void testInformWithBomProcessingFailedNotification() throws Exception { - super.testInformWithBomProcessingFailedNotification(); - - wireMock.verifyThat(postRequestedFor(urlPathEqualTo("/rest/api/2/issue")) - .withHeader("Authorization", equalTo("Basic amlyYVVzZXI6amlyYVBhc3N3b3Jk")) - .withHeader("Content-Type", equalTo("application/json")) - .withRequestBody(equalToJson(""" - { - "fields" : { - "project" : { - "key" : "PROJECT" - }, - "issuetype" : { - "name" : "Task" - }, - "summary" : "[Dependency-Track] [GROUP_BOM_PROCESSING_FAILED] Bill of Materials Processing Failed", - "description" : "An error occurred while processing a BOM\\n\\\\\\\\\\n\\\\\\\\\\n*Level*\\nLEVEL_ERROR\\n\\n" - } - } - """))); - } - - @Test - @Override - @TestTransaction - void testInformWithBomProcessingFailedNotificationAndNoSpecVersionInSubject() throws Exception { - super.testInformWithBomProcessingFailedNotificationAndNoSpecVersionInSubject(); - - wireMock.verifyThat(postRequestedFor(urlPathEqualTo("/rest/api/2/issue")) - .withHeader("Authorization", equalTo("Basic amlyYVVzZXI6amlyYVBhc3N3b3Jk")) - .withHeader("Content-Type", equalTo("application/json")) - .withRequestBody(equalToJson(""" - { - "fields" : { - "project" : { - "key" : "PROJECT" - }, - "issuetype" : { - "name" : "Task" - }, - "summary" : "[Dependency-Track] [GROUP_BOM_PROCESSING_FAILED] Bill of Materials Processing Failed", - "description" : "An error occurred while processing a BOM\\n\\\\\\\\\\n\\\\\\\\\\n*Level*\\nLEVEL_ERROR\\n\\n" - } - } - """))); - } - - @Test - @Override - @TestTransaction - void testInformWithBomValidationFailedNotificationSubject() throws Exception { - super.testInformWithBomValidationFailedNotificationSubject(); - - wireMock.verifyThat(postRequestedFor(urlPathEqualTo("/rest/api/2/issue")) - .withHeader("Authorization", equalTo("Basic amlyYVVzZXI6amlyYVBhc3N3b3Jk")) - .withHeader("Content-Type", equalTo("application/json")) - .withRequestBody(equalToJson(""" - { - "fields" : { - "project" : { - "key" : "PROJECT" - }, - "issuetype" : { - "name" : "Task" - }, - "summary" : "[Dependency-Track] [GROUP_BOM_VALIDATION_FAILED] Bill of Materials Validation Failed", - "description" : "An error occurred while validating a BOM\\n\\\\\\\\\\n\\\\\\\\\\n*Level*\\nLEVEL_ERROR\\n\\n" - } - } - """))); - } - - @Test - @Override - @TestTransaction - void testInformWithDataSourceMirroringNotification() throws Exception { - super.testInformWithDataSourceMirroringNotification(); - - wireMock.verifyThat(postRequestedFor(urlPathEqualTo("/rest/api/2/issue")) - .withHeader("Authorization", equalTo("Basic amlyYVVzZXI6amlyYVBhc3N3b3Jk")) - .withHeader("Content-Type", equalTo("application/json")) - .withRequestBody(equalToJson(""" - { - "fields" : { - "project" : { - "key" : "PROJECT" - }, - "issuetype" : { - "name" : "Task" - }, - "summary" : "[Dependency-Track] [GROUP_DATASOURCE_MIRRORING] GitHub Advisory Mirroring", - "description" : "An error occurred mirroring the contents of GitHub Advisories. Check log for details.\\n\\\\\\\\\\n\\\\\\\\\\n*Level*\\nLEVEL_ERROR\\n\\n" - } - } - """))); - } - - @Test - @Override - @TestTransaction - void testInformWithNewVulnerabilityNotification() throws Exception { - super.testInformWithNewVulnerabilityNotification(); - - wireMock.verifyThat(postRequestedFor(urlPathEqualTo("/rest/api/2/issue")) - .withHeader("Authorization", equalTo("Basic amlyYVVzZXI6amlyYVBhc3N3b3Jk")) - .withHeader("Content-Type", equalTo("application/json")) - .withRequestBody(equalToJson(""" - { - "fields" : { - "project" : { - "key" : "PROJECT" - }, - "issuetype" : { - "name" : "Task" - }, - "summary" : "[Dependency-Track] [GROUP_NEW_VULNERABILITY] [MEDIUM] New medium vulnerability identified: INT-001", - "description" : "A new vulnerability has been identified on your project(s).\\n\\\\\\\\\\n\\\\\\\\\\n*Vulnerability description*\\n{code:none|bgColor=white|borderStyle=none}vulnerabilityDescription{code}\\n\\n*VulnID*\\nINT-001\\n\\n*Severity*\\nMedium\\n\\n*Component*\\n[componentName : componentVersion|https://example.com/components/94f87321-a5d1-4c2f-b2fe-95165debebc6]\\n\\n*Affected project(s)*\\n- [projectName (projectVersion)|https://example.com/projects/c9c9539a-e381-4b36-ac52-6a7ab83b2c95]\\n" - } - } - """))); - } - - @Test - @Override - @TestTransaction - void testInformWithProjectAuditChangeNotification() throws Exception { - super.testInformWithProjectAuditChangeNotification(); - - wireMock.verifyThat(postRequestedFor(urlPathEqualTo("/rest/api/2/issue")) - .withHeader("Authorization", equalTo("Basic amlyYVVzZXI6amlyYVBhc3N3b3Jk")) - .withHeader("Content-Type", equalTo("application/json")) - .withRequestBody(equalToJson(""" - { - "fields" : { - "project" : { - "key" : "PROJECT" - }, - "issuetype" : { - "name" : "Task" - }, - "summary" : "[Dependency-Track] [GROUP_PROJECT_AUDIT_CHANGE] Analysis Decision: Finding Suppressed", - "description" : "\\n\\\\\\\\\\n\\\\\\\\\\n*Level*\\nLEVEL_INFORMATIONAL\\n\\n" - } - } - """))); - } - - @Test - @Override - @TestTransaction - public void testInformWithNewVulnerableDependencyNotification() throws Exception { - super.testInformWithNewVulnerableDependencyNotification(); - - wireMock.verifyThat(postRequestedFor(urlPathEqualTo("/rest/api/2/issue")) - .withHeader("Authorization", equalTo("Basic amlyYVVzZXI6amlyYVBhc3N3b3Jk")) - .withHeader("Content-Type", equalTo("application/json")) - .withRequestBody(equalToJson(""" - { - "fields": { - "project": { - "key": "PROJECT" - }, - "issuetype": { - "name": "Task" - }, - "summary": "[Dependency-Track] [GROUP_NEW_VULNERABLE_DEPENDENCY] Vulnerable dependency introduced on project projectName", - "description": "A component which contains one or more vulnerabilities has been added to your project.\\n\\\\\\\\\\n\\\\\\\\\\n*Project*\\n[pkg:maven/org.acme/projectName@projectVersion|https://example.com/projects/c9c9539a-e381-4b36-ac52-6a7ab83b2c95]\\n\\n*Component*\\n[componentName : componentVersion|https://example.com/components/94f87321-a5d1-4c2f-b2fe-95165debebc6]\\n\\n*Vulnerabilities*\\n- INT-001 (Medium)\\n" - } - } - """))); - } - - @Test - @Override - @TestTransaction - void testInformWithTemplateInclude() throws Exception { - super.testInformWithTemplateInclude(); - } - -} diff --git a/notification-publisher/src/test/java/org/dependencytrack/notification/publisher/MattermostPublisherTest.java b/notification-publisher/src/test/java/org/dependencytrack/notification/publisher/MattermostPublisherTest.java deleted file mode 100644 index 69b77dda66..0000000000 --- a/notification-publisher/src/test/java/org/dependencytrack/notification/publisher/MattermostPublisherTest.java +++ /dev/null @@ -1,199 +0,0 @@ -/* - * 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 io.quarkus.test.TestTransaction; -import io.quarkus.test.junit.QuarkusTest; -import io.quarkus.test.junit.QuarkusTestProfile; -import io.quarkus.test.junit.TestProfile; -import jakarta.json.JsonObjectBuilder; -import org.junit.jupiter.api.Test; - -import java.util.Map; - -import static com.github.tomakehurst.wiremock.client.WireMock.anyUrl; -import static com.github.tomakehurst.wiremock.client.WireMock.equalTo; -import static com.github.tomakehurst.wiremock.client.WireMock.equalToJson; -import static com.github.tomakehurst.wiremock.client.WireMock.postRequestedFor; - -@QuarkusTest -@TestProfile(MattermostPublisherTest.TestProfile.class) -public class MattermostPublisherTest extends AbstractWebhookPublisherTest { - - public static class TestProfile implements QuarkusTestProfile { - - @Override - public Map getConfigOverrides() { - return Map.ofEntries( - Map.entry("dtrack.general.base.url", "https://example.com") - ); - } - - } - - @Override - JsonObjectBuilder extraConfig() { - return super.extraConfig() - .add(Publisher.CONFIG_DESTINATION, "http://localhost:" + wireMockPort); - } - - @Test - @Override - @TestTransaction - void testInformWithBomConsumedNotification() throws Exception { - super.testInformWithBomConsumedNotification(); - - wireMock.verifyThat(postRequestedFor(anyUrl()) - .withHeader("Content-Type", equalTo("application/json")) - .withRequestBody(equalToJson(""" - { - "username": "Dependency Track", - "icon_url": "https://raw.githubusercontent.com/DependencyTrack/branding/master/dt-logo-symbol-blue-background.png", - "text": "#### Bill of Materials Consumed\\nA CycloneDX BOM was consumed and will be processed\\n**Project**: pkg:maven/org.acme/projectName@projectVersion\\n[View Project](https://example.com/projects/c9c9539a-e381-4b36-ac52-6a7ab83b2c95)" - } - """))); - } - - @Test - @Override - @TestTransaction - void testInformWithBomProcessingFailedNotification() throws Exception { - super.testInformWithBomProcessingFailedNotification(); - - wireMock.verifyThat(postRequestedFor(anyUrl()) - .withHeader("Content-Type", equalTo("application/json")) - .withRequestBody(equalToJson(""" - { - "username": "Dependency Track", - "icon_url": "https://raw.githubusercontent.com/DependencyTrack/branding/master/dt-logo-symbol-blue-background.png", - "text": "#### Bill of Materials Processing Failed\\nAn error occurred while processing a BOM\\n" - } - """))); - } - - @Test - @Override - @TestTransaction - void testInformWithBomProcessingFailedNotificationAndNoSpecVersionInSubject() throws Exception { - super.testInformWithBomProcessingFailedNotificationAndNoSpecVersionInSubject(); - - wireMock.verifyThat(postRequestedFor(anyUrl()) - .withHeader("Content-Type", equalTo("application/json")) - .withRequestBody(equalToJson(""" - { - "username": "Dependency Track", - "icon_url": "https://raw.githubusercontent.com/DependencyTrack/branding/master/dt-logo-symbol-blue-background.png", - "text": "#### Bill of Materials Processing Failed\\nAn error occurred while processing a BOM\\n" - } - """))); - } - - @Test - @Override - @TestTransaction - public void testInformWithBomValidationFailedNotificationSubject() throws Exception { - super.testInformWithBomValidationFailedNotificationSubject(); - - wireMock.verifyThat(postRequestedFor(anyUrl()) - .withHeader("Content-Type", equalTo("application/json")) - .withRequestBody(equalToJson(""" - { - "username": "Dependency Track", - "icon_url": "https://raw.githubusercontent.com/DependencyTrack/branding/master/dt-logo-symbol-blue-background.png", - "text": "#### Bill of Materials Validation Failed\\nAn error occurred while validating a BOM\\n" - } - """))); - } - - @Test - @Override - @TestTransaction - void testInformWithDataSourceMirroringNotification() throws Exception { - super.testInformWithDataSourceMirroringNotification(); - - wireMock.verifyThat(postRequestedFor(anyUrl()) - .withHeader("Content-Type", equalTo("application/json")) - .withRequestBody(equalToJson(""" - { - "username" : "Dependency Track", - "icon_url" : "https://raw.githubusercontent.com/DependencyTrack/branding/master/dt-logo-symbol-blue-background.png", - "text" : "#### GitHub Advisory Mirroring\\nAn error occurred mirroring the contents of GitHub Advisories. Check log for details.\\n" - } - """))); - } - - @Test - @Override - @TestTransaction - void testInformWithNewVulnerabilityNotification() throws Exception { - super.testInformWithNewVulnerabilityNotification(); - - wireMock.verifyThat(postRequestedFor(anyUrl()) - .withHeader("Content-Type", equalTo("application/json")) - .withRequestBody(equalToJson(""" - { - "username": "Dependency Track", - "icon_url": "https://raw.githubusercontent.com/DependencyTrack/branding/master/dt-logo-symbol-blue-background.png", - "text": "#### New Vulnerability Identified\\n\\n**Component**: componentName : componentVersion\\n**Vulnerability**: INT-001, MEDIUM\\n[View Component](https://example.com/components/94f87321-a5d1-4c2f-b2fe-95165debebc6) - [View Vulnerability](https://example.com/vulnerabilities/INTERNAL/INT-001)" - } - """))); - } - - @Test - @Override - @TestTransaction - void testInformWithProjectAuditChangeNotification() throws Exception { - super.testInformWithProjectAuditChangeNotification(); - - wireMock.verifyThat(postRequestedFor(anyUrl()) - .withHeader("Content-Type", equalTo("application/json")) - .withRequestBody(equalToJson(""" - { - "username": "Dependency Track", - "icon_url": "https://raw.githubusercontent.com/DependencyTrack/branding/master/dt-logo-symbol-blue-background.png", - "text": "#### Analysis Decision: Finding Suppressed\\n\\n**Project**: pkg:maven/org.acme/projectName@projectVersion\\n**Component**: componentName : componentVersion\\n**Vulnerability**: INT-001, MEDIUM\\n**Analysis**: FALSE_POSITIVE, suppressed: true\\n[View Project](https://example.com/projects/c9c9539a-e381-4b36-ac52-6a7ab83b2c95) - [View Component](https://example.com/components/94f87321-a5d1-4c2f-b2fe-95165debebc6) - [View Vulnerability](https://example.com/vulnerabilities/INTERNAL/INT-001)" - } - """))); - } - - @Test - @Override - @TestTransaction - public void testInformWithNewVulnerableDependencyNotification() throws Exception { - super.testInformWithNewVulnerableDependencyNotification(); - - wireMock.verifyThat(postRequestedFor(anyUrl()) - .withHeader("Content-Type", equalTo("application/json")) - .withRequestBody(equalToJson(""" - { - "username" : "Dependency Track", - "icon_url" : "https://raw.githubusercontent.com/DependencyTrack/branding/master/dt-logo-symbol-blue-background.png", - "text" : "#### Vulnerable Dependency Introduced\\n\\n**Project**: pkg:maven/org.acme/projectName@projectVersion\\n**Component**: componentName : componentVersion\\n[View Project](https://example.com/projects/c9c9539a-e381-4b36-ac52-6a7ab83b2c95) - [View Component](https://example.com/components/94f87321-a5d1-4c2f-b2fe-95165debebc6)" - } - """))); - } - - @Test - @Override - @TestTransaction - void testInformWithTemplateInclude() throws Exception { - super.testInformWithTemplateInclude(); - } - -} diff --git a/notification-publisher/src/test/java/org/dependencytrack/notification/publisher/MsTeamsPublisherTest.java b/notification-publisher/src/test/java/org/dependencytrack/notification/publisher/MsTeamsPublisherTest.java deleted file mode 100644 index d5ef165c53..0000000000 --- a/notification-publisher/src/test/java/org/dependencytrack/notification/publisher/MsTeamsPublisherTest.java +++ /dev/null @@ -1,430 +0,0 @@ -/* - * 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 io.quarkus.test.TestTransaction; -import io.quarkus.test.junit.QuarkusTest; -import io.quarkus.test.junit.QuarkusTestProfile; -import io.quarkus.test.junit.TestProfile; -import jakarta.json.JsonObjectBuilder; -import org.junit.jupiter.api.Test; - -import java.util.Map; - -import static com.github.tomakehurst.wiremock.client.WireMock.anyUrl; -import static com.github.tomakehurst.wiremock.client.WireMock.equalTo; -import static com.github.tomakehurst.wiremock.client.WireMock.equalToJson; -import static com.github.tomakehurst.wiremock.client.WireMock.postRequestedFor; - -@QuarkusTest -@TestProfile(MsTeamsPublisherTest.TestProfile.class) -class MsTeamsPublisherTest extends AbstractWebhookPublisherTest { - - public static class TestProfile implements QuarkusTestProfile { - - @Override - public Map getConfigOverrides() { - return Map.ofEntries( - Map.entry("dtrack.general.base.url", "https://example.com") - ); - } - } - - @Override - JsonObjectBuilder extraConfig() { - return super.extraConfig() - .add(Publisher.CONFIG_DESTINATION, "http://localhost:" + wireMockPort); - } - - @Test - @Override - @TestTransaction - void testInformWithBomConsumedNotification() throws Exception { - super.testInformWithBomConsumedNotification(); - - wireMock.verifyThat(postRequestedFor(anyUrl()) - .withHeader("Content-Type", equalTo("application/json")) - .withRequestBody(equalToJson(""" - { - "@type": "MessageCard", - "@context": "http://schema.org/extensions", - "summary": "Bill of Materials Consumed", - "title": "Bill of Materials Consumed", - "sections": [ - { - "activityTitle": "Dependency-Track", - "activitySubtitle": "1970-01-01T18:31:06.000Z", - "activityImage": "https://raw.githubusercontent.com/DependencyTrack/branding/master/dt-logo-symbol-blue-background.png", - "facts": [ - { - "name": "Level", - "value": "LEVEL_INFORMATIONAL" - }, - { - "name": "Scope", - "value": "SCOPE_PORTFOLIO" - }, - { - "name": "Group", - "value": "GROUP_BOM_CONSUMED" - } - ], - "text": "A CycloneDX BOM was consumed and will be processed" - } - ] - } - """))); - } - - @Test - @Override - @TestTransaction - void testInformWithBomProcessingFailedNotification() throws Exception { - super.testInformWithBomProcessingFailedNotification(); - - wireMock.verifyThat(postRequestedFor(anyUrl()) - .withHeader("Content-Type", equalTo("application/json")) - .withRequestBody(equalToJson(""" - { - "@type": "MessageCard", - "@context": "http://schema.org/extensions", - "summary": "Bill of Materials Processing Failed", - "title": "Bill of Materials Processing Failed", - "sections": [ - { - "activityTitle": "Dependency-Track", - "activitySubtitle": "1970-01-01T18:31:06.000Z", - "activityImage": "https://raw.githubusercontent.com/DependencyTrack/branding/master/dt-logo-symbol-blue-background.png", - "facts": [ - { - "name": "Level", - "value": "LEVEL_ERROR" - }, - { - "name": "Scope", - "value": "SCOPE_PORTFOLIO" - }, - { - "name": "Group", - "value": "GROUP_BOM_PROCESSING_FAILED" - }, - { - "name" : "Project", - "value" : "pkg:maven/org.acme/projectName@projectVersion" - }, - { - "name" : "Project URL", - "value" : "https://example.com/projects/c9c9539a-e381-4b36-ac52-6a7ab83b2c95" - } - ], - "text": "An error occurred while processing a BOM" - } - ] - } - """))); - } - - @Test - @Override - @TestTransaction - void testInformWithBomValidationFailedNotificationSubject() throws Exception { - super.testInformWithBomValidationFailedNotificationSubject(); - - wireMock.verifyThat(postRequestedFor(anyUrl()) - .withHeader("Content-Type", equalTo("application/json")) - .withRequestBody(equalToJson(""" - { - "@type": "MessageCard", - "@context": "http://schema.org/extensions", - "summary": "Bill of Materials Validation Failed", - "title": "Bill of Materials Validation Failed", - "sections": [ - { - "activityTitle": "Dependency-Track", - "activitySubtitle": "1970-01-01T18:31:06.000Z", - "activityImage": "https://raw.githubusercontent.com/DependencyTrack/branding/master/dt-logo-symbol-blue-background.png", - "facts": [ - { - "name": "Level", - "value": "LEVEL_ERROR" - }, - { - "name": "Scope", - "value": "SCOPE_PORTFOLIO" - }, - { - "name": "Group", - "value": "GROUP_BOM_VALIDATION_FAILED" - }, - { - "name" : "Project", - "value" : "pkg:maven/org.acme/projectName@projectVersion" - }, - { - "name" : "Project URL", - "value" : "https://example.com/projects/c9c9539a-e381-4b36-ac52-6a7ab83b2c95" - }, - { - "name" : "Errors", - "value" : "[cause 1, cause 2]" - } - ], - "text": "An error occurred while validating a BOM" - } - ] - } - """))); - } - - @Test - @Override - @TestTransaction - void testInformWithBomProcessingFailedNotificationAndNoSpecVersionInSubject() throws Exception { - super.testInformWithBomProcessingFailedNotificationAndNoSpecVersionInSubject(); - - wireMock.verifyThat(postRequestedFor(anyUrl()) - .withHeader("Content-Type", equalTo("application/json")) - .withRequestBody(equalToJson(""" - { - "@type": "MessageCard", - "@context": "http://schema.org/extensions", - "summary": "Bill of Materials Processing Failed", - "title": "Bill of Materials Processing Failed", - "sections": [ - { - "activityTitle": "Dependency-Track", - "activitySubtitle": "1970-01-01T18:31:06.000Z", - "activityImage": "https://raw.githubusercontent.com/DependencyTrack/branding/master/dt-logo-symbol-blue-background.png", - "facts": [ - { - "name": "Level", - "value": "LEVEL_ERROR" - }, - { - "name": "Scope", - "value": "SCOPE_PORTFOLIO" - }, - { - "name": "Group", - "value": "GROUP_BOM_PROCESSING_FAILED" - }, - { - "name" : "Project", - "value" : "pkg:maven/org.acme/projectName@projectVersion" - }, - { - "name" : "Project URL", - "value" : "https://example.com/projects/c9c9539a-e381-4b36-ac52-6a7ab83b2c95" - } - ], - "text": "An error occurred while processing a BOM" - } - ] - } - """))); - } - - @Test - @Override - @TestTransaction - void testInformWithDataSourceMirroringNotification() throws Exception { - super.testInformWithDataSourceMirroringNotification(); - - wireMock.verifyThat(postRequestedFor(anyUrl()) - .withHeader("Content-Type", equalTo("application/json")) - .withRequestBody(equalToJson(""" - { - "@type": "MessageCard", - "@context": "http://schema.org/extensions", - "summary": "GitHub Advisory Mirroring", - "title": "GitHub Advisory Mirroring", - "sections": [ - { - "activityTitle": "Dependency-Track", - "activitySubtitle": "1970-01-01T18:31:06.000Z", - "activityImage": "https://raw.githubusercontent.com/DependencyTrack/branding/master/dt-logo-symbol-blue-background.png", - "facts": [ - { - "name": "Level", - "value": "LEVEL_ERROR" - }, - { - "name": "Scope", - "value": "SCOPE_SYSTEM" - }, - { - "name": "Group", - "value": "GROUP_DATASOURCE_MIRRORING" - } - ], - "text": "An error occurred mirroring the contents of GitHub Advisories. Check log for details." - } - ] - } - """))); - } - - @Test - @Override - @TestTransaction - void testInformWithNewVulnerabilityNotification() throws Exception { - super.testInformWithNewVulnerabilityNotification(); - - wireMock.verifyThat(postRequestedFor(anyUrl()) - .withHeader("Content-Type", equalTo("application/json")) - .withRequestBody(equalToJson(""" - { - "@type": "MessageCard", - "@context": "http://schema.org/extensions", - "summary": "New Vulnerability Identified", - "title": "New Vulnerability Identified", - "sections": [ - { - "activityTitle": "Dependency-Track", - "activitySubtitle": "1970-01-01T18:31:06.000Z", - "activityImage": "https://raw.githubusercontent.com/DependencyTrack/branding/master/dt-logo-symbol-blue-background.png", - "facts": [ - { - "name": "VulnID", - "value": "INT-001" - }, - { - "name": "Severity", - "value": "MEDIUM" - }, - { - "name": "Source", - "value": "INTERNAL" - }, - { - "name": "Component", - "value": "componentName : componentVersion" - } - ], - "text": "" - } - ] - } - """))); - } - - @Test - @Override - @TestTransaction - void testInformWithProjectAuditChangeNotification() throws Exception { - super.testInformWithProjectAuditChangeNotification(); - - wireMock.verifyThat(postRequestedFor(anyUrl()) - .withHeader("Content-Type", equalTo("application/json")) - .withRequestBody(equalToJson(""" - { - "@type": "MessageCard", - "@context": "http://schema.org/extensions", - "summary": "Analysis Decision: Finding Suppressed", - "title": "Analysis Decision: Finding Suppressed", - "sections": [ - { - "activityTitle": "Dependency-Track", - "activitySubtitle": "1970-01-01T18:31:06.000Z", - "activityImage": "https://raw.githubusercontent.com/DependencyTrack/branding/master/dt-logo-symbol-blue-background.png", - "facts": [ - { - "name": "Analysis Type", - "value": "Project Analysis" - }, - { - "name": "Analysis State", - "value": "FALSE_POSITIVE" - }, - { - "name": "Suppressed", - "value": "true" - }, - { - "name": "VulnID", - "value": "INT-001" - }, - { - "name": "Severity", - "value": "MEDIUM" - }, - { - "name": "Source", - "value": "INTERNAL" - }, - { - "name": "Component", - "value": "componentName : componentVersion" - }, - { - "name": "Project", - "value": "pkg:maven/org.acme/projectName@projectVersion" - } - ], - "text": "" - } - ] - } - """))); - } - - @Test - @Override - @TestTransaction - public void testInformWithNewVulnerableDependencyNotification() throws Exception { - super.testInformWithNewVulnerableDependencyNotification(); - - wireMock.verifyThat(postRequestedFor(anyUrl()) - .withHeader("Content-Type", equalTo("application/json")) - .withRequestBody(equalToJson(""" - { - "@type": "MessageCard", - "@context": "http://schema.org/extensions", - "summary": "Vulnerable Dependency Introduced", - "title": "Vulnerable Dependency Introduced", - "sections": [ - { - "activityTitle": "Dependency-Track", - "activitySubtitle": "1970-01-01T18:31:06.000Z", - "activityImage": "https://raw.githubusercontent.com/DependencyTrack/branding/master/dt-logo-symbol-blue-background.png", - "facts": [ - { - "name": "Project", - "value": "pkg:maven/org.acme/projectName@projectVersion" - }, - { - "name": "Component", - "value": "componentName : componentVersion" - } - ], - "text": "" - } - ] - } - """))); - } - - @Test - @Override - @TestTransaction - void testInformWithTemplateInclude() throws Exception { - super.testInformWithTemplateInclude(); - } - -} diff --git a/notification-publisher/src/test/java/org/dependencytrack/notification/publisher/PublishContextTest.java b/notification-publisher/src/test/java/org/dependencytrack/notification/publisher/PublishContextTest.java deleted file mode 100644 index c6d0c5fed3..0000000000 --- a/notification-publisher/src/test/java/org/dependencytrack/notification/publisher/PublishContextTest.java +++ /dev/null @@ -1,598 +0,0 @@ -/* - * 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 com.google.protobuf.Any; -import com.google.protobuf.util.Timestamps; -import org.apache.kafka.clients.consumer.ConsumerRecord; -import org.dependencytrack.persistence.model.NotificationLevel; -import org.dependencytrack.persistence.model.NotificationRule; -import org.dependencytrack.persistence.model.NotificationScope; -import org.dependencytrack.proto.notification.v1.BomConsumedOrProcessedSubject; -import org.dependencytrack.proto.notification.v1.BomProcessingFailedSubject; -import org.dependencytrack.proto.notification.v1.BomValidationFailedSubject; -import org.dependencytrack.proto.notification.v1.Component; -import org.dependencytrack.proto.notification.v1.NewVulnerabilitySubject; -import org.dependencytrack.proto.notification.v1.NewVulnerableDependencySubject; -import org.dependencytrack.proto.notification.v1.Notification; -import org.dependencytrack.proto.notification.v1.PolicyViolationAnalysisDecisionChangeSubject; -import org.dependencytrack.proto.notification.v1.PolicyViolationSubject; -import org.dependencytrack.proto.notification.v1.Project; -import org.dependencytrack.proto.notification.v1.ProjectVulnAnalysisCompleteSubject; -import org.dependencytrack.proto.notification.v1.UserSubject; -import org.dependencytrack.proto.notification.v1.VexConsumedOrProcessedSubject; -import org.dependencytrack.proto.notification.v1.VulnerabilityAnalysisDecisionChangeSubject; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.dependencytrack.proto.notification.v1.Group.GROUP_BOM_CONSUMED; -import static org.dependencytrack.proto.notification.v1.Group.GROUP_BOM_PROCESSED; -import static org.dependencytrack.proto.notification.v1.Group.GROUP_BOM_PROCESSING_FAILED; -import static org.dependencytrack.proto.notification.v1.Group.GROUP_BOM_VALIDATION_FAILED; -import static org.dependencytrack.proto.notification.v1.Group.GROUP_NEW_VULNERABILITY; -import static org.dependencytrack.proto.notification.v1.Group.GROUP_NEW_VULNERABLE_DEPENDENCY; -import static org.dependencytrack.proto.notification.v1.Group.GROUP_POLICY_VIOLATION; -import static org.dependencytrack.proto.notification.v1.Group.GROUP_PROJECT_AUDIT_CHANGE; -import static org.dependencytrack.proto.notification.v1.Group.GROUP_PROJECT_CREATED; -import static org.dependencytrack.proto.notification.v1.Group.GROUP_PROJECT_VULN_ANALYSIS_COMPLETE; -import static org.dependencytrack.proto.notification.v1.Group.GROUP_USER_CREATED; -import static org.dependencytrack.proto.notification.v1.Group.GROUP_VEX_CONSUMED; -import static org.dependencytrack.proto.notification.v1.Group.GROUP_VEX_PROCESSED; -import static org.dependencytrack.proto.notification.v1.Level.LEVEL_INFORMATIONAL; -import static org.dependencytrack.proto.notification.v1.Scope.SCOPE_PORTFOLIO; -import static org.dependencytrack.proto.notification.v1.Scope.SCOPE_SYSTEM; - -class PublishContextTest { - - @Nested - class FromRecordTest { - - @Test - void testWithBomConsumedSubject() throws Exception { - final var notification = Notification.newBuilder() - .setGroup(GROUP_BOM_CONSUMED) - .setLevel(LEVEL_INFORMATIONAL) - .setScope(SCOPE_PORTFOLIO) - .setTimestamp(Timestamps.fromSeconds(666)) - .setSubject(Any.pack(BomConsumedOrProcessedSubject.newBuilder() - .setProject(Project.newBuilder() - .setUuid("projectUuid") - .setName("projectName") - .setVersion("projectVersion")) - .build())) - .build(); - - final PublishContext ctx = PublishContext.fromRecord(new ConsumerRecord<>("topic", 1, 2L, "key", notification)); - assertThat(ctx.kafkaTopic()).isEqualTo("topic"); - assertThat(ctx.kafkaTopicPartition()).isEqualTo(1); - assertThat(ctx.kafkaPartitionOffset()).isEqualTo(2L); - assertThat(ctx.notificationGroup()).isEqualTo("GROUP_BOM_CONSUMED"); - assertThat(ctx.notificationLevel()).isEqualTo("LEVEL_INFORMATIONAL"); - assertThat(ctx.notificationScope()).isEqualTo("SCOPE_PORTFOLIO"); - assertThat(ctx.notificationTimestamp()).isEqualTo("1970-01-01T00:11:06.000Z"); - assertThat(ctx.notificationSubjects()).hasEntrySatisfying("project", projectObj -> { - assertThat(projectObj).isInstanceOf(PublishContext.Project.class); - final var project = (PublishContext.Project) projectObj; - assertThat(project.uuid()).isEqualTo("projectUuid"); - assertThat(project.name()).isEqualTo("projectName"); - assertThat(project.version()).isEqualTo("projectVersion"); - }); - } - - @Test - void testWithBomProcessedSubject() throws Exception { - final var notification = Notification.newBuilder() - .setGroup(GROUP_BOM_PROCESSED) - .setLevel(LEVEL_INFORMATIONAL) - .setScope(SCOPE_PORTFOLIO) - .setTimestamp(Timestamps.fromSeconds(666)) - .setSubject(Any.pack(BomConsumedOrProcessedSubject.newBuilder() - .setProject(Project.newBuilder() - .setUuid("projectUuid") - .setName("projectName") - .setVersion("projectVersion")) - .build())) - .build(); - - final PublishContext ctx = PublishContext.fromRecord(new ConsumerRecord<>("topic", 1, 2L, "key", notification)); - assertThat(ctx.kafkaTopic()).isEqualTo("topic"); - assertThat(ctx.kafkaTopicPartition()).isEqualTo(1); - assertThat(ctx.kafkaPartitionOffset()).isEqualTo(2L); - assertThat(ctx.notificationGroup()).isEqualTo("GROUP_BOM_PROCESSED"); - assertThat(ctx.notificationLevel()).isEqualTo("LEVEL_INFORMATIONAL"); - assertThat(ctx.notificationScope()).isEqualTo("SCOPE_PORTFOLIO"); - assertThat(ctx.notificationTimestamp()).isEqualTo("1970-01-01T00:11:06.000Z"); - assertThat(ctx.notificationSubjects()).hasEntrySatisfying("project", projectObj -> { - assertThat(projectObj).isInstanceOf(PublishContext.Project.class); - final var project = (PublishContext.Project) projectObj; - assertThat(project.uuid()).isEqualTo("projectUuid"); - assertThat(project.name()).isEqualTo("projectName"); - assertThat(project.version()).isEqualTo("projectVersion"); - }); - } - - @Test - void testWithBomProcessingFailedSubject() throws Exception { - final var notification = Notification.newBuilder() - .setGroup(GROUP_BOM_PROCESSING_FAILED) - .setLevel(LEVEL_INFORMATIONAL) - .setScope(SCOPE_PORTFOLIO) - .setTimestamp(Timestamps.fromSeconds(666)) - .setSubject(Any.pack(BomProcessingFailedSubject.newBuilder() - .setProject(Project.newBuilder() - .setUuid("projectUuid") - .setName("projectName") - .setVersion("projectVersion")) - .build())) - .build(); - - final PublishContext ctx = PublishContext.fromRecord(new ConsumerRecord<>("topic", 1, 2L, "key", notification)); - assertThat(ctx.kafkaTopic()).isEqualTo("topic"); - assertThat(ctx.kafkaTopicPartition()).isEqualTo(1); - assertThat(ctx.kafkaPartitionOffset()).isEqualTo(2L); - assertThat(ctx.notificationGroup()).isEqualTo("GROUP_BOM_PROCESSING_FAILED"); - assertThat(ctx.notificationLevel()).isEqualTo("LEVEL_INFORMATIONAL"); - assertThat(ctx.notificationScope()).isEqualTo("SCOPE_PORTFOLIO"); - assertThat(ctx.notificationTimestamp()).isEqualTo("1970-01-01T00:11:06.000Z"); - assertThat(ctx.notificationSubjects()).hasEntrySatisfying("project", projectObj -> { - assertThat(projectObj).isInstanceOf(PublishContext.Project.class); - final var project = (PublishContext.Project) projectObj; - assertThat(project.uuid()).isEqualTo("projectUuid"); - assertThat(project.name()).isEqualTo("projectName"); - assertThat(project.version()).isEqualTo("projectVersion"); - }); - } - - @Test - void testWithBomValidationFailedSubject() throws Exception { - final var notification = Notification.newBuilder() - .setGroup(GROUP_BOM_VALIDATION_FAILED) - .setLevel(LEVEL_INFORMATIONAL) - .setScope(SCOPE_PORTFOLIO) - .setTimestamp(Timestamps.fromSeconds(666)) - .setSubject(Any.pack(BomValidationFailedSubject.newBuilder() - .setProject(Project.newBuilder() - .setUuid("projectUuid") - .setName("projectName") - .setVersion("projectVersion")) - .build())) - .build(); - - final PublishContext ctx = PublishContext.fromRecord(new ConsumerRecord<>("topic", 1, 2L, "key", notification)); - assertThat(ctx.kafkaTopic()).isEqualTo("topic"); - assertThat(ctx.kafkaTopicPartition()).isEqualTo(1); - assertThat(ctx.kafkaPartitionOffset()).isEqualTo(2L); - assertThat(ctx.notificationGroup()).isEqualTo("GROUP_BOM_VALIDATION_FAILED"); - assertThat(ctx.notificationLevel()).isEqualTo("LEVEL_INFORMATIONAL"); - assertThat(ctx.notificationScope()).isEqualTo("SCOPE_PORTFOLIO"); - assertThat(ctx.notificationTimestamp()).isEqualTo("1970-01-01T00:11:06.000Z"); - assertThat(ctx.notificationSubjects()).hasEntrySatisfying("project", projectObj -> { - assertThat(projectObj).isInstanceOf(PublishContext.Project.class); - final var project = (PublishContext.Project) projectObj; - assertThat(project.uuid()).isEqualTo("projectUuid"); - assertThat(project.name()).isEqualTo("projectName"); - assertThat(project.version()).isEqualTo("projectVersion"); - }); - } - - @Test - void testWithNewVulnerabilitySubject() throws Exception { - final var notification = Notification.newBuilder() - .setGroup(GROUP_NEW_VULNERABILITY) - .setLevel(LEVEL_INFORMATIONAL) - .setScope(SCOPE_PORTFOLIO) - .setTimestamp(Timestamps.fromSeconds(666)) - .setSubject(Any.pack(NewVulnerabilitySubject.newBuilder() - .setProject(Project.newBuilder() - .setUuid("projectUuid") - .setName("projectName") - .setVersion("projectVersion")) - .setComponent(Component.newBuilder() - .setUuid("componentUuid") - .setGroup("componentGroup") - .setName("componentName") - .setVersion("componentVersion")) - .build())) - .build(); - - final PublishContext ctx = PublishContext.fromRecord(new ConsumerRecord<>("topic", 1, 2L, "key", notification)); - assertThat(ctx.kafkaTopic()).isEqualTo("topic"); - assertThat(ctx.kafkaTopicPartition()).isEqualTo(1); - assertThat(ctx.kafkaPartitionOffset()).isEqualTo(2L); - assertThat(ctx.notificationGroup()).isEqualTo("GROUP_NEW_VULNERABILITY"); - assertThat(ctx.notificationLevel()).isEqualTo("LEVEL_INFORMATIONAL"); - assertThat(ctx.notificationScope()).isEqualTo("SCOPE_PORTFOLIO"); - assertThat(ctx.notificationTimestamp()).isEqualTo("1970-01-01T00:11:06.000Z"); - assertThat(ctx.notificationSubjects()) - .hasEntrySatisfying("project", projectObj -> { - assertThat(projectObj).isInstanceOf(PublishContext.Project.class); - final var project = (PublishContext.Project) projectObj; - assertThat(project.uuid()).isEqualTo("projectUuid"); - assertThat(project.name()).isEqualTo("projectName"); - assertThat(project.version()).isEqualTo("projectVersion"); - }) - .hasEntrySatisfying("component", componentObj -> { - assertThat(componentObj).isInstanceOf(PublishContext.Component.class); - final var component = (PublishContext.Component) componentObj; - assertThat(component.uuid()).isEqualTo("componentUuid"); - assertThat(component.group()).isEqualTo("componentGroup"); - assertThat(component.name()).isEqualTo("componentName"); - assertThat(component.version()).isEqualTo("componentVersion"); - }); - } - - @Test - void testWithNewVulnerableDependencySubject() throws Exception { - final var notification = Notification.newBuilder() - .setGroup(GROUP_NEW_VULNERABLE_DEPENDENCY) - .setLevel(LEVEL_INFORMATIONAL) - .setScope(SCOPE_PORTFOLIO) - .setTimestamp(Timestamps.fromSeconds(666)) - .setSubject(Any.pack(NewVulnerableDependencySubject.newBuilder() - .setProject(Project.newBuilder() - .setUuid("projectUuid") - .setName("projectName") - .setVersion("projectVersion")) - .setComponent(Component.newBuilder() - .setUuid("componentUuid") - .setGroup("componentGroup") - .setName("componentName") - .setVersion("componentVersion")) - .build())) - .build(); - - final PublishContext ctx = PublishContext.fromRecord(new ConsumerRecord<>("topic", 1, 2L, "key", notification)); - assertThat(ctx.kafkaTopic()).isEqualTo("topic"); - assertThat(ctx.kafkaTopicPartition()).isEqualTo(1); - assertThat(ctx.kafkaPartitionOffset()).isEqualTo(2L); - assertThat(ctx.notificationGroup()).isEqualTo("GROUP_NEW_VULNERABLE_DEPENDENCY"); - assertThat(ctx.notificationLevel()).isEqualTo("LEVEL_INFORMATIONAL"); - assertThat(ctx.notificationScope()).isEqualTo("SCOPE_PORTFOLIO"); - assertThat(ctx.notificationTimestamp()).isEqualTo("1970-01-01T00:11:06.000Z"); - assertThat(ctx.notificationSubjects()) - .hasEntrySatisfying("project", projectObj -> { - assertThat(projectObj).isInstanceOf(PublishContext.Project.class); - final var project = (PublishContext.Project) projectObj; - assertThat(project.uuid()).isEqualTo("projectUuid"); - assertThat(project.name()).isEqualTo("projectName"); - assertThat(project.version()).isEqualTo("projectVersion"); - }) - .hasEntrySatisfying("component", componentObj -> { - assertThat(componentObj).isInstanceOf(PublishContext.Component.class); - final var component = (PublishContext.Component) componentObj; - assertThat(component.uuid()).isEqualTo("componentUuid"); - assertThat(component.group()).isEqualTo("componentGroup"); - assertThat(component.name()).isEqualTo("componentName"); - assertThat(component.version()).isEqualTo("componentVersion"); - }); - } - - @Test - void testWithProjectCreatedSubject() throws Exception { - final var notification = Notification.newBuilder() - .setGroup(GROUP_PROJECT_CREATED) - .setLevel(LEVEL_INFORMATIONAL) - .setScope(SCOPE_PORTFOLIO) - .setTimestamp(Timestamps.fromSeconds(666)) - .setSubject(Any.pack(Project.newBuilder() - .setUuid("projectUuid") - .setName("projectName") - .setVersion("projectVersion") - .build())) - .build(); - - final PublishContext ctx = PublishContext.fromRecord(new ConsumerRecord<>("topic", 1, 2L, "key", notification)); - assertThat(ctx.kafkaTopic()).isEqualTo("topic"); - assertThat(ctx.kafkaTopicPartition()).isEqualTo(1); - assertThat(ctx.kafkaPartitionOffset()).isEqualTo(2L); - assertThat(ctx.notificationGroup()).isEqualTo("GROUP_PROJECT_CREATED"); - assertThat(ctx.notificationLevel()).isEqualTo("LEVEL_INFORMATIONAL"); - assertThat(ctx.notificationScope()).isEqualTo("SCOPE_PORTFOLIO"); - assertThat(ctx.notificationTimestamp()).isEqualTo("1970-01-01T00:11:06.000Z"); - assertThat(ctx.notificationSubjects()).hasEntrySatisfying("project", projectObj -> { - assertThat(projectObj).isInstanceOf(PublishContext.Project.class); - final var project = (PublishContext.Project) projectObj; - assertThat(project.uuid()).isEqualTo("projectUuid"); - assertThat(project.name()).isEqualTo("projectName"); - assertThat(project.version()).isEqualTo("projectVersion"); - }); - } - - @Test - void testWithProjectVulnAnalysisCompletedSubject() throws Exception { - final var notification = Notification.newBuilder() - .setGroup(GROUP_PROJECT_VULN_ANALYSIS_COMPLETE) - .setLevel(LEVEL_INFORMATIONAL) - .setScope(SCOPE_PORTFOLIO) - .setTimestamp(Timestamps.fromSeconds(666)) - .setSubject(Any.pack(ProjectVulnAnalysisCompleteSubject.newBuilder() - .setProject(Project.newBuilder() - .setUuid("projectUuid") - .setName("projectName") - .setVersion("projectVersion")) - .build())) - .build(); - - final PublishContext ctx = PublishContext.fromRecord(new ConsumerRecord<>("topic", 1, 2L, "key", notification)); - assertThat(ctx.kafkaTopic()).isEqualTo("topic"); - assertThat(ctx.kafkaTopicPartition()).isEqualTo(1); - assertThat(ctx.kafkaPartitionOffset()).isEqualTo(2L); - assertThat(ctx.notificationGroup()).isEqualTo("GROUP_PROJECT_VULN_ANALYSIS_COMPLETE"); - assertThat(ctx.notificationLevel()).isEqualTo("LEVEL_INFORMATIONAL"); - assertThat(ctx.notificationScope()).isEqualTo("SCOPE_PORTFOLIO"); - assertThat(ctx.notificationTimestamp()).isEqualTo("1970-01-01T00:11:06.000Z"); - assertThat(ctx.notificationSubjects()).hasEntrySatisfying("project", projectObj -> { - assertThat(projectObj).isInstanceOf(PublishContext.Project.class); - final var project = (PublishContext.Project) projectObj; - assertThat(project.uuid()).isEqualTo("projectUuid"); - assertThat(project.name()).isEqualTo("projectName"); - assertThat(project.version()).isEqualTo("projectVersion"); - }); - } - - @Test - void testWithPolicyViolationSubject() throws Exception { - final var notification = Notification.newBuilder() - .setGroup(GROUP_POLICY_VIOLATION) - .setLevel(LEVEL_INFORMATIONAL) - .setScope(SCOPE_PORTFOLIO) - .setTimestamp(Timestamps.fromSeconds(666)) - .setSubject(Any.pack(PolicyViolationSubject.newBuilder() - .setProject(Project.newBuilder() - .setUuid("projectUuid") - .setName("projectName") - .setVersion("projectVersion")) - .setComponent(Component.newBuilder() - .setUuid("componentUuid") - .setGroup("componentGroup") - .setName("componentName") - .setVersion("componentVersion")) - .build())) - .build(); - - final PublishContext ctx = PublishContext.fromRecord(new ConsumerRecord<>("topic", 1, 2L, "key", notification)); - assertThat(ctx.kafkaTopic()).isEqualTo("topic"); - assertThat(ctx.kafkaTopicPartition()).isEqualTo(1); - assertThat(ctx.kafkaPartitionOffset()).isEqualTo(2L); - assertThat(ctx.notificationGroup()).isEqualTo("GROUP_POLICY_VIOLATION"); - assertThat(ctx.notificationLevel()).isEqualTo("LEVEL_INFORMATIONAL"); - assertThat(ctx.notificationScope()).isEqualTo("SCOPE_PORTFOLIO"); - assertThat(ctx.notificationTimestamp()).isEqualTo("1970-01-01T00:11:06.000Z"); - assertThat(ctx.notificationSubjects()) - .hasEntrySatisfying("project", projectObj -> { - assertThat(projectObj).isInstanceOf(PublishContext.Project.class); - final var project = (PublishContext.Project) projectObj; - assertThat(project.uuid()).isEqualTo("projectUuid"); - assertThat(project.name()).isEqualTo("projectName"); - assertThat(project.version()).isEqualTo("projectVersion"); - }) - .hasEntrySatisfying("component", componentObj -> { - assertThat(componentObj).isInstanceOf(PublishContext.Component.class); - final var component = (PublishContext.Component) componentObj; - assertThat(component.uuid()).isEqualTo("componentUuid"); - assertThat(component.group()).isEqualTo("componentGroup"); - assertThat(component.name()).isEqualTo("componentName"); - assertThat(component.version()).isEqualTo("componentVersion"); - }); - } - - @Test - void testWithPolicyViolationAnalysisDecisionChangeSubject() throws Exception { - final var notification = Notification.newBuilder() - .setGroup(GROUP_PROJECT_AUDIT_CHANGE) - .setLevel(LEVEL_INFORMATIONAL) - .setScope(SCOPE_PORTFOLIO) - .setTimestamp(Timestamps.fromSeconds(666)) - .setSubject(Any.pack(PolicyViolationAnalysisDecisionChangeSubject.newBuilder() - .setProject(Project.newBuilder() - .setUuid("projectUuid") - .setName("projectName") - .setVersion("projectVersion")) - .setComponent(Component.newBuilder() - .setUuid("componentUuid") - .setGroup("componentGroup") - .setName("componentName") - .setVersion("componentVersion")) - .build())) - .build(); - - final PublishContext ctx = PublishContext.fromRecord(new ConsumerRecord<>("topic", 1, 2L, "key", notification)); - assertThat(ctx.kafkaTopic()).isEqualTo("topic"); - assertThat(ctx.kafkaTopicPartition()).isEqualTo(1); - assertThat(ctx.kafkaPartitionOffset()).isEqualTo(2L); - assertThat(ctx.notificationGroup()).isEqualTo("GROUP_PROJECT_AUDIT_CHANGE"); - assertThat(ctx.notificationLevel()).isEqualTo("LEVEL_INFORMATIONAL"); - assertThat(ctx.notificationScope()).isEqualTo("SCOPE_PORTFOLIO"); - assertThat(ctx.notificationTimestamp()).isEqualTo("1970-01-01T00:11:06.000Z"); - assertThat(ctx.notificationSubjects()) - .hasEntrySatisfying("project", projectObj -> { - assertThat(projectObj).isInstanceOf(PublishContext.Project.class); - final var project = (PublishContext.Project) projectObj; - assertThat(project.uuid()).isEqualTo("projectUuid"); - assertThat(project.name()).isEqualTo("projectName"); - assertThat(project.version()).isEqualTo("projectVersion"); - }) - .hasEntrySatisfying("component", componentObj -> { - assertThat(componentObj).isInstanceOf(PublishContext.Component.class); - final var component = (PublishContext.Component) componentObj; - assertThat(component.uuid()).isEqualTo("componentUuid"); - assertThat(component.group()).isEqualTo("componentGroup"); - assertThat(component.name()).isEqualTo("componentName"); - assertThat(component.version()).isEqualTo("componentVersion"); - }); - } - - @Test - void testWithVulnerabilityAnalysisDecisionChangeSubject() throws Exception { - final var notification = Notification.newBuilder() - .setGroup(GROUP_PROJECT_AUDIT_CHANGE) - .setLevel(LEVEL_INFORMATIONAL) - .setScope(SCOPE_PORTFOLIO) - .setTimestamp(Timestamps.fromSeconds(666)) - .setSubject(Any.pack(VulnerabilityAnalysisDecisionChangeSubject.newBuilder() - .setProject(Project.newBuilder() - .setUuid("projectUuid") - .setName("projectName") - .setVersion("projectVersion")) - .setComponent(Component.newBuilder() - .setUuid("componentUuid") - .setGroup("componentGroup") - .setName("componentName") - .setVersion("componentVersion")) - .build())) - .build(); - - final PublishContext ctx = PublishContext.fromRecord(new ConsumerRecord<>("topic", 1, 2L, "key", notification)); - assertThat(ctx.kafkaTopic()).isEqualTo("topic"); - assertThat(ctx.kafkaTopicPartition()).isEqualTo(1); - assertThat(ctx.kafkaPartitionOffset()).isEqualTo(2L); - assertThat(ctx.notificationGroup()).isEqualTo("GROUP_PROJECT_AUDIT_CHANGE"); - assertThat(ctx.notificationLevel()).isEqualTo("LEVEL_INFORMATIONAL"); - assertThat(ctx.notificationScope()).isEqualTo("SCOPE_PORTFOLIO"); - assertThat(ctx.notificationTimestamp()).isEqualTo("1970-01-01T00:11:06.000Z"); - assertThat(ctx.notificationSubjects()) - .hasEntrySatisfying("project", projectObj -> { - assertThat(projectObj).isInstanceOf(PublishContext.Project.class); - final var project = (PublishContext.Project) projectObj; - assertThat(project.uuid()).isEqualTo("projectUuid"); - assertThat(project.name()).isEqualTo("projectName"); - assertThat(project.version()).isEqualTo("projectVersion"); - }) - .hasEntrySatisfying("component", componentObj -> { - assertThat(componentObj).isInstanceOf(PublishContext.Component.class); - final var component = (PublishContext.Component) componentObj; - assertThat(component.uuid()).isEqualTo("componentUuid"); - assertThat(component.group()).isEqualTo("componentGroup"); - assertThat(component.name()).isEqualTo("componentName"); - assertThat(component.version()).isEqualTo("componentVersion"); - }); - } - - @Test - void testWithVexConsumedSubject() throws Exception { - final var notification = Notification.newBuilder() - .setGroup(GROUP_VEX_CONSUMED) - .setLevel(LEVEL_INFORMATIONAL) - .setScope(SCOPE_PORTFOLIO) - .setTimestamp(Timestamps.fromSeconds(666)) - .setSubject(Any.pack(VexConsumedOrProcessedSubject.newBuilder() - .setProject(Project.newBuilder() - .setUuid("projectUuid") - .setName("projectName") - .setVersion("projectVersion")) - .build())) - .build(); - - final PublishContext ctx = PublishContext.fromRecord(new ConsumerRecord<>("topic", 1, 2L, "key", notification)); - assertThat(ctx.kafkaTopic()).isEqualTo("topic"); - assertThat(ctx.kafkaTopicPartition()).isEqualTo(1); - assertThat(ctx.kafkaPartitionOffset()).isEqualTo(2L); - assertThat(ctx.notificationGroup()).isEqualTo("GROUP_VEX_CONSUMED"); - assertThat(ctx.notificationLevel()).isEqualTo("LEVEL_INFORMATIONAL"); - assertThat(ctx.notificationScope()).isEqualTo("SCOPE_PORTFOLIO"); - assertThat(ctx.notificationTimestamp()).isEqualTo("1970-01-01T00:11:06.000Z"); - assertThat(ctx.notificationSubjects()).hasEntrySatisfying("project", projectObj -> { - assertThat(projectObj).isInstanceOf(PublishContext.Project.class); - final var project = (PublishContext.Project) projectObj; - assertThat(project.uuid()).isEqualTo("projectUuid"); - assertThat(project.name()).isEqualTo("projectName"); - assertThat(project.version()).isEqualTo("projectVersion"); - }); - } - - @Test - void testWithVexProcessedSubject() throws Exception { - final var notification = Notification.newBuilder() - .setGroup(GROUP_VEX_PROCESSED) - .setLevel(LEVEL_INFORMATIONAL) - .setScope(SCOPE_PORTFOLIO) - .setTimestamp(Timestamps.fromSeconds(666)) - .setSubject(Any.pack(VexConsumedOrProcessedSubject.newBuilder() - .setProject(Project.newBuilder() - .setUuid("projectUuid") - .setName("projectName") - .setVersion("projectVersion")) - .build())) - .build(); - - final PublishContext ctx = PublishContext.fromRecord(new ConsumerRecord<>("topic", 1, 2L, "key", notification)); - assertThat(ctx.kafkaTopic()).isEqualTo("topic"); - assertThat(ctx.kafkaTopicPartition()).isEqualTo(1); - assertThat(ctx.kafkaPartitionOffset()).isEqualTo(2L); - assertThat(ctx.notificationGroup()).isEqualTo("GROUP_VEX_PROCESSED"); - assertThat(ctx.notificationLevel()).isEqualTo("LEVEL_INFORMATIONAL"); - assertThat(ctx.notificationScope()).isEqualTo("SCOPE_PORTFOLIO"); - assertThat(ctx.notificationTimestamp()).isEqualTo("1970-01-01T00:11:06.000Z"); - assertThat(ctx.notificationSubjects()).hasEntrySatisfying("project", projectObj -> { - assertThat(projectObj).isInstanceOf(PublishContext.Project.class); - final var project = (PublishContext.Project) projectObj; - assertThat(project.uuid()).isEqualTo("projectUuid"); - assertThat(project.name()).isEqualTo("projectName"); - assertThat(project.version()).isEqualTo("projectVersion"); - }); - } - - @Test - void testWithUserSubject() throws Exception { - final var notification = Notification.newBuilder() - .setGroup(GROUP_USER_CREATED) - .setLevel(LEVEL_INFORMATIONAL) - .setScope(SCOPE_SYSTEM) - .setTimestamp(Timestamps.fromSeconds(666)) - .setSubject(Any.pack(UserSubject.newBuilder() - .setUsername("username") - .setEmail("email.com") - .build())) - .build(); - - final PublishContext ctx = PublishContext.fromRecord(new ConsumerRecord<>("topic", 1, 2L, "key", notification)); - assertThat(ctx.kafkaTopic()).isEqualTo("topic"); - assertThat(ctx.kafkaTopicPartition()).isEqualTo(1); - assertThat(ctx.kafkaPartitionOffset()).isEqualTo(2L); - assertThat(ctx.notificationGroup()).isEqualTo("GROUP_USER_CREATED"); - assertThat(ctx.notificationLevel()).isEqualTo("LEVEL_INFORMATIONAL"); - assertThat(ctx.notificationScope()).isEqualTo("SCOPE_SYSTEM"); - assertThat(ctx.notificationTimestamp()).isEqualTo("1970-01-01T00:11:06.000Z"); - assertThat(ctx.notificationSubjects()) - .hasEntrySatisfying("user", userObj -> { - assertThat(userObj).isInstanceOf(PublishContext.User.class); - final var user = (PublishContext.User) userObj; - assertThat(user.username()).isEqualTo("username"); - assertThat(user.email()).isEqualTo("email.com"); - }); - } - } - - @Test - void testWithRule() { - var ctx = new PublishContext(null, 0, 0L, null, null, null, null, null); - - final var notificationRule = new NotificationRule(); - notificationRule.setName("foo"); - notificationRule.setNotificationLevel(NotificationLevel.ERROR); - notificationRule.setScope(NotificationScope.SYSTEM); - - ctx = ctx.withRule(notificationRule); - assertThat(ctx.ruleName()).isEqualTo("foo"); - assertThat(ctx.ruleLevel()).isEqualTo("ERROR"); - assertThat(ctx.ruleScope()).isEqualTo("SYSTEM"); - } - -} \ No newline at end of file diff --git a/notification-publisher/src/test/java/org/dependencytrack/notification/publisher/PublisherTestUtil.java b/notification-publisher/src/test/java/org/dependencytrack/notification/publisher/PublisherTestUtil.java deleted file mode 100644 index 04d1bff011..0000000000 --- a/notification-publisher/src/test/java/org/dependencytrack/notification/publisher/PublisherTestUtil.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * 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.json.Json; -import jakarta.json.JsonObject; -import jakarta.json.JsonObjectBuilder; -import org.apache.kafka.clients.consumer.ConsumerRecord; -import org.dependencytrack.proto.notification.v1.Notification; - -import java.io.IOException; - -public class PublisherTestUtil { - - static JsonObject getConfig(String publisher, String destination) { - return Json.createObjectBuilder() - .add(Publisher.CONFIG_TEMPLATE_MIME_TYPE_KEY, "testType") - .add(Publisher.CONFIG_TEMPLATE_KEY, getTemplateContent(publisher)) - .add(Publisher.CONFIG_DESTINATION, destination) - .addAll(getExtraConfig()) - .build(); - } - - public static String getTemplateContent(String notificationPublisher) { - switch(notificationPublisher) { - case "CONSOLE": return "--------------------------------------------------------------------------------\n" + - "Notification\n" + - " -- timestamp: {{ timestamp }}\n" + - " -- level: {{ notification.level }}\n" + - " -- scope: {{ notification.scope }}\n" + - " -- group: {{ notification.group }}\n" + - " -- title: {{ notification.title }}\n" + - " -- content: {{ notification.content }}"; - - case "WEBHOOK": return "{\n" + - " \"notification\": {\n" + - " \"level\": \"{{ notification.level | escape(strategy=\"json\") }}\",\n" + - " \"scope\": \"{{ notification.scope | escape(strategy=\"json\") }}\",\n" + - " \"group\": \"{{ notification.group | escape(strategy=\"json\") }}\",\n" + - " \"timestamp\": \"{{ timestamp }}\",\n" + - " \"title\": \"{{ notification.title | escape(strategy=\"json\") }}\",\n" + - " \"content\": \"{{ notification.content | escape(strategy=\"json\") }}\",\n" + - " \"subject\": {{ subjectJson | raw }}\n" + - " }\n" + - "}"; - - default: return "templateContent"; - } - } - - static JsonObjectBuilder getExtraConfig() { - return Json.createObjectBuilder(); - } - - public static PublishContext createPublisherContext(final Notification notification) throws IOException { - return PublishContext.fromRecord(new ConsumerRecord<>("topic", 1, 2L, "key", notification)); - } - -} diff --git a/notification-publisher/src/test/java/org/dependencytrack/notification/publisher/SendMailPublisherTest.java b/notification-publisher/src/test/java/org/dependencytrack/notification/publisher/SendMailPublisherTest.java deleted file mode 100644 index dc2f529dbf..0000000000 --- a/notification-publisher/src/test/java/org/dependencytrack/notification/publisher/SendMailPublisherTest.java +++ /dev/null @@ -1,703 +0,0 @@ -/* - * 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 com.google.protobuf.util.Timestamps; -import io.quarkiverse.mailpit.test.InjectMailbox; -import io.quarkiverse.mailpit.test.Mailbox; -import io.quarkiverse.mailpit.test.WithMailbox; -import io.quarkus.test.TestTransaction; -import io.quarkus.test.junit.QuarkusTest; -import io.quarkus.test.junit.QuarkusTestProfile; -import io.quarkus.test.junit.TestProfile; -import org.dependencytrack.notification.NotificationConstants; -import org.dependencytrack.persistence.model.Team; -import org.dependencytrack.proto.notification.v1.Notification; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; - -import jakarta.json.Json; -import jakarta.json.JsonObject; -import jakarta.json.JsonObjectBuilder; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.UUID; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatNoException; -import static org.dependencytrack.proto.notification.v1.Group.GROUP_ANALYZER; -import static org.dependencytrack.proto.notification.v1.Level.LEVEL_ERROR; -import static org.dependencytrack.proto.notification.v1.Scope.SCOPE_SYSTEM; - -@QuarkusTest -@WithMailbox -@TestProfile(SendMailPublisherTest.TestProfile.class) -public class SendMailPublisherTest extends AbstractPublisherTest { - - public static class TestProfile implements QuarkusTestProfile { - - @Override - public Map getConfigOverrides() { - return Map.ofEntries( - Map.entry("dtrack.general.base.url", "https://example.com"), - Map.entry("dtrack.email.smtp.enabled", "true"), - Map.entry("dtrack.email.smtp.server.hostname", "localhost"), - Map.entry("dtrack.email.smtp.server.port", "${mailpit.smtp.port}"), - Map.entry("dtrack.email.smtp.from.address", "dtrack@example.com"), - Map.entry("dtrack.email.subject.prefix", "[Dependency-Track]") - ); - } - - } - - @InjectMailbox - Mailbox mailbox; - - @AfterEach - void afterEach() { - mailbox.clear(); - } - - @Override - JsonObjectBuilder extraConfig() { - return super.extraConfig() - .add(Publisher.CONFIG_DESTINATION, "recipient@example.com"); - } - - @Test - @Override - @TestTransaction - void testInformWithBomConsumedNotification() throws Exception { - super.testInformWithBomConsumedNotification(); - - assertThat(mailbox.findFirst("recipient@example.com")).satisfies(message -> { - assertThat(message.getFrom()).isNotNull(); - assertThat(message.getFrom().getAddress()).isEqualTo("dtrack@example.com"); - assertThat(message.getSubject()).isEqualTo("[Dependency-Track] Bill of Materials Consumed"); - assertThat(message.getText()).isEqualToIgnoringNewLines(""" - Bill of Materials Consumed - - -------------------------------------------------------------------------------- - - Project: projectName - Version: projectVersion - Description: projectDescription - Project URL: https://example.com/projects/c9c9539a-e381-4b36-ac52-6a7ab83b2c95 - - -------------------------------------------------------------------------------- - - A CycloneDX BOM was consumed and will be processed - - -------------------------------------------------------------------------------- - - 1970-01-01T18:31:06.000Z - """); - }); - } - - @Test - @Override - @TestTransaction - void testInformWithBomProcessingFailedNotification() throws Exception { - super.testInformWithBomProcessingFailedNotification(); - - assertThat(mailbox.findFirst("recipient@example.com")).satisfies(message -> { - assertThat(message.getFrom()).isNotNull(); - assertThat(message.getFrom().getAddress()).isEqualTo("dtrack@example.com"); - assertThat(message.getSubject()).isEqualTo("[Dependency-Track] Bill of Materials Processing Failed"); - assertThat(message.getText()).isEqualToIgnoringNewLines(""" - Bill of Materials Processing Failed - - -------------------------------------------------------------------------------- - - Project: projectName - Version: projectVersion - Description: projectDescription - Project URL: https://example.com/projects/c9c9539a-e381-4b36-ac52-6a7ab83b2c95 - - -------------------------------------------------------------------------------- - - Cause: - cause - - -------------------------------------------------------------------------------- - - An error occurred while processing a BOM - - -------------------------------------------------------------------------------- - - 1970-01-01T18:31:06.000Z - """); - }); - } - - @Test - @Override - @TestTransaction - void testInformWithBomProcessingFailedNotificationAndNoSpecVersionInSubject() throws Exception { - super.testInformWithBomProcessingFailedNotificationAndNoSpecVersionInSubject(); - - assertThat(mailbox.findFirst("recipient@example.com")).satisfies(message -> { - assertThat(message.getFrom()).isNotNull(); - assertThat(message.getFrom().getAddress()).isEqualTo("dtrack@example.com"); - assertThat(message.getSubject()).isEqualTo("[Dependency-Track] Bill of Materials Processing Failed"); - assertThat(message.getText()).isEqualToIgnoringNewLines(""" - Bill of Materials Processing Failed - - -------------------------------------------------------------------------------- - - Project: projectName - Version: projectVersion - Description: projectDescription - Project URL: https://example.com/projects/c9c9539a-e381-4b36-ac52-6a7ab83b2c95 - - -------------------------------------------------------------------------------- - - Cause: - cause - - -------------------------------------------------------------------------------- - - An error occurred while processing a BOM - - -------------------------------------------------------------------------------- - - 1970-01-01T18:31:06.000Z - """); - }); - } - - @Test - @Override - @TestTransaction - void testInformWithBomValidationFailedNotificationSubject() throws Exception { - super.testInformWithBomValidationFailedNotificationSubject(); - - assertThat(mailbox.findFirst("recipient@example.com")).satisfies(message -> { - assertThat(message.getFrom()).isNotNull(); - assertThat(message.getFrom().getAddress()).isEqualTo("dtrack@example.com"); - assertThat(message.getSubject()).isEqualTo("[Dependency-Track] Bill of Materials Validation Failed"); - assertThat(message.getText()).isEqualToIgnoringNewLines(""" - Bill of Materials Validation Failed - - -------------------------------------------------------------------------------- - - Project: projectName - Version: projectVersion - Description: projectDescription - Project URL: https://example.com/projects/c9c9539a-e381-4b36-ac52-6a7ab83b2c95 - - -------------------------------------------------------------------------------- - - Errors: - cause 1 - cause 2 - - -------------------------------------------------------------------------------- - - An error occurred while validating a BOM - - -------------------------------------------------------------------------------- - - 1970-01-01T18:31:06.000Z - """); - }); - } - - @Test - @Override - @TestTransaction - void testInformWithDataSourceMirroringNotification() throws Exception { - super.testInformWithDataSourceMirroringNotification(); - - assertThat(mailbox.findFirst("recipient@example.com")).satisfies(message -> { - assertThat(message.getFrom()).isNotNull(); - assertThat(message.getFrom().getAddress()).isEqualTo("dtrack@example.com"); - assertThat(message.getSubject()).isEqualTo("[Dependency-Track] GitHub Advisory Mirroring"); - assertThat(message.getText()).isEqualToIgnoringNewLines(""" - GitHub Advisory Mirroring - - -------------------------------------------------------------------------------- - - Level: LEVEL_ERROR - Scope: SCOPE_SYSTEM - Group: GROUP_DATASOURCE_MIRRORING - - -------------------------------------------------------------------------------- - - An error occurred mirroring the contents of GitHub Advisories. Check log for details. - - -------------------------------------------------------------------------------- - - 1970-01-01T18:31:06.000Z - """); - }); - } - - @Test - @Override - @TestTransaction - void testInformWithNewVulnerabilityNotification() throws Exception { - super.testInformWithNewVulnerabilityNotification(); - - assertThat(mailbox.findFirst("recipient@example.com")).satisfies(message -> { - assertThat(message.getFrom()).isNotNull(); - assertThat(message.getFrom().getAddress()).isEqualTo("dtrack@example.com"); - assertThat(message.getSubject()).isEqualTo("[Dependency-Track] New Vulnerability Identified"); - assertThat(message.getText()).isEqualToIgnoringNewLines(""" - New Vulnerability Identified - - -------------------------------------------------------------------------------- - - Vulnerability ID: INT-001 - Vulnerability URL: https://example.com/vulnerability/?source=INTERNAL&vulnId=INT-001 - Severity: MEDIUM - Source: INTERNAL - Component: componentName : componentVersion - Component URL: https://example.com/component/?uuid=94f87321-a5d1-4c2f-b2fe-95165debebc6 - Project: projectName - Version: projectVersion - Description: projectDescription - Project URL: https://example.com/projects/c9c9539a-e381-4b36-ac52-6a7ab83b2c95 - - -------------------------------------------------------------------------------- - - Other affected projects: https://example.com/vulnerabilities/INTERNAL/INT-001/affectedProjects - - -------------------------------------------------------------------------------- - - - - -------------------------------------------------------------------------------- - - 1970-01-01T18:31:06.000Z - """); - }); - } - - @Test - @Override - @TestTransaction - void testInformWithProjectAuditChangeNotification() throws Exception { - super.testInformWithProjectAuditChangeNotification(); - - assertThat(mailbox.findFirst("recipient@example.com")).satisfies(message -> { - assertThat(message.getFrom()).isNotNull(); - assertThat(message.getFrom().getAddress()).isEqualTo("dtrack@example.com"); - assertThat(message.getSubject()).isEqualTo("[Dependency-Track] Analysis Decision: Finding Suppressed"); - assertThat(message.getText()).isEqualToIgnoringNewLines(""" - Analysis Decision: Finding Suppressed - - -------------------------------------------------------------------------------- - - Analysis Type: Project Analysis - - Analysis State: FALSE_POSITIVE - Suppressed: true - Vulnerability ID: INT-001 - Vulnerability URL: https://example.com/vulnerability/?source=INTERNAL&vulnId=INT-001 - Severity: MEDIUM - Source: INTERNAL - - Component: componentName : componentVersion - Component URL: https://example.com/component/?uuid=94f87321-a5d1-4c2f-b2fe-95165debebc6 - Project: pkg:maven/org.acme/projectName@projectVersion - Description: projectDescription - Project URL: https://example.com/projects/c9c9539a-e381-4b36-ac52-6a7ab83b2c95 - - -------------------------------------------------------------------------------- - - - - -------------------------------------------------------------------------------- - - 1970-01-01T18:31:06.000Z - """); - }); - } - - @Test - @Override - @TestTransaction - void testInformWithTemplateInclude() throws Exception { - final var notification = Notification.newBuilder() - .setScope(SCOPE_SYSTEM) - .setGroup(GROUP_ANALYZER) - .setTitle(NotificationConstants.Title.NOTIFICATION_TEST) - .setLevel(LEVEL_ERROR) - .setTimestamp(Timestamps.fromSeconds(66666)) - .build(); - - final JsonObject config = Json.createObjectBuilder(createConfig()) - .add(Publisher.CONFIG_TEMPLATE_KEY, "{% include '/some/path' %}") - .build(); - - // NB: In contrast to other publishers, SendMailPublisher catches and logs - // failures during template evaluation. Instead of expecting an exception - // being thrown, we verify that no email was sent. - assertThatNoException() - .isThrownBy(() -> publisherInstance.inform(createPublishContext(notification), notification, config)); - - assertThat(mailbox.findFirst("recipient@example.com")).isNull(); - } - - @Test - void testSingleDestination() { - JsonObject config = configWithDestination("john@doe.com"); - Assertions.assertArrayEquals(new String[]{"john@doe.com"}, SendMailPublisher.parseDestination(config)); - } - - @Test - public void testNullDestination() { - Assertions.assertArrayEquals(null, SendMailPublisher.parseDestination(Json.createObjectBuilder().build())); - } - - @Test - public void testMultipleDestinations() { - JsonObject config = configWithDestination("john@doe.com,steve@jobs.org"); - Assertions.assertArrayEquals(new String[]{"john@doe.com", "steve@jobs.org"}, - SendMailPublisher.parseDestination(config)); - } - - @Test - public void testEmptyDestinations() { - JsonObject config = configWithDestination(""); - Assertions.assertArrayEquals(null, SendMailPublisher.parseDestination(config)); - } - - @Test - @TestTransaction - public void testSingleTeamAsDestination() { - final JsonObject config = configWithDestination(""); - - final var managedUserId = createManagedUser("managedUserTest", "managedUser@Test.com"); - final var ldapUserId = createLdapUser("ldapUserTest", "ldapUser@Test.com"); - final var oidcUserId = createOidcUser("oidcUserTest", "oidcUser@Test.com"); - final var team = createTeam("foo", List.of(managedUserId, ldapUserId, oidcUserId)); - - assertThat(publisherInstance.parseDestination(config, List.of(team))) - .containsExactlyInAnyOrder( - "managedUser@Test.com", - "ldapUser@Test.com", - "oidcUser@Test.com" - ); - } - - @Test - @TestTransaction - public void testMultipleTeamsAsDestination() { - final JsonObject config = configWithDestination(""); - - final var managedUserIdA = createManagedUser("managedUserTest", "managedUser@Test.com"); - final var ldapUserIdA = createLdapUser("ldapUserTest", "ldapUser@Test.com"); - final var oidcUserIdA = createOidcUser("oidcUserTest", "oidcUser@Test.com"); - final var teamA = createTeam("teamA", List.of(managedUserIdA, ldapUserIdA, oidcUserIdA)); - - final var managedUserIdB = createManagedUser("anotherManagedUserTest", "anotherManagedUser@Test.com"); - final var ldapUserIdB = createLdapUser("anotherLdapUserTest", "anotherLdapUser@Test.com"); - final var oidcUserIdB = createOidcUser("anotherOidcUserTest", "anotherOidcUser@Test.com"); - final var teamB = createTeam("teamB", List.of(managedUserIdB, ldapUserIdB, oidcUserIdB)); - - assertThat(publisherInstance.parseDestination(config, List.of(teamA, teamB))) - .containsExactlyInAnyOrder( - "managedUser@Test.com", - "ldapUser@Test.com", - "oidcUser@Test.com", - "anotherManagedUser@Test.com", - "anotherLdapUser@Test.com", - "anotherOidcUser@Test.com" - ); - } - - @Test - @TestTransaction - public void testDuplicateTeamAsDestination() { - final JsonObject config = configWithDestination(""); - - final var managedUserIdA = createManagedUser("managedUserTest", "managedUser@Test.com"); - final var ldapUserIdA = createLdapUser("ldapUserTest", "ldapUser@Test.com"); - final var oidcUserIdA = createOidcUser("oidcUserTest", "oidcUser@Test.com"); - final var teamA = createTeam("teamA", List.of(managedUserIdA, ldapUserIdA, oidcUserIdA)); - - final var managedUserIdB = createManagedUser("anotherManagedUserTest", "anotherManagedUser@Test.com"); - final var ldapUserIdB = createLdapUser("anotherLdapUserTest", "anotherLdapUser@Test.com"); - final var oidcUserIdB = createOidcUser("anotherOidcUserTest", "anotherOidcUser@Test.com"); - final var teamB = createTeam("teamB", - List.of(managedUserIdB, managedUserIdA, ldapUserIdB, ldapUserIdA, oidcUserIdB, oidcUserIdA)); - - assertThat(publisherInstance.parseDestination(config, List.of(teamA, teamB))) - .containsExactlyInAnyOrder( - "managedUser@Test.com", - "ldapUser@Test.com", - "oidcUser@Test.com", - "anotherManagedUser@Test.com", - "anotherLdapUser@Test.com", - "anotherOidcUser@Test.com" - ); - } - - @Test - @TestTransaction - public void testDuplicateUserAsDestination() { - final JsonObject config = configWithDestination(""); - - final var managedUserId = createManagedUser("managedUserTest", "managedUser@Test.com"); - final var ldapUserId = createLdapUser("ldapUserTest", "ldapUser@Test.com"); - final var oidcUserId = createOidcUser("oidcUserTest", "oidcUser@Test.com"); - final var team = createTeam("foo", List.of(managedUserId, ldapUserId, oidcUserId)); - - assertThat(publisherInstance.parseDestination(config, List.of(team, team))) - .containsExactlyInAnyOrder("managedUser@Test.com", "ldapUser@Test.com", "oidcUser@Test.com"); - } - - @Test - @TestTransaction - public void testEmptyTeamAsDestination() { - final JsonObject config = configWithDestination(""); - - final var team = new Team(); - team.setId(666); - team.setName("foo"); - - assertThat(publisherInstance.parseDestination(config, List.of(team))).isNull(); - } - - @Test - @TestTransaction - public void testEmptyTeamsAsDestination() { - final JsonObject config = configWithDestination(""); - - assertThat(publisherInstance.parseDestination(config, Collections.emptyList())).isNull(); - } - - @Test - @TestTransaction - public void testEmptyUserEmailsAsDestination() { - final JsonObject config = configWithDestination(""); - - final var managedUserId = createManagedUser("managedUserTest", null); - final var ldapUserId = createLdapUser("ldapUserTest", null); - final var oidcUserId = createOidcUser("oidcUserTest", null); - final var team = createTeam("foo", List.of(managedUserId, ldapUserId, oidcUserId)); - - assertThat(publisherInstance.parseDestination(config, List.of(team))).isNull(); - } - - @Test - @TestTransaction - public void testConfigDestinationAndTeamAsDestination() { - final JsonObject config = configWithDestination("john@doe.com,steve@jobs.org"); - - final var managedUserId = createManagedUser("managedUserTest", "managedUser@Test.com"); - final var ldapUserId = createLdapUser("ldapUserTest", "ldapUser@Test.com"); - final var oidcUserId = createOidcUser("oidcUserTest", "john@doe.com"); - final var team = createTeam("foo", List.of(managedUserId, ldapUserId, oidcUserId)); - - assertThat(publisherInstance.parseDestination(config, List.of(team))) - .containsExactlyInAnyOrder( - "john@doe.com", - "steve@jobs.org", - "managedUser@Test.com", - "ldapUser@Test.com" - ); - } - - @Test - @TestTransaction - public void testNullConfigDestinationAndTeamsDestination() { - final JsonObject config = Json.createObjectBuilder().build(); - - final var managedUserId = createManagedUser("managedUserTest", "managedUser@Test.com"); - final var ldapUserId = createLdapUser("ldapUserTest", "ldapUser@Test.com"); - final var oidcUserId = createOidcUser("oidcUserTest", "john@doe.com"); - final var team = createTeam("foo", List.of(managedUserId, ldapUserId, oidcUserId)); - - assertThat(publisherInstance.parseDestination(config, List.of(team))) - .containsExactlyInAnyOrder( - "managedUser@Test.com", - "ldapUser@Test.com", - "john@doe.com" - ); - } - - @Test - @TestTransaction - public void testEmptyManagedUsersAsDestination() { - final JsonObject config = configWithDestination("john@doe.com,steve@jobs.org"); - - final var ldapUserId = createLdapUser("ldapUserTest", "ldapUser@Test.com"); - final var oidcUserId = createOidcUser("oidcUserTest", "oidcUser@Test.com"); - final var team = createTeam("foo", List.of(ldapUserId, oidcUserId)); - - assertThat(publisherInstance.parseDestination(config, List.of(team))) - .containsExactlyInAnyOrder( - "john@doe.com", - "steve@jobs.org", - "ldapUser@Test.com", - "oidcUser@Test.com" - ); - } - - @Test - @TestTransaction - public void testEmptyLdapUsersAsDestination() { - final JsonObject config = configWithDestination("john@doe.com,steve@jobs.org"); - - final var managedUserId = createManagedUser("managedUserTest", "managedUser@Test.com"); - final var oidcUserId = createOidcUser("oidcUserTest", "oidcUser@Test.com"); - final var team = createTeam("foo", List.of(managedUserId, oidcUserId)); - - assertThat(publisherInstance.parseDestination(config, List.of(team))) - .containsExactlyInAnyOrder( - "john@doe.com", - "steve@jobs.org", - "managedUser@Test.com", - "oidcUser@Test.com" - ); - } - - @Test - @TestTransaction - public void testEmptyOidcUsersAsDestination() { - final JsonObject config = configWithDestination("john@doe.com,steve@jobs.org"); - - final var managedUserId = createManagedUser("managedUserTest", "managedUser@Test.com"); - final var ldapUserId = createLdapUser("ldapUserTest", "ldapUser@Test.com"); - final var team = createTeam("foo", List.of(managedUserId, ldapUserId)); - - assertThat(publisherInstance.parseDestination(config, List.of(team))) - .containsExactlyInAnyOrder( - "john@doe.com", - "steve@jobs.org", - "managedUser@Test.com", - "ldapUser@Test.com" - ); - } - - @Test - @Override - @TestTransaction - public void testInformWithNewVulnerableDependencyNotification() throws Exception { - super.testInformWithNewVulnerableDependencyNotification(); - - assertThat(mailbox.findFirst("recipient@example.com")).satisfies(message -> { - assertThat(message.getFrom()).isNotNull(); - assertThat(message.getFrom().getAddress()).isEqualTo("dtrack@example.com"); - assertThat(message.getSubject()).isEqualTo("[Dependency-Track] Vulnerable Dependency Introduced"); - assertThat(message.getText()).isEqualToIgnoringNewLines(""" - Vulnerable Dependency Introduced - - -------------------------------------------------------------------------------- - - Project: pkg:maven/org.acme/projectName@projectVersion - Project URL: https://example.com/projects/c9c9539a-e381-4b36-ac52-6a7ab83b2c95 - Component: componentName : componentVersion - Component URL: https://example.com/component/?uuid=94f87321-a5d1-4c2f-b2fe-95165debebc6 - - Vulnerabilities - - Vulnerability ID: INT-001 - Vulnerability URL: https://example.com/vulnerability/?source=INTERNAL&vulnId=INT-001 - Severity: MEDIUM - Source: INTERNAL - Description: - vulnerabilityDescription - - - - -------------------------------------------------------------------------------- - - - - -------------------------------------------------------------------------------- - - 1970-01-01T18:31:06.000Z - """); - }); - } - - private Long createManagedUser(final String username, final String email) { - return (Long) entityManager.createNativeQuery(""" - INSERT INTO "USER" ("TYPE", "USERNAME", "EMAIL", "PASSWORD", "FORCE_PASSWORD_CHANGE", "LAST_PASSWORD_CHANGE", "NON_EXPIRY_PASSWORD", "SUSPENDED") VALUES - ('MANAGED', :username, :email, 'password', FALSE, NOW(), TRUE, FALSE) - RETURNING "ID"; - """) - .setParameter("username", username) - .setParameter("email", email) - .getSingleResult(); - } - - private Long createLdapUser(final String username, final String email) { - return (Long) entityManager.createNativeQuery(""" - INSERT INTO "USER" ("TYPE", "USERNAME", "EMAIL", "DN") VALUES - ('LDAP', :username, :email, :dn) - RETURNING "ID"; - """) - .setParameter("username", username) - .setParameter("email", email) - .setParameter("dn", UUID.randomUUID().toString()) - .getSingleResult(); - } - - private Long createOidcUser(final String username, final String email) { - return (Long) entityManager.createNativeQuery(""" - INSERT INTO "USER" ("TYPE", "USERNAME", "EMAIL") VALUES - ('OIDC', :username, :email) - RETURNING "ID"; - """) - .setParameter("username", username) - .setParameter("email", email) - .getSingleResult(); - } - - private Team createTeam(final String name, final Collection userIds) { - final var teamId = (Long) entityManager.createNativeQuery(""" - INSERT INTO "TEAM" ("NAME", "UUID") VALUES - (:name, :uuid) - RETURNING "ID"; - """) - .setParameter("name", name) - .setParameter("uuid", UUID.randomUUID().toString()) - .getSingleResult(); - - if (userIds != null) { - for (final Long managedUserId : userIds) { - entityManager.createNativeQuery(""" - INSERT INTO "USERS_TEAMS" ("USER_ID", "TEAM_ID") VALUES - (:userId, :teamId); - """) - .setParameter("userId", managedUserId) - .setParameter("teamId", teamId) - .executeUpdate(); - } - } - - final var team = new Team(); - team.setId(teamId); - team.setName(name); - return team; - } - - private static JsonObject configWithDestination(final String destination) { - return Json.createObjectBuilder().add("destination", destination).build(); - } -} diff --git a/notification-publisher/src/test/java/org/dependencytrack/notification/publisher/SlackPublisherTest.java b/notification-publisher/src/test/java/org/dependencytrack/notification/publisher/SlackPublisherTest.java deleted file mode 100644 index ed165ba198..0000000000 --- a/notification-publisher/src/test/java/org/dependencytrack/notification/publisher/SlackPublisherTest.java +++ /dev/null @@ -1,627 +0,0 @@ -/* - * 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 io.quarkus.test.TestTransaction; -import io.quarkus.test.junit.QuarkusTest; -import io.quarkus.test.junit.QuarkusTestProfile; -import io.quarkus.test.junit.TestProfile; -import jakarta.json.JsonObjectBuilder; -import org.junit.jupiter.api.Test; - -import java.util.Map; - -import static com.github.tomakehurst.wiremock.client.WireMock.anyUrl; -import static com.github.tomakehurst.wiremock.client.WireMock.equalTo; -import static com.github.tomakehurst.wiremock.client.WireMock.equalToJson; -import static com.github.tomakehurst.wiremock.client.WireMock.postRequestedFor; - -@QuarkusTest -@TestProfile(SlackPublisherTest.TestProfile.class) -public class SlackPublisherTest extends AbstractWebhookPublisherTest { - - public static class TestProfile implements QuarkusTestProfile { - - @Override - public Map getConfigOverrides() { - return Map.ofEntries( - Map.entry("dtrack.general.base.url", "https://example.com") - ); - } - } - - @Override - JsonObjectBuilder extraConfig() { - return super.extraConfig() - .add(Publisher.CONFIG_DESTINATION, "http://localhost:" + wireMockPort); - } - - @Test - @Override - @TestTransaction - void testInformWithBomConsumedNotification() throws Exception { - super.testInformWithBomConsumedNotification(); - - wireMock.verifyThat(postRequestedFor(anyUrl()) - .withHeader("Content-Type", equalTo("application/json")) - .withRequestBody(equalToJson(""" - { - "blocks": [ - { - "type": "header", - "text": { - "type": "plain_text", - "text": "GROUP_BOM_CONSUMED" - } - }, - { - "type": "context", - "elements": [ - { - "text": "*LEVEL_INFORMATIONAL* | *SCOPE_PORTFOLIO*", - "type": "mrkdwn" - } - ] - }, - { - "type": "divider" - }, - { - "type": "section", - "text": { - "text": "Bill of Materials Consumed", - "type": "plain_text" - } - }, - { - "type": "section", - "text": { - "text": "A CycloneDX BOM was consumed and will be processed", - "type": "plain_text" - } - } - ] - } - """))); - } - - @Test - @Override - @TestTransaction - void testInformWithBomProcessingFailedNotification() throws Exception { - super.testInformWithBomProcessingFailedNotification(); - - wireMock.verifyThat(postRequestedFor(anyUrl()) - .withHeader("Content-Type", equalTo("application/json")) - .withRequestBody(equalToJson(""" - { - "blocks": [ - { - "type": "header", - "text": { - "type": "plain_text", - "text": "GROUP_BOM_PROCESSING_FAILED" - } - }, - { - "type": "context", - "elements": [ - { - "text": "*LEVEL_ERROR* | *SCOPE_PORTFOLIO*", - "type": "mrkdwn" - } - ] - }, - { - "type": "divider" - }, - { - "type": "section", - "text": { - "text": "Bill of Materials Processing Failed", - "type": "plain_text" - } - }, - { - "type": "section", - "text": { - "text": "An error occurred while processing a BOM", - "type": "plain_text" - } - } - ] - } - """))); - } - - @Test - @Override - @TestTransaction - void testInformWithBomProcessingFailedNotificationAndNoSpecVersionInSubject() throws Exception { - super.testInformWithBomProcessingFailedNotificationAndNoSpecVersionInSubject(); - - wireMock.verifyThat(postRequestedFor(anyUrl()) - .withHeader("Content-Type", equalTo("application/json")) - .withRequestBody(equalToJson(""" - { - "blocks": [ - { - "type": "header", - "text": { - "type": "plain_text", - "text": "GROUP_BOM_PROCESSING_FAILED" - } - }, - { - "type": "context", - "elements": [ - { - "text": "*LEVEL_ERROR* | *SCOPE_PORTFOLIO*", - "type": "mrkdwn" - } - ] - }, - { - "type": "divider" - }, - { - "type": "section", - "text": { - "text": "Bill of Materials Processing Failed", - "type": "plain_text" - } - }, - { - "type": "section", - "text": { - "text": "An error occurred while processing a BOM", - "type": "plain_text" - } - } - ] - } - """))); - } - - @Test - @Override - @TestTransaction - void testInformWithBomValidationFailedNotificationSubject() throws Exception { - super.testInformWithBomValidationFailedNotificationSubject(); - - wireMock.verifyThat(postRequestedFor(anyUrl()) - .withHeader("Content-Type", equalTo("application/json")) - .withRequestBody(equalToJson(""" - { - "blocks": [ - { - "type": "header", - "text": { - "type": "plain_text", - "text": "GROUP_BOM_VALIDATION_FAILED" - } - }, - { - "type": "context", - "elements": [ - { - "text": "*LEVEL_ERROR* | *SCOPE_PORTFOLIO*", - "type": "mrkdwn" - } - ] - }, - { - "type": "divider" - }, - { - "type": "section", - "text": { - "text": "Bill of Materials Validation Failed", - "type": "plain_text" - } - }, - { - "type": "section", - "text": { - "text": "An error occurred while validating a BOM", - "type": "plain_text" - } - } - ] - } - """))); - } - - @Test - @Override - @TestTransaction - void testInformWithDataSourceMirroringNotification() throws Exception { - super.testInformWithDataSourceMirroringNotification(); - - wireMock.verifyThat(postRequestedFor(anyUrl()) - .withHeader("Content-Type", equalTo("application/json")) - .withRequestBody(equalToJson(""" - { - "blocks": [ - { - "type": "header", - "text": { - "type": "plain_text", - "text": "GROUP_DATASOURCE_MIRRORING" - } - }, - { - "type": "context", - "elements": [ - { - "text": "*LEVEL_ERROR* | *SCOPE_SYSTEM*", - "type": "mrkdwn" - } - ] - }, - { - "type": "divider" - }, - { - "type": "section", - "text": { - "text": "GitHub Advisory Mirroring", - "type": "plain_text" - } - }, - { - "type": "section", - "text": { - "text": "An error occurred mirroring the contents of GitHub Advisories. Check log for details.", - "type": "plain_text" - } - } - ] - } - """))); - } - - @Test - @Override - @TestTransaction - void testInformWithNewVulnerabilityNotification() throws Exception { - super.testInformWithNewVulnerabilityNotification(); - - wireMock.verifyThat(postRequestedFor(anyUrl()) - .withHeader("Content-Type", equalTo("application/json")) - .withRequestBody(equalToJson(""" - { - "blocks": [ - { - "type": "header", - "text": { - "type": "plain_text", - "text": "New Vulnerability" - } - }, - { - "type": "context", - "elements": [ - { - "text": "*LEVEL_INFORMATIONAL* | *SCOPE_PORTFOLIO*", - "type": "mrkdwn" - } - ] - }, - { - "type": "divider" - }, - { - "type": "section", - "text": { - "text": "New Vulnerability Identified", - "type": "mrkdwn" - }, - "fields": [ - { - "type": "mrkdwn", - "text": "*VulnID*" - }, - { - "type": "plain_text", - "text": "INT-001" - }, - { - "type": "mrkdwn", - "text": "*Severity*" - }, - { - "type": "plain_text", - "text": "MEDIUM" - }, - { - "type": "mrkdwn", - "text": "*Source*" - }, - { - "type": "plain_text", - "text": "INTERNAL" - }, - { - "type": "mrkdwn", - "text": "*Component*" - }, - { - "type": "plain_text", - "text": "componentName : componentVersion" - } - ] - }, - { - "type": "actions", - "elements": [ - { - "type": "button", - "text": { - "type": "plain_text", - "text": "View Vulnerability" - }, - "action_id": "actionId-1", - "url": "https://example.com/vulnerabilities/INTERNAL/INT-001" - }, - { - "type": "button", - "text": { - "type": "plain_text", - "text": "View Component" - }, - "action_id": "actionId-2", - "url": "https://example.com/components/94f87321-a5d1-4c2f-b2fe-95165debebc6" - } - ] - } - ] - } - """))); - } - - @Test - @Override - @TestTransaction - public void testInformWithNewVulnerableDependencyNotification() throws Exception { - super.testInformWithNewVulnerableDependencyNotification(); - - wireMock.verifyThat(postRequestedFor(anyUrl()) - .withHeader("Content-Type", equalTo("application/json")) - .withRequestBody(equalToJson(""" - { - "blocks": [ - { - "type": "header", - "text": { - "type": "plain_text", - "text": "New Vulnerable Dependency" - } - }, - { - "type": "context", - "elements": [ - { - "text": "*LEVEL_INFORMATIONAL* | *SCOPE_PORTFOLIO*", - "type": "mrkdwn" - } - ] - }, - { - "type": "divider" - }, - { - "type": "section", - "text": { - "text": "Vulnerable Dependency Introduced", - "type": "mrkdwn" - }, - "fields": [ - { - "type": "mrkdwn", - "text": "*Component*" - }, - { - "type": "plain_text", - "text": "componentName : componentVersion" - }, - { - "type": "mrkdwn", - "text": "*Project*" - }, - { - "type": "plain_text", - "text": "pkg:maven/org.acme/projectName@projectVersion" - } - ] - }, - { - "type": "actions", - "elements": [ - { - "type": "button", - "text": { - "type": "plain_text", - "text": "View Project" - }, - "action_id": "actionId-1", - "url": "https://example.com/projects/c9c9539a-e381-4b36-ac52-6a7ab83b2c95" - }, - { - "type": "button", - "text": { - "type": "plain_text", - "text": "View Component" - }, - "action_id": "actionId-2", - "url": "https://example.com/components/94f87321-a5d1-4c2f-b2fe-95165debebc6" - } - ] - } - ] - } - """))); - } - - @Test - @Override - @TestTransaction - void testInformWithProjectAuditChangeNotification() throws Exception { - super.testInformWithProjectAuditChangeNotification(); - - wireMock.verifyThat(postRequestedFor(anyUrl()) - .withHeader("Content-Type", equalTo("application/json")) - .withRequestBody(equalToJson(""" - { - "blocks": [ - { - "type": "header", - "text": { - "type": "plain_text", - "text": "Project Audit Change" - } - }, - { - "type": "context", - "elements": [ - { - "text": "*LEVEL_INFORMATIONAL* | *SCOPE_PORTFOLIO*", - "type": "mrkdwn" - } - ] - }, - { - "type": "divider" - }, - { - "type": "section", - "text": { - "text": "Analysis Decision: Finding Suppressed", - "type": "plain_text" - }, - "fields": [ - { - "type": "mrkdwn", - "text": "*Analysis State*" - }, - { - "type": "plain_text", - "emoji": true, - "text": "FALSE_POSITIVE" - }, - { - "type": "mrkdwn", - "text": "*Suppressed*" - }, - { - "type": "plain_text", - "text": "true" - }, - { - "type": "mrkdwn", - "text": "*VulnID*" - }, - { - "type": "plain_text", - "text": "INT-001" - }, - { - "type": "mrkdwn", - "text": "*Severity*" - }, - { - "type": "plain_text", - "text": "MEDIUM" - }, - { - "type": "mrkdwn", - "text": "*Source*" - }, - { - "type": "plain_text", - "text": "INTERNAL" - } - ] - }, - { - "type": "section", - "fields": [ - { - "type": "mrkdwn", - "text": "*Component*" - }, - { - "type": "plain_text", - "text": "componentName : componentVersion" - }, - { - "type": "mrkdwn", - "text": "*Project*" - }, - { - "type": "plain_text", - "text": "pkg:maven/org.acme/projectName@projectVersion" - } - ] - }, - { - "type": "actions", - "elements": [ - { - "type": "button", - "text": { - "type": "plain_text", - "text": "View Project" - }, - "action_id": "actionId-1", - "url": "https://example.com/projects/c9c9539a-e381-4b36-ac52-6a7ab83b2c95" - }, - { - "type": "button", - "text": { - "type": "plain_text", - "text": "View Component" - }, - "action_id": "actionId-2", - "url": "https://example.com/components/94f87321-a5d1-4c2f-b2fe-95165debebc6" - }, - { - "type": "button", - "text": { - "type": "plain_text", - "text": "View Vulnerability" - }, - "action_id": "actionId-3", - "url": "https://example.com/vulnerabilities/INTERNAL/INT-001" - } - ] - } - ] - } - """))); - } - - @Test - @Override - @TestTransaction - void testInformWithTemplateInclude() throws Exception { - super.testInformWithTemplateInclude(); - } - -} diff --git a/notification-publisher/src/test/java/org/dependencytrack/notification/publisher/SlackPublisherWithoutBaseUrlTest.java b/notification-publisher/src/test/java/org/dependencytrack/notification/publisher/SlackPublisherWithoutBaseUrlTest.java deleted file mode 100644 index e6846e0e5c..0000000000 --- a/notification-publisher/src/test/java/org/dependencytrack/notification/publisher/SlackPublisherWithoutBaseUrlTest.java +++ /dev/null @@ -1,293 +0,0 @@ -/* - * 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 io.quarkus.test.TestTransaction; -import io.quarkus.test.junit.QuarkusTest; -import io.quarkus.test.junit.QuarkusTestProfile; -import io.quarkus.test.junit.TestProfile; -import jakarta.json.JsonObjectBuilder; -import org.junit.jupiter.api.Test; - -import java.util.Map; - -import static com.github.tomakehurst.wiremock.client.WireMock.anyUrl; -import static com.github.tomakehurst.wiremock.client.WireMock.equalTo; -import static com.github.tomakehurst.wiremock.client.WireMock.equalToJson; -import static com.github.tomakehurst.wiremock.client.WireMock.postRequestedFor; - -@QuarkusTest -@TestProfile(SlackPublisherWithoutBaseUrlTest.TestProfile.class) -public class SlackPublisherWithoutBaseUrlTest extends AbstractWebhookPublisherTest { - - public static class TestProfile implements QuarkusTestProfile { - - @Override - public Map getConfigOverrides() { - return Map.ofEntries( - Map.entry("dtrack.general.base.url", "") - ); - } - } - - @Override - JsonObjectBuilder extraConfig() { - return super.extraConfig() - .add(Publisher.CONFIG_DESTINATION, "http://localhost:" + wireMockPort); - } - - @Test - @TestTransaction - public void testInformWithNewVulnerabilityNotificationWithoutBaseUrl() throws Exception { - super.testInformWithNewVulnerabilityNotification(); - - wireMock.verifyThat(postRequestedFor(anyUrl()) - .withHeader("Content-Type", equalTo("application/json")) - .withRequestBody(equalToJson(""" - { - "blocks": [ - { - "type": "header", - "text": { - "type": "plain_text", - "text": "New Vulnerability" - } - }, - { - "type": "context", - "elements": [ - { - "text": "*LEVEL_INFORMATIONAL* | *SCOPE_PORTFOLIO*", - "type": "mrkdwn" - } - ] - }, - { - "type": "divider" - }, - { - "type": "section", - "text": { - "text": "New Vulnerability Identified", - "type": "mrkdwn" - }, - "fields": [ - { - "type": "mrkdwn", - "text": "*VulnID*" - }, - { - "type": "plain_text", - "text": "INT-001" - }, - { - "type": "mrkdwn", - "text": "*Severity*" - }, - { - "type": "plain_text", - "text": "MEDIUM" - }, - { - "type": "mrkdwn", - "text": "*Source*" - }, - { - "type": "plain_text", - "text": "INTERNAL" - }, - { - "type": "mrkdwn", - "text": "*Component*" - }, - { - "type": "plain_text", - "text": "componentName : componentVersion" - } - ] - } - ] - } - """))); - } - - @Test - @TestTransaction - public void testInformWithNewVulnerableDependencyNotificationWithoutBaseUrl() throws Exception { - super.testInformWithNewVulnerableDependencyNotification(); - - wireMock.verifyThat(postRequestedFor(anyUrl()) - .withHeader("Content-Type", equalTo("application/json")) - .withRequestBody(equalToJson(""" - { - "blocks": [ - { - "type": "header", - "text": { - "type": "plain_text", - "text": "New Vulnerable Dependency" - } - }, - { - "type": "context", - "elements": [ - { - "text": "*LEVEL_INFORMATIONAL* | *SCOPE_PORTFOLIO*", - "type": "mrkdwn" - } - ] - }, - { - "type": "divider" - }, - { - "type": "section", - "text": { - "text": "Vulnerable Dependency Introduced", - "type": "mrkdwn" - }, - "fields": [ - { - "type": "mrkdwn", - "text": "*Component*" - }, - { - "type": "plain_text", - "text": "componentName : componentVersion" - }, - { - "type": "mrkdwn", - "text": "*Project*" - }, - { - "type": "plain_text", - "text": "pkg:maven/org.acme/projectName@projectVersion" - } - ] - } - ] - } - """))); - } - - @Test - @TestTransaction - public void testInformWithProjectAuditChangeNotificationWithoutBaseUrl() throws Exception { - super.testInformWithProjectAuditChangeNotification(); - - wireMock.verifyThat(postRequestedFor(anyUrl()) - .withHeader("Content-Type", equalTo("application/json")) - .withRequestBody(equalToJson(""" - { - "blocks": [ - { - "type": "header", - "text": { - "type": "plain_text", - "text": "Project Audit Change" - } - }, - { - "type": "context", - "elements": [ - { - "text": "*LEVEL_INFORMATIONAL* | *SCOPE_PORTFOLIO*", - "type": "mrkdwn" - } - ] - }, - { - "type": "divider" - }, - { - "type": "section", - "text": { - "text": "Analysis Decision: Finding Suppressed", - "type": "plain_text" - }, - "fields": [ - { - "type": "mrkdwn", - "text": "*Analysis State*" - }, - { - "type": "plain_text", - "emoji": true, - "text": "FALSE_POSITIVE" - }, - { - "type": "mrkdwn", - "text": "*Suppressed*" - }, - { - "type": "plain_text", - "text": "true" - }, - { - "type": "mrkdwn", - "text": "*VulnID*" - }, - { - "type": "plain_text", - "text": "INT-001" - }, - { - "type": "mrkdwn", - "text": "*Severity*" - }, - { - "type": "plain_text", - "text": "MEDIUM" - }, - { - "type": "mrkdwn", - "text": "*Source*" - }, - { - "type": "plain_text", - "text": "INTERNAL" - } - ] - }, - { - "type": "section", - "fields": [ - { - "type": "mrkdwn", - "text": "*Component*" - }, - { - "type": "plain_text", - "text": "componentName : componentVersion" - }, - { - "type": "mrkdwn", - "text": "*Project*" - }, - { - "type": "plain_text", - "text": "pkg:maven/org.acme/projectName@projectVersion" - } - ] - } - ] - } - """))); - } -} diff --git a/notification-publisher/src/test/java/org/dependencytrack/notification/publisher/WebhookPublisherTest.java b/notification-publisher/src/test/java/org/dependencytrack/notification/publisher/WebhookPublisherTest.java deleted file mode 100644 index ad92a35115..0000000000 --- a/notification-publisher/src/test/java/org/dependencytrack/notification/publisher/WebhookPublisherTest.java +++ /dev/null @@ -1,501 +0,0 @@ -/* - * 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 io.quarkus.test.TestTransaction; -import io.quarkus.test.junit.QuarkusTest; -import io.quarkus.test.junit.QuarkusTestProfile; -import io.quarkus.test.junit.TestProfile; -import jakarta.json.JsonObjectBuilder; -import org.junit.jupiter.api.Test; - -import java.util.Map; - -import static com.github.tomakehurst.wiremock.client.WireMock.anyUrl; -import static com.github.tomakehurst.wiremock.client.WireMock.equalTo; -import static com.github.tomakehurst.wiremock.client.WireMock.equalToJson; -import static com.github.tomakehurst.wiremock.client.WireMock.postRequestedFor; - -@QuarkusTest -@TestProfile(WebhookPublisherTest.TestProfile.class) -class WebhookPublisherTest extends AbstractWebhookPublisherTest { - - public static class TestProfile implements QuarkusTestProfile { - - @Override - public Map getConfigOverrides() { - return Map.ofEntries( - Map.entry("dtrack.general.base.url", "https://example.com") - ); - } - - } - - @Override - JsonObjectBuilder extraConfig() { - return super.extraConfig() - .add(Publisher.CONFIG_DESTINATION, "http://localhost:" + wireMockPort); - } - - @Test - @Override - @TestTransaction - void testInformWithBomConsumedNotification() throws Exception { - super.testInformWithBomConsumedNotification(); - - wireMock.verifyThat(postRequestedFor(anyUrl()) - .withHeader("Content-Type", equalTo("application/json")) - .withRequestBody(equalToJson(""" - { - "notification": { - "id" : "019a224d-5b71-778d-953f-594edb4a44e8", - "level": "LEVEL_INFORMATIONAL", - "scope": "SCOPE_PORTFOLIO", - "group": "GROUP_BOM_CONSUMED", - "timestamp": "1970-01-01T18:31:06.000Z", - "title": "Bill of Materials Consumed", - "content": "A CycloneDX BOM was consumed and will be processed", - "subject": { - "project": { - "uuid": "c9c9539a-e381-4b36-ac52-6a7ab83b2c95", - "name": "projectName", - "version": "projectVersion", - "description": "projectDescription", - "purl": "pkg:maven/org.acme/projectName@projectVersion", - "isActive" : true, - "tags": [ - "tag1", - "tag2" - ] - }, - "bom": { - "content": "bomContent", - "format": "CycloneDX", - "specVersion": "1.5" - } - } - } - } - """))); - } - - @Test - @Override - @TestTransaction - void testInformWithBomProcessingFailedNotification() throws Exception { - super.testInformWithBomProcessingFailedNotification(); - - wireMock.verifyThat(postRequestedFor(anyUrl()) - .withHeader("Content-Type", equalTo("application/json")) - .withRequestBody(equalToJson(""" - { - "notification" : { - "id" : "019a224d-5b71-778d-953f-594edb4a44e8", - "level": "LEVEL_ERROR", - "scope": "SCOPE_PORTFOLIO", - "group": "GROUP_BOM_PROCESSING_FAILED", - "timestamp": "1970-01-01T18:31:06.000Z", - "title": "Bill of Materials Processing Failed", - "content": "An error occurred while processing a BOM", - "subject": { - "project": { - "uuid": "c9c9539a-e381-4b36-ac52-6a7ab83b2c95", - "name": "projectName", - "version": "projectVersion", - "description": "projectDescription", - "purl": "pkg:maven/org.acme/projectName@projectVersion", - "isActive" : true, - "tags": [ - "tag1", - "tag2" - ] - }, - "bom": { - "content": "bomContent", - "format": "CycloneDX", - "specVersion": "1.5" - }, - "cause": "cause" - } - } - } - """))); - } - - @Test - @Override - @TestTransaction - void testInformWithBomProcessingFailedNotificationAndNoSpecVersionInSubject() throws Exception { - super.testInformWithBomProcessingFailedNotificationAndNoSpecVersionInSubject(); - - wireMock.verifyThat(postRequestedFor(anyUrl()) - .withHeader("Content-Type", equalTo("application/json")) - .withRequestBody(equalToJson(""" - { - "notification" : { - "id" : "019a224d-5b71-778d-953f-594edb4a44e8", - "level": "LEVEL_ERROR", - "scope": "SCOPE_PORTFOLIO", - "group": "GROUP_BOM_PROCESSING_FAILED", - "timestamp": "1970-01-01T18:31:06.000Z", - "title": "Bill of Materials Processing Failed", - "content": "An error occurred while processing a BOM", - "subject": { - "project": { - "uuid": "c9c9539a-e381-4b36-ac52-6a7ab83b2c95", - "name": "projectName", - "version": "projectVersion", - "description": "projectDescription", - "purl": "pkg:maven/org.acme/projectName@projectVersion", - "isActive" : true, - "tags": [ - "tag1", - "tag2" - ] - }, - "bom": { - "content": "bomContent", - "format": "CycloneDX" - }, - "cause": "cause" - } - } - } - """))); - } - - @Test - @Override - @TestTransaction - public void testInformWithBomValidationFailedNotificationSubject() throws Exception { - super.testInformWithBomValidationFailedNotificationSubject(); - - wireMock.verifyThat(postRequestedFor(anyUrl()) - .withHeader("Content-Type", equalTo("application/json")) - .withRequestBody(equalToJson(""" - { - "notification" : { - "id" : "019a224d-5b71-778d-953f-594edb4a44e8", - "level": "LEVEL_ERROR", - "scope": "SCOPE_PORTFOLIO", - "group": "GROUP_BOM_VALIDATION_FAILED", - "timestamp" : "1970-01-01T18:31:06.000Z", - "title" : "Bill of Materials Validation Failed", - "content" : "An error occurred while validating a BOM", - "subject" : { - "project" : { - "uuid" : "c9c9539a-e381-4b36-ac52-6a7ab83b2c95", - "name" : "projectName", - "version" : "projectVersion", - "description" : "projectDescription", - "purl" : "pkg:maven/org.acme/projectName@projectVersion", - "isActive" : true, - "tags" : [ - "tag1", - "tag2" - ] - }, - "bom" : { - "content" : "bomContent", - "format" : "CycloneDX" - }, - "errors" : [ - "cause 1", - "cause 2" - ] - } - } - } - """))); - } - - @Test - @Override - @TestTransaction - void testInformWithNewVulnerabilityNotification() throws Exception { - super.testInformWithNewVulnerabilityNotification(); - - wireMock.verifyThat(postRequestedFor(anyUrl()) - .withHeader("Content-Type", equalTo("application/json")) - .withRequestBody(equalToJson(""" - { - "notification": { - "id" : "019a224d-5b71-778d-953f-594edb4a44e8", - "level": "LEVEL_INFORMATIONAL", - "scope": "SCOPE_PORTFOLIO", - "group": "GROUP_NEW_VULNERABILITY", - "timestamp": "1970-01-01T18:31:06.000Z", - "title": "New Vulnerability Identified", - "content": "", - "subject": { - "component": { - "uuid": "94f87321-a5d1-4c2f-b2fe-95165debebc6", - "name": "componentName", - "version": "componentVersion" - }, - "project": { - "uuid": "c9c9539a-e381-4b36-ac52-6a7ab83b2c95", - "name": "projectName", - "version": "projectVersion", - "description": "projectDescription", - "purl": "pkg:maven/org.acme/projectName@projectVersion", - "isActive" : true, - "tags": [ "tag1", "tag2" ] - }, - "vulnerabilityAnalysisLevel": "BOM_UPLOAD_ANALYSIS", - "vulnerability": { - "uuid": "bccec5d5-ec21-4958-b3e8-22a7a866a05a", - "vulnId": "INT-001", - "source": "INTERNAL", - "aliases": [ - { - "source": "OSV", - "vulnId": "OSV-001" - } - ], - "title": "vulnerabilityTitle", - "subtitle": "vulnerabilitySubTitle", - "description": "vulnerabilityDescription", - "recommendation": "vulnerabilityRecommendation", - "cvssv2": 5.5, - "cvssv3": 6.6, - "owaspRRLikelihood": 1.1, - "owaspRRTechnicalImpact": 2.2, - "owaspRRBusinessImpact": 3.3, - "severity": "MEDIUM", - "cwes": [ - { - "cweId": 666, - "name": "Operation on Resource in Wrong Phase of Lifetime" - }, - { - "cweId": 777, - "name": "Regular Expression without Anchors" - } - ] - }, - "affectedProjects": [ - { - "uuid": "c9c9539a-e381-4b36-ac52-6a7ab83b2c95", - "name": "projectName", - "version": "projectVersion", - "description": "projectDescription", - "purl": "pkg:maven/org.acme/projectName@projectVersion", - "isActive" : true, - "tags": [ - "tag1", - "tag2" - ] - } - ], - "affectedProjectsReference" : { - "apiUri" : "/api/v1/vulnerability/source/INTERNAL/vuln/INT-001/projects", - "frontendUri" : "/vulnerabilities/INTERNAL/INT-001/affectedProjects" - } - } - } - } - """))); - } - - @Test - @Override - @TestTransaction - void testInformWithProjectAuditChangeNotification() throws Exception { - super.testInformWithProjectAuditChangeNotification(); - - wireMock.verifyThat(postRequestedFor(anyUrl()) - .withHeader("Content-Type", equalTo("application/json")) - .withRequestBody(equalToJson(""" - { - "notification": { - "id" : "019a224d-5b71-778d-953f-594edb4a44e8", - "level": "LEVEL_INFORMATIONAL", - "scope": "SCOPE_PORTFOLIO", - "group": "GROUP_PROJECT_AUDIT_CHANGE", - "timestamp": "1970-01-01T18:31:06.000Z", - "title": "Analysis Decision: Finding Suppressed", - "content": "", - "subject": { - "component": { - "uuid": "94f87321-a5d1-4c2f-b2fe-95165debebc6", - "name": "componentName", - "version": "componentVersion" - }, - "project" : { - "uuid" : "c9c9539a-e381-4b36-ac52-6a7ab83b2c95", - "name" : "projectName", - "version" : "projectVersion", - "description" : "projectDescription", - "purl" : "pkg:maven/org.acme/projectName@projectVersion", - "isActive" : true, - "tags" : [ "tag1", "tag2" ] - }, - "vulnerability": { - "uuid": "bccec5d5-ec21-4958-b3e8-22a7a866a05a", - "vulnId": "INT-001", - "source": "INTERNAL", - "aliases": [ - { - "source": "OSV", - "vulnId": "OSV-001" - } - ], - "title": "vulnerabilityTitle", - "subtitle": "vulnerabilitySubTitle", - "description": "vulnerabilityDescription", - "recommendation": "vulnerabilityRecommendation", - "cvssv2": 5.5, - "cvssv3": 6.6, - "owaspRRLikelihood": 1.1, - "owaspRRTechnicalImpact": 2.2, - "owaspRRBusinessImpact": 3.3, - "severity": "MEDIUM", - "cwes": [ - { - "cweId": 666, - "name": "Operation on Resource in Wrong Phase of Lifetime" - }, - { - "cweId": 777, - "name": "Regular Expression without Anchors" - } - ] - }, - "analysis": { - "suppressed": true, - "state": "FALSE_POSITIVE", - "component" : { - "uuid" : "94f87321-a5d1-4c2f-b2fe-95165debebc6", - "name" : "componentName", - "version" : "componentVersion" - }, - "vulnerability" : { - "uuid" : "bccec5d5-ec21-4958-b3e8-22a7a866a05a", - "vulnId" : "INT-001", - "source" : "INTERNAL", - "aliases" : [ { - "vulnId" : "OSV-001", - "source" : "OSV" - } ], - "title" : "vulnerabilityTitle", - "subtitle" : "vulnerabilitySubTitle", - "description" : "vulnerabilityDescription", - "recommendation" : "vulnerabilityRecommendation", - "cvssv2" : 5.5, - "cvssv3" : 6.6, - "owaspRRLikelihood" : 1.1, - "owaspRRTechnicalImpact" : 2.2, - "owaspRRBusinessImpact" : 3.3, - "severity" : "MEDIUM", - "cwes" : [ { - "cweId" : 666, - "name" : "Operation on Resource in Wrong Phase of Lifetime" - }, { - "cweId" : 777, - "name" : "Regular Expression without Anchors" - } ] - } - } - } - } - } - """))); - } - - @Test - @Override - @TestTransaction - public void testInformWithNewVulnerableDependencyNotification() throws Exception { - super.testInformWithNewVulnerableDependencyNotification(); - - wireMock.verifyThat(postRequestedFor(anyUrl()) - .withHeader("Content-Type", equalTo("application/json")) - .withRequestBody(equalToJson(""" - { - "notification": { - "id" : "019a224d-5b71-778d-953f-594edb4a44e8", - "level": "LEVEL_INFORMATIONAL", - "scope": "SCOPE_PORTFOLIO", - "group": "GROUP_NEW_VULNERABLE_DEPENDENCY", - "timestamp": "1970-01-01T18:31:06.000Z", - "title": "Vulnerable Dependency Introduced", - "content": "", - "subject": { - "component": { - "uuid": "94f87321-a5d1-4c2f-b2fe-95165debebc6", - "name": "componentName", - "version": "componentVersion" - }, - "project": { - "uuid": "c9c9539a-e381-4b36-ac52-6a7ab83b2c95", - "name": "projectName", - "version": "projectVersion", - "description": "projectDescription", - "purl": "pkg:maven/org.acme/projectName@projectVersion", - "isActive" : true, - "tags" : [ "tag1", "tag2" ] - }, - "vulnerabilities": [ - { - "uuid": "bccec5d5-ec21-4958-b3e8-22a7a866a05a", - "vulnId": "INT-001", - "source": "INTERNAL", - "aliases": [ - { - "vulnId": "OSV-001", - "source": "OSV" - } - ], - "title": "vulnerabilityTitle", - "subtitle": "vulnerabilitySubTitle", - "description": "vulnerabilityDescription", - "recommendation": "vulnerabilityRecommendation", - "cvssv2": 5.5, - "cvssv3": 6.6, - "owaspRRLikelihood": 1.1, - "owaspRRTechnicalImpact": 2.2, - "owaspRRBusinessImpact": 3.3, - "severity": "MEDIUM", - "cwes": [ - { - "cweId": 666, - "name": "Operation on Resource in Wrong Phase of Lifetime" - }, - { - "cweId": 777, - "name": "Regular Expression without Anchors" - } - ] - } - ] - } - } - } - """))); - } - - @Test - @Override - @TestTransaction - void testInformWithTemplateInclude() throws Exception { - super.testInformWithTemplateInclude(); - } - -} diff --git a/notification-publisher/src/test/java/org/dependencytrack/notification/util/ModelConverterTest.java b/notification-publisher/src/test/java/org/dependencytrack/notification/util/ModelConverterTest.java deleted file mode 100644 index 402772a096..0000000000 --- a/notification-publisher/src/test/java/org/dependencytrack/notification/util/ModelConverterTest.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * 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.util; - -import org.dependencytrack.persistence.model.NotificationGroup; -import org.dependencytrack.persistence.model.NotificationLevel; -import org.dependencytrack.proto.notification.v1.Group; -import org.dependencytrack.proto.notification.v1.Level; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.CsvSource; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.dependencytrack.proto.notification.v1.Group.GROUP_UNSPECIFIED; -import static org.dependencytrack.proto.notification.v1.Level.LEVEL_UNSPECIFIED; - -class ModelConverterTest { - - @ParameterizedTest - @CsvSource(value = { - "LEVEL_ERROR, ERROR", - "LEVEL_WARNING, WARNING", - "LEVEL_INFORMATIONAL, INFORMATIONAL" - }) - void testConvertLevel(final Level given, final NotificationLevel expected) { - assertThat(ModelConverter.convert(given)).isEqualTo(expected); - } - - @Test - void testConvertLevelUnknown() { - assertThatExceptionOfType(IllegalArgumentException.class) - .isThrownBy(() -> ModelConverter.convert(LEVEL_UNSPECIFIED)); - } - - @Test - void testConvertLevelNull() { - assertThatExceptionOfType(IllegalArgumentException.class) - .isThrownBy(() -> ModelConverter.convert((Level) null)); - } - - @ParameterizedTest - @CsvSource(value = { - "GROUP_CONFIGURATION, CONFIGURATION", - "GROUP_DATASOURCE_MIRRORING, DATASOURCE_MIRRORING", - "GROUP_REPOSITORY, REPOSITORY", - "GROUP_INTEGRATION, INTEGRATION", - "GROUP_FILE_SYSTEM, FILE_SYSTEM", - "GROUP_ANALYZER, ANALYZER", - "GROUP_NEW_VULNERABILITY, NEW_VULNERABILITY", - "GROUP_NEW_VULNERABLE_DEPENDENCY, NEW_VULNERABLE_DEPENDENCY", - "GROUP_PROJECT_AUDIT_CHANGE, PROJECT_AUDIT_CHANGE", - "GROUP_BOM_CONSUMED, BOM_CONSUMED", - "GROUP_BOM_PROCESSED, BOM_PROCESSED", - "GROUP_VEX_CONSUMED, VEX_CONSUMED", - "GROUP_VEX_PROCESSED, VEX_PROCESSED", - "GROUP_POLICY_VIOLATION, POLICY_VIOLATION", - "GROUP_PROJECT_CREATED, PROJECT_CREATED" - }) - void testConvertGroup(final Group given, final NotificationGroup expected) { - assertThat(ModelConverter.convert(given)).isEqualTo(expected); - } - - @Test - void testConvertGroupUnknown() { - assertThatExceptionOfType(IllegalArgumentException.class) - .isThrownBy(() -> ModelConverter.convert(GROUP_UNSPECIFIED)); - } - - @Test - void testConvertGroupNull() { - assertThatExceptionOfType(IllegalArgumentException.class) - .isThrownBy(() -> ModelConverter.convert((Group) null)); - } - -} \ No newline at end of file diff --git a/notification-publisher/src/test/resources/secret.key b/notification-publisher/src/test/resources/secret.key deleted file mode 100644 index 58c6b04872..0000000000 --- a/notification-publisher/src/test/resources/secret.key +++ /dev/null @@ -1,2 +0,0 @@ -��ő:��� -iE�>� CC[O����gw#vZ�| \ No newline at end of file diff --git a/notification-publisher/src/test/resources/templates/console.peb b/notification-publisher/src/test/resources/templates/console.peb deleted file mode 100644 index ffe5b577a9..0000000000 --- a/notification-publisher/src/test/resources/templates/console.peb +++ /dev/null @@ -1,8 +0,0 @@ --------------------------------------------------------------------------------- -Notification - -- timestamp: {{ timestamp }} - -- level: {{ notification.level }} - -- scope: {{ notification.scope }} - -- group: {{ notification.group }} - -- title: {{ notification.title }} - -- content: {{ notification.content }} diff --git a/notification-publisher/src/test/resources/templates/cswebex.peb b/notification-publisher/src/test/resources/templates/cswebex.peb deleted file mode 100644 index 7249ea8f6f..0000000000 --- a/notification-publisher/src/test/resources/templates/cswebex.peb +++ /dev/null @@ -1 +0,0 @@ -{"markdown":"**{{ notification.title | escape(strategy="json") }}**{% if notification.group == "GROUP_NEW_VULNERABILITY" %}\n**VulnID:** {{ subject.vulnerability.vulnId | escape(strategy="json") }}\n**Severity:** {{ subject.vulnerability.severity | escape(strategy="json") }}\n**Source:** {{ subject.vulnerability.source | escape(strategy="json") }}\n**Component:** {{ subject.component | summarize | escape(strategy="json") }}\n**Actions:**\n[View Vulnerability]({{ baseUrl }}/vulnerability/?source={{ subject.vulnerability.source | escape(strategy="json") }}&vulnId={{ subject.vulnerability.vulnId | escape(strategy="json") }}){% elseif notification.group == "GROUP_NEW_VULNERABLE_DEPENDENCY" %}\n**Project:** {{ subject.project | summarize | escape(strategy="json") }}\n**Component:** {{ subject.component | summarize | escape(strategy="json") }}\n**Actions:**\n[View Project]({{ baseUrl }}/projects/?uuid={{ subject.project.uuid | escape(strategy="json") }}){% endif %}\n[View Component]({{ baseUrl }}/component/?uuid={{ subject.component.uuid | escape(strategy="json") }})\n**Description:** {{ notification.content | escape(strategy="json") }}"} \ No newline at end of file diff --git a/notification-publisher/src/test/resources/templates/email.peb b/notification-publisher/src/test/resources/templates/email.peb deleted file mode 100644 index 8bfaf55b75..0000000000 --- a/notification-publisher/src/test/resources/templates/email.peb +++ /dev/null @@ -1,115 +0,0 @@ -{{ notification.title }} - --------------------------------------------------------------------------------- -{% if notification.group == "GROUP_NEW_VULNERABILITY" %} -Vulnerability ID: {{ subject.vulnerability.vulnId }} -Vulnerability URL: {{ baseUrl }}/vulnerability/?source={{ subject.vulnerability.source }}&vulnId={{ subject.vulnerability.vulnId }} -Severity: {{ subject.vulnerability.severity }} -Source: {{ subject.vulnerability.source }} -Component: {{ subject.component | summarize }} -Component URL: {{ baseUrl }}/component/?uuid={{ subject.component.uuid }} -Project: {{ subject.project.name }} -Version: {{ subject.project.version }} -Description: {{ subject.project.description }} -Project URL: {{ baseUrl }}/projects/{{ subject.project.uuid }} --------------------------------------------------------------------------------- - -Other affected projects: {{ baseUrl }}{{ subject.affectedProjectsReference.frontendUri }} -{% elseif notification.group == "GROUP_NEW_VULNERABLE_DEPENDENCY" %} -Project: {{ subject.project | summarize }} -Project URL: {{ baseUrl }}/projects/{{ subject.project.uuid }} -Component: {{ subject.component | summarize }} -Component URL: {{ baseUrl }}/component/?uuid={{ subject.component.uuid }} - -Vulnerabilities -{% for vulnerability in subject.vulnerabilitiesList %} -Vulnerability ID: {{ vulnerability.vulnId }} -Vulnerability URL: {{ baseUrl }}/vulnerability/?source={{ vulnerability.source }}&vulnId={{ vulnerability.vulnId }} -Severity: {{ vulnerability.severity }} -Source: {{ vulnerability.source }} -Description: -{{ vulnerability.description }} - -{% endfor %} -{% elseif notification.group == "GROUP_PROJECT_AUDIT_CHANGE" %} -Analysis Type: Project Analysis -{% if subject.analysis is null %}{% for comment in subject.violationAnalysis.analysisComments %} {% if loop.last and comment.commenter is not null %} -Commenter: {{ comment.commenter}}{% endif %}{% endfor %} -Violation Analysis State: {{ subject.violationAnalysis.state }} -Suppressed: {{ subject.violationAnalysis.suppressed }} -Policy: {{ subject.policyViolation.policyCondition.policy.name }} -Policy Violation State: {{ subject.policyViolation.policyCondition.policy.violationState }} -Policy Condition: subject == {{ subject.policyViolation.policyCondition.subject }} && value {{ subject.policyViolation.policyCondition.operator }} {{ subject.policyViolation.policyCondition.value }} -{% else %}{% for comment in subject.analysis.analysisComments %} {% if loop.last and comment.commenter is not null %} -Commenter: {{ comment.commenter}}{% endif %}{% endfor %} -Analysis State: {{ subject.analysis.state }} -Suppressed: {{ subject.analysis.suppressed }} -Vulnerability ID: {{ subject.vulnerability.vulnId }} -Vulnerability URL: {{ baseUrl }}/vulnerability/?source={{ subject.vulnerability.source }}&vulnId={{ subject.vulnerability.vulnId }} -Severity: {{ subject.vulnerability.severity }} -Source: {{ subject.vulnerability.source }} -{% endif %} -Component: {{ subject.component | summarize }} -Component URL: {{ baseUrl }}/component/?uuid={{ subject.component.uuid }} -Project: {{ subject.project | summarize }} -Description: {{ subject.project.description }} -Project URL: {{ baseUrl }}/projects/{{ subject.project.uuid }} -{% elseif notification.group == "GROUP_BOM_CONSUMED" %} -Project: {{ subject.project.name }} -Version: {{ subject.project.version }} -Description: {{ subject.project.description }} -Project URL: {{ baseUrl }}/projects/{{ subject.project.uuid }} -{% elseif notification.group == "GROUP_BOM_PROCESSED" %} -Project: {{ subject.project.name }} -Version: {{ subject.project.version }} -Description: {{ subject.project.description }} -Project URL: {{ baseUrl }}/projects/{{ subject.project.uuid }} -{% elseif notification.group == "GROUP_BOM_PROCESSING_FAILED" %} -Project: {{ subject.project.name }} -Version: {{ subject.project.version }} -Description: {{ subject.project.description }} -Project URL: {{ baseUrl }}/projects/{{ subject.project.uuid }} - --------------------------------------------------------------------------------- - -Cause: -{{ subject.cause }} -{% elseif notification.group == "GROUP_BOM_VALIDATION_FAILED" %} -Project: {{ subject.project.name }} -Version: {{ subject.project.version }} -Description: {{ subject.project.description }} -Project URL: {{ baseUrl }}/projects/{{ subject.project.uuid }} - --------------------------------------------------------------------------------- - -Errors: -{% for error in subject.errorsList %} -{{ error }} -{% endfor %} -{% elseif notification.group == "GROUP_VEX_CONSUMED" %} -Project: {{ subject.project.name }} -Version: {{ subject.project.version }} -Description: {{ subject.project.description }} -Project URL: {{ baseUrl }}/projects/{{ subject.project.uuid }} -{% elseif notification.group == "GROUP_VEX_PROCESSED" %} -Project: {{ subject.project.name }} -Version: {{ subject.project.version }} -Description: {{ subject.project.description }} -Project URL: {{ baseUrl }}/projects/{{ subject.project.uuid }} -{% elseif notification.group == "GROUP_POLICY_VIOLATION" %} -Project: {{ subject.project.name }} -Version: {{ subject.project.version }} -Description: {{ subject.project.description }} -Project URL: {{ baseUrl }}/projects/{{ subject.project.uuid }} -{% else %} -Level: {{ notification.level }} -Scope: {{ notification.scope }} -Group: {{ notification.group }} -{% endif %} --------------------------------------------------------------------------------- - -{{ notification.content }} - --------------------------------------------------------------------------------- - -{{ timestamp }} diff --git a/notification-publisher/src/test/resources/templates/jira.peb b/notification-publisher/src/test/resources/templates/jira.peb deleted file mode 100644 index 9042b66a78..0000000000 --- a/notification-publisher/src/test/resources/templates/jira.peb +++ /dev/null @@ -1,18 +0,0 @@ -{ - "fields": { - "project": { - "key": "{{ jiraProjectKey }}" - }, - "issuetype": { - "name": "{{ jiraTicketType }}" - }, - "summary": "[Dependency-Track] [{{ notification.group | escape(strategy="json") }}] {% if notification.group == "GROUP_NEW_VULNERABILITY" %}[{{ subject.vulnerability.severity }}] New {{ subject.vulnerability.severity | lower }} vulnerability identified: {{ subject.vulnerability.vulnId }}{% elseif notification.group == "GROUP_NEW_VULNERABLE_DEPENDENCY" %}Vulnerable dependency introduced on project {{ subject.project.name | escape(strategy="json") }}{% else %}{{ notification.title | escape(strategy="json") }}{% endif %}", - {% if notification.group == "GROUP_NEW_VULNERABILITY" %} - "description": "A new vulnerability has been identified on your project(s).\n\\\\\n\\\\\n*Vulnerability description*\n{code:none|bgColor=white|borderStyle=none}{{ subject.vulnerability.description | escape(strategy="json") }}{code}\n\n*VulnID*\n{{ subject.vulnerability.vulnId }}\n\n*Severity*\n{{ subject.vulnerability.severity | lower | capitalize }}\n\n*Component*\n[{{ subject.component | summarize | escape(strategy="json") }}|{{ baseUrl }}/components/{{ subject.component.uuid }}]\n\n*Affected project(s)*\n{% for project in subject.affectedProjectsList %}- [{{ project.name | escape(strategy="json") }} ({{ project.version | escape(strategy="json") }})|{{ baseUrl }}/projects/{{ project.uuid }}]\n{% endfor %}" - {% elseif notification.group == "GROUP_NEW_VULNERABLE_DEPENDENCY" %} - "description": "A component which contains one or more vulnerabilities has been added to your project.\n\\\\\n\\\\\n*Project*\n[{{ subject.project | summarize | escape(strategy="json") }}|{{ baseUrl }}/projects/{{ subject.project.uuid }}]\n\n*Component*\n[{{ subject.component | summarize | escape(strategy="json") }}|{{ baseUrl }}/components/{{ subject.component.uuid }}]\n\n*Vulnerabilities*\n{% for vulnerability in subject.vulnerabilitiesList %}- {{ vulnerability.vulnId }} ({{ vulnerability.severity | lower | capitalize }})\n{% endfor %}" - {% else %} - "description": "{{ notification.content | escape(strategy="json") }}\n\\\\\n\\\\\n*Level*\n{{ notification.level }}\n\n" - {% endif %} - } -} \ No newline at end of file diff --git a/notification-publisher/src/test/resources/templates/mattermost.peb b/notification-publisher/src/test/resources/templates/mattermost.peb deleted file mode 100644 index 9ab35449be..0000000000 --- a/notification-publisher/src/test/resources/templates/mattermost.peb +++ /dev/null @@ -1,5 +0,0 @@ -{ - "username": "Dependency Track", - "icon_url": "https://raw.githubusercontent.com/DependencyTrack/branding/master/dt-logo-symbol-blue-background.png", - "text": "#### {{ notification.title | escape(strategy="json") }}\n{{ notification.content | escape(strategy="json") }}\n{% if notification.group == "GROUP_NEW_VULNERABILITY" %}**Component**: {{ subject.component | summarize | escape(strategy="json") }}\n**Vulnerability**: {{ subject.vulnerability.vulnId | escape(strategy="json") }}, {{ subject.vulnerability.severity | escape(strategy="json") }}\n[View Component]({{ baseUrl }}/components/{{ subject.component.uuid | escape(strategy="json") }}) - [View Vulnerability]({{ baseUrl }}/vulnerabilities/{{ subject.vulnerability.source | escape(strategy="json") }}/{{ subject.vulnerability.vulnId | escape(strategy="json") }}){% elseif notification.group == "GROUP_NEW_VULNERABLE_DEPENDENCY" %}**Project**: {{ subject.project | summarize | escape(strategy="json") }}\n**Component**: {{ subject.component | summarize | escape(strategy="json") }}\n[View Project]({{ baseUrl }}/projects/{{ subject.project.uuid | escape(strategy="json") }}) - [View Component]({{ baseUrl }}/components/{{ subject.component.uuid | escape(strategy="json") }}){% elseif notification.group == "GROUP_PROJECT_AUDIT_CHANGE" or notification.group == "GROUP_GLOBAL_AUDIT_CHANGE" %}**Project**: {{ subject.project | summarize | escape(strategy="json") }}\n**Component**: {{ subject.component | summarize | escape(strategy="json") }}\n**Vulnerability**: {{ subject.vulnerability.vulnId | escape(strategy="json") }}, {{ subject.vulnerability.severity | escape(strategy="json") }}\n**Analysis**: {{ subject.analysis.state | escape(strategy="json") }}, suppressed: {{ subject.analysis.suppressed | escape(strategy="json") }}\n[View Project]({{ baseUrl }}/projects/{{ subject.project.uuid | escape(strategy="json") }}) - [View Component]({{ baseUrl }}/components/{{ subject.component.uuid | escape(strategy="json") }}) - [View Vulnerability]({{ baseUrl }}/vulnerabilities/{{ subject.vulnerability.source | escape(strategy="json") }}/{{ subject.vulnerability.vulnId | escape(strategy="json") }}){% elseif notification.group == "GROUP_BOM_CONSUMED" or notification.group == "GROUP_BOM_PROCESSED" %}**Project**: {{ subject.project | summarize | escape(strategy="json") }}\n[View Project]({{ baseUrl }}/projects/{{ subject.project.uuid | escape(strategy="json") }}){% elseif notification.group == "GROUP_POLICY_VIOLATION" %}**Project**: {{ subject.project | summarize | escape(strategy="json") }}\n**Component**: {{ subject.component | summarize | escape(strategy="json") }}\n**Policy**: {{ subject.policyViolation.policyCondition.policy.violationState | escape(strategy="json") }}, {{ subject.policyViolation.policyCondition.policy.name | escape(strategy="json") }}\n[View Project]({{ baseUrl }}/projects/{{ subject.project.uuid | escape(strategy="json") }}) - [View Component]({{ baseUrl }}/components/{{ subject.component.uuid | escape(strategy="json") }}){% endif %}" -} \ No newline at end of file diff --git a/notification-publisher/src/test/resources/templates/msteams.peb b/notification-publisher/src/test/resources/templates/msteams.peb deleted file mode 100644 index ef9211a0f6..0000000000 --- a/notification-publisher/src/test/resources/templates/msteams.peb +++ /dev/null @@ -1,168 +0,0 @@ -{ - "@type": "MessageCard", - "@context": "http://schema.org/extensions", - "summary": "{{ notification.title | escape(strategy="json") }}", - "title": "{{ notification.title | escape(strategy="json") }}", - "sections": [ - { - "activityTitle": "Dependency-Track", - "activitySubtitle": "{{ timestamp }}", - "activityImage": "https://raw.githubusercontent.com/DependencyTrack/branding/master/dt-logo-symbol-blue-background.png", - {% if notification.group == "GROUP_NEW_VULNERABILITY" %} - "facts": [ - { - "name": "VulnID", - "value": "{{ subject.vulnerability.vulnId | escape(strategy="json") }}" - }, - { - "name": "Severity", - "value": "{{ subject.vulnerability.severity | escape(strategy="json") }}" - }, - { - "name": "Source", - "value": "{{ subject.vulnerability.source | escape(strategy="json") }}" - }, - { - "name": "Component", - "value": "{{ subject.component | summarize | escape(strategy="json") }}" - } - ], - {% elseif notification.group == "GROUP_NEW_VULNERABLE_DEPENDENCY" %} - "facts": [ - { - "name": "Project", - "value": "{{ subject.project | summarize | escape(strategy="json") }}" - }, - { - "name": "Component", - "value": "{{ subject.component | summarize | escape(strategy="json") }}" - } - ], - {% elseif notification.group == "GROUP_PROJECT_AUDIT_CHANGE" %} - "facts": [ - { - "name": "Analysis Type", - "value": "Project Analysis" - }, - { - "name": "Analysis State", - "value": "{{ subject.analysis.state | escape(strategy="json") }}" - }, - { - "name": "Suppressed", - "value": "{{ subject.analysis.suppressed | escape(strategy="json") }}" - }, - { - "name": "VulnID", - "value": "{{ subject.vulnerability.vulnId | escape(strategy="json") }}" - }, - { - "name": "Severity", - "value": "{{ subject.vulnerability.severity | escape(strategy="json") }}" - }, - { - "name": "Source", - "value": "{{ subject.vulnerability.source | escape(strategy="json") }}" - }, - { - "name": "Component", - "value": "{{ subject.component | summarize | escape(strategy="json") }}" - }, - { - "name": "Project", - "value": "{{ subject.project | summarize | escape(strategy="json") }}" - } - ], - {% elseif notification.group == "GROUP_POLICY_VIOLATION" %} - "facts": [ - { - "name": "Subject", - "value": "{{ subject.policyViolation.policyCondition.subject | escape(strategy="json") }}" - }, - { - "name": "Operator", - "value": "{{ subject.policyViolation.policyCondition.operator | escape(strategy="json") }}" - }, - { - "name": "Value", - "value": "{{ subject.policyViolation.policyCondition.value | escape(strategy="json") }}" - }, - { - "name": "Component", - "value": "{{ subject.component | summarize | escape(strategy="json") }}" - }, - { - "name": "Project", - "value": "{{ subject.project | summarize | escape(strategy="json") }}" - } - ], - {% elseif notification.group == "GROUP_BOM_PROCESSING_FAILED" %} - "facts": [ - { - "name": "Level", - "value": "{{ notification.level | escape(strategy="json") }}" - }, - { - "name": "Scope", - "value": "{{ notification.scope | escape(strategy="json") }}" - }, - { - "name": "Group", - "value": "{{ notification.group | escape(strategy="json") }}" - }, - { - "name": "Project", - "value": "{{ subject.project | summarize | escape(strategy="json") }}" - }, - { - "name": "Project URL", - "value": "{{ baseUrl }}/projects/{{ subject.project.uuid | escape(strategy='json') }}" - } - ], - {% elseif notification.group == "GROUP_BOM_VALIDATION_FAILED" %} - "facts": [ - { - "name": "Level", - "value": "{{ notification.level | escape(strategy="json") }}" - }, - { - "name": "Scope", - "value": "{{ notification.scope | escape(strategy="json") }}" - }, - { - "name": "Group", - "value": "{{ notification.group | escape(strategy="json") }}" - }, - { - "name": "Project", - "value": "{{ subject.project | summarize | escape(strategy="json") }}" - }, - { - "name": "Project URL", - "value": "{{ baseUrl }}/projects/{{ subject.project.uuid | escape(strategy='json') }}" - }, - { - "name": "Errors", - "value": "{{ subject.errorsList.toString | escape(strategy='json') }}" - } - ], - {% else %} - "facts": [ - { - "name": "Level", - "value": "{{ notification.level | escape(strategy="json") }}" - }, - { - "name": "Scope", - "value": "{{ notification.scope | escape(strategy="json") }}" - }, - { - "name": "Group", - "value": "{{ notification.group | escape(strategy="json") }}" - } - ], - {% endif %} - "text": "{{ notification.content | escape(strategy="json") }}" - } - ] -} diff --git a/notification-publisher/src/test/resources/templates/slack.peb b/notification-publisher/src/test/resources/templates/slack.peb deleted file mode 100644 index 423346d0b8..0000000000 --- a/notification-publisher/src/test/resources/templates/slack.peb +++ /dev/null @@ -1,473 +0,0 @@ -{% if notification.group == "GROUP_NEW_VULNERABILITY" %} -{ - "blocks": [ - { - "type": "header", - "text": { - "type": "plain_text", - "text": "New Vulnerability" - } - }, - { - "type": "context", - "elements": [ - { - "text": "*{{ notification.level | escape(strategy="json") }}* | *{{ notification.scope | escape(strategy="json") }}*", - "type": "mrkdwn" - } - ] - }, - { - "type": "divider" - }, - { - "type": "section", - "text": { - "text": "{{ notification.title | escape(strategy="json") }}", - "type": "mrkdwn" - }, - "fields": [ - { - "type": "mrkdwn", - "text": "*VulnID*" - }, - { - "type": "plain_text", - "text": "{{ subject.vulnerability.vulnId | escape(strategy="json") }}" - }, - { - "type": "mrkdwn", - "text": "*Severity*" - }, - { - "type": "plain_text", - "text": "{{ subject.vulnerability.severity | escape(strategy="json") }}" - }, - { - "type": "mrkdwn", - "text": "*Source*" - }, - { - "type": "plain_text", - "text": "{{ subject.vulnerability.source | escape(strategy="json") }}" - }, - { - "type": "mrkdwn", - "text": "*Component*" - }, - { - "type": "plain_text", - "text": "{{ subject.component | summarize | escape(strategy="json") }}" - } - ] - }{% if baseUrl is not empty %}, - { - "type": "actions", - "elements": [ - { - "type": "button", - "text": { - "type": "plain_text", - "text": "View Vulnerability" - }, - "action_id": "actionId-1", - "url": "{{ baseUrl }}/vulnerabilities/{{ subject.vulnerability.source | escape(strategy="json") }}/{{ subject.vulnerability.vulnId | escape(strategy="json") }}" - }, - { - "type": "button", - "text": { - "type": "plain_text", - "text": "View Component" - }, - "action_id": "actionId-2", - "url": "{{ baseUrl }}/components/{{ subject.component.uuid | escape(strategy="json") }}" - } - ] - } - {% endif %} - ] -} -{% elseif notification.group == "GROUP_NEW_VULNERABLE_DEPENDENCY" %} -{ - "blocks": [ - { - "type": "header", - "text": { - "type": "plain_text", - "text": "New Vulnerable Dependency" - } - }, - { - "type": "context", - "elements": [ - { - "text": "*{{ notification.level | escape(strategy="json") }}* | *{{ notification.scope | escape(strategy="json") }}*", - "type": "mrkdwn" - } - ] - }, - { - "type": "divider" - }, - { - "type": "section", - "text": { - "text": "{{ notification.title | escape(strategy="json") }}", - "type": "mrkdwn" - }, - "fields": [ - { - "type": "mrkdwn", - "text": "*Component*" - }, - { - "type": "plain_text", - "text": "{{ subject.component | summarize | escape(strategy="json") }}" - }, - { - "type": "mrkdwn", - "text": "*Project*" - }, - { - "type": "plain_text", - "text": "{{ subject.project | summarize | escape(strategy="json") }}" - } - ] - }{% if baseUrl is not empty %}, - { - "type": "actions", - "elements": [ - { - "type": "button", - "text": { - "type": "plain_text", - "text": "View Project" - }, - "action_id": "actionId-1", - "url": "{{ baseUrl }}/projects/{{ subject.project.uuid | escape(strategy="json") }}" - }, - { - "type": "button", - "text": { - "type": "plain_text", - "text": "View Component" - }, - "action_id": "actionId-2", - "url": "{{ baseUrl }}/components/{{ subject.component.uuid | escape(strategy="json") }}" - } - ] - } - {% endif %} - ] -} -{% elseif notification.group == "GROUP_PROJECT_AUDIT_CHANGE" %} -{ - "blocks": [ - { - "type": "header", - "text": { - "type": "plain_text", - "text": "Project Audit Change" - } - }, - { - "type": "context", - "elements": [ - { - "text": "*{{ notification.level | escape(strategy="json") }}* | *{{ notification.scope | escape(strategy="json") }}*", - "type": "mrkdwn" - } - ] - }, - { - "type": "divider" - }, - { - "type": "section", - "text": { - "text": "{{ notification.title | escape(strategy="json") }}", - "type": "plain_text" - }, - "fields": [ - { - "type": "mrkdwn", - "text": "*Analysis State*" - }, - { - "type": "plain_text", - "emoji": true, - "text": "{{ subject.analysis.state | escape(strategy="json") }}" - }, - { - "type": "mrkdwn", - "text": "*Suppressed*" - }, - { - "type": "plain_text", - "text": "{{ subject.analysis.suppressed | escape(strategy="json") }}" - }, - { - "type": "mrkdwn", - "text": "*VulnID*" - }, - { - "type": "plain_text", - "text": "{{ subject.vulnerability.vulnId | escape(strategy="json") }}" - }, - { - "type": "mrkdwn", - "text": "*Severity*" - }, - { - "type": "plain_text", - "text": "{{ subject.vulnerability.severity | escape(strategy="json") }}" - }, - { - "type": "mrkdwn", - "text": "*Source*" - }, - { - "type": "plain_text", - "text": "{{ subject.vulnerability.source | escape(strategy="json") }}" - } - ] - }, - { - "type": "section", - "fields": [ - { - "type": "mrkdwn", - "text": "*Component*" - }, - { - "type": "plain_text", - "text": "{{ subject.component | summarize | escape(strategy="json") }}" - }, - { - "type": "mrkdwn", - "text": "*Project*" - }, - { - "type": "plain_text", - "text": "{{ subject.project | summarize | escape(strategy="json") }}" - } - ] - }{% if baseUrl is not empty %}, - { - "type": "actions", - "elements": [ - { - "type": "button", - "text": { - "type": "plain_text", - "text": "View Project" - }, - "action_id": "actionId-1", - "url": "{{ baseUrl }}/projects/{{ subject.project.uuid | escape(strategy="json") }}" - }, - { - "type": "button", - "text": { - "type": "plain_text", - "text": "View Component" - }, - "action_id": "actionId-2", - "url": "{{ baseUrl }}/components/{{ subject.component.uuid | escape(strategy="json") }}" - }, - { - "type": "button", - "text": { - "type": "plain_text", - "text": "View Vulnerability" - }, - "action_id": "actionId-3", - "url": "{{ baseUrl }}/vulnerabilities/{{ subject.vulnerability.source | escape(strategy="json") }}/{{ subject.vulnerability.vulnId | escape(strategy="json") }}" - } - ] - } - {% endif %} - ] -} -{% elseif notification.group == "GROUP_POLICY_VIOLATION" %} -{ - "blocks": [ - { - "type": "header", - "text": { - "type": "plain_text", - "text": "Policy Violation" - } - }, - { - "type": "context", - "elements": [ - { - "text": "*{{ notification.level | escape(strategy="json") }}* | *{{ notification.scope | escape(strategy="json") }}* | *{{ subject.policyViolation.type | escape(strategy="json") }}*", - "type": "mrkdwn" - } - ] - }, - { - "type": "divider" - }, - { - "type": "section", - "text": { - "text": "OWASP Dependency-Track detected a policy violation. Details of the violation follow.", - "type": "plain_text" - }, - "fields": [ - { - "type": "mrkdwn", - "text": "*Subject*" - }, - { - "type": "plain_text", - "emoji": true, - "text": "{{ subject.policyViolation.policyCondition.subject | escape(strategy="json") }}" - }, - { - "type": "mrkdwn", - "text": "*Operator*" - }, - { - "type": "plain_text", - "emoji": true, - "text": "{{ subject.policyViolation.policyCondition.operator | escape(strategy="json") }}" - }, - { - "type": "mrkdwn", - "text": "*Value*" - }, - { - "type": "mrkdwn", - "text": "`{{ subject.policyViolation.policyCondition.value | escape(strategy="json") }}`" - }, - { - "type": "mrkdwn", - "text": "*Component*" - }, - { - "type": "plain_text", - "text": "{{ subject.component | summarize | escape(strategy="json") }}" - }, - { - "type": "mrkdwn", - "text": "*Project*" - }, - { - "type": "plain_text", - "text": "{{ subject.project.toString | escape(strategy="json") }}" - } - ] - }{% if baseUrl is not empty %}, - { - "type": "actions", - "elements": [ - { - "type": "button", - "text": { - "type": "plain_text", - "text": "View Project" - }, - "action_id": "actionId-1", - "url": "{{ baseUrl }}/projects/{{ subject.project.uuid | escape(strategy="json") }}" - }, - { - "type": "button", - "text": { - "type": "plain_text", - "text": "View Component" - }, - "action_id": "actionId-2", - "url": "{{ baseUrl }}/components/{{ subject.component.uuid | escape(strategy="json") }}" - } - ] - } - {% endif %} - ] -} -{% elseif notification.group == "BOM_VALIDATION_FAILED" %} -{ - "blocks": [ - { - "type": "header", - "text": { - "type": "plain_text", - "text": "{{ notification.group | escape(strategy="json") }} | {{ subject.project.toString | escape(strategy="json") }}" - } - }, - { - "type": "context", - "elements": [ - { - "text": "*{{ notification.level | escape(strategy="json") }}* | *{{ notification.scope | escape(strategy="json") }}*", - "type": "mrkdwn" - } - ] - }, - { - "type": "divider" - }, - { - "type": "section", - "text": { - "text": "{{ notification.title | escape(strategy="json") }}", - "type": "plain_text" - } - }, - { - "type": "section", - "text": { - "text": "{{ notification.content | escape(strategy="json") }}", - "type": "plain_text" - } - }, - { - "type": "section", - "text": { - "text": "{{ subject.errors.toString | escape(strategy="json") }}", - "type": "plain_text" - } - } - ] -} -{% else %} -{ - "blocks": [ - { - "type": "header", - "text": { - "type": "plain_text", - "text": "{{ notification.group | escape(strategy="json") }}" - } - }, - { - "type": "context", - "elements": [ - { - "text": "*{{ notification.level | escape(strategy="json") }}* | *{{ notification.scope | escape(strategy="json") }}*", - "type": "mrkdwn" - } - ] - }, - { - "type": "divider" - }, - { - "type": "section", - "text": { - "text": "{{ notification.title | escape(strategy="json") }}", - "type": "plain_text" - } - }, - { - "type": "section", - "text": { - "text": "{{ notification.content | escape(strategy="json") }}", - "type": "plain_text" - } - } - ] -} -{% endif %} diff --git a/notification-publisher/src/test/resources/templates/webhook.peb b/notification-publisher/src/test/resources/templates/webhook.peb deleted file mode 100644 index 9d1cc42389..0000000000 --- a/notification-publisher/src/test/resources/templates/webhook.peb +++ /dev/null @@ -1,12 +0,0 @@ -{ - "notification": { - "id": "{{ notification.id | escape(strategy="json") }}", - "level": "{{ notification.level | escape(strategy="json") }}", - "scope": "{{ notification.scope | escape(strategy="json") }}", - "group": "{{ notification.group | escape(strategy="json") }}", - "timestamp": "{{ timestamp }}", - "title": "{{ notification.title | escape(strategy="json") }}", - "content": "{{ notification.content | escape(strategy="json") }}", - "subject": {% if subjectJson != null %}{{ subjectJson | raw }}{% else %}null{% endif %} - } -} \ No newline at end of file diff --git a/pom.xml b/pom.xml index 219d46bb62..159b0232af 100644 --- a/pom.xml +++ b/pom.xml @@ -30,7 +30,6 @@ commons commons-kstreams commons-persistence - notification-publisher repository-meta-analyzer proto vulnerability-analyzer @@ -66,7 +65,6 @@ 1.28.0 3.0.1 4.5.0 - 0.5.3.3 2.5.2 1.5.1 2.1.8 @@ -79,12 +77,10 @@ 8.6.0 13.6 20251224 - 4.1.0 2.3.0 1.5.0 4.33.4 1.330.0 - 2.0.0 1.1.10.8 2.35.2 3.31.0 @@ -156,12 +152,6 @@ ${lib.commons-collections4.version} - - io.confluent.parallelconsumer - parallel-consumer-core - ${lib.confluent-parallel-consumer.version} - - org.apache.httpcomponents.client5 httpclient5 @@ -244,12 +234,6 @@ ${lib.packageurl-java.version} - - io.pebbletemplates - pebble - ${lib.pebble.version} - - org.dependencytrack quarkus-config-dependencytrack @@ -272,16 +256,6 @@ quarkus-wiremock-test ${quarkus.wiremock.version} - - io.quarkiverse.mailpit - quarkus-mailpit - ${lib.quarkus-mailpit.version} - - - io.quarkiverse.mailpit - quarkus-mailpit-testing - ${lib.quarkus-mailpit.version} -