Improvements for system notifications; add test

This commit is contained in:
ViacheslavKlimov 2023-08-22 12:56:21 +03:00
parent 2a87f2ee7e
commit c55ac0efc6
4 changed files with 69 additions and 17 deletions

View File

@ -182,7 +182,7 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple
NotificationRequest notificationRequest = NotificationRequest.builder() NotificationRequest notificationRequest = NotificationRequest.builder()
.tenantId(tenantId) .tenantId(tenantId)
.template(template) .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) .status(NotificationRequestStatus.PROCESSING)
.build(); .build();
try { try {

View File

@ -46,7 +46,7 @@ public class DefaultJwtSettingsService implements JwtSettingsService {
private final AdminSettingsService adminSettingsService; private final AdminSettingsService adminSettingsService;
@Lazy @Lazy
private final Optional<TbClusterService> tbClusterService; private final Optional<TbClusterService> tbClusterService;
private final NotificationCenter notificationCenter; private final Optional<NotificationCenter> notificationCenter;
private final JwtSettingsValidator jwtSettingsValidator; private final JwtSettingsValidator jwtSettingsValidator;
@Value("${security.jwt.tokenExpirationTime:9000}") @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. " + 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. " + "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."); "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; this.jwtSettings = result;
} }

View File

@ -16,14 +16,22 @@
package org.thingsboard.server.service.security.auth; package org.thingsboard.server.service.security.auth;
import io.jsonwebtoken.Claims; import io.jsonwebtoken.Claims;
import org.junit.BeforeClass; import org.junit.Before;
import org.junit.Test; 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.CustomerId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId; 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.Authority;
import org.thingsboard.server.common.data.security.model.JwtSettings; import org.thingsboard.server.common.data.security.model.JwtSettings;
import org.thingsboard.server.common.data.security.model.JwtToken; 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.auth.jwt.settings.JwtSettingsService;
import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.service.security.model.UserPrincipal; 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.Calendar;
import java.util.Date; import java.util.Date;
import java.util.Optional;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import static org.assertj.core.api.Assertions.assertThat; 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.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
public class JwtTokenFactoryTest { public class JwtTokenFactoryTest {
private static JwtTokenFactory tokenFactory; private JwtTokenFactory tokenFactory;
private static JwtSettings jwtSettings; private AdminSettingsService adminSettingsService;
private NotificationCenter notificationCenter;
private JwtSettingsService jwtSettingsService;
@BeforeClass private JwtSettings jwtSettings;
public static void beforeAll() {
@Before
public void beforeEach() {
jwtSettings = new JwtSettings(); jwtSettings = new JwtSettings();
jwtSettings.setTokenIssuer("tb"); jwtSettings.setTokenIssuer("tb");
jwtSettings.setTokenSigningKey("abewafaf"); jwtSettings.setTokenSigningKey("abewafaf");
jwtSettings.setTokenExpirationTime((int) TimeUnit.HOURS.toSeconds(2)); jwtSettings.setTokenExpirationTime((int) TimeUnit.HOURS.toSeconds(2));
jwtSettings.setRefreshTokenExpTime((int) TimeUnit.DAYS.toSeconds(7)); jwtSettings.setRefreshTokenExpTime((int) TimeUnit.DAYS.toSeconds(7));
JwtSettingsService jwtSettingsService = mock(JwtSettingsService.class); adminSettingsService = mock(AdminSettingsService.class);
willReturn(jwtSettings).given(jwtSettingsService).getJwtSettings(); notificationCenter = mock(NotificationCenter.class);
jwtSettingsService = mockJwtSettingsService();
mockJwtSettings(jwtSettings);
tokenFactory = new JwtTokenFactory(jwtSettingsService); 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) { private void checkExpirationTime(JwtToken jwtToken, int tokenLifetime) {
Claims claims = tokenFactory.parseTokenClaims(jwtToken).getBody(); Claims claims = tokenFactory.parseTokenClaims(jwtToken).getBody();
assertThat(claims.getExpiration()).matches(actualExpirationTime -> { assertThat(claims.getExpiration()).matches(actualExpirationTime -> {

View File

@ -66,6 +66,9 @@ import static org.thingsboard.server.dao.DaoUtil.toUUIDs;
@RequiredArgsConstructor @RequiredArgsConstructor
public class DefaultNotifications { public class DefaultNotifications {
private static final String YELLOW_COLOR = "#F9D916";
private static final String RED_COLOR = "#e91a1a";
public static final DefaultNotification maintenanceWork = DefaultNotification.builder() public static final DefaultNotification maintenanceWork = DefaultNotification.builder()
.name("Maintenance work notification") .name("Maintenance work notification")
.subject("Infrastructure maintenance") .subject("Infrastructure maintenance")
@ -77,7 +80,7 @@ public class DefaultNotifications {
.type(NotificationType.ENTITIES_LIMIT) .type(NotificationType.ENTITIES_LIMIT)
.subject("${entityType}s limit will be reached soon for tenant ${tenantName}") .subject("${entityType}s limit will be reached soon for tenant ${tenantName}")
.text("${entityType}s usage: ${currentCount}/${limit} (${percents}%)") .text("${entityType}s usage: ${currentCount}/${limit} (${percents}%)")
.icon("warning").color("#F9D916") .icon("warning").color(YELLOW_COLOR)
.rule(DefaultRule.builder() .rule(DefaultRule.builder()
.name("Entities count limit (sysadmin)") .name("Entities count limit (sysadmin)")
.triggerConfig(EntitiesLimitNotificationRuleTriggerConfig.builder() .triggerConfig(EntitiesLimitNotificationRuleTriggerConfig.builder()
@ -100,7 +103,7 @@ public class DefaultNotifications {
.type(NotificationType.API_USAGE_LIMIT) .type(NotificationType.API_USAGE_LIMIT)
.subject("${feature} feature will be disabled soon for tenant ${tenantName}") .subject("${feature} feature will be disabled soon for tenant ${tenantName}")
.text("Usage: ${currentValue} out of ${limit} ${unitLabel}s") .text("Usage: ${currentValue} out of ${limit} ${unitLabel}s")
.icon("warning").color("#F9D916") .icon("warning").color(YELLOW_COLOR)
.rule(DefaultRule.builder() .rule(DefaultRule.builder()
.name("API feature warning (sysadmin)") .name("API feature warning (sysadmin)")
.triggerConfig(ApiUsageLimitNotificationRuleTriggerConfig.builder() .triggerConfig(ApiUsageLimitNotificationRuleTriggerConfig.builder()
@ -123,7 +126,7 @@ public class DefaultNotifications {
.type(NotificationType.API_USAGE_LIMIT) .type(NotificationType.API_USAGE_LIMIT)
.subject("${feature} feature was disabled for tenant ${tenantName}") .subject("${feature} feature was disabled for tenant ${tenantName}")
.text("Used ${currentValue} out of ${limit} ${unitLabel}s") .text("Used ${currentValue} out of ${limit} ${unitLabel}s")
.icon("block").color("#e91a1a") .icon("block").color(RED_COLOR)
.rule(DefaultRule.builder() .rule(DefaultRule.builder()
.name("API feature disabled (sysadmin)") .name("API feature disabled (sysadmin)")
.triggerConfig(ApiUsageLimitNotificationRuleTriggerConfig.builder() .triggerConfig(ApiUsageLimitNotificationRuleTriggerConfig.builder()
@ -147,7 +150,7 @@ public class DefaultNotifications {
.type(NotificationType.RATE_LIMITS) .type(NotificationType.RATE_LIMITS)
.subject("Rate limits exceeded") .subject("Rate limits exceeded")
.text("Rate limits for ${api} exceeded") .text("Rate limits for ${api} exceeded")
.icon("block").color("#e91a1a") .icon("block").color(RED_COLOR)
.rule(DefaultRule.builder() .rule(DefaultRule.builder()
.name("Per-tenant rate limits exceeded") .name("Per-tenant rate limits exceeded")
.triggerConfig(RateLimitsNotificationRuleTriggerConfig.builder() .triggerConfig(RateLimitsNotificationRuleTriggerConfig.builder()
@ -164,7 +167,7 @@ public class DefaultNotifications {
.type(NotificationType.RATE_LIMITS) .type(NotificationType.RATE_LIMITS)
.subject("Rate limits exceeded") .subject("Rate limits exceeded")
.text("Rate limits for ${api} exceeded for '${limitLevelEntityName}'") .text("Rate limits for ${api} exceeded for '${limitLevelEntityName}'")
.icon("block").color("#e91a1a") .icon("block").color(RED_COLOR)
.rule(DefaultRule.builder() .rule(DefaultRule.builder()
.name("Per-entity rate limits exceeded") .name("Per-entity rate limits exceeded")
.triggerConfig(RateLimitsNotificationRuleTriggerConfig.builder() .triggerConfig(RateLimitsNotificationRuleTriggerConfig.builder()
@ -328,7 +331,7 @@ public class DefaultNotifications {
.type(NotificationType.GENERAL) .type(NotificationType.GENERAL)
.subject("WARNING: security issue") .subject("WARNING: security issue")
.text("The platform is configured to use default JWT Signing Key. Please change it on the security settings page") .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") .button("Go to settings").link("/security-settings/general")
.build(); .build();