Merge branch 'develop/3.5.2' into feature/widget-bundles
This commit is contained in:
commit
7f47472092
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@ -188,11 +188,6 @@ public class ThingsboardSecurityConfiguration {
|
||||
return auth.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
protected BCryptPasswordEncoder passwordEncoder() {
|
||||
return new BCryptPasswordEncoder();
|
||||
}
|
||||
|
||||
@Autowired
|
||||
private OAuth2AuthorizationRequestResolver oAuth2AuthorizationRequestResolver;
|
||||
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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)
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -265,4 +265,8 @@ public abstract class AbstractNotificationApiTest extends AbstractControllerTest
|
||||
return (NotificationApiWsClient) super.getWsClient();
|
||||
}
|
||||
|
||||
@Override
|
||||
public NotificationApiWsClient getAnotherWsClient() {
|
||||
return (NotificationApiWsClient) super.getAnotherWsClient();
|
||||
}
|
||||
}
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
|
||||
@ -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 -> {
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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" />-->
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user