Merge branch 'develop/3.5.2' into feature/widget-bundles

This commit is contained in:
Igor Kulikov 2023-09-04 18:05:47 +03:00
commit 7f47472092
15 changed files with 324 additions and 43 deletions

View File

@ -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();
}
}

View File

@ -188,11 +188,6 @@ public class ThingsboardSecurityConfiguration {
return auth.build();
}
@Bean
protected BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Autowired
private OAuth2AuthorizationRequestResolver oAuth2AuthorizationRequestResolver;

View File

@ -23,18 +23,18 @@ import com.google.api.client.auth.oauth2.TokenResponse;
import com.google.api.client.http.GenericUrl;
import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.api.client.json.gson.GsonFactory;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.lang.Nullable;
import org.springframework.mail.MailException;
import org.springframework.mail.MailSendException;
import org.springframework.mail.javamail.JavaMailSenderImpl;
import org.thingsboard.server.common.data.AdminSettings;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.mail.MailOauth2Provider;
import org.thingsboard.server.dao.exception.IncorrectParameterException;
import javax.mail.MessagingException;
import javax.mail.internet.MimeMessage;
import java.time.Duration;
import java.time.Instant;
@ -50,6 +50,7 @@ public class TbMailSender extends JavaMailSenderImpl {
private static final String MAIL_PROP = "mail.";
private final TbMailContextComponent ctx;
private final Lock lock;
private final Boolean oauth2Enabled;
private volatile String accessToken;
private volatile long tokenExpires;
@ -70,14 +71,39 @@ public class TbMailSender extends JavaMailSenderImpl {
setJavaMailProperties(createJavaMailProperties(jsonConfig));
}
@SneakyThrows
public Boolean getOauth2Enabled() {
return oauth2Enabled;
}
public long getTokenExpires() {
return tokenExpires;
}
@Override
public void doSend(MimeMessage[] mimeMessages, @Nullable Object[] originalMessages) {
if (oauth2Enabled && (System.currentTimeMillis() > tokenExpires)){
protected void doSend(MimeMessage[] mimeMessages, @Nullable Object[] originalMessages) throws MailException {
updateOauth2PasswordIfExpired();
doSendSuper(mimeMessages, originalMessages);
}
public void doSendSuper(MimeMessage[] mimeMessages, Object[] originalMessages) {
super.doSend(mimeMessages, originalMessages);
}
@Override
public void testConnection() throws MessagingException {
updateOauth2PasswordIfExpired();
testConnectionSuper();
}
public void testConnectionSuper() throws MessagingException {
super.testConnection();
}
public void updateOauth2PasswordIfExpired() {
if (getOauth2Enabled() && (System.currentTimeMillis() > getTokenExpires())){
refreshAccessToken();
setPassword(accessToken);
}
super.doSend(mimeMessages, originalMessages);
}
private Properties createJavaMailProperties(JsonNode jsonConfig) {
@ -125,10 +151,10 @@ public class TbMailSender extends JavaMailSenderImpl {
return javaMailProperties;
}
public void refreshAccessToken() throws ThingsboardException {
public void refreshAccessToken() {
lock.lock();
try {
if (System.currentTimeMillis() > tokenExpires) {
if (System.currentTimeMillis() > getTokenExpires()) {
AdminSettings settings = ctx.getAdminSettingsService().findAdminSettingsByKey(TenantId.SYS_TENANT_ID, "mail");
JsonNode jsonValue = settings.getJsonValue();
@ -151,8 +177,8 @@ public class TbMailSender extends JavaMailSenderImpl {
tokenExpires = System.currentTimeMillis() + (tokenResponse.getExpiresInSeconds().intValue() * 1000);
}
} catch (Exception e) {
log.warn("Unable to retrieve access token: {}", e.getMessage());
throw new ThingsboardException("Error while retrieving access token: " + e.getMessage(), ThingsboardErrorCode.GENERAL);
log.error("Unable to retrieve access token: {}", e.getMessage());
throw new RuntimeException("Error while retrieving access token: " + e.getMessage());
} finally {
lock.unlock();
}

View File

@ -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();
notificationExecutor.submit(() -> {
for (NotificationTarget target : targets) {
processForTarget(target, ctx);
}
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<NotificationTarget> targets, Consumer<NotificationRequestStats> callback) {
notificationExecutor.submit(() -> {
NotificationRequestId requestId = ctx.getRequest().getId();
for (NotificationTarget target : targets) {
try {
processForTarget(target, ctx);
} catch (Exception e) {
log.error("[{}] Failed to process notification request for target {}", requestId, target.getId(), e);
}
}
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) {

View File

@ -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> tbClusterService;
private final Optional<NotificationCenter> 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;
}

View File

@ -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

View File

@ -0,0 +1,95 @@
/**
* 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.service.mail;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.mockito.Mockito;
import javax.mail.MessagingException;
import javax.mail.Session;
import javax.mail.internet.MimeMessage;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.stream.Stream;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.willCallRealMethod;
import static org.mockito.BDDMockito.willReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
public class TbMailSenderTest {
private TbMailSender tbMailSender;
@BeforeEach
void setUp() {
tbMailSender = mock(TbMailSender.class);
}
@Test
public void testDoSendSendMail() {
MimeMessage mimeMsg = new MimeMessage(Session.getInstance(new Properties()));
List<MimeMessage> mimeMessages = new ArrayList<>(1);
mimeMessages.add(mimeMsg);
willCallRealMethod().given(tbMailSender).doSend(any(), any());
tbMailSender.doSend(mimeMessages.toArray(new MimeMessage[0]), null);
Mockito.verify(tbMailSender, times(1)).updateOauth2PasswordIfExpired();
Mockito.verify(tbMailSender, times(1)).doSendSuper(any(), any());
}
@Test
public void testTestConnection() throws MessagingException {
willCallRealMethod().given(tbMailSender).testConnection();
tbMailSender.testConnection();
Mockito.verify(tbMailSender, times(1)).updateOauth2PasswordIfExpired();
Mockito.verify(tbMailSender, times(1)).testConnectionSuper();
}
@ParameterizedTest
@MethodSource("provideSenderConfiguration")
public void testUpdateOauth2PasswordIfExpiredIfOauth2Enabled(boolean oauth2, long expiresIn, boolean passwordUpdateNeeded) {
willReturn(oauth2).given(tbMailSender).getOauth2Enabled();
willReturn(expiresIn).given(tbMailSender).getTokenExpires();
willCallRealMethod().given(tbMailSender).updateOauth2PasswordIfExpired();
tbMailSender.updateOauth2PasswordIfExpired();
if (passwordUpdateNeeded) {
Mockito.verify(tbMailSender, times(1)).refreshAccessToken();
Mockito.verify(tbMailSender, times(1)).setPassword(any());
} else {
Mockito.verify(tbMailSender, Mockito.never()).refreshAccessToken();
Mockito.verify(tbMailSender, Mockito.never()).setPassword(any());
}
}
private static Stream<Arguments> provideSenderConfiguration() {
return Stream.of(
Arguments.of(true, 0L, true),
Arguments.of(true, System.currentTimeMillis() + 5000, false),
Arguments.of(false, 0L, false),
Arguments.of(false, System.currentTimeMillis() + 5000, false)
);
}
}

View File

@ -265,4 +265,8 @@ public abstract class AbstractNotificationApiTest extends AbstractControllerTest
return (NotificationApiWsClient) super.getWsClient();
}
@Override
public NotificationApiWsClient getAnotherWsClient() {
return (NotificationApiWsClient) super.getAnotherWsClient();
}
}

View File

@ -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();
}

View File

@ -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 -> {

View File

@ -42,6 +42,7 @@ import org.thingsboard.server.dao.timeseries.TimeseriesService;
import org.thingsboard.server.queue.discovery.PartitionService;
import org.thingsboard.server.queue.discovery.QueueKey;
import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent;
import org.thingsboard.server.queue.usagestats.DefaultTbApiUsageReportClient;
import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService;
import java.util.Collections;
@ -86,7 +87,7 @@ public class DefaultDeviceStateServiceTest {
@Before
public void setUp() {
service = spy(new DefaultDeviceStateService(deviceService, attributesService, tsService, clusterService, partitionService, entityQueryRepository, null, null, mock(NotificationRuleProcessor.class)));
service = spy(new DefaultDeviceStateService(deviceService, attributesService, tsService, clusterService, partitionService, entityQueryRepository, null, mock(DefaultTbApiUsageReportClient.class), mock(NotificationRuleProcessor.class)));
telemetrySubscriptionService = Mockito.mock(TelemetrySubscriptionService.class);
ReflectionTestUtils.setField(service, "tsSubService", telemetrySubscriptionService);
ReflectionTestUtils.setField(service, "defaultStateCheckIntervalInSec", 60);

View File

@ -28,6 +28,10 @@
<logger name="org.thingsboard.server.transport.lwm2m.server" level="INFO"/>
<logger name="org.eclipse.californium.core" level="INFO"/>
<!-- To reduce logs -->
<logger name="org.apache.catalina.loader.WebappClassLoaderBase" level="ERROR" />
<logger name="org.thingsboard.server.service.queue.DefaultTbClusterService" level="ERROR" />
<!-- Coap client context debug for the test scope -->
<!-- <logger name="org.thingsboard.server.transport.coap.client.DefaultCoapClientContext" level="TRACE" />-->

View File

@ -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;

View File

@ -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<NotificationRequestStats> callback);
void sendGeneralWebNotification(TenantId tenantId, UsersFilter recipients, NotificationTemplate template);
void deleteNotificationRequest(TenantId tenantId, NotificationRequestId notificationRequestId);
void markNotificationAsRead(TenantId tenantId, UserId recipientId, NotificationId notificationId);

View File

@ -33,8 +33,8 @@ public class TbMathNodeConfiguration implements NodeConfiguration<TbMathNodeConf
public TbMathNodeConfiguration defaultConfiguration() {
TbMathNodeConfiguration configuration = new TbMathNodeConfiguration();
configuration.setOperation(TbRuleNodeMathFunctionType.CUSTOM);
configuration.setCustomFunction("(t - 32) / 1.8");
configuration.setArguments(List.of(new TbMathArgument("t", TbMathArgumentType.MESSAGE_BODY, "temperature")));
configuration.setCustomFunction("(x - 32) / 1.8");
configuration.setArguments(List.of(new TbMathArgument("x", TbMathArgumentType.MESSAGE_BODY, "temperature")));
configuration.setResult(new TbMathResult(TbMathArgumentType.MESSAGE_BODY, "temperatureCelsius", 2, false, false, null));
return configuration;
}