diff --git a/application/src/main/java/org/thingsboard/server/config/CryptoConfig.java b/application/src/main/java/org/thingsboard/server/config/CryptoConfig.java new file mode 100644 index 0000000000..e960d62f8e --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/config/CryptoConfig.java @@ -0,0 +1,30 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * 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. + */ +package org.thingsboard.server.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; + +@Configuration +public class CryptoConfig { + + @Bean + protected BCryptPasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } + +} diff --git a/application/src/main/java/org/thingsboard/server/config/ThingsboardSecurityConfiguration.java b/application/src/main/java/org/thingsboard/server/config/ThingsboardSecurityConfiguration.java index 7acc77a183..55370b04b8 100644 --- a/application/src/main/java/org/thingsboard/server/config/ThingsboardSecurityConfiguration.java +++ b/application/src/main/java/org/thingsboard/server/config/ThingsboardSecurityConfiguration.java @@ -188,11 +188,6 @@ public class ThingsboardSecurityConfiguration { return auth.build(); } - @Bean - protected BCryptPasswordEncoder passwordEncoder() { - return new BCryptPasswordEncoder(); - } - @Autowired private OAuth2AuthorizationRequestResolver oAuth2AuthorizationRequestResolver; 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..f6c77691da 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)) // this is temporary and will be removed when 'create from scratch' functionality is implemented for recipients + .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(), e); + } } - - 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..d350a86f91 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 Optional notificationCenter; private final JwtSettingsValidator jwtSettingsValidator; @Value("${security.jwt.tokenExpirationTime:9000}") @@ -124,6 +128,9 @@ 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.ifPresent(notificationCenter -> { + notificationCenter.sendGeneralWebNotification(TenantId.SYS_TENANT_ID, new SystemAdministratorsFilter(), DefaultNotifications.jwtSigningKeyIssue.toTemplate()); + }); } this.jwtSettings = result; } diff --git a/application/src/main/java/org/thingsboard/server/service/security/system/DefaultSystemSecurityService.java b/application/src/main/java/org/thingsboard/server/service/security/system/DefaultSystemSecurityService.java index 3739958e7a..2c2956b288 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/system/DefaultSystemSecurityService.java +++ b/application/src/main/java/org/thingsboard/server/service/security/system/DefaultSystemSecurityService.java @@ -55,7 +55,6 @@ import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.settings.AdminSettingsService; import org.thingsboard.server.dao.user.UserService; import org.thingsboard.server.dao.user.UserServiceImpl; -import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.security.auth.rest.RestAuthenticationDetails; import org.thingsboard.server.service.security.exception.UserPasswordExpiredException; import org.thingsboard.server.service.security.model.SecurityUser; @@ -73,7 +72,6 @@ import static org.thingsboard.server.common.data.CacheConstants.SECURITY_SETTING @Service @Slf4j -@TbCoreComponent public class DefaultSystemSecurityService implements SystemSecurityService { @Autowired 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..1b3fce3c3c 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,23 @@ 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()); + } + @Test public void testMicrosoftTeamsNotifications() throws Exception { RestTemplate restTemplate = mock(RestTemplate.class); @@ -688,7 +708,7 @@ public class NotificationApiTest extends AbstractNotificationApiTest { protected void connectOtherWsClient() throws Exception { loginCustomerUser(); - otherWsClient = (NotificationApiWsClient) super.getAnotherWsClient(); + otherWsClient = super.getAnotherWsClient(); loginTenantAdmin(); } diff --git a/application/src/test/java/org/thingsboard/server/service/security/auth/JwtTokenFactoryTest.java b/application/src/test/java/org/thingsboard/server/service/security/auth/JwtTokenFactoryTest.java index b7dc40ec6d..7bb4f1e559 100644 --- a/application/src/test/java/org/thingsboard/server/service/security/auth/JwtTokenFactoryTest.java +++ b/application/src/test/java/org/thingsboard/server/service/security/auth/JwtTokenFactoryTest.java @@ -16,14 +16,22 @@ package org.thingsboard.server.service.security.auth; import io.jsonwebtoken.Claims; -import org.junit.BeforeClass; +import org.junit.Before; import org.junit.Test; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.rule.engine.api.NotificationCenter; +import org.thingsboard.server.common.data.AdminSettings; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.UserId; +import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; +import org.thingsboard.server.common.data.notification.targets.platform.SystemAdministratorsFilter; import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.common.data.security.model.JwtSettings; import org.thingsboard.server.common.data.security.model.JwtToken; +import org.thingsboard.server.dao.settings.AdminSettingsService; +import org.thingsboard.server.service.security.auth.jwt.settings.DefaultJwtSettingsService; +import org.thingsboard.server.service.security.auth.jwt.settings.DefaultJwtSettingsValidator; import org.thingsboard.server.service.security.auth.jwt.settings.JwtSettingsService; import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.model.UserPrincipal; @@ -33,28 +41,40 @@ import org.thingsboard.server.service.security.model.token.RawAccessJwtToken; import java.util.Calendar; import java.util.Date; +import java.util.Optional; import java.util.UUID; import java.util.concurrent.TimeUnit; import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.BDDMockito.willReturn; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isA; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; public class JwtTokenFactoryTest { - private static JwtTokenFactory tokenFactory; - private static JwtSettings jwtSettings; + private JwtTokenFactory tokenFactory; + private AdminSettingsService adminSettingsService; + private NotificationCenter notificationCenter; + private JwtSettingsService jwtSettingsService; - @BeforeClass - public static void beforeAll() { + private JwtSettings jwtSettings; + + @Before + public void beforeEach() { jwtSettings = new JwtSettings(); jwtSettings.setTokenIssuer("tb"); jwtSettings.setTokenSigningKey("abewafaf"); jwtSettings.setTokenExpirationTime((int) TimeUnit.HOURS.toSeconds(2)); jwtSettings.setRefreshTokenExpTime((int) TimeUnit.DAYS.toSeconds(7)); - JwtSettingsService jwtSettingsService = mock(JwtSettingsService.class); - willReturn(jwtSettings).given(jwtSettingsService).getJwtSettings(); + adminSettingsService = mock(AdminSettingsService.class); + notificationCenter = mock(NotificationCenter.class); + jwtSettingsService = mockJwtSettingsService(); + mockJwtSettings(jwtSettings); tokenFactory = new JwtTokenFactory(jwtSettingsService); } @@ -150,6 +170,33 @@ public class JwtTokenFactoryTest { }); } + @Test + public void testJwtSigningKeyIssueNotification() { + JwtSettings badJwtSettings = jwtSettings; + badJwtSettings.setTokenSigningKey(JwtSettingsService.TOKEN_SIGNING_KEY_DEFAULT); + mockJwtSettings(badJwtSettings); + jwtSettingsService = mockJwtSettingsService(); + + for (int i = 0; i < 5; i++) { // to check if notification is not sent twice + jwtSettingsService.getJwtSettings(); + } + verify(notificationCenter, times(1)).sendGeneralWebNotification(eq(TenantId.SYS_TENANT_ID), + isA(SystemAdministratorsFilter.class), argThat(template -> template.getConfiguration().getDeliveryMethodsTemplates().get(NotificationDeliveryMethod.WEB) + .getBody().contains("The platform is configured to use default JWT Signing Key"))); + } + + private void mockJwtSettings(JwtSettings settings) { + AdminSettings adminJwtSettings = new AdminSettings(); + adminJwtSettings.setJsonValue(JacksonUtil.valueToTree(settings)); + when(adminSettingsService.findAdminSettingsByKey(TenantId.SYS_TENANT_ID, JwtSettingsService.ADMIN_SETTINGS_JWT_KEY)) + .thenReturn(adminJwtSettings); + } + + private DefaultJwtSettingsService mockJwtSettingsService() { + return new DefaultJwtSettingsService(adminSettingsService, Optional.empty(), + Optional.of(notificationCenter), new DefaultJwtSettingsValidator()); + } + private void checkExpirationTime(JwtToken jwtToken, int tokenLifetime) { Claims claims = tokenFactory.parseTokenClaims(jwtToken).getBody(); assertThat(claims.getExpiration()).matches(actualExpirationTime -> { 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..29c7928a24 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 @@ -66,6 +66,9 @@ import static org.thingsboard.server.dao.DaoUtil.toUUIDs; @RequiredArgsConstructor public class DefaultNotifications { + private static final String YELLOW_COLOR = "#F9D916"; + private static final String RED_COLOR = "#e91a1a"; + public static final DefaultNotification maintenanceWork = DefaultNotification.builder() .name("Maintenance work notification") .subject("Infrastructure maintenance") @@ -77,7 +80,7 @@ public class DefaultNotifications { .type(NotificationType.ENTITIES_LIMIT) .subject("${entityType}s limit will be reached soon for tenant ${tenantName}") .text("${entityType}s usage: ${currentCount}/${limit} (${percents}%)") - .icon("warning").color("#F9D916") + .icon("warning").color(YELLOW_COLOR) .rule(DefaultRule.builder() .name("Entities count limit (sysadmin)") .triggerConfig(EntitiesLimitNotificationRuleTriggerConfig.builder() @@ -100,7 +103,7 @@ public class DefaultNotifications { .type(NotificationType.API_USAGE_LIMIT) .subject("${feature} feature will be disabled soon for tenant ${tenantName}") .text("Usage: ${currentValue} out of ${limit} ${unitLabel}s") - .icon("warning").color("#F9D916") + .icon("warning").color(YELLOW_COLOR) .rule(DefaultRule.builder() .name("API feature warning (sysadmin)") .triggerConfig(ApiUsageLimitNotificationRuleTriggerConfig.builder() @@ -123,7 +126,7 @@ public class DefaultNotifications { .type(NotificationType.API_USAGE_LIMIT) .subject("${feature} feature was disabled for tenant ${tenantName}") .text("Used ${currentValue} out of ${limit} ${unitLabel}s") - .icon("block").color("#e91a1a") + .icon("block").color(RED_COLOR) .rule(DefaultRule.builder() .name("API feature disabled (sysadmin)") .triggerConfig(ApiUsageLimitNotificationRuleTriggerConfig.builder() @@ -147,7 +150,7 @@ public class DefaultNotifications { .type(NotificationType.RATE_LIMITS) .subject("Rate limits exceeded") .text("Rate limits for ${api} exceeded") - .icon("block").color("#e91a1a") + .icon("block").color(RED_COLOR) .rule(DefaultRule.builder() .name("Per-tenant rate limits exceeded") .triggerConfig(RateLimitsNotificationRuleTriggerConfig.builder() @@ -164,7 +167,7 @@ public class DefaultNotifications { .type(NotificationType.RATE_LIMITS) .subject("Rate limits exceeded") .text("Rate limits for ${api} exceeded for '${limitLevelEntityName}'") - .icon("block").color("#e91a1a") + .icon("block").color(RED_COLOR) .rule(DefaultRule.builder() .name("Per-entity rate limits exceeded") .triggerConfig(RateLimitsNotificationRuleTriggerConfig.builder() @@ -323,6 +326,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(YELLOW_COLOR) + .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);