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 561ce9f4ee..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 @@ -182,7 +182,7 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple NotificationRequest notificationRequest = NotificationRequest.builder() .tenantId(tenantId) .template(template) - .targets(List.of(EntityId.NULL_UUID)) // TODO: refactor + .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 { 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 8ab8cec5f2..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 @@ -46,7 +46,7 @@ public class DefaultJwtSettingsService implements JwtSettingsService { private final AdminSettingsService adminSettingsService; @Lazy private final Optional tbClusterService; - private final NotificationCenter notificationCenter; + private final Optional notificationCenter; private final JwtSettingsValidator jwtSettingsValidator; @Value("${security.jwt.tokenExpirationTime:9000}") @@ -128,7 +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.sendGeneralWebNotification(TenantId.SYS_TENANT_ID, new SystemAdministratorsFilter(), DefaultNotifications.jwtSigningKeyIssue.toTemplate()); + notificationCenter.ifPresent(notificationCenter -> { + 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/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 34a2839f92..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() @@ -328,7 +331,7 @@ public class DefaultNotifications { .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") + .icon("warning").color(YELLOW_COLOR) .button("Go to settings").link("/security-settings/general") .build();