diff --git a/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationCenter.java b/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationCenter.java index 16d4c44d63..6e3717f88d 100644 --- a/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationCenter.java +++ b/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationCenter.java @@ -22,6 +22,7 @@ import org.springframework.stereotype.Service; import org.thingsboard.rule.engine.api.NotificationCenter; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.User; +import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.NotificationId; import org.thingsboard.server.common.data.id.NotificationRequestId; import org.thingsboard.server.common.data.id.NotificationRuleId; @@ -39,11 +40,12 @@ import org.thingsboard.server.common.data.notification.NotificationRequestStatus import org.thingsboard.server.common.data.notification.NotificationStatus; import org.thingsboard.server.common.data.notification.info.RuleOriginatedNotificationInfo; import org.thingsboard.server.common.data.notification.settings.NotificationSettings; -import org.thingsboard.server.common.data.notification.targets.MicrosoftTeamsNotificationTargetConfig; import org.thingsboard.server.common.data.notification.settings.UserNotificationSettings; +import org.thingsboard.server.common.data.notification.targets.MicrosoftTeamsNotificationTargetConfig; import org.thingsboard.server.common.data.notification.targets.NotificationRecipient; import org.thingsboard.server.common.data.notification.targets.NotificationTarget; import org.thingsboard.server.common.data.notification.targets.platform.PlatformUsersNotificationTargetConfig; +import org.thingsboard.server.common.data.notification.targets.platform.UsersFilter; import org.thingsboard.server.common.data.notification.targets.slack.SlackNotificationTargetConfig; import org.thingsboard.server.common.data.notification.template.DeliveryMethodNotificationTemplate; import org.thingsboard.server.common.data.notification.template.NotificationTemplate; @@ -165,16 +167,54 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple .settings(settings) .build(); + processNotificationRequestAsync(ctx, targets, callback); + return request; + } + + @Override + public void sendGeneralWebNotification(TenantId tenantId, UsersFilter recipients, NotificationTemplate template) { + NotificationTarget target = new NotificationTarget(); + target.setTenantId(tenantId); + PlatformUsersNotificationTargetConfig targetConfig = new PlatformUsersNotificationTargetConfig(); + targetConfig.setUsersFilter(recipients); + target.setConfiguration(targetConfig); + + NotificationRequest notificationRequest = NotificationRequest.builder() + .tenantId(tenantId) + .template(template) + .targets(List.of(EntityId.NULL_UUID)) // TODO: refactor + .status(NotificationRequestStatus.PROCESSING) + .build(); + try { + notificationRequest = notificationRequestService.saveNotificationRequest(tenantId, notificationRequest); + NotificationProcessingContext ctx = NotificationProcessingContext.builder() + .tenantId(tenantId) + .request(notificationRequest) + .deliveryMethods(Set.of(NotificationDeliveryMethod.WEB)) + .template(template) + .build(); + + processNotificationRequestAsync(ctx, List.of(target), null); + } catch (Exception e) { + log.error("Failed to process notification request for recipients {} for template '{}'", recipients, template.getName(), e); + } + } + + private void processNotificationRequestAsync(NotificationProcessingContext ctx, List targets, Consumer callback) { notificationExecutor.submit(() -> { + NotificationRequestId requestId = ctx.getRequest().getId(); for (NotificationTarget target : targets) { - processForTarget(target, ctx); + try { + processForTarget(target, ctx); + } catch (Exception e) { + log.error("[{}] Failed to process notification request for target {}", requestId, target.getId()); + } } - - NotificationRequestId requestId = ctx.getRequest().getId(); log.debug("[{}] Notification request processing is finished", requestId); + NotificationRequestStats stats = ctx.getStats(); try { - notificationRequestService.updateNotificationRequest(tenantId, requestId, NotificationRequestStatus.SENT, stats); + notificationRequestService.updateNotificationRequest(ctx.getTenantId(), requestId, NotificationRequestStatus.SENT, stats); } catch (Exception e) { log.error("[{}] Failed to update stats for notification request", requestId, e); } @@ -187,8 +227,6 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple } } }); - - return request; } private void processForTarget(NotificationTarget target, NotificationProcessingContext ctx) { diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/settings/DefaultJwtSettingsService.java b/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/settings/DefaultJwtSettingsService.java index d159c469c0..8ab8cec5f2 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/settings/DefaultJwtSettingsService.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/settings/DefaultJwtSettingsService.java @@ -22,11 +22,14 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.rule.engine.api.NotificationCenter; import org.thingsboard.server.cluster.TbClusterService; import org.thingsboard.server.common.data.AdminSettings; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.notification.targets.platform.SystemAdministratorsFilter; import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; import org.thingsboard.server.common.data.security.model.JwtSettings; +import org.thingsboard.server.dao.notification.DefaultNotifications; import org.thingsboard.server.dao.settings.AdminSettingsService; import java.nio.charset.StandardCharsets; @@ -43,6 +46,7 @@ public class DefaultJwtSettingsService implements JwtSettingsService { private final AdminSettingsService adminSettingsService; @Lazy private final Optional tbClusterService; + private final NotificationCenter notificationCenter; private final JwtSettingsValidator jwtSettingsValidator; @Value("${security.jwt.tokenExpirationTime:9000}") @@ -124,6 +128,7 @@ public class DefaultJwtSettingsService implements JwtSettingsService { log.warn("WARNING: The platform is configured to use default JWT Signing Key. " + "This is a security issue that needs to be resolved. Please change the JWT Signing Key using the Web UI. " + "Navigate to \"System settings -> Security settings\" while logged in as a System Administrator."); + notificationCenter.sendGeneralWebNotification(TenantId.SYS_TENANT_ID, new SystemAdministratorsFilter(), DefaultNotifications.jwtSigningKeyIssue.toTemplate()); } this.jwtSettings = result; } diff --git a/application/src/test/java/org/thingsboard/server/service/notification/AbstractNotificationApiTest.java b/application/src/test/java/org/thingsboard/server/service/notification/AbstractNotificationApiTest.java index fb7675bd9e..104b5afef4 100644 --- a/application/src/test/java/org/thingsboard/server/service/notification/AbstractNotificationApiTest.java +++ b/application/src/test/java/org/thingsboard/server/service/notification/AbstractNotificationApiTest.java @@ -265,4 +265,8 @@ public abstract class AbstractNotificationApiTest extends AbstractControllerTest return (NotificationApiWsClient) super.getWsClient(); } + @Override + public NotificationApiWsClient getAnotherWsClient() { + return (NotificationApiWsClient) super.getAnotherWsClient(); + } } diff --git a/application/src/test/java/org/thingsboard/server/service/notification/NotificationApiTest.java b/application/src/test/java/org/thingsboard/server/service/notification/NotificationApiTest.java index 1a7e9e1420..e44914d356 100644 --- a/application/src/test/java/org/thingsboard/server/service/notification/NotificationApiTest.java +++ b/application/src/test/java/org/thingsboard/server/service/notification/NotificationApiTest.java @@ -30,6 +30,7 @@ import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.NotificationRuleId; import org.thingsboard.server.common.data.id.NotificationTargetId; +import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.notification.Notification; import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; import org.thingsboard.server.common.data.notification.NotificationRequest; @@ -47,6 +48,7 @@ import org.thingsboard.server.common.data.notification.targets.MicrosoftTeamsNot import org.thingsboard.server.common.data.notification.targets.NotificationTarget; import org.thingsboard.server.common.data.notification.targets.platform.CustomerUsersFilter; import org.thingsboard.server.common.data.notification.targets.platform.PlatformUsersNotificationTargetConfig; +import org.thingsboard.server.common.data.notification.targets.platform.SystemAdministratorsFilter; import org.thingsboard.server.common.data.notification.targets.platform.UserListFilter; import org.thingsboard.server.common.data.notification.targets.slack.SlackConversation; import org.thingsboard.server.common.data.notification.targets.slack.SlackConversationType; @@ -61,6 +63,7 @@ import org.thingsboard.server.common.data.notification.template.SlackDeliveryMet import org.thingsboard.server.common.data.notification.template.SmsDeliveryMethodNotificationTemplate; import org.thingsboard.server.common.data.notification.template.WebDeliveryMethodNotificationTemplate; import org.thingsboard.server.common.data.security.Authority; +import org.thingsboard.server.dao.notification.DefaultNotifications; import org.thingsboard.server.dao.notification.NotificationDao; import org.thingsboard.server.dao.service.DaoSqlTest; import org.thingsboard.server.service.executors.DbCallbackExecutorService; @@ -601,6 +604,24 @@ public class NotificationApiTest extends AbstractNotificationApiTest { assertThat(stats.getErrors().get(NotificationDeliveryMethod.SLACK).values()).containsExactly(errorMessage); } + @Test + public void testInternalGeneralWebNotifications() throws Exception { + loginSysAdmin(); + getAnotherWsClient().subscribeForUnreadNotifications(10).waitForReply(true); + + getAnotherWsClient().registerWaitForUpdate(); + + DefaultNotifications.DefaultNotification expectedNotification = DefaultNotifications.maintenanceWork; + notificationCenter.sendGeneralWebNotification(TenantId.SYS_TENANT_ID, new SystemAdministratorsFilter(), + expectedNotification.toTemplate()); + + getAnotherWsClient().waitForUpdate(true); + Notification notification = getAnotherWsClient().getLastDataUpdate().getUpdate(); + assertThat(notification.getSubject()).isEqualTo(expectedNotification.getSubject()); + assertThat(notification.getText()).isEqualTo(expectedNotification.getText()); + System.err.println(notification); + } + @Test public void testMicrosoftTeamsNotifications() throws Exception { RestTemplate restTemplate = mock(RestTemplate.class); @@ -688,7 +709,7 @@ public class NotificationApiTest extends AbstractNotificationApiTest { protected void connectOtherWsClient() throws Exception { loginCustomerUser(); - otherWsClient = (NotificationApiWsClient) super.getAnotherWsClient(); + otherWsClient = super.getAnotherWsClient(); loginTenantAdmin(); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotifications.java b/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotifications.java index a6c558a388..34a2839f92 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotifications.java +++ b/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotifications.java @@ -323,6 +323,15 @@ public class DefaultNotifications { .build()) .build(); + public static final DefaultNotification jwtSigningKeyIssue = DefaultNotification.builder() + .name("JWT Signing Key issue notification") + .type(NotificationType.GENERAL) + .subject("WARNING: security issue") + .text("The platform is configured to use default JWT Signing Key. Please change it on the security settings page") + .icon("warning").color("#F9D916") + .button("Go to settings").link("/security-settings/general") + .build(); + private final NotificationTemplateService templateService; private final NotificationRuleService ruleService; diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/NotificationCenter.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/NotificationCenter.java index a91b809270..772a861dbd 100644 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/NotificationCenter.java +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/NotificationCenter.java @@ -22,6 +22,8 @@ import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; import org.thingsboard.server.common.data.notification.NotificationRequest; import org.thingsboard.server.common.data.notification.NotificationRequestStats; +import org.thingsboard.server.common.data.notification.targets.platform.UsersFilter; +import org.thingsboard.server.common.data.notification.template.NotificationTemplate; import java.util.Set; import java.util.function.Consumer; @@ -30,6 +32,8 @@ public interface NotificationCenter { NotificationRequest processNotificationRequest(TenantId tenantId, NotificationRequest notificationRequest, Consumer callback); + void sendGeneralWebNotification(TenantId tenantId, UsersFilter recipients, NotificationTemplate template); + void deleteNotificationRequest(TenantId tenantId, NotificationRequestId notificationRequestId); void markNotificationAsRead(TenantId tenantId, UserId recipientId, NotificationId notificationId);