Browse Source

Merge with develop/3.5.2. Version set to 3.6.0-SNAPSHOT

pull/8688/head
Andrii Shvaika 3 years ago
parent
commit
da90c21b6c
  1. 2
      application/pom.xml
  2. 8
      application/src/main/data/upgrade/3.5.0/schema_update.sql
  3. 54
      application/src/main/data/upgrade/3.5.1/schema_update.sql
  4. 64
      application/src/main/java/org/thingsboard/server/config/RateLimitProcessingFilter.java
  5. 17
      application/src/main/java/org/thingsboard/server/controller/AuthController.java
  6. 37
      application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketHandler.java
  7. 17
      application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java
  8. 37
      application/src/main/java/org/thingsboard/server/service/apiusage/limits/LimitedApi.java
  9. 21
      application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java
  10. 3
      application/src/main/java/org/thingsboard/server/service/install/update/DefaultCacheCleanupService.java
  11. 4
      application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationCenter.java
  12. 4
      application/src/main/java/org/thingsboard/server/service/notification/rule/DefaultNotificationRuleProcessor.java
  13. 4
      application/src/main/java/org/thingsboard/server/service/resource/DefaultTbResourceService.java
  14. 52
      application/src/main/java/org/thingsboard/server/service/security/auth/mfa/DefaultTwoFactorAuthService.java
  15. 25
      application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/CookieUtils.java
  16. 7
      application/src/main/java/org/thingsboard/server/service/sync/ie/DefaultEntitiesExportImportService.java
  17. 58
      application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/DefaultExportableEntitiesService.java
  18. 11
      application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/BaseEntityExportService.java
  19. 103
      application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/NotificationRuleExportService.java
  20. 55
      application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/NotificationTargetExportService.java
  21. 42
      application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/NotificationTemplateExportService.java
  22. 150
      application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/NotificationRuleImportService.java
  23. 109
      application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/NotificationTargetImportService.java
  24. 72
      application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/NotificationTemplateImportService.java
  25. 3
      application/src/main/java/org/thingsboard/server/utils/LwM2mObjectModelUtils.java
  26. 4
      application/src/main/resources/thingsboard.yml
  27. 110
      application/src/test/java/org/thingsboard/server/service/limits/RateLimitServiceTest.java
  28. 9
      application/src/test/java/org/thingsboard/server/service/notification/NotificationRuleApiTest.java
  29. 85
      application/src/test/java/org/thingsboard/server/service/resource/sql/BaseTbResourceServiceTest.java
  30. 66
      application/src/test/java/org/thingsboard/server/service/security/auth/oauth2/HttpCookieOAuth2AuthorizationRequestRepositoryTest.java
  31. 2
      common/actor/pom.xml
  32. 2
      common/cache/pom.xml
  33. 2
      common/cluster-api/pom.xml
  34. 2
      common/coap-server/pom.xml
  35. 2
      common/dao-api/pom.xml
  36. 4
      common/dao-api/src/main/java/org/thingsboard/server/dao/entity/EntityDaoService.java
  37. 7
      common/data/pom.xml
  38. 108
      common/data/src/main/java/org/thingsboard/server/common/data/BaseDataWithAdditionalInfo.java
  39. 7
      common/data/src/main/java/org/thingsboard/server/common/data/Device.java
  40. 31
      common/data/src/main/java/org/thingsboard/server/common/data/exception/TenantProfileNotFoundException.java
  41. 6
      common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/NotificationRule.java
  42. 16
      common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/NotificationTarget.java
  43. 17
      common/data/src/main/java/org/thingsboard/server/common/data/notification/template/NotificationTemplate.java
  44. 9
      common/data/src/main/java/org/thingsboard/server/common/data/notification/template/NotificationTemplateConfig.java
  45. 8
      common/data/src/main/java/org/thingsboard/server/common/data/sync/JsonTbEntity.java
  46. 272
      common/data/src/main/java/org/thingsboard/server/common/data/util/TbDDFFileParser.java
  47. 2
      common/edge-api/pom.xml
  48. 2
      common/message/pom.xml
  49. 2
      common/pom.xml
  50. 2
      common/queue/pom.xml
  51. 2
      common/script/pom.xml
  52. 2
      common/script/remote-js-client/pom.xml
  53. 2
      common/script/script-api/pom.xml
  54. 2
      common/stats/pom.xml
  55. 2
      common/transport/coap/pom.xml
  56. 2
      common/transport/http/pom.xml
  57. 2
      common/transport/lwm2m/pom.xml
  58. 5
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportServerHelper.java
  59. 3
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mVersionedModelProvider.java
  60. 2
      common/transport/mqtt/pom.xml
  61. 2
      common/transport/pom.xml
  62. 2
      common/transport/snmp/pom.xml
  63. 2
      common/transport/transport-api/pom.xml
  64. 2
      common/util/pom.xml
  65. 2
      common/version-control/pom.xml
  66. 2
      dao/pom.xml
  67. 8
      dao/src/main/java/org/thingsboard/server/dao/DaoUtil.java
  68. 6
      dao/src/main/java/org/thingsboard/server/dao/asset/AssetProfileServiceImpl.java
  69. 6
      dao/src/main/java/org/thingsboard/server/dao/asset/BaseAssetService.java
  70. 6
      dao/src/main/java/org/thingsboard/server/dao/customer/CustomerServiceImpl.java
  71. 6
      dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardServiceImpl.java
  72. 6
      dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java
  73. 6
      dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java
  74. 7
      dao/src/main/java/org/thingsboard/server/dao/model/BaseSqlEntity.java
  75. 17
      dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractDeviceEntity.java
  76. 6
      dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationRuleEntity.java
  77. 5
      dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationTargetEntity.java
  78. 5
      dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationTemplateEntity.java
  79. 6
      dao/src/main/java/org/thingsboard/server/dao/nosql/CassandraBufferedRateReadExecutor.java
  80. 6
      dao/src/main/java/org/thingsboard/server/dao/nosql/CassandraBufferedRateWriteExecutor.java
  81. 5
      dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationRuleService.java
  82. 5
      dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationTargetService.java
  83. 5
      dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationTemplateService.java
  84. 3
      dao/src/main/java/org/thingsboard/server/dao/notification/NotificationRuleDao.java
  85. 3
      dao/src/main/java/org/thingsboard/server/dao/notification/NotificationTargetDao.java
  86. 4
      dao/src/main/java/org/thingsboard/server/dao/notification/NotificationTemplateDao.java
  87. 6
      dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java
  88. 2
      dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDao.java
  89. 42
      dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceRepository.java
  90. 4
      dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceDao.java
  91. 20
      dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationRuleDao.java
  92. 20
      dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationTargetDao.java
  93. 21
      dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationTemplateDao.java
  94. 10
      dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationRuleRepository.java
  95. 10
      dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationTargetRepository.java
  96. 10
      dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationTemplateRepository.java
  97. 44
      dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultEntityQueryRepository.java
  98. 44
      dao/src/main/java/org/thingsboard/server/dao/util/AbstractBufferedRateExecutor.java
  99. 58
      dao/src/main/java/org/thingsboard/server/dao/util/limits/DefaultRateLimitService.java
  100. 67
      dao/src/main/java/org/thingsboard/server/dao/util/limits/LimitedApi.java

2
application/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>3.5.2-SNAPSHOT</version> <version>3.6.0-SNAPSHOT</version>
<artifactId>thingsboard</artifactId> <artifactId>thingsboard</artifactId>
</parent> </parent>
<artifactId>application</artifactId> <artifactId>application</artifactId>

8
application/src/main/data/upgrade/3.5.0/schema_update.sql

@ -14,4 +14,10 @@
-- limitations under the License. -- limitations under the License.
-- --
ALTER TABLE notification_rule ADD COLUMN IF NOT EXISTS enabled BOOLEAN NOT NULL DEFAULT true; -- FIX DASHBOARD TEMPLATES AFTER ANGULAR MIGRATION TO VER.15
UPDATE dashboard SET configuration = REPLACE(configuration, 'mat-button mat-icon-button', 'mat-icon-button')
WHERE configuration like '%mat-button mat-icon-button%';
UPDATE widget_type SET descriptor = REPLACE(descriptor, 'mat-button mat-icon-button', 'mat-icon-button')
WHERE descriptor like '%mat-button mat-icon-button%';

54
application/src/main/data/upgrade/3.5.1/schema_update.sql

@ -0,0 +1,54 @@
--
-- 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.
--
ALTER TABLE notification_rule ADD COLUMN IF NOT EXISTS enabled BOOLEAN NOT NULL DEFAULT true;
-- NOTIFICATION CONFIGS VERSION CONTROL START
ALTER TABLE notification_template
ADD COLUMN IF NOT EXISTS external_id UUID;
DO
$$
BEGIN
IF NOT EXISTS(SELECT 1 FROM pg_constraint WHERE conname = 'uq_notification_template_external_id') THEN
ALTER TABLE notification_template ADD CONSTRAINT uq_notification_template_external_id UNIQUE (tenant_id, external_id);
END IF;
END;
$$;
ALTER TABLE notification_target
ADD COLUMN IF NOT EXISTS external_id UUID;
DO
$$
BEGIN
IF NOT EXISTS(SELECT 1 FROM pg_constraint WHERE conname = 'uq_notification_target_external_id') THEN
ALTER TABLE notification_target ADD CONSTRAINT uq_notification_target_external_id UNIQUE (tenant_id, external_id);
END IF;
END;
$$;
ALTER TABLE notification_rule
ADD COLUMN IF NOT EXISTS external_id UUID;
DO
$$
BEGIN
IF NOT EXISTS(SELECT 1 FROM pg_constraint WHERE conname = 'uq_notification_rule_external_id') THEN
ALTER TABLE notification_rule ADD CONSTRAINT uq_notification_rule_external_id UNIQUE (tenant_id, external_id);
END IF;
END;
$$;
-- NOTIFICATION CONFIGS VERSION CONTROL END

64
application/src/main/java/org/thingsboard/server/config/RateLimitProcessingFilter.java

@ -15,64 +15,53 @@
*/ */
package org.thingsboard.server.config; package org.thingsboard.server.config;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter; import org.springframework.web.filter.OncePerRequestFilter;
import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.exception.TenantProfileNotFoundException;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.msg.tools.TbRateLimits;
import org.thingsboard.server.common.msg.tools.TbRateLimitsException; import org.thingsboard.server.common.msg.tools.TbRateLimitsException;
import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import org.thingsboard.server.exception.ThingsboardErrorResponseHandler; import org.thingsboard.server.exception.ThingsboardErrorResponseHandler;
import org.thingsboard.server.dao.util.limits.LimitedApi;
import org.thingsboard.server.dao.util.limits.RateLimitService;
import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.model.SecurityUser;
import javax.servlet.FilterChain; import javax.servlet.FilterChain;
import javax.servlet.ServletException; import javax.servlet.ServletException;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import java.io.IOException; import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
@Slf4j @Slf4j
@Component @Component
@RequiredArgsConstructor
public class RateLimitProcessingFilter extends OncePerRequestFilter { public class RateLimitProcessingFilter extends OncePerRequestFilter {
@Autowired private final ThingsboardErrorResponseHandler errorResponseHandler;
private ThingsboardErrorResponseHandler errorResponseHandler; private final RateLimitService rateLimitService;
@Autowired
@Lazy
private TbTenantProfileCache tenantProfileCache;
private final ConcurrentMap<TenantId, TbRateLimits> perTenantLimits = new ConcurrentHashMap<>();
private final ConcurrentMap<CustomerId, TbRateLimits> perCustomerLimits = new ConcurrentHashMap<>();
@Override @Override
public void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException { public void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
SecurityUser user = getCurrentUser(); SecurityUser user = getCurrentUser();
if (user != null && !user.isSystemAdmin()) { if (user != null && !user.isSystemAdmin()) {
var profile = tenantProfileCache.get(user.getTenantId()); try {
if (profile == null) { if (!rateLimitService.checkRateLimit(LimitedApi.REST_REQUESTS, user.getTenantId())) {
rateLimitExceeded(EntityType.TENANT, response);
return;
}
} catch (TenantProfileNotFoundException e) {
log.debug("[{}] Failed to lookup tenant profile", user.getTenantId()); log.debug("[{}] Failed to lookup tenant profile", user.getTenantId());
errorResponseHandler.handle(new BadCredentialsException("Failed to lookup tenant profile"), response); errorResponseHandler.handle(new BadCredentialsException("Failed to lookup tenant profile"), response);
return; return;
} }
var profileConfiguration = profile.getDefaultProfileConfiguration();
if (!checkRateLimits(user.getTenantId(), profileConfiguration.getTenantServerRestLimitsConfiguration(), perTenantLimits, response)) {
return;
}
if (user.isCustomerUser()) { if (user.isCustomerUser()) {
if (!checkRateLimits(user.getCustomerId(), profileConfiguration.getCustomerServerRestLimitsConfiguration(), perCustomerLimits, response)) { if (!rateLimitService.checkRateLimit(LimitedApi.REST_REQUESTS, user.getTenantId(), user.getCustomerId())) {
rateLimitExceeded(EntityType.CUSTOMER, response);
return; return;
} }
} }
@ -90,23 +79,8 @@ public class RateLimitProcessingFilter extends OncePerRequestFilter {
return false; return false;
} }
private <I extends EntityId> boolean checkRateLimits(I ownerId, String rateLimitConfig, Map<I, TbRateLimits> rateLimitsMap, ServletResponse response) { private void rateLimitExceeded(EntityType type, HttpServletResponse response) {
if (StringUtils.isNotEmpty(rateLimitConfig)) { errorResponseHandler.handle(new TbRateLimitsException(type), response);
TbRateLimits rateLimits = rateLimitsMap.get(ownerId);
if (rateLimits == null || !rateLimits.getConfiguration().equals(rateLimitConfig)) {
rateLimits = new TbRateLimits(rateLimitConfig);
rateLimitsMap.put(ownerId, rateLimits);
}
if (!rateLimits.tryConsume()) {
errorResponseHandler.handle(new TbRateLimitsException(ownerId.getEntityType()), (HttpServletResponse) response);
return false;
}
} else {
rateLimitsMap.remove(ownerId);
}
return true;
} }
protected SecurityUser getCurrentUser() { protected SecurityUser getCurrentUser() {

17
application/src/main/java/org/thingsboard/server/controller/AuthController.java

@ -42,16 +42,15 @@ import org.thingsboard.server.common.data.edge.EdgeEventActionType;
import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.exception.ThingsboardException;
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.security.UserCredentials; import org.thingsboard.server.common.data.security.UserCredentials;
import org.thingsboard.server.common.data.security.event.UserCredentialsInvalidationEvent; import org.thingsboard.server.common.data.security.event.UserCredentialsInvalidationEvent;
import org.thingsboard.server.common.data.security.event.UserSessionInvalidationEvent; import org.thingsboard.server.common.data.security.event.UserSessionInvalidationEvent;
import org.thingsboard.server.common.data.security.model.JwtPair; import org.thingsboard.server.common.data.security.model.JwtPair;
import org.thingsboard.server.common.data.security.model.SecuritySettings; import org.thingsboard.server.common.data.security.model.SecuritySettings;
import org.thingsboard.server.common.data.security.model.UserPasswordPolicy; import org.thingsboard.server.common.data.security.model.UserPasswordPolicy;
import org.thingsboard.server.common.msg.tools.TbRateLimits;
import org.thingsboard.server.dao.audit.AuditLogService;
import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.dao.util.limits.LimitedApi;
import org.thingsboard.server.dao.util.limits.RateLimitService;
import org.thingsboard.server.service.security.auth.rest.RestAuthenticationDetails; import org.thingsboard.server.service.security.auth.rest.RestAuthenticationDetails;
import org.thingsboard.server.service.security.model.ActivateUserRequest; import org.thingsboard.server.service.security.model.ActivateUserRequest;
import org.thingsboard.server.service.security.model.ChangePasswordRequest; import org.thingsboard.server.service.security.model.ChangePasswordRequest;
@ -65,8 +64,6 @@ import org.thingsboard.server.service.security.system.SystemSecurityService;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import java.net.URI; import java.net.URI;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
@RestController @RestController
@TbCoreComponent @TbCoreComponent
@ -77,12 +74,11 @@ public class AuthController extends BaseController {
@Value("${server.rest.rate_limits.reset_password_per_user:5:3600}") @Value("${server.rest.rate_limits.reset_password_per_user:5:3600}")
private String defaultLimitsConfiguration; private String defaultLimitsConfiguration;
private final ConcurrentMap<UserId, TbRateLimits> resetPasswordRateLimits = new ConcurrentHashMap<>();
private final BCryptPasswordEncoder passwordEncoder; private final BCryptPasswordEncoder passwordEncoder;
private final JwtTokenFactory tokenFactory; private final JwtTokenFactory tokenFactory;
private final MailService mailService; private final MailService mailService;
private final SystemSecurityService systemSecurityService; private final SystemSecurityService systemSecurityService;
private final AuditLogService auditLogService; private final RateLimitService rateLimitService;
private final ApplicationEventPublisher eventPublisher; private final ApplicationEventPublisher eventPublisher;
@ -210,8 +206,7 @@ public class AuthController extends BaseController {
UserCredentials userCredentials = userService.findUserCredentialsByResetToken(TenantId.SYS_TENANT_ID, resetToken); UserCredentials userCredentials = userService.findUserCredentialsByResetToken(TenantId.SYS_TENANT_ID, resetToken);
if (userCredentials != null) { if (userCredentials != null) {
TbRateLimits tbRateLimits = getTbRateLimits(userCredentials.getUserId()); if (!rateLimitService.checkRateLimit(LimitedApi.PASSWORD_RESET, userCredentials.getUserId(), defaultLimitsConfiguration)) {
if (!tbRateLimits.tryConsume()) {
return ResponseEntity.status(HttpStatus.TOO_MANY_REQUESTS).build(); return ResponseEntity.status(HttpStatus.TOO_MANY_REQUESTS).build();
} }
try { try {
@ -314,8 +309,4 @@ public class AuthController extends BaseController {
eventPublisher.publishEvent(new UserSessionInvalidationEvent(user.getSessionId())); eventPublisher.publishEvent(new UserSessionInvalidationEvent(user.getSessionId()));
} }
private TbRateLimits getTbRateLimits(UserId userId) {
return resetPasswordRateLimits.computeIfAbsent(userId,
key -> new TbRateLimits(defaultLimitsConfiguration, true));
}
} }

37
application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketHandler.java

@ -28,16 +28,16 @@ import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession; import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.adapter.NativeWebSocketSession; import org.springframework.web.socket.adapter.NativeWebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler; import org.springframework.web.socket.handler.TextWebSocketHandler;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.TenantProfile;
import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
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.tenant.profile.DefaultTenantProfileConfiguration; import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
import org.thingsboard.server.common.msg.tools.TbRateLimits;
import org.thingsboard.server.config.WebSocketConfiguration; import org.thingsboard.server.config.WebSocketConfiguration;
import org.thingsboard.server.dao.tenant.TbTenantProfileCache; import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import org.thingsboard.server.dao.util.limits.LimitedApi;
import org.thingsboard.server.dao.util.limits.RateLimitService;
import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.queue.util.TbCoreComponent;
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;
@ -80,6 +80,9 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements WebSocke
@Autowired @Autowired
private TbTenantProfileCache tenantProfileCache; private TbTenantProfileCache tenantProfileCache;
@Autowired
private RateLimitService rateLimitService;
@Value("${server.ws.send_timeout:5000}") @Value("${server.ws.send_timeout:5000}")
private long sendTimeout; private long sendTimeout;
@Value("${server.ws.ping_timeout:30000}") @Value("${server.ws.ping_timeout:30000}")
@ -88,7 +91,6 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements WebSocke
private int wsMaxQueueMessagesPerSession; private int wsMaxQueueMessagesPerSession;
private final ConcurrentMap<String, WebSocketSessionRef> blacklistedSessions = new ConcurrentHashMap<>(); private final ConcurrentMap<String, WebSocketSessionRef> blacklistedSessions = new ConcurrentHashMap<>();
private final ConcurrentMap<String, TbRateLimits> perSessionUpdateLimits = new ConcurrentHashMap<>();
private final ConcurrentMap<TenantId, Set<String>> tenantSessionsMap = new ConcurrentHashMap<>(); private final ConcurrentMap<TenantId, Set<String>> tenantSessionsMap = new ConcurrentHashMap<>();
private final ConcurrentMap<CustomerId, Set<String>> customerSessionsMap = new ConcurrentHashMap<>(); private final ConcurrentMap<CustomerId, Set<String>> customerSessionsMap = new ConcurrentHashMap<>();
@ -331,24 +333,17 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements WebSocke
if (internalId != null) { if (internalId != null) {
SessionMetaData sessionMd = internalSessionMap.get(internalId); SessionMetaData sessionMd = internalSessionMap.get(internalId);
if (sessionMd != null) { if (sessionMd != null) {
var tenantProfileConfiguration = getTenantProfileConfiguration(sessionRef); TenantId tenantId = sessionRef.getSecurityCtx().getTenantId();
if (tenantProfileConfiguration != null) { if (!rateLimitService.checkRateLimit(LimitedApi.WS_UPDATES_PER_SESSION, tenantId, (Object) sessionRef.getSessionId())) {
if (StringUtils.isNotEmpty(tenantProfileConfiguration.getWsUpdatesPerSessionRateLimit())) { if (blacklistedSessions.putIfAbsent(externalId, sessionRef) == null) {
TbRateLimits rateLimits = perSessionUpdateLimits.computeIfAbsent(sessionRef.getSessionId(), sid -> new TbRateLimits(tenantProfileConfiguration.getWsUpdatesPerSessionRateLimit())); log.info("[{}][{}][{}] Failed to process session update. Max session updates limit reached"
if (!rateLimits.tryConsume()) { , tenantId, sessionRef.getSecurityCtx().getId(), externalId);
if (blacklistedSessions.putIfAbsent(externalId, sessionRef) == null) { sessionMd.sendMsg("{\"subscriptionId\":" + subscriptionId + ", \"errorCode\":" + ThingsboardErrorCode.TOO_MANY_UPDATES.getErrorCode() + ", \"errorMsg\":\"Too many updates!\"}");
log.info("[{}][{}][{}] Failed to process session update. Max session updates limit reached"
, sessionRef.getSecurityCtx().getTenantId(), sessionRef.getSecurityCtx().getId(), externalId);
sessionMd.sendMsg("{\"subscriptionId\":" + subscriptionId + ", \"errorCode\":" + ThingsboardErrorCode.TOO_MANY_UPDATES.getErrorCode() + ", \"errorMsg\":\"Too many updates!\"}");
}
return;
} else {
log.debug("[{}][{}][{}] Session is no longer blacklisted.", sessionRef.getSecurityCtx().getTenantId(), sessionRef.getSecurityCtx().getId(), externalId);
blacklistedSessions.remove(externalId);
}
} else {
perSessionUpdateLimits.remove(sessionRef.getSessionId());
} }
return;
} else {
log.debug("[{}][{}][{}] Session is no longer blacklisted.", tenantId, sessionRef.getSecurityCtx().getId(), externalId);
blacklistedSessions.remove(externalId);
} }
sessionMd.sendMsg(msg); sessionMd.sendMsg(msg);
} else { } else {
@ -464,7 +459,7 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements WebSocke
if (tenantProfileConfiguration == null) return; if (tenantProfileConfiguration == null) return;
String sessionId = session.getId(); String sessionId = session.getId();
perSessionUpdateLimits.remove(sessionRef.getSessionId()); rateLimitService.cleanUp(LimitedApi.WS_UPDATES_PER_SESSION, sessionRef.getSessionId());
blacklistedSessions.remove(sessionRef.getSessionId()); blacklistedSessions.remove(sessionRef.getSessionId());
if (tenantProfileConfiguration.getMaxWsSessionsPerTenant() > 0) { if (tenantProfileConfiguration.getMaxWsSessionsPerTenant() > 0) {
Set<String> tenantSessions = tenantSessionsMap.computeIfAbsent(sessionRef.getSecurityCtx().getTenantId(), id -> ConcurrentHashMap.newKeySet()); Set<String> tenantSessions = tenantSessionsMap.computeIfAbsent(sessionRef.getSecurityCtx().getTenantId(), id -> ConcurrentHashMap.newKeySet());

17
application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java

@ -257,18 +257,19 @@ public class ThingsboardInstallService {
case "3.5.0": case "3.5.0":
log.info("Upgrading ThingsBoard from version 3.5.0 to 3.5.1 ..."); log.info("Upgrading ThingsBoard from version 3.5.0 to 3.5.1 ...");
databaseEntitiesUpgradeService.upgradeDatabase("3.5.0"); databaseEntitiesUpgradeService.upgradeDatabase("3.5.0");
case "3.5.1":
entityDatabaseSchemaService.createOrUpdateViewsAndFunctions(); log.info("Upgrading ThingsBoard from version 3.5.1 to 3.5.2 ...");
entityDatabaseSchemaService.createOrUpdateDeviceInfoView(persistToTelemetry); databaseEntitiesUpgradeService.upgradeDatabase("3.5.1");
log.info("Updating system data..."); //TODO DON'T FORGET to update switch statement in the CacheCleanupService if you need to clear the cache
systemDataLoaderService.updateSystemWidgets();
installScripts.loadSystemLwm2mResources();
break; break;
//TODO update CacheCleanupService on the next version upgrade
default: default:
throw new RuntimeException("Unable to upgrade ThingsBoard, unsupported fromVersion: " + upgradeFromVersion); throw new RuntimeException("Unable to upgrade ThingsBoard, unsupported fromVersion: " + upgradeFromVersion);
} }
entityDatabaseSchemaService.createOrUpdateViewsAndFunctions();
entityDatabaseSchemaService.createOrUpdateDeviceInfoView(persistToTelemetry);
log.info("Updating system data...");
systemDataLoaderService.updateSystemWidgets();
installScripts.loadSystemLwm2mResources();
} }
log.info("Upgrade finished successfully!"); log.info("Upgrade finished successfully!");

37
application/src/main/java/org/thingsboard/server/service/apiusage/limits/LimitedApi.java

@ -1,37 +0,0 @@
/**
* 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.apiusage.limits;
import lombok.RequiredArgsConstructor;
import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
import java.util.function.Function;
@RequiredArgsConstructor
public enum LimitedApi {
ENTITY_EXPORT(DefaultTenantProfileConfiguration::getTenantEntityExportRateLimit),
ENTITY_IMPORT(DefaultTenantProfileConfiguration::getTenantEntityImportRateLimit),
NOTIFICATION_REQUESTS(DefaultTenantProfileConfiguration::getTenantNotificationRequestsRateLimit),
NOTIFICATION_REQUESTS_PER_RULE(DefaultTenantProfileConfiguration::getTenantNotificationRequestsPerRuleRateLimit);
private final Function<DefaultTenantProfileConfiguration, String> configExtractor;
public String getLimitConfig(DefaultTenantProfileConfiguration profileConfiguration) {
return configExtractor.apply(profileConfiguration);
}
}

21
application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java

@ -727,6 +727,27 @@ public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService
log.error("Failed updating schema!!!", e); log.error("Failed updating schema!!!", e);
} }
break; break;
case "3.5.1":
try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) {
log.info("Updating schema ...");
if (isOldSchema(conn, 3005001)) {
schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "3.5.1", SCHEMA_UPDATE_SQL);
loadSql(schemaUpdateFile, conn);
try {
String[] entityNames = new String[]{"device"};
for (String entityName : entityNames) {
conn.createStatement().execute("ALTER TABLE " + entityName + " DROP COLUMN search_text CASCADE");
}
} catch (Exception e) {}
conn.createStatement().execute("UPDATE tb_schema_settings SET schema_version = 3005002;");
}
log.info("Schema updated.");
} catch (Exception e) {
log.error("Failed updating schema!!!", e);
}
break;
default: default:
throw new RuntimeException("Unable to upgrade SQL database, unsupported fromVersion: " + fromVersion); throw new RuntimeException("Unable to upgrade SQL database, unsupported fromVersion: " + fromVersion);
} }

3
application/src/main/java/org/thingsboard/server/service/install/update/DefaultCacheCleanupService.java

@ -84,7 +84,8 @@ public class DefaultCacheCleanupService implements CacheCleanupService {
break; break;
case "3.4.4": case "3.4.4":
log.info("Clearing cache to upgrade from version 3.4.4 to 3.5.0"); log.info("Clearing cache to upgrade from version 3.4.4 to 3.5.0");
clearCacheByName("deviceProfiles"); clearAll();
break;
default: default:
//Do nothing, since cache cleanup is optional. //Do nothing, since cache cleanup is optional.
} }

4
application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationCenter.java

@ -64,8 +64,8 @@ import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.queue.common.TbProtoQueueMsg; import org.thingsboard.server.queue.common.TbProtoQueueMsg;
import org.thingsboard.server.queue.discovery.NotificationsTopicService; import org.thingsboard.server.queue.discovery.NotificationsTopicService;
import org.thingsboard.server.queue.provider.TbQueueProducerProvider; import org.thingsboard.server.queue.provider.TbQueueProducerProvider;
import org.thingsboard.server.service.apiusage.limits.LimitedApi; import org.thingsboard.server.dao.util.limits.LimitedApi;
import org.thingsboard.server.service.apiusage.limits.RateLimitService; import org.thingsboard.server.dao.util.limits.RateLimitService;
import org.thingsboard.server.service.executors.DbCallbackExecutorService; import org.thingsboard.server.service.executors.DbCallbackExecutorService;
import org.thingsboard.server.service.executors.NotificationExecutorService; import org.thingsboard.server.service.executors.NotificationExecutorService;
import org.thingsboard.server.service.notification.channels.NotificationChannel; import org.thingsboard.server.service.notification.channels.NotificationChannel;

4
application/src/main/java/org/thingsboard/server/service/notification/rule/DefaultNotificationRuleProcessor.java

@ -44,10 +44,10 @@ import org.thingsboard.server.common.msg.notification.trigger.RuleEngineMsgTrigg
import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg; import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg;
import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.dao.notification.NotificationRequestService; import org.thingsboard.server.dao.notification.NotificationRequestService;
import org.thingsboard.server.dao.util.limits.LimitedApi;
import org.thingsboard.server.dao.util.limits.RateLimitService;
import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.queue.discovery.PartitionService;
import org.thingsboard.server.queue.notification.NotificationRuleProcessor; import org.thingsboard.server.queue.notification.NotificationRuleProcessor;
import org.thingsboard.server.service.apiusage.limits.LimitedApi;
import org.thingsboard.server.service.apiusage.limits.RateLimitService;
import org.thingsboard.server.service.executors.NotificationExecutorService; import org.thingsboard.server.service.executors.NotificationExecutorService;
import org.thingsboard.server.service.notification.rule.cache.NotificationRulesCache; import org.thingsboard.server.service.notification.rule.cache.NotificationRulesCache;
import org.thingsboard.server.service.notification.rule.trigger.NotificationRuleTriggerProcessor; import org.thingsboard.server.service.notification.rule.trigger.NotificationRuleTriggerProcessor;

4
application/src/main/java/org/thingsboard/server/service/resource/DefaultTbResourceService.java

@ -16,8 +16,6 @@
package org.thingsboard.server.service.resource; package org.thingsboard.server.service.resource;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.eclipse.leshan.core.model.DDFFileParser;
import org.eclipse.leshan.core.model.DefaultDDFFileValidator;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.ResourceType; import org.thingsboard.server.common.data.ResourceType;
@ -53,11 +51,9 @@ import static org.thingsboard.server.utils.LwM2mObjectModelUtils.toLwm2mResource
public class DefaultTbResourceService extends AbstractTbEntityService implements TbResourceService { public class DefaultTbResourceService extends AbstractTbEntityService implements TbResourceService {
private final ResourceService resourceService; private final ResourceService resourceService;
private final DDFFileParser ddfFileParser;
public DefaultTbResourceService(ResourceService resourceService) { public DefaultTbResourceService(ResourceService resourceService) {
this.resourceService = resourceService; this.resourceService = resourceService;
this.ddfFileParser = new DDFFileParser(new DefaultDDFFileValidator());
} }
@Override @Override

52
application/src/main/java/org/thingsboard/server/service/security/auth/mfa/DefaultTwoFactorAuthService.java

@ -16,6 +16,7 @@
package org.thingsboard.server.service.security.auth.mfa; package org.thingsboard.server.service.security.auth.mfa;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.tuple.Pair;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.LockedException; import org.springframework.security.authentication.LockedException;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@ -29,8 +30,9 @@ import org.thingsboard.server.common.data.security.model.mfa.PlatformTwoFaSettin
import org.thingsboard.server.common.data.security.model.mfa.account.TwoFaAccountConfig; import org.thingsboard.server.common.data.security.model.mfa.account.TwoFaAccountConfig;
import org.thingsboard.server.common.data.security.model.mfa.provider.TwoFaProviderConfig; import org.thingsboard.server.common.data.security.model.mfa.provider.TwoFaProviderConfig;
import org.thingsboard.server.common.data.security.model.mfa.provider.TwoFaProviderType; import org.thingsboard.server.common.data.security.model.mfa.provider.TwoFaProviderType;
import org.thingsboard.server.common.msg.tools.TbRateLimits;
import org.thingsboard.server.dao.user.UserService; import org.thingsboard.server.dao.user.UserService;
import org.thingsboard.server.dao.util.limits.LimitedApi;
import org.thingsboard.server.dao.util.limits.RateLimitService;
import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.security.auth.mfa.config.TwoFaConfigManager; import org.thingsboard.server.service.security.auth.mfa.config.TwoFaConfigManager;
import org.thingsboard.server.service.security.auth.mfa.provider.TwoFaProvider; import org.thingsboard.server.service.security.auth.mfa.provider.TwoFaProvider;
@ -41,8 +43,6 @@ import java.util.Collection;
import java.util.EnumMap; import java.util.EnumMap;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
@Service @Service
@RequiredArgsConstructor @RequiredArgsConstructor
@ -52,14 +52,13 @@ public class DefaultTwoFactorAuthService implements TwoFactorAuthService {
private final TwoFaConfigManager configManager; private final TwoFaConfigManager configManager;
private final SystemSecurityService systemSecurityService; private final SystemSecurityService systemSecurityService;
private final UserService userService; private final UserService userService;
private final RateLimitService rateLimitService;
private final Map<TwoFaProviderType, TwoFaProvider<TwoFaProviderConfig, TwoFaAccountConfig>> providers = new EnumMap<>(TwoFaProviderType.class); private final Map<TwoFaProviderType, TwoFaProvider<TwoFaProviderConfig, TwoFaAccountConfig>> providers = new EnumMap<>(TwoFaProviderType.class);
private static final ThingsboardException ACCOUNT_NOT_CONFIGURED_ERROR = new ThingsboardException("2FA is not configured for account", ThingsboardErrorCode.BAD_REQUEST_PARAMS); private static final ThingsboardException ACCOUNT_NOT_CONFIGURED_ERROR = new ThingsboardException("2FA is not configured for account", ThingsboardErrorCode.BAD_REQUEST_PARAMS);
private static final ThingsboardException PROVIDER_NOT_CONFIGURED_ERROR = new ThingsboardException("2FA provider is not configured", ThingsboardErrorCode.BAD_REQUEST_PARAMS); private static final ThingsboardException PROVIDER_NOT_CONFIGURED_ERROR = new ThingsboardException("2FA provider is not configured", ThingsboardErrorCode.BAD_REQUEST_PARAMS);
private static final ThingsboardException PROVIDER_NOT_AVAILABLE_ERROR = new ThingsboardException("2FA provider is not available", ThingsboardErrorCode.GENERAL); private static final ThingsboardException PROVIDER_NOT_AVAILABLE_ERROR = new ThingsboardException("2FA provider is not available", ThingsboardErrorCode.GENERAL);
private static final ThingsboardException TOO_MANY_REQUESTS_ERROR = new ThingsboardException("Too many requests", ThingsboardErrorCode.TOO_MANY_REQUESTS);
private final ConcurrentMap<UserId, ConcurrentMap<TwoFaProviderType, TbRateLimits>> verificationCodeSendingRateLimits = new ConcurrentHashMap<>();
private final ConcurrentMap<UserId, ConcurrentMap<TwoFaProviderType, TbRateLimits>> verificationCodeCheckingRateLimits = new ConcurrentHashMap<>();
@Override @Override
public boolean isTwoFaEnabled(TenantId tenantId, UserId userId) { public boolean isTwoFaEnabled(TenantId tenantId, UserId userId) {
@ -91,7 +90,10 @@ public class DefaultTwoFactorAuthService implements TwoFactorAuthService {
if (minVerificationCodeSendPeriod != null && minVerificationCodeSendPeriod > 4) { if (minVerificationCodeSendPeriod != null && minVerificationCodeSendPeriod > 4) {
rateLimit = "1:" + minVerificationCodeSendPeriod; rateLimit = "1:" + minVerificationCodeSendPeriod;
} }
checkRateLimits(user.getId(), accountConfig.getProviderType(), rateLimit, verificationCodeSendingRateLimits); if (!rateLimitService.checkRateLimit(LimitedApi.TWO_FA_VERIFICATION_CODE_SEND,
Pair.of(user.getId(), accountConfig.getProviderType()), rateLimit)) {
throw TOO_MANY_REQUESTS_ERROR;
}
} }
TwoFaProviderConfig providerConfig = twoFaSettings.getProviderConfig(accountConfig.getProviderType()) TwoFaProviderConfig providerConfig = twoFaSettings.getProviderConfig(accountConfig.getProviderType())
@ -116,7 +118,10 @@ public class DefaultTwoFactorAuthService implements TwoFactorAuthService {
PlatformTwoFaSettings twoFaSettings = configManager.getPlatformTwoFaSettings(user.getTenantId(), true) PlatformTwoFaSettings twoFaSettings = configManager.getPlatformTwoFaSettings(user.getTenantId(), true)
.orElseThrow(() -> PROVIDER_NOT_CONFIGURED_ERROR); .orElseThrow(() -> PROVIDER_NOT_CONFIGURED_ERROR);
if (checkLimits) { if (checkLimits) {
checkRateLimits(user.getId(), accountConfig.getProviderType(), twoFaSettings.getVerificationCodeCheckRateLimit(), verificationCodeCheckingRateLimits); if (!rateLimitService.checkRateLimit(LimitedApi.TWO_FA_VERIFICATION_CODE_CHECK,
Pair.of(user.getId(), accountConfig.getProviderType()), twoFaSettings.getVerificationCodeCheckRateLimit())) {
throw TOO_MANY_REQUESTS_ERROR;
}
} }
TwoFaProviderConfig providerConfig = twoFaSettings.getProviderConfig(accountConfig.getProviderType()) TwoFaProviderConfig providerConfig = twoFaSettings.getProviderConfig(accountConfig.getProviderType())
.orElseThrow(() -> PROVIDER_NOT_CONFIGURED_ERROR); .orElseThrow(() -> PROVIDER_NOT_CONFIGURED_ERROR);
@ -131,43 +136,28 @@ public class DefaultTwoFactorAuthService implements TwoFactorAuthService {
try { try {
systemSecurityService.validateTwoFaVerification(user, verificationSuccess, twoFaSettings); systemSecurityService.validateTwoFaVerification(user, verificationSuccess, twoFaSettings);
} catch (LockedException e) { } catch (LockedException e) {
verificationCodeCheckingRateLimits.remove(user.getId()); cleanUpRateLimits(user.getId());
verificationCodeSendingRateLimits.remove(user.getId());
throw new ThingsboardException(e.getMessage(), ThingsboardErrorCode.AUTHENTICATION); throw new ThingsboardException(e.getMessage(), ThingsboardErrorCode.AUTHENTICATION);
} }
if (verificationSuccess) { if (verificationSuccess) {
verificationCodeCheckingRateLimits.remove(user.getId()); cleanUpRateLimits(user.getId());
verificationCodeSendingRateLimits.remove(user.getId());
} }
} }
return verificationSuccess; return verificationSuccess;
} }
private void checkRateLimits(UserId userId, TwoFaProviderType providerType, String rateLimitConfig,
ConcurrentMap<UserId, ConcurrentMap<TwoFaProviderType, TbRateLimits>> rateLimits) throws ThingsboardException {
if (StringUtils.isNotEmpty(rateLimitConfig)) {
ConcurrentMap<TwoFaProviderType, TbRateLimits> providersRateLimits = rateLimits.computeIfAbsent(userId, i -> new ConcurrentHashMap<>());
TbRateLimits rateLimit = providersRateLimits.get(providerType);
if (rateLimit == null || !rateLimit.getConfiguration().equals(rateLimitConfig)) {
rateLimit = new TbRateLimits(rateLimitConfig, true);
providersRateLimits.put(providerType, rateLimit);
}
if (!rateLimit.tryConsume()) {
throw new ThingsboardException("Too many requests", ThingsboardErrorCode.TOO_MANY_REQUESTS);
}
} else {
rateLimits.remove(userId);
}
}
@Override @Override
public TwoFaAccountConfig generateNewAccountConfig(User user, TwoFaProviderType providerType) throws ThingsboardException { public TwoFaAccountConfig generateNewAccountConfig(User user, TwoFaProviderType providerType) throws ThingsboardException {
TwoFaProviderConfig providerConfig = getTwoFaProviderConfig(user.getTenantId(), providerType); TwoFaProviderConfig providerConfig = getTwoFaProviderConfig(user.getTenantId(), providerType);
return getTwoFaProvider(providerType).generateNewAccountConfig(user, providerConfig); return getTwoFaProvider(providerType).generateNewAccountConfig(user, providerConfig);
} }
private void cleanUpRateLimits(UserId userId) {
for (TwoFaProviderType providerType : TwoFaProviderType.values()) {
rateLimitService.cleanUp(LimitedApi.TWO_FA_VERIFICATION_CODE_SEND, Pair.of(userId, providerType));
rateLimitService.cleanUp(LimitedApi.TWO_FA_VERIFICATION_CODE_CHECK, Pair.of(userId, providerType));
}
}
private TwoFaProviderConfig getTwoFaProviderConfig(TenantId tenantId, TwoFaProviderType providerType) throws ThingsboardException { private TwoFaProviderConfig getTwoFaProviderConfig(TenantId tenantId, TwoFaProviderType providerType) throws ThingsboardException {
return configManager.getPlatformTwoFaSettings(tenantId, true) return configManager.getPlatformTwoFaSettings(tenantId, true)

25
application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/CookieUtils.java

@ -15,14 +15,20 @@
*/ */
package org.thingsboard.server.service.security.auth.oauth2; package org.thingsboard.server.service.security.auth.oauth2;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.SerializationUtils; import org.springframework.util.SerializationUtils;
import javax.servlet.http.Cookie; import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectStreamClass;
import java.util.Base64; import java.util.Base64;
import java.util.Optional; import java.util.Optional;
@Slf4j
public class CookieUtils { public class CookieUtils {
public static Optional<Cookie> getCookie(HttpServletRequest request, String name) { public static Optional<Cookie> getCookie(HttpServletRequest request, String name) {
@ -67,7 +73,22 @@ public class CookieUtils {
} }
public static <T> T deserialize(Cookie cookie, Class<T> cls) { public static <T> T deserialize(Cookie cookie, Class<T> cls) {
return cls.cast(SerializationUtils.deserialize( byte[] decodedBytes = Base64.getUrlDecoder().decode(cookie.getValue());
Base64.getUrlDecoder().decode(cookie.getValue()))); try (ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(decodedBytes)) {
@Override
protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {
String name = desc.getName();
if (!cls.getName().equals(name)) {
throw new ClassNotFoundException("Class not allowed for deserialization: " + name);
}
return super.resolveClass(desc);
}
}) {
return cls.cast(ois.readObject());
} catch (Exception e) {
log.debug("Failed to deserialize class from cookie.", e.getCause());
return null;
}
} }
} }

7
application/src/main/java/org/thingsboard/server/service/sync/ie/DefaultEntitiesExportImportService.java

@ -32,8 +32,8 @@ import org.thingsboard.server.common.data.util.ThrowingRunnable;
import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.relation.RelationService; import org.thingsboard.server.dao.relation.RelationService;
import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.apiusage.limits.LimitedApi; import org.thingsboard.server.dao.util.limits.LimitedApi;
import org.thingsboard.server.service.apiusage.limits.RateLimitService; import org.thingsboard.server.dao.util.limits.RateLimitService;
import org.thingsboard.server.service.entitiy.TbNotificationEntityService; import org.thingsboard.server.service.entitiy.TbNotificationEntityService;
import org.thingsboard.server.service.sync.ie.exporting.EntityExportService; import org.thingsboard.server.service.sync.ie.exporting.EntityExportService;
import org.thingsboard.server.service.sync.ie.exporting.impl.BaseEntityExportService; import org.thingsboard.server.service.sync.ie.exporting.impl.BaseEntityExportService;
@ -67,7 +67,8 @@ public class DefaultEntitiesExportImportService implements EntitiesExportImportS
protected static final List<EntityType> SUPPORTED_ENTITY_TYPES = List.of( protected static final List<EntityType> SUPPORTED_ENTITY_TYPES = List.of(
EntityType.CUSTOMER, EntityType.ASSET_PROFILE, EntityType.ASSET, EntityType.RULE_CHAIN, EntityType.CUSTOMER, EntityType.ASSET_PROFILE, EntityType.ASSET, EntityType.RULE_CHAIN,
EntityType.DASHBOARD, EntityType.DEVICE_PROFILE, EntityType.DEVICE, EntityType.DASHBOARD, EntityType.DEVICE_PROFILE, EntityType.DEVICE,
EntityType.ENTITY_VIEW, EntityType.WIDGETS_BUNDLE EntityType.ENTITY_VIEW, EntityType.WIDGETS_BUNDLE,
EntityType.NOTIFICATION_TEMPLATE, EntityType.NOTIFICATION_TARGET, EntityType.NOTIFICATION_RULE
); );

58
application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/DefaultExportableEntitiesService.java

@ -22,36 +22,21 @@ import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.ExportableEntity; import org.thingsboard.server.common.data.ExportableEntity;
import org.thingsboard.server.common.data.HasTenantId; import org.thingsboard.server.common.data.HasTenantId;
import org.thingsboard.server.common.data.id.AssetId;
import org.thingsboard.server.common.data.id.AssetProfileId;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DashboardId;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.DeviceProfileId;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.HasId; import org.thingsboard.server.common.data.id.HasId;
import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.WidgetsBundleId;
import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.dao.Dao; import org.thingsboard.server.dao.Dao;
import org.thingsboard.server.dao.ExportableEntityDao; import org.thingsboard.server.dao.ExportableEntityDao;
import org.thingsboard.server.dao.asset.AssetProfileService; import org.thingsboard.server.dao.entity.EntityDaoService;
import org.thingsboard.server.dao.asset.AssetService; import org.thingsboard.server.dao.entity.EntityServiceRegistry;
import org.thingsboard.server.dao.customer.CustomerService;
import org.thingsboard.server.dao.dashboard.DashboardService;
import org.thingsboard.server.dao.device.DeviceProfileService;
import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.dao.rule.RuleChainService;
import org.thingsboard.server.dao.widget.WidgetsBundleService;
import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.security.permission.AccessControlService; import org.thingsboard.server.service.security.permission.AccessControlService;
import java.util.Collection; import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.function.BiConsumer;
@Service @Service
@TbCoreComponent @TbCoreComponent
@ -60,11 +45,10 @@ import java.util.function.BiConsumer;
public class DefaultExportableEntitiesService implements ExportableEntitiesService { public class DefaultExportableEntitiesService implements ExportableEntitiesService {
private final Map<EntityType, Dao<?>> daos = new HashMap<>(); private final Map<EntityType, Dao<?>> daos = new HashMap<>();
private final Map<EntityType, BiConsumer<TenantId, EntityId>> removers = new HashMap<>();
private final EntityServiceRegistry entityServiceRegistry;
private final AccessControlService accessControlService; private final AccessControlService accessControlService;
@Override @Override
public <E extends ExportableEntity<I>, I extends EntityId> E findEntityByTenantIdAndExternalId(TenantId tenantId, I externalId) { public <E extends ExportableEntity<I>, I extends EntityId> E findEntityByTenantIdAndExternalId(TenantId tenantId, I externalId) {
EntityType entityType = externalId.getEntityType(); EntityType entityType = externalId.getEntityType();
@ -152,11 +136,11 @@ public class DefaultExportableEntitiesService implements ExportableEntitiesServi
@Override @Override
public <I extends EntityId> void removeById(TenantId tenantId, I id) { public <I extends EntityId> void removeById(TenantId tenantId, I id) {
EntityType entityType = id.getEntityType(); EntityType entityType = id.getEntityType();
BiConsumer<TenantId, EntityId> entityRemover = removers.get(entityType); EntityDaoService entityService = entityServiceRegistry.getServiceByEntityType(entityType);
if (entityRemover == null) { if (entityService == null) {
throw new IllegalArgumentException("Unsupported entity type " + entityType); throw new IllegalArgumentException("Unsupported entity type " + entityType);
} }
entityRemover.accept(tenantId, id); entityService.deleteEntity(tenantId, id);
} }
private <I extends EntityId, E extends ExportableEntity<I>> ExportableEntityDao<I, E> getExportableEntityDao(EntityType entityType) { private <I extends EntityId, E extends ExportableEntity<I>> ExportableEntityDao<I, E> getExportableEntityDao(EntityType entityType) {
@ -182,34 +166,4 @@ public class DefaultExportableEntitiesService implements ExportableEntitiesServi
}); });
} }
@Autowired
private void setRemovers(CustomerService customerService, AssetService assetService, RuleChainService ruleChainService,
DashboardService dashboardService, DeviceProfileService deviceProfileService,
AssetProfileService assetProfileService, DeviceService deviceService, WidgetsBundleService widgetsBundleService) {
removers.put(EntityType.CUSTOMER, (tenantId, entityId) -> {
customerService.deleteCustomer(tenantId, (CustomerId) entityId);
});
removers.put(EntityType.ASSET, (tenantId, entityId) -> {
assetService.deleteAsset(tenantId, (AssetId) entityId);
});
removers.put(EntityType.RULE_CHAIN, (tenantId, entityId) -> {
ruleChainService.deleteRuleChainById(tenantId, (RuleChainId) entityId);
});
removers.put(EntityType.DASHBOARD, (tenantId, entityId) -> {
dashboardService.deleteDashboard(tenantId, (DashboardId) entityId);
});
removers.put(EntityType.DEVICE_PROFILE, (tenantId, entityId) -> {
deviceProfileService.deleteDeviceProfile(tenantId, (DeviceProfileId) entityId);
});
removers.put(EntityType.ASSET_PROFILE, (tenantId, entityId) -> {
assetProfileService.deleteAssetProfile(tenantId, (AssetProfileId) entityId);
});
removers.put(EntityType.DEVICE, (tenantId, entityId) -> {
deviceService.deleteDevice(tenantId, (DeviceId) entityId);
});
removers.put(EntityType.WIDGETS_BUNDLE, (tenantId, entityId) -> {
widgetsBundleService.deleteWidgetsBundle(tenantId, (WidgetsBundleId) entityId);
});
}
} }

11
application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/BaseEntityExportService.java

@ -24,7 +24,11 @@ import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.sync.ie.EntityExportData; import org.thingsboard.server.common.data.sync.ie.EntityExportData;
import org.thingsboard.server.service.sync.vc.data.EntitiesExportCtx; import org.thingsboard.server.service.sync.vc.data.EntitiesExportCtx;
import java.util.Collection;
import java.util.Set; import java.util.Set;
import java.util.UUID;
import java.util.function.Function;
import java.util.stream.Stream;
public abstract class BaseEntityExportService<I extends EntityId, E extends ExportableEntity<I>, D extends EntityExportData<E>> extends DefaultEntityExportService<I, E, D> { public abstract class BaseEntityExportService<I extends EntityId, E extends ExportableEntity<I>, D extends EntityExportData<E>> extends DefaultEntityExportService<I, E, D> {
@ -47,4 +51,11 @@ public abstract class BaseEntityExportService<I extends EntityId, E extends Expo
JacksonUtil.replaceUuidsRecursively(node, skipFieldsSet, uuid -> getExternalIdOrElseInternalByUuid(ctx, uuid)); JacksonUtil.replaceUuidsRecursively(node, skipFieldsSet, uuid -> getExternalIdOrElseInternalByUuid(ctx, uuid));
} }
protected Stream<UUID> toExternalIds(Collection<UUID> internalIds, Function<UUID, EntityId> entityIdCreator,
EntitiesExportCtx<?> ctx) {
return internalIds.stream().map(entityIdCreator)
.map(entityId -> getExternalIdOrElseInternal(ctx, entityId))
.map(EntityId::getId);
}
} }

103
application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/NotificationRuleExportService.java

@ -0,0 +1,103 @@
/**
* 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.sync.ie.exporting.impl;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.ExportableEntity;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.DeviceProfileId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.NotificationRuleId;
import org.thingsboard.server.common.data.id.NotificationTargetId;
import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.notification.rule.DefaultNotificationRuleRecipientsConfig;
import org.thingsboard.server.common.data.notification.rule.EscalatedNotificationRuleRecipientsConfig;
import org.thingsboard.server.common.data.notification.rule.NotificationRule;
import org.thingsboard.server.common.data.notification.rule.NotificationRuleRecipientsConfig;
import org.thingsboard.server.common.data.notification.rule.trigger.DeviceActivityNotificationRuleTriggerConfig;
import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerConfig;
import org.thingsboard.server.common.data.notification.rule.trigger.RuleEngineComponentLifecycleEventNotificationRuleTriggerConfig;
import org.thingsboard.server.common.data.sync.ie.EntityExportData;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.sync.vc.data.EntitiesExportCtx;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
@Service
@TbCoreComponent
public class NotificationRuleExportService<I extends EntityId, E extends ExportableEntity<I>, D extends EntityExportData<E>> extends BaseEntityExportService<NotificationRuleId, NotificationRule, EntityExportData<NotificationRule>> {
@Override
protected void setRelatedEntities(EntitiesExportCtx<?> ctx, NotificationRule notificationRule, EntityExportData<NotificationRule> exportData) {
notificationRule.setTemplateId(getExternalIdOrElseInternal(ctx, notificationRule.getTemplateId()));
NotificationRuleTriggerConfig ruleTriggerConfig = notificationRule.getTriggerConfig();
switch (ruleTriggerConfig.getTriggerType()) {
case DEVICE_ACTIVITY: {
DeviceActivityNotificationRuleTriggerConfig triggerConfig = (DeviceActivityNotificationRuleTriggerConfig) ruleTriggerConfig;
Set<UUID> devices = triggerConfig.getDevices();
if (devices != null) {
triggerConfig.setDevices(toExternalIds(devices, DeviceId::new, ctx).collect(Collectors.toSet()));
}
Set<UUID> deviceProfiles = triggerConfig.getDeviceProfiles();
if (deviceProfiles != null) {
triggerConfig.setDeviceProfiles(toExternalIds(deviceProfiles, DeviceProfileId::new, ctx).collect(Collectors.toSet()));
}
break;
}
case RULE_ENGINE_COMPONENT_LIFECYCLE_EVENT:
RuleEngineComponentLifecycleEventNotificationRuleTriggerConfig triggerConfig = (RuleEngineComponentLifecycleEventNotificationRuleTriggerConfig) ruleTriggerConfig;
Set<UUID> ruleChains = triggerConfig.getRuleChains();
if (ruleChains != null) {
triggerConfig.setRuleChains(toExternalIds(ruleChains, RuleChainId::new, ctx).collect(Collectors.toSet()));
}
break;
}
NotificationRuleRecipientsConfig ruleRecipientsConfig = notificationRule.getRecipientsConfig();
switch (ruleTriggerConfig.getTriggerType()) {
case ALARM: {
EscalatedNotificationRuleRecipientsConfig recipientsConfig = (EscalatedNotificationRuleRecipientsConfig) ruleRecipientsConfig;
Map<Integer, List<UUID>> escalationTable = new LinkedHashMap<>(recipientsConfig.getEscalationTable());
escalationTable.replaceAll((delay, targets) -> {
return toExternalIds(targets, NotificationTargetId::new, ctx).collect(Collectors.toList());
});
recipientsConfig.setEscalationTable(escalationTable);
break;
}
default: {
DefaultNotificationRuleRecipientsConfig recipientsConfig = (DefaultNotificationRuleRecipientsConfig) ruleRecipientsConfig;
List<UUID> targets = recipientsConfig.getTargets();
targets = toExternalIds(targets, NotificationTargetId::new, ctx).collect(Collectors.toList());
recipientsConfig.setTargets(targets);
break;
}
}
}
@Override
public Set<EntityType> getSupportedEntityTypes() {
return Set.of(EntityType.NOTIFICATION_RULE);
}
}

55
application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/NotificationTargetExportService.java

@ -0,0 +1,55 @@
/**
* 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.sync.ie.exporting.impl;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.NotificationTargetId;
import org.thingsboard.server.common.data.notification.targets.NotificationTarget;
import org.thingsboard.server.common.data.notification.targets.NotificationTargetType;
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.UsersFilter;
import org.thingsboard.server.common.data.sync.ie.EntityExportData;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.sync.vc.data.EntitiesExportCtx;
import java.util.Set;
@Service
@TbCoreComponent
public class NotificationTargetExportService extends BaseEntityExportService<NotificationTargetId, NotificationTarget, EntityExportData<NotificationTarget>> {
@Override
protected void setRelatedEntities(EntitiesExportCtx<?> ctx, NotificationTarget notificationTarget, EntityExportData<NotificationTarget> exportData) {
if (notificationTarget.getConfiguration().getType() == NotificationTargetType.PLATFORM_USERS) {
UsersFilter usersFilter = ((PlatformUsersNotificationTargetConfig) notificationTarget.getConfiguration()).getUsersFilter();
switch (usersFilter.getType()) {
case CUSTOMER_USERS:
CustomerUsersFilter customerUsersFilter = (CustomerUsersFilter) usersFilter;
customerUsersFilter.setCustomerId(getExternalIdOrElseInternal(ctx, new CustomerId(customerUsersFilter.getCustomerId())).getId());
break;
}
}
}
@Override
public Set<EntityType> getSupportedEntityTypes() {
return Set.of(EntityType.NOTIFICATION_TARGET);
}
}

42
application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/NotificationTemplateExportService.java

@ -0,0 +1,42 @@
/**
* 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.sync.ie.exporting.impl;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.id.NotificationTemplateId;
import org.thingsboard.server.common.data.notification.template.NotificationTemplate;
import org.thingsboard.server.common.data.sync.ie.EntityExportData;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.sync.vc.data.EntitiesExportCtx;
import java.util.Set;
@Service
@TbCoreComponent
public class NotificationTemplateExportService extends BaseEntityExportService<NotificationTemplateId, NotificationTemplate, EntityExportData<NotificationTemplate>> {
@Override
protected void setRelatedEntities(EntitiesExportCtx<?> ctx, NotificationTemplate notificationTemplate, EntityExportData<NotificationTemplate> exportData) {
}
@Override
public Set<EntityType> getSupportedEntityTypes() {
return Set.of(EntityType.NOTIFICATION_TEMPLATE);
}
}

150
application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/NotificationRuleImportService.java

@ -0,0 +1,150 @@
/**
* 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.sync.ie.importing.impl;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.DeviceProfileId;
import org.thingsboard.server.common.data.id.NotificationRuleId;
import org.thingsboard.server.common.data.id.NotificationTargetId;
import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UUIDBased;
import org.thingsboard.server.common.data.notification.rule.DefaultNotificationRuleRecipientsConfig;
import org.thingsboard.server.common.data.notification.rule.EscalatedNotificationRuleRecipientsConfig;
import org.thingsboard.server.common.data.notification.rule.NotificationRule;
import org.thingsboard.server.common.data.notification.rule.NotificationRuleRecipientsConfig;
import org.thingsboard.server.common.data.notification.rule.trigger.DeviceActivityNotificationRuleTriggerConfig;
import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerConfig;
import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType;
import org.thingsboard.server.common.data.notification.rule.trigger.RuleEngineComponentLifecycleEventNotificationRuleTriggerConfig;
import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
import org.thingsboard.server.common.data.sync.ie.EntityExportData;
import org.thingsboard.server.dao.notification.NotificationRuleService;
import org.thingsboard.server.dao.service.ConstraintValidator;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.sync.vc.data.EntitiesImportCtx;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
@Service
@TbCoreComponent
@RequiredArgsConstructor
public class NotificationRuleImportService extends BaseEntityImportService<NotificationRuleId, NotificationRule, EntityExportData<NotificationRule>> {
private final NotificationRuleService notificationRuleService;
@Override
protected void setOwner(TenantId tenantId, NotificationRule notificationRule, IdProvider idProvider) {
notificationRule.setTenantId(tenantId);
}
@Override
protected NotificationRule prepare(EntitiesImportCtx ctx, NotificationRule notificationRule, NotificationRule oldNotificationRule, EntityExportData<NotificationRule> exportData, IdProvider idProvider) {
notificationRule.setTemplateId(idProvider.getInternalId(notificationRule.getTemplateId()));
NotificationRuleTriggerConfig ruleTriggerConfig = notificationRule.getTriggerConfig();
NotificationRuleTriggerType triggerType = ruleTriggerConfig.getTriggerType();
switch (triggerType) {
case DEVICE_ACTIVITY: {
DeviceActivityNotificationRuleTriggerConfig triggerConfig = (DeviceActivityNotificationRuleTriggerConfig) ruleTriggerConfig;
Set<UUID> devices = triggerConfig.getDevices();
if (devices != null) {
triggerConfig.setDevices(devices.stream().map(DeviceId::new)
.map(idProvider::getInternalId).map(UUIDBased::getId)
.collect(Collectors.toSet()));
}
Set<UUID> deviceProfiles = triggerConfig.getDeviceProfiles();
if (deviceProfiles != null) {
triggerConfig.setDeviceProfiles(deviceProfiles.stream().map(DeviceProfileId::new)
.map(idProvider::getInternalId).map(UUIDBased::getId)
.collect(Collectors.toSet()));
}
break;
}
case RULE_ENGINE_COMPONENT_LIFECYCLE_EVENT:
RuleEngineComponentLifecycleEventNotificationRuleTriggerConfig triggerConfig = (RuleEngineComponentLifecycleEventNotificationRuleTriggerConfig) ruleTriggerConfig;
Set<UUID> ruleChains = triggerConfig.getRuleChains();
if (ruleChains != null) {
triggerConfig.setRuleChains(ruleChains.stream().map(RuleChainId::new)
.map(idProvider::getInternalId).map(UUIDBased::getId)
.collect(Collectors.toSet()));
}
break;
}
if (!triggerType.isTenantLevel()) {
throw new IllegalArgumentException("Trigger type " + triggerType + " is not available for tenants");
}
NotificationRuleRecipientsConfig ruleRecipientsConfig = notificationRule.getRecipientsConfig();
switch (triggerType) {
case ALARM: {
EscalatedNotificationRuleRecipientsConfig recipientsConfig = (EscalatedNotificationRuleRecipientsConfig) ruleRecipientsConfig;
Map<Integer, List<UUID>> escalationTable = new LinkedHashMap<>(recipientsConfig.getEscalationTable());
escalationTable.replaceAll((delay, targets) -> targets.stream()
.map(NotificationTargetId::new).map(idProvider::getInternalId)
.map(UUIDBased::getId).collect(Collectors.toList()));
recipientsConfig.setEscalationTable(escalationTable);
break;
}
default: {
DefaultNotificationRuleRecipientsConfig recipientsConfig = (DefaultNotificationRuleRecipientsConfig) ruleRecipientsConfig;
List<UUID> targets = recipientsConfig.getTargets().stream()
.map(NotificationTargetId::new).map(idProvider::getInternalId)
.map(UUIDBased::getId).collect(Collectors.toList());
recipientsConfig.setTargets(targets);
break;
}
}
return notificationRule;
}
@Override
protected NotificationRule saveOrUpdate(EntitiesImportCtx ctx, NotificationRule notificationRule, EntityExportData<NotificationRule> exportData, IdProvider idProvider) {
ConstraintValidator.validateFields(notificationRule);
return notificationRuleService.saveNotificationRule(ctx.getTenantId(), notificationRule);
}
@Override
protected void onEntitySaved(User user, NotificationRule savedEntity, NotificationRule oldEntity) throws ThingsboardException {
entityActionService.logEntityAction(user, savedEntity.getId(), savedEntity, null,
oldEntity == null ? ActionType.ADDED : ActionType.UPDATED, null);
clusterService.broadcastEntityStateChangeEvent(user.getTenantId(), savedEntity.getId(),
oldEntity == null ? ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED);
}
@Override
protected NotificationRule deepCopy(NotificationRule notificationRule) {
return new NotificationRule(notificationRule);
}
@Override
public EntityType getEntityType() {
return EntityType.NOTIFICATION_RULE;
}
}

109
application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/NotificationTargetImportService.java

@ -0,0 +1,109 @@
/**
* 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.sync.ie.importing.impl;
import lombok.RequiredArgsConstructor;
import org.apache.commons.collections.CollectionUtils;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.NotificationTargetId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UUIDBased;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.notification.targets.NotificationTarget;
import org.thingsboard.server.common.data.notification.targets.NotificationTargetType;
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.TenantAdministratorsFilter;
import org.thingsboard.server.common.data.notification.targets.platform.UserListFilter;
import org.thingsboard.server.common.data.notification.targets.platform.UsersFilter;
import org.thingsboard.server.common.data.sync.ie.EntityExportData;
import org.thingsboard.server.dao.notification.NotificationTargetService;
import org.thingsboard.server.dao.service.ConstraintValidator;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.sync.vc.data.EntitiesImportCtx;
import java.util.stream.Collectors;
@Service
@TbCoreComponent
@RequiredArgsConstructor
public class NotificationTargetImportService extends BaseEntityImportService<NotificationTargetId, NotificationTarget, EntityExportData<NotificationTarget>> {
private final NotificationTargetService notificationTargetService;
@Override
protected void setOwner(TenantId tenantId, NotificationTarget notificationTarget, IdProvider idProvider) {
notificationTarget.setTenantId(tenantId);
}
@Override
protected NotificationTarget prepare(EntitiesImportCtx ctx, NotificationTarget notificationTarget, NotificationTarget oldNotificationTarget, EntityExportData<NotificationTarget> exportData, IdProvider idProvider) {
if (notificationTarget.getConfiguration().getType() == NotificationTargetType.PLATFORM_USERS) {
UsersFilter usersFilter = ((PlatformUsersNotificationTargetConfig) notificationTarget.getConfiguration()).getUsersFilter();
switch (usersFilter.getType()) {
case CUSTOMER_USERS:
CustomerUsersFilter customerUsersFilter = (CustomerUsersFilter) usersFilter;
customerUsersFilter.setCustomerId(idProvider.getInternalId(new CustomerId(customerUsersFilter.getCustomerId())).getId());
break;
case USER_LIST:
UserListFilter userListFilter = (UserListFilter) usersFilter;
userListFilter.setUsersIds(userListFilter.getUsersIds().stream()
.map(UserId::new).map(idProvider::getInternalId)
.map(UUIDBased::getId).collect(Collectors.toList())
);
break;
case TENANT_ADMINISTRATORS:
if (CollectionUtils.isNotEmpty(((TenantAdministratorsFilter) usersFilter).getTenantsIds()) ||
CollectionUtils.isNotEmpty(((TenantAdministratorsFilter) usersFilter).getTenantProfilesIds())) {
throw new IllegalArgumentException("Permission denied");
}
break;
case SYSTEM_ADMINISTRATORS:
throw new AccessDeniedException("Permission denied");
}
}
return notificationTarget;
}
@Override
protected NotificationTarget saveOrUpdate(EntitiesImportCtx ctx, NotificationTarget notificationTarget, EntityExportData<NotificationTarget> exportData, IdProvider idProvider) {
ConstraintValidator.validateFields(notificationTarget);
return notificationTargetService.saveNotificationTarget(ctx.getTenantId(), notificationTarget);
}
@Override
protected void onEntitySaved(User user, NotificationTarget savedEntity, NotificationTarget oldEntity) throws ThingsboardException {
entityActionService.logEntityAction(user, savedEntity.getId(), savedEntity, null,
oldEntity == null ? ActionType.ADDED : ActionType.UPDATED, null);
}
@Override
protected NotificationTarget deepCopy(NotificationTarget notificationTarget) {
return new NotificationTarget(notificationTarget);
}
@Override
public EntityType getEntityType() {
return EntityType.NOTIFICATION_TARGET;
}
}

72
application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/NotificationTemplateImportService.java

@ -0,0 +1,72 @@
/**
* 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.sync.ie.importing.impl;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.NotificationTemplateId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.notification.template.NotificationTemplate;
import org.thingsboard.server.common.data.sync.ie.EntityExportData;
import org.thingsboard.server.dao.notification.NotificationTemplateService;
import org.thingsboard.server.dao.service.ConstraintValidator;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.sync.vc.data.EntitiesImportCtx;
@Service
@TbCoreComponent
@RequiredArgsConstructor
public class NotificationTemplateImportService extends BaseEntityImportService<NotificationTemplateId, NotificationTemplate, EntityExportData<NotificationTemplate>> {
private final NotificationTemplateService notificationTemplateService;
@Override
protected void setOwner(TenantId tenantId, NotificationTemplate notificationTemplate, IdProvider idProvider) {
notificationTemplate.setTenantId(tenantId);
}
@Override
protected NotificationTemplate prepare(EntitiesImportCtx ctx, NotificationTemplate notificationTemplate, NotificationTemplate oldEntity, EntityExportData<NotificationTemplate> exportData, IdProvider idProvider) {
return notificationTemplate;
}
@Override
protected NotificationTemplate saveOrUpdate(EntitiesImportCtx ctx, NotificationTemplate notificationTemplate, EntityExportData<NotificationTemplate> exportData, IdProvider idProvider) {
ConstraintValidator.validateFields(notificationTemplate);
return notificationTemplateService.saveNotificationTemplate(ctx.getTenantId(), notificationTemplate);
}
@Override
protected void onEntitySaved(User user, NotificationTemplate savedEntity, NotificationTemplate oldEntity) throws ThingsboardException {
entityActionService.logEntityAction(user, savedEntity.getId(), savedEntity, null,
oldEntity == null ? ActionType.ADDED : ActionType.UPDATED, null);
}
@Override
protected NotificationTemplate deepCopy(NotificationTemplate notificationTemplate) {
return new NotificationTemplate(notificationTemplate);
}
@Override
public EntityType getEntityType() {
return EntityType.NOTIFICATION_TEMPLATE;
}
}

3
application/src/main/java/org/thingsboard/server/utils/LwM2mObjectModelUtils.java

@ -27,6 +27,7 @@ import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.lwm2m.LwM2mInstance; import org.thingsboard.server.common.data.lwm2m.LwM2mInstance;
import org.thingsboard.server.common.data.lwm2m.LwM2mObject; import org.thingsboard.server.common.data.lwm2m.LwM2mObject;
import org.thingsboard.server.common.data.lwm2m.LwM2mResourceObserve; import org.thingsboard.server.common.data.lwm2m.LwM2mResourceObserve;
import org.thingsboard.server.common.data.util.TbDDFFileParser;
import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.exception.DataValidationException;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
@ -41,7 +42,7 @@ import static org.thingsboard.server.common.data.lwm2m.LwM2mConstants.LWM2M_SEPA
@Slf4j @Slf4j
public class LwM2mObjectModelUtils { public class LwM2mObjectModelUtils {
private static final DDFFileParser ddfFileParser = new DDFFileParser(new DefaultDDFFileValidator()); private static final TbDDFFileParser ddfFileParser = new TbDDFFileParser();
public static void toLwm2mResource (TbResource resource) throws ThingsboardException { public static void toLwm2mResource (TbResource resource) throws ThingsboardException {
try { try {

4
application/src/main/resources/thingsboard.yml

@ -495,8 +495,8 @@ cache:
timeToLiveInMinutes: "${CACHE_SPECS_NOTIFICATION_RULES_TTL:30}" timeToLiveInMinutes: "${CACHE_SPECS_NOTIFICATION_RULES_TTL:30}"
maxSize: "${CACHE_SPECS_NOTIFICATION_RULES_MAX_SIZE:1000}" maxSize: "${CACHE_SPECS_NOTIFICATION_RULES_MAX_SIZE:1000}"
rateLimits: rateLimits:
timeToLiveInMinutes: "${CACHE_SPECS_RATE_LIMITS_TTL:60}" timeToLiveInMinutes: "${CACHE_SPECS_RATE_LIMITS_TTL:120}"
maxSize: "${CACHE_SPECS_RATE_LIMITS_MAX_SIZE:100000}" maxSize: "${CACHE_SPECS_RATE_LIMITS_MAX_SIZE:200000}"
#Disable this because it is not required. #Disable this because it is not required.
spring.data.redis.repositories.enabled: false spring.data.redis.repositories.enabled: false

110
application/src/test/java/org/thingsboard/server/service/limits/RateLimitServiceTest.java

@ -0,0 +1,110 @@
/**
* 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.limits;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;
import org.thingsboard.server.common.data.TenantProfile;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.NotificationRuleId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
import org.thingsboard.server.common.data.tenant.profile.TenantProfileData;
import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import org.thingsboard.server.dao.util.limits.DefaultRateLimitService;
import org.thingsboard.server.dao.util.limits.LimitedApi;
import org.thingsboard.server.dao.util.limits.RateLimitService;
import java.util.List;
import java.util.UUID;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.when;
@RunWith(MockitoJUnitRunner.class)
public class RateLimitServiceTest {
private RateLimitService rateLimitService;
private TbTenantProfileCache tenantProfileCache;
private TenantId tenantId;
@Before
public void beforeEach() {
tenantProfileCache = Mockito.mock(TbTenantProfileCache.class);
rateLimitService = new DefaultRateLimitService(tenantProfileCache, 60, 100);
tenantId = new TenantId(UUID.randomUUID());
}
@Test
public void testRateLimits() {
int max = 2;
String rateLimit = max + ":600";
DefaultTenantProfileConfiguration profileConfiguration = new DefaultTenantProfileConfiguration();
profileConfiguration.setTenantEntityExportRateLimit(rateLimit);
profileConfiguration.setTenantEntityImportRateLimit(rateLimit);
profileConfiguration.setTenantNotificationRequestsRateLimit(rateLimit);
profileConfiguration.setTenantNotificationRequestsPerRuleRateLimit(rateLimit);
profileConfiguration.setTenantServerRestLimitsConfiguration(rateLimit);
profileConfiguration.setCustomerServerRestLimitsConfiguration(rateLimit);
profileConfiguration.setWsUpdatesPerSessionRateLimit(rateLimit);
profileConfiguration.setCassandraQueryTenantRateLimitsConfiguration(rateLimit);
updateTenantProfileConfiguration(profileConfiguration);
for (LimitedApi limitedApi : List.of(
LimitedApi.ENTITY_EXPORT,
LimitedApi.ENTITY_IMPORT,
LimitedApi.NOTIFICATION_REQUESTS,
LimitedApi.REST_REQUESTS,
LimitedApi.CASSANDRA_QUERIES
)) {
testRateLimits(limitedApi, max, tenantId);
}
CustomerId customerId = new CustomerId(UUID.randomUUID());
testRateLimits(LimitedApi.REST_REQUESTS, max, customerId);
NotificationRuleId notificationRuleId = new NotificationRuleId(UUID.randomUUID());
testRateLimits(LimitedApi.NOTIFICATION_REQUESTS_PER_RULE, max, notificationRuleId);
String wsSessionId = UUID.randomUUID().toString();
testRateLimits(LimitedApi.WS_UPDATES_PER_SESSION, max, wsSessionId);
}
private void testRateLimits(LimitedApi limitedApi, int max, Object level) {
for (int i = 1; i <= max; i++) {
boolean success = rateLimitService.checkRateLimit(limitedApi, tenantId, level);
assertTrue(success);
}
boolean success = rateLimitService.checkRateLimit(limitedApi, tenantId, level);
assertFalse(success);
}
private void updateTenantProfileConfiguration(DefaultTenantProfileConfiguration profileConfiguration) {
reset(tenantProfileCache);
TenantProfile tenantProfile = new TenantProfile();
TenantProfileData profileData = new TenantProfileData();
profileData.setConfiguration(profileConfiguration);
tenantProfile.setProfileData(profileData);
when(tenantProfileCache.get(eq(tenantId))).thenReturn(tenantProfile);
}
}

9
application/src/test/java/org/thingsboard/server/service/notification/NotificationRuleApiTest.java

@ -72,9 +72,9 @@ import org.thingsboard.server.common.msg.notification.trigger.NewPlatformVersion
import org.thingsboard.server.dao.notification.NotificationRequestService; import org.thingsboard.server.dao.notification.NotificationRequestService;
import org.thingsboard.server.dao.rule.RuleChainService; import org.thingsboard.server.dao.rule.RuleChainService;
import org.thingsboard.server.dao.service.DaoSqlTest; import org.thingsboard.server.dao.service.DaoSqlTest;
import org.thingsboard.server.dao.util.limits.LimitedApi;
import org.thingsboard.server.dao.util.limits.RateLimitService;
import org.thingsboard.server.queue.notification.NotificationRuleProcessor; import org.thingsboard.server.queue.notification.NotificationRuleProcessor;
import org.thingsboard.server.service.apiusage.limits.LimitedApi;
import org.thingsboard.server.service.apiusage.limits.RateLimitService;
import org.thingsboard.server.service.telemetry.AlarmSubscriptionService; import org.thingsboard.server.service.telemetry.AlarmSubscriptionService;
import java.util.ArrayList; import java.util.ArrayList;
@ -480,7 +480,7 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest {
assertThat(getMyNotifications(false, 100)).size().isZero(); assertThat(getMyNotifications(false, 100)).size().isZero();
createDevice("Device 1", "default", "111"); createDevice("Device 1", "default", "111");
await().atMost(15, TimeUnit.SECONDS) await().atMost(30, TimeUnit.SECONDS)
.untilAsserted(() -> { .untilAsserted(() -> {
assertThat(getMyNotifications(false, 100)).size().isEqualTo(1); assertThat(getMyNotifications(false, 100)).size().isEqualTo(1);
}); });
@ -494,9 +494,10 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest {
rule.setEnabled(true); rule.setEnabled(true);
saveNotificationRule(rule); saveNotificationRule(rule);
TimeUnit.SECONDS.sleep(2); // for rule update event to reach rules cache
createDevice("Device 3", "default", "333"); createDevice("Device 3", "default", "333");
await().atMost(15, TimeUnit.SECONDS) await().atMost(30, TimeUnit.SECONDS)
.untilAsserted(() -> { .untilAsserted(() -> {
assertThat(getMyNotifications(false, 100)).size().isEqualTo(2); assertThat(getMyNotifications(false, 100)).size().isEqualTo(2);
}); });

85
application/src/test/java/org/thingsboard/server/service/resource/sql/BaseTbResourceServiceTest.java

@ -45,6 +45,8 @@ import java.util.Collections;
import java.util.List; import java.util.List;
import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThrows;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@DaoSqlTest @DaoSqlTest
@ -75,6 +77,32 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest {
"</Object>\n" + "</Object>\n" +
"</LWM2M>"; "</LWM2M>";
private static final String LWM2M_TEST_MODEL_WITH_XXE = "<!DOCTYPE replace [<!ENTITY ObjectVersion SYSTEM \"file:///etc/hostname\"> ]>" +
"<LWM2M xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:noNamespaceSchemaLocation=\"http://www.openmobilealliance.org/tech/profiles/LWM2M-v1_1.xsd\">\n" +
"<Object ObjectType=\"MODefinition\">\n" +
"<Name>My first resource</Name>\n" +
"<Description1></Description1>\n" +
"<ObjectID>0</ObjectID>\n" +
"<ObjectURN></ObjectURN>\n" +
"<ObjectVersion>&ObjectVersion;</ObjectVersion>\n" +
"<MultipleInstances>Multiple</MultipleInstances>\n" +
"<Mandatory>Mandatory</Mandatory>\n" +
"<Resources>\n" +
"<Item ID=\"0\">\n" +
"<Name>LWM2M</Name>\n" +
"<Operations>RW</Operations>\n" +
"<MultipleInstances>Single</MultipleInstances>\n" +
"<Mandatory>Mandatory</Mandatory>\n" +
"<Type>String</Type>\n" +
"<RangeEnumeration>0..255</RangeEnumeration>\n" +
"<Units></Units>\n" +
"<Description></Description>\n" +
"</Item>\n" +
"</Resources>\n" +
"<Description2></Description2>\n" +
"</Object>\n" +
"</LWM2M>";
private static final String DEFAULT_FILE_NAME = "test.jks"; private static final String DEFAULT_FILE_NAME = "test.jks";
private IdComparator<TbResourceInfo> idComparator = new IdComparator<>(); private IdComparator<TbResourceInfo> idComparator = new IdComparator<>();
@ -126,11 +154,11 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest {
loginTenantAdmin(); loginTenantAdmin();
Assert.assertEquals(0, resourceService.sumDataSizeByTenantId(tenantId)); assertEquals(0, resourceService.sumDataSizeByTenantId(tenantId));
createResource("test", DEFAULT_FILE_NAME); createResource("test", DEFAULT_FILE_NAME);
Assert.assertEquals(1, resourceService.sumDataSizeByTenantId(tenantId)); assertEquals(1, resourceService.sumDataSizeByTenantId(tenantId));
try { try {
assertThatThrownBy(() -> createResource("test1", 1 + DEFAULT_FILE_NAME)) assertThatThrownBy(() -> createResource("test1", 1 + DEFAULT_FILE_NAME))
@ -145,19 +173,19 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest {
@Test @Test
public void sumDataSizeByTenantId() throws Exception { public void sumDataSizeByTenantId() throws Exception {
Assert.assertEquals(0, resourceService.sumDataSizeByTenantId(tenantId)); assertEquals(0, resourceService.sumDataSizeByTenantId(tenantId));
createResource("test", DEFAULT_FILE_NAME); createResource("test", DEFAULT_FILE_NAME);
Assert.assertEquals(1, resourceService.sumDataSizeByTenantId(tenantId)); assertEquals(1, resourceService.sumDataSizeByTenantId(tenantId));
int maxSumDataSize = 8; int maxSumDataSize = 8;
for (int i = 2; i <= maxSumDataSize; i++) { for (int i = 2; i <= maxSumDataSize; i++) {
createResource("test" + i, i + DEFAULT_FILE_NAME); createResource("test" + i, i + DEFAULT_FILE_NAME);
Assert.assertEquals(i, resourceService.sumDataSizeByTenantId(tenantId)); assertEquals(i, resourceService.sumDataSizeByTenantId(tenantId));
} }
Assert.assertEquals(maxSumDataSize, resourceService.sumDataSizeByTenantId(tenantId)); assertEquals(maxSumDataSize, resourceService.sumDataSizeByTenantId(tenantId));
} }
private TbResource createResource(String title, String filename) throws Exception { private TbResource createResource(String title, String filename) throws Exception {
@ -184,16 +212,16 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest {
Assert.assertNotNull(savedResource); Assert.assertNotNull(savedResource);
Assert.assertNotNull(savedResource.getId()); Assert.assertNotNull(savedResource.getId());
Assert.assertTrue(savedResource.getCreatedTime() > 0); Assert.assertTrue(savedResource.getCreatedTime() > 0);
Assert.assertEquals(resource.getTenantId(), savedResource.getTenantId()); assertEquals(resource.getTenantId(), savedResource.getTenantId());
Assert.assertEquals(resource.getTitle(), savedResource.getTitle()); assertEquals(resource.getTitle(), savedResource.getTitle());
Assert.assertEquals(resource.getResourceKey(), savedResource.getResourceKey()); assertEquals(resource.getResourceKey(), savedResource.getResourceKey());
Assert.assertEquals(resource.getData(), savedResource.getData()); assertEquals(resource.getData(), savedResource.getData());
savedResource.setTitle("My new resource"); savedResource.setTitle("My new resource");
resourceService.save(savedResource); resourceService.save(savedResource);
TbResource foundResource = resourceService.findResourceById(tenantId, savedResource.getId()); TbResource foundResource = resourceService.findResourceById(tenantId, savedResource.getId());
Assert.assertEquals(foundResource.getTitle(), savedResource.getTitle()); assertEquals(foundResource.getTitle(), savedResource.getTitle());
resourceService.delete(savedResource, null); resourceService.delete(savedResource, null);
} }
@ -211,10 +239,10 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest {
Assert.assertNotNull(savedResource); Assert.assertNotNull(savedResource);
Assert.assertNotNull(savedResource.getId()); Assert.assertNotNull(savedResource.getId());
Assert.assertTrue(savedResource.getCreatedTime() > 0); Assert.assertTrue(savedResource.getCreatedTime() > 0);
Assert.assertEquals(resource.getTenantId(), savedResource.getTenantId()); assertEquals(resource.getTenantId(), savedResource.getTenantId());
Assert.assertEquals("My first resource id=0 v1.0", savedResource.getTitle()); assertEquals("My first resource id=0 v1.0", savedResource.getTitle());
Assert.assertEquals("0_1.0", savedResource.getResourceKey()); assertEquals("0_1.0", savedResource.getResourceKey());
Assert.assertEquals(resource.getData(), savedResource.getData()); assertEquals(resource.getData(), savedResource.getData());
resourceService.delete(savedResource, null); resourceService.delete(savedResource, null);
} }
@ -228,7 +256,7 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest {
resource.setData("Test Data"); resource.setData("Test Data");
TbResource savedResource = resourceService.save(resource); TbResource savedResource = resourceService.save(resource);
Assert.assertEquals(TenantId.SYS_TENANT_ID, savedResource.getTenantId()); assertEquals(TenantId.SYS_TENANT_ID, savedResource.getTenantId());
resourceService.delete(savedResource, null); resourceService.delete(savedResource, null);
} }
@ -285,6 +313,21 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest {
}); });
} }
@Test
public void testSaveLwm2mTbResourceWithXXE() {
TbResource resource = new TbResource();
resource.setTenantId(tenantId);
resource.setResourceType(ResourceType.LWM2M_MODEL);
resource.setFileName("xxe_test_model.xml");
resource.setData(Base64.getEncoder().encodeToString(LWM2M_TEST_MODEL_WITH_XXE.getBytes()));
DataValidationException thrown = assertThrows(DataValidationException.class, () -> {
resourceService.save(resource);
});
assertEquals("Failed to parse file xxe_test_model.xml", thrown.getMessage());
}
@Test @Test
public void testFindResourceById() throws Exception { public void testFindResourceById() throws Exception {
TbResource resource = new TbResource(); TbResource resource = new TbResource();
@ -296,7 +339,7 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest {
TbResource foundResource = resourceService.findResourceById(tenantId, savedResource.getId()); TbResource foundResource = resourceService.findResourceById(tenantId, savedResource.getId());
Assert.assertNotNull(foundResource); Assert.assertNotNull(foundResource);
Assert.assertEquals(savedResource, foundResource); assertEquals(savedResource, foundResource);
resourceService.delete(savedResource, null); resourceService.delete(savedResource, null);
} }
@ -312,7 +355,7 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest {
TbResource foundResource = resourceService.getResource(tenantId, savedResource.getResourceType(), savedResource.getResourceKey()); TbResource foundResource = resourceService.getResource(tenantId, savedResource.getResourceType(), savedResource.getResourceKey());
Assert.assertNotNull(foundResource); Assert.assertNotNull(foundResource);
Assert.assertEquals(savedResource, foundResource); assertEquals(savedResource, foundResource);
resourceService.delete(savedResource, null); resourceService.delete(savedResource, null);
} }
@ -366,7 +409,7 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest {
Collections.sort(resources, idComparator); Collections.sort(resources, idComparator);
Collections.sort(loadedResources, idComparator); Collections.sort(loadedResources, idComparator);
Assert.assertEquals(resources, loadedResources); assertEquals(resources, loadedResources);
resourceService.deleteResourcesByTenantId(tenantId); resourceService.deleteResourcesByTenantId(tenantId);
@ -427,14 +470,14 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest {
Collections.sort(resources, idComparator); Collections.sort(resources, idComparator);
Collections.sort(loadedResources, idComparator); Collections.sort(loadedResources, idComparator);
Assert.assertEquals(resources, loadedResources); assertEquals(resources, loadedResources);
resourceService.deleteResourcesByTenantId(tenantId); resourceService.deleteResourcesByTenantId(tenantId);
pageLink = new PageLink(100); pageLink = new PageLink(100);
pageData = resourceService.findAllTenantResourcesByTenantId(tenantId, pageLink); pageData = resourceService.findAllTenantResourcesByTenantId(tenantId, pageLink);
Assert.assertFalse(pageData.hasNext()); Assert.assertFalse(pageData.hasNext());
Assert.assertEquals(pageData.getData().size(), 100); assertEquals(pageData.getData().size(), 100);
resourceService.deleteResourcesByTenantId(TenantId.SYS_TENANT_ID); resourceService.deleteResourcesByTenantId(TenantId.SYS_TENANT_ID);

66
application/src/test/java/org/thingsboard/server/service/security/auth/oauth2/HttpCookieOAuth2AuthorizationRequestRepositoryTest.java

@ -0,0 +1,66 @@
/**
* 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.security.auth.oauth2;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mockito;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;
import static org.junit.Assert.assertEquals;
import static org.thingsboard.server.service.security.auth.oauth2.HttpCookieOAuth2AuthorizationRequestRepository.OAUTH2_AUTHORIZATION_REQUEST_COOKIE_NAME;
public class HttpCookieOAuth2AuthorizationRequestRepositoryTest {
private static final String SERIALIZED_ATTACK_STRING =
"rO0ABXNyAHVvcmcudGhpbmdzYm9hcmQuc2VydmVyLnNlcnZpY2Uuc2VjdXJpdHkuYXV0aC5vYXV0aDIuSHR0cENvb2tpZU9BdXRoMkF1dGhvcml6YXRpb25SZXF1ZXN0UmVwb3NpdG9yeVRlc3QkTWFsaWNpb3VzQ2xhc3MAAAAAAAAAAAIAAHhw";
private static int maliciousMethodInvocationCounter;
@Before
public void resetInvocationCounter() {
maliciousMethodInvocationCounter = 0;
}
@Test
public void whenLoadAuthorizationRequest_thenMaliciousMethodNotInvoked() {
HttpCookieOAuth2AuthorizationRequestRepository cookieRequestRepo = new HttpCookieOAuth2AuthorizationRequestRepository();
HttpServletRequest request = Mockito.mock(HttpServletRequest.class);
Cookie cookie = new Cookie(OAUTH2_AUTHORIZATION_REQUEST_COOKIE_NAME, SERIALIZED_ATTACK_STRING);
Mockito.when(request.getCookies()).thenReturn(new Cookie[]{cookie});
cookieRequestRepo.loadAuthorizationRequest(request);
assertEquals(0, maliciousMethodInvocationCounter);
}
private static class MaliciousClass implements Serializable {
private static final long serialVersionUID = 0L;
public void maliciousMethod() {
maliciousMethodInvocationCounter++;
}
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
maliciousMethod();
}
}
}

2
common/actor/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>3.5.2-SNAPSHOT</version> <version>3.6.0-SNAPSHOT</version>
<artifactId>common</artifactId> <artifactId>common</artifactId>
</parent> </parent>
<groupId>org.thingsboard.common</groupId> <groupId>org.thingsboard.common</groupId>

2
common/cache/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>3.5.2-SNAPSHOT</version> <version>3.6.0-SNAPSHOT</version>
<artifactId>common</artifactId> <artifactId>common</artifactId>
</parent> </parent>
<groupId>org.thingsboard.common</groupId> <groupId>org.thingsboard.common</groupId>

2
common/cluster-api/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>3.5.2-SNAPSHOT</version> <version>3.6.0-SNAPSHOT</version>
<artifactId>common</artifactId> <artifactId>common</artifactId>
</parent> </parent>
<groupId>org.thingsboard.common</groupId> <groupId>org.thingsboard.common</groupId>

2
common/coap-server/pom.xml

@ -22,7 +22,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>3.5.2-SNAPSHOT</version> <version>3.6.0-SNAPSHOT</version>
<artifactId>common</artifactId> <artifactId>common</artifactId>
</parent> </parent>
<groupId>org.thingsboard.common</groupId> <groupId>org.thingsboard.common</groupId>

2
common/dao-api/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>3.5.2-SNAPSHOT</version> <version>3.6.0-SNAPSHOT</version>
<artifactId>common</artifactId> <artifactId>common</artifactId>
</parent> </parent>
<groupId>org.thingsboard.common</groupId> <groupId>org.thingsboard.common</groupId>

4
common/dao-api/src/main/java/org/thingsboard/server/dao/entity/EntityDaoService.java

@ -30,6 +30,10 @@ public interface EntityDaoService {
throw new IllegalArgumentException("Not implemented for " + getEntityType()); throw new IllegalArgumentException("Not implemented for " + getEntityType());
} }
default void deleteEntity(TenantId tenantId, EntityId id) {
throw new IllegalArgumentException(getEntityType().getNormalName() + " deletion not supported");
}
EntityType getEntityType(); EntityType getEntityType();
} }

7
common/data/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>3.5.2-SNAPSHOT</version> <version>3.6.0-SNAPSHOT</version>
<artifactId>common</artifactId> <artifactId>common</artifactId>
</parent> </parent>
<groupId>org.thingsboard.common</groupId> <groupId>org.thingsboard.common</groupId>
@ -116,6 +116,11 @@
<groupId>com.google.protobuf</groupId> <groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java-util</artifactId> <artifactId>protobuf-java-util</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.eclipse.leshan</groupId>
<artifactId>leshan-core</artifactId>
<scope>compile</scope>
</dependency>
</dependencies> </dependencies>
<build> <build>

108
common/data/src/main/java/org/thingsboard/server/common/data/BaseDataWithAdditionalInfo.java

@ -0,0 +1,108 @@
/**
* 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.common.data;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.common.data.id.UUIDBased;
import org.thingsboard.server.common.data.validation.NoXss;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.function.Supplier;
/**
* Created by ashvayka on 19.02.18.
*/
@Slf4j
public abstract class BaseDataWithAdditionalInfo<I extends UUIDBased> extends BaseData<I> implements HasAdditionalInfo {
public static final ObjectMapper mapper = new ObjectMapper();
@NoXss
private transient JsonNode additionalInfo;
@JsonIgnore
private byte[] additionalInfoBytes;
public BaseDataWithAdditionalInfo() {
super();
}
public BaseDataWithAdditionalInfo(I id) {
super(id);
}
public BaseDataWithAdditionalInfo(BaseDataWithAdditionalInfo<I> baseData) {
super(baseData);
setAdditionalInfo(baseData.getAdditionalInfo());
}
@Override
public JsonNode getAdditionalInfo() {
return getJson(() -> additionalInfo, () -> additionalInfoBytes);
}
public void setAdditionalInfo(JsonNode addInfo) {
setJson(addInfo, json -> this.additionalInfo = json, bytes -> this.additionalInfoBytes = bytes);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
if (!super.equals(o)) return false;
BaseDataWithAdditionalInfo<?> that = (BaseDataWithAdditionalInfo<?>) o;
return Arrays.equals(additionalInfoBytes, that.additionalInfoBytes);
}
@Override
public int hashCode() {
return Objects.hash(super.hashCode(), additionalInfoBytes);
}
public static JsonNode getJson(Supplier<JsonNode> jsonData, Supplier<byte[]> binaryData) {
JsonNode json = jsonData.get();
if (json != null) {
return json;
} else {
byte[] data = binaryData.get();
if (data != null) {
try {
return mapper.readTree(new ByteArrayInputStream(data));
} catch (IOException e) {
log.warn("Can't deserialize json data: ", e);
return null;
}
} else {
return null;
}
}
}
public static void setJson(JsonNode json, Consumer<JsonNode> jsonConsumer, Consumer<byte[]> bytesConsumer) {
jsonConsumer.accept(json);
try {
bytesConsumer.accept(mapper.writeValueAsBytes(json));
} catch (JsonProcessingException e) {
log.warn("Can't serialize json data: ", e);
}
}
}

7
common/data/src/main/java/org/thingsboard/server/common/data/Device.java

@ -40,7 +40,7 @@ import java.util.Optional;
@ApiModel @ApiModel
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
@Slf4j @Slf4j
public class Device extends SearchTextBasedWithAdditionalInfo<DeviceId> implements HasLabel, HasTenantId, HasCustomerId, HasOtaPackage, ExportableEntity<DeviceId> { public class Device extends BaseDataWithAdditionalInfo<DeviceId> implements HasLabel, HasTenantId, HasCustomerId, HasOtaPackage, ExportableEntity<DeviceId> {
private static final long serialVersionUID = 2807343040519543363L; private static final long serialVersionUID = 2807343040519543363L;
@ -201,11 +201,6 @@ public class Device extends SearchTextBasedWithAdditionalInfo<DeviceId> implemen
} }
} }
@Override
public String getSearchText() {
return getName();
}
@ApiModelProperty(position = 10, value = "JSON object with Ota Package Id.") @ApiModelProperty(position = 10, value = "JSON object with Ota Package Id.")
public OtaPackageId getFirmwareId() { public OtaPackageId getFirmwareId() {
return firmwareId; return firmwareId;

31
common/data/src/main/java/org/thingsboard/server/common/data/exception/TenantProfileNotFoundException.java

@ -0,0 +1,31 @@
/**
* 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.common.data.exception;
import lombok.Getter;
import org.thingsboard.server.common.data.id.TenantId;
public class TenantProfileNotFoundException extends RuntimeException {
@Getter
private final TenantId tenantId;
public TenantProfileNotFoundException(TenantId tenantId) {
super("Profile for tenant with id " + tenantId + " not found");
this.tenantId = tenantId;
}
}

6
common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/NotificationRule.java

@ -20,6 +20,7 @@ import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import org.thingsboard.server.common.data.BaseData; import org.thingsboard.server.common.data.BaseData;
import org.thingsboard.server.common.data.ExportableEntity;
import org.thingsboard.server.common.data.HasName; import org.thingsboard.server.common.data.HasName;
import org.thingsboard.server.common.data.HasTenantId; import org.thingsboard.server.common.data.HasTenantId;
import org.thingsboard.server.common.data.id.NotificationRuleId; import org.thingsboard.server.common.data.id.NotificationRuleId;
@ -39,7 +40,7 @@ import java.io.Serializable;
@Data @Data
@NoArgsConstructor @NoArgsConstructor
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
public class NotificationRule extends BaseData<NotificationRuleId> implements HasTenantId, HasName, Serializable { public class NotificationRule extends BaseData<NotificationRuleId> implements HasTenantId, HasName, ExportableEntity<NotificationRuleId>, Serializable {
private TenantId tenantId; private TenantId tenantId;
@NotBlank @NotBlank
@ -61,6 +62,8 @@ public class NotificationRule extends BaseData<NotificationRuleId> implements Ha
private NotificationRuleConfig additionalConfig; private NotificationRuleConfig additionalConfig;
private NotificationRuleId externalId;
public NotificationRule(NotificationRule other) { public NotificationRule(NotificationRule other) {
super(other); super(other);
this.tenantId = other.tenantId; this.tenantId = other.tenantId;
@ -71,6 +74,7 @@ public class NotificationRule extends BaseData<NotificationRuleId> implements Ha
this.triggerConfig = other.triggerConfig; this.triggerConfig = other.triggerConfig;
this.recipientsConfig = other.recipientsConfig; this.recipientsConfig = other.recipientsConfig;
this.additionalConfig = other.additionalConfig; this.additionalConfig = other.additionalConfig;
this.externalId = other.externalId;
} }
@JsonIgnore @JsonIgnore

16
common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/NotificationTarget.java

@ -18,6 +18,7 @@ package org.thingsboard.server.common.data.notification.targets;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import org.thingsboard.server.common.data.BaseData; import org.thingsboard.server.common.data.BaseData;
import org.thingsboard.server.common.data.ExportableEntity;
import org.thingsboard.server.common.data.HasName; import org.thingsboard.server.common.data.HasName;
import org.thingsboard.server.common.data.HasTenantId; import org.thingsboard.server.common.data.HasTenantId;
import org.thingsboard.server.common.data.id.NotificationTargetId; import org.thingsboard.server.common.data.id.NotificationTargetId;
@ -31,7 +32,7 @@ import javax.validation.constraints.NotNull;
@Data @Data
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
public class NotificationTarget extends BaseData<NotificationTargetId> implements HasTenantId, HasName { public class NotificationTarget extends BaseData<NotificationTargetId> implements HasTenantId, HasName, ExportableEntity<NotificationTargetId> {
private TenantId tenantId; private TenantId tenantId;
@NotBlank @NotBlank
@ -42,4 +43,17 @@ public class NotificationTarget extends BaseData<NotificationTargetId> implement
@Valid @Valid
private NotificationTargetConfig configuration; private NotificationTargetConfig configuration;
private NotificationTargetId externalId;
public NotificationTarget() {
}
public NotificationTarget(NotificationTarget other) {
super(other);
this.tenantId = other.tenantId;
this.name = other.name;
this.configuration = other.configuration;
this.externalId = other.externalId;
}
} }

17
common/data/src/main/java/org/thingsboard/server/common/data/notification/template/NotificationTemplate.java

@ -18,6 +18,7 @@ package org.thingsboard.server.common.data.notification.template;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import org.thingsboard.server.common.data.BaseData; import org.thingsboard.server.common.data.BaseData;
import org.thingsboard.server.common.data.ExportableEntity;
import org.thingsboard.server.common.data.HasName; import org.thingsboard.server.common.data.HasName;
import org.thingsboard.server.common.data.HasTenantId; import org.thingsboard.server.common.data.HasTenantId;
import org.thingsboard.server.common.data.id.NotificationTemplateId; import org.thingsboard.server.common.data.id.NotificationTemplateId;
@ -32,7 +33,7 @@ import javax.validation.constraints.NotNull;
@Data @Data
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
public class NotificationTemplate extends BaseData<NotificationTemplateId> implements HasTenantId, HasName { public class NotificationTemplate extends BaseData<NotificationTemplateId> implements HasTenantId, HasName, ExportableEntity<NotificationTemplateId> {
private TenantId tenantId; private TenantId tenantId;
@NoXss @NoXss
@ -46,4 +47,18 @@ public class NotificationTemplate extends BaseData<NotificationTemplateId> imple
@NotNull @NotNull
private NotificationTemplateConfig configuration; private NotificationTemplateConfig configuration;
private NotificationTemplateId externalId;
public NotificationTemplate() {
}
public NotificationTemplate(NotificationTemplate other) {
super(other);
this.tenantId = other.tenantId;
this.name = other.name;
this.notificationType = other.notificationType;
this.configuration = other.configuration != null ? other.configuration.copy() : null;
this.externalId = other.externalId;
}
} }

9
common/data/src/main/java/org/thingsboard/server/common/data/notification/template/NotificationTemplateConfig.java

@ -20,6 +20,7 @@ import org.thingsboard.server.common.data.notification.NotificationDeliveryMetho
import javax.validation.Valid; import javax.validation.Valid;
import javax.validation.constraints.NotEmpty; import javax.validation.constraints.NotEmpty;
import java.util.HashMap;
import java.util.Map; import java.util.Map;
@Data @Data
@ -29,4 +30,12 @@ public class NotificationTemplateConfig {
@NotEmpty @NotEmpty
private Map<NotificationDeliveryMethod, DeliveryMethodNotificationTemplate> deliveryMethodsTemplates; private Map<NotificationDeliveryMethod, DeliveryMethodNotificationTemplate> deliveryMethodsTemplates;
public NotificationTemplateConfig copy() {
Map<NotificationDeliveryMethod, DeliveryMethodNotificationTemplate> templates = new HashMap<>(deliveryMethodsTemplates);
templates.replaceAll((deliveryMethod, template) -> template.copy());
NotificationTemplateConfig copy = new NotificationTemplateConfig();
copy.setDeliveryMethodsTemplates(templates);
return copy;
}
} }

8
common/data/src/main/java/org/thingsboard/server/common/data/sync/JsonTbEntity.java

@ -27,6 +27,9 @@ import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.EntityView; import org.thingsboard.server.common.data.EntityView;
import org.thingsboard.server.common.data.asset.Asset; import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.asset.AssetProfile; import org.thingsboard.server.common.data.asset.AssetProfile;
import org.thingsboard.server.common.data.notification.rule.NotificationRule;
import org.thingsboard.server.common.data.notification.targets.NotificationTarget;
import org.thingsboard.server.common.data.notification.template.NotificationTemplate;
import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.data.widget.WidgetsBundle; import org.thingsboard.server.common.data.widget.WidgetsBundle;
@ -48,7 +51,10 @@ import java.lang.annotation.Target;
@Type(name = "DASHBOARD", value = Dashboard.class), @Type(name = "DASHBOARD", value = Dashboard.class),
@Type(name = "CUSTOMER", value = Customer.class), @Type(name = "CUSTOMER", value = Customer.class),
@Type(name = "ENTITY_VIEW", value = EntityView.class), @Type(name = "ENTITY_VIEW", value = EntityView.class),
@Type(name = "WIDGETS_BUNDLE", value = WidgetsBundle.class) @Type(name = "WIDGETS_BUNDLE", value = WidgetsBundle.class),
@Type(name = "NOTIFICATION_TEMPLATE", value = NotificationTemplate.class),
@Type(name = "NOTIFICATION_TARGET", value = NotificationTarget.class),
@Type(name = "NOTIFICATION_RULE", value = NotificationRule.class)
}) })
@JsonIgnoreProperties(value = {"tenantId", "createdTime"}, ignoreUnknown = true) @JsonIgnoreProperties(value = {"tenantId", "createdTime"}, ignoreUnknown = true)
public @interface JsonTbEntity { public @interface JsonTbEntity {

272
common/data/src/main/java/org/thingsboard/server/common/data/util/TbDDFFileParser.java

@ -0,0 +1,272 @@
/**
* 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.common.data.util;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.leshan.core.LwM2m;
import org.eclipse.leshan.core.model.DDFFileValidator;
import org.eclipse.leshan.core.model.DefaultDDFFileValidator;
import org.eclipse.leshan.core.model.InvalidDDFFileException;
import org.eclipse.leshan.core.model.ObjectModel;
import org.eclipse.leshan.core.model.ResourceModel;
import org.eclipse.leshan.core.util.StringUtils;
import org.w3c.dom.DOMException;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Slf4j
public class TbDDFFileParser {
private static final DDFFileValidator ddfFileValidator = new DefaultDDFFileValidator();
public List<ObjectModel> parse(InputStream inputStream, String streamName)
throws InvalidDDFFileException, IOException {
streamName = streamName == null ? "" : streamName;
log.debug("Parsing DDF file {}", streamName);
try {
// Parse XML file
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setNamespaceAware(true);
factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
DocumentBuilder builder = factory.newDocumentBuilder();
Document document = builder.parse(inputStream);
// Get DDF file validator
LwM2m.LwM2mVersion lwm2mVersion = null;
ddfFileValidator.validate(document);
// Build list of ObjectModel
ArrayList<ObjectModel> objects = new ArrayList<>();
NodeList nodeList = document.getDocumentElement().getElementsByTagName("Object");
for (int i = 0; i < nodeList.getLength(); i++) {
objects.add(parseObject(nodeList.item(i), streamName, lwm2mVersion, true));
}
return objects;
} catch (InvalidDDFFileException | SAXException e) {
throw new InvalidDDFFileException(e, "Invalid DDF file %s", streamName);
}
catch (ParserConfigurationException e) {
throw new IllegalStateException("Unable to create Document Builder", e);
}
}
private ObjectModel parseObject(Node object, String streamName, LwM2m.LwM2mVersion schemaVersion, boolean validate)
throws InvalidDDFFileException {
Node objectType = object.getAttributes().getNamedItem("ObjectType");
if (validate && (objectType == null || !"MODefinition".equals(objectType.getTextContent()))) {
throw new InvalidDDFFileException(
"Object element in %s MUST have a ObjectType attribute equals to 'MODefinition'.", streamName);
}
Integer id = null;
String name = null;
String description = null;
String version = ObjectModel.DEFAULT_VERSION;
Boolean multiple = null;
Boolean mandatory = null;
Map<Integer, ResourceModel> resources = new HashMap<>();
String urn = null;
String description2 = null;
String lwm2mVersion = ObjectModel.DEFAULT_VERSION;
for (int i = 0; i < object.getChildNodes().getLength(); i++) {
Node field = object.getChildNodes().item(i);
if (field.getNodeType() != Node.ELEMENT_NODE)
continue;
switch (field.getNodeName()) {
case "ObjectID":
id = Integer.valueOf(field.getTextContent());
break;
case "Name":
name = field.getTextContent();
break;
case "Description1":
description = field.getTextContent();
break;
case "ObjectVersion":
if (!StringUtils.isEmpty(field.getTextContent())) {
version = field.getTextContent();
}
break;
case "MultipleInstances":
if ("Multiple".equals(field.getTextContent())) {
multiple = true;
} else if ("Single".equals(field.getTextContent())) {
multiple = false;
}
break;
case "Mandatory":
if ("Mandatory".equals(field.getTextContent())) {
mandatory = true;
} else if ("Optional".equals(field.getTextContent())) {
mandatory = false;
}
break;
case "Resources":
for (int j = 0; j < field.getChildNodes().getLength(); j++) {
Node item = field.getChildNodes().item(j);
if (item.getNodeType() != Node.ELEMENT_NODE)
continue;
if (item.getNodeName().equals("Item")) {
ResourceModel resource = parseResource(item, streamName);
if (validate && resources.containsKey(resource.id)) {
throw new InvalidDDFFileException(
"Object %s in %s contains at least 2 resources with same id %s.",
id != null ? id : "", streamName, resource.id);
} else {
resources.put(resource.id, resource);
}
}
}
break;
case "ObjectURN":
urn = field.getTextContent();
break;
case "LWM2MVersion":
if (!StringUtils.isEmpty(field.getTextContent())) {
lwm2mVersion = field.getTextContent();
if (schemaVersion != null && !schemaVersion.toString().equals(lwm2mVersion)) {
throw new InvalidDDFFileException(
"LWM2MVersion is not consistent with xml shema(xsi:noNamespaceSchemaLocation) in %s : %s expected but was %s.",
streamName, schemaVersion, lwm2mVersion);
}
}
break;
case "Description2":
description2 = field.getTextContent();
break;
default:
break;
}
}
return new ObjectModel(id, name, description, version, multiple, mandatory, resources.values(), urn,
lwm2mVersion, description2);
}
private ResourceModel parseResource(Node item, String streamName) throws DOMException, InvalidDDFFileException {
Integer id = Integer.valueOf(item.getAttributes().getNamedItem("ID").getTextContent());
String name = null;
ResourceModel.Operations operations = null;
Boolean multiple = false;
Boolean mandatory = false;
ResourceModel.Type type = null;
String rangeEnumeration = null;
String units = null;
String description = null;
for (int i = 0; i < item.getChildNodes().getLength(); i++) {
Node field = item.getChildNodes().item(i);
if (field.getNodeType() != Node.ELEMENT_NODE)
continue;
switch (field.getNodeName()) {
case "Name":
name = field.getTextContent();
break;
case "Operations":
String strOp = field.getTextContent();
if (strOp != null && !strOp.isEmpty()) {
operations = ResourceModel.Operations.valueOf(strOp);
} else {
operations = ResourceModel.Operations.NONE;
}
break;
case "MultipleInstances":
if ("Multiple".equals(field.getTextContent())) {
multiple = true;
} else if ("Single".equals(field.getTextContent())) {
multiple = false;
}
break;
case "Mandatory":
if ("Mandatory".equals(field.getTextContent())) {
mandatory = true;
} else if ("Optional".equals(field.getTextContent())) {
mandatory = false;
}
break;
case "Type":
switch (field.getTextContent()) {
case "String":
type = ResourceModel.Type.STRING;
break;
case "Integer":
type = ResourceModel.Type.INTEGER;
break;
case "Float":
type = ResourceModel.Type.FLOAT;
break;
case "Boolean":
type = ResourceModel.Type.BOOLEAN;
break;
case "Opaque":
type = ResourceModel.Type.OPAQUE;
break;
case "Time":
type = ResourceModel.Type.TIME;
break;
case "Objlnk":
type = ResourceModel.Type.OBJLNK;
break;
case "Unsigned Integer":
type = ResourceModel.Type.UNSIGNED_INTEGER;
break;
case "Corelnk":
type = ResourceModel.Type.CORELINK;
break;
case "":
type = ResourceModel.Type.NONE;
break;
default:
break;
}
break;
case "RangeEnumeration":
rangeEnumeration = field.getTextContent();
break;
case "Units":
units = field.getTextContent();
break;
case "Description":
description = field.getTextContent();
break;
default:
break;
}
}
return new ResourceModel(id, name, operations, multiple, mandatory, type, rangeEnumeration, units, description);
}
}

2
common/edge-api/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>3.5.2-SNAPSHOT</version> <version>3.6.0-SNAPSHOT</version>
<artifactId>common</artifactId> <artifactId>common</artifactId>
</parent> </parent>
<groupId>org.thingsboard.common</groupId> <groupId>org.thingsboard.common</groupId>

2
common/message/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>3.5.2-SNAPSHOT</version> <version>3.6.0-SNAPSHOT</version>
<artifactId>common</artifactId> <artifactId>common</artifactId>
</parent> </parent>
<groupId>org.thingsboard.common</groupId> <groupId>org.thingsboard.common</groupId>

2
common/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>3.5.2-SNAPSHOT</version> <version>3.6.0-SNAPSHOT</version>
<artifactId>thingsboard</artifactId> <artifactId>thingsboard</artifactId>
</parent> </parent>
<artifactId>common</artifactId> <artifactId>common</artifactId>

2
common/queue/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>3.5.2-SNAPSHOT</version> <version>3.6.0-SNAPSHOT</version>
<artifactId>common</artifactId> <artifactId>common</artifactId>
</parent> </parent>
<groupId>org.thingsboard.common</groupId> <groupId>org.thingsboard.common</groupId>

2
common/script/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>3.5.2-SNAPSHOT</version> <version>3.6.0-SNAPSHOT</version>
<artifactId>common</artifactId> <artifactId>common</artifactId>
</parent> </parent>
<groupId>org.thingsboard.common</groupId> <groupId>org.thingsboard.common</groupId>

2
common/script/remote-js-client/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard.common</groupId> <groupId>org.thingsboard.common</groupId>
<version>3.5.2-SNAPSHOT</version> <version>3.6.0-SNAPSHOT</version>
<artifactId>script</artifactId> <artifactId>script</artifactId>
</parent> </parent>
<groupId>org.thingsboard.common.script</groupId> <groupId>org.thingsboard.common.script</groupId>

2
common/script/script-api/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard.common</groupId> <groupId>org.thingsboard.common</groupId>
<version>3.5.2-SNAPSHOT</version> <version>3.6.0-SNAPSHOT</version>
<artifactId>script</artifactId> <artifactId>script</artifactId>
</parent> </parent>
<groupId>org.thingsboard.common.script</groupId> <groupId>org.thingsboard.common.script</groupId>

2
common/stats/pom.xml

@ -22,7 +22,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>3.5.2-SNAPSHOT</version> <version>3.6.0-SNAPSHOT</version>
<artifactId>common</artifactId> <artifactId>common</artifactId>
</parent> </parent>
<groupId>org.thingsboard.common</groupId> <groupId>org.thingsboard.common</groupId>

2
common/transport/coap/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard.common</groupId> <groupId>org.thingsboard.common</groupId>
<version>3.5.2-SNAPSHOT</version> <version>3.6.0-SNAPSHOT</version>
<artifactId>transport</artifactId> <artifactId>transport</artifactId>
</parent> </parent>
<groupId>org.thingsboard.common.transport</groupId> <groupId>org.thingsboard.common.transport</groupId>

2
common/transport/http/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard.common</groupId> <groupId>org.thingsboard.common</groupId>
<version>3.5.2-SNAPSHOT</version> <version>3.6.0-SNAPSHOT</version>
<artifactId>transport</artifactId> <artifactId>transport</artifactId>
</parent> </parent>
<groupId>org.thingsboard.common.transport</groupId> <groupId>org.thingsboard.common.transport</groupId>

2
common/transport/lwm2m/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard.common</groupId> <groupId>org.thingsboard.common</groupId>
<version>3.5.2-SNAPSHOT</version> <version>3.6.0-SNAPSHOT</version>
<artifactId>transport</artifactId> <artifactId>transport</artifactId>
</parent> </parent>
<groupId>org.thingsboard.common.transport</groupId> <groupId>org.thingsboard.common.transport</groupId>

5
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportServerHelper.java

@ -25,6 +25,7 @@ import org.eclipse.leshan.core.model.ObjectModel;
import org.eclipse.leshan.core.model.ResourceModel; import org.eclipse.leshan.core.model.ResourceModel;
import org.eclipse.leshan.core.node.codec.CodecException; import org.eclipse.leshan.core.node.codec.CodecException;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.util.TbDDFFileParser;
import org.thingsboard.server.common.transport.TransportServiceCallback; import org.thingsboard.server.common.transport.TransportServiceCallback;
import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse;
import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos;
@ -142,9 +143,9 @@ public class LwM2mTransportServerHelper {
.build(); .build();
} }
public ObjectModel parseFromXmlToObjectModel(byte[] xmlByte, String streamName, DefaultDDFFileValidator ddfValidator) { public ObjectModel parseFromXmlToObjectModel(byte[] xmlByte, String streamName) {
try { try {
DDFFileParser ddfFileParser = new DDFFileParser(ddfValidator); TbDDFFileParser ddfFileParser = new TbDDFFileParser();
return ddfFileParser.parse(new ByteArrayInputStream(xmlByte), streamName).get(0); return ddfFileParser.parse(new ByteArrayInputStream(xmlByte), streamName).get(0);
} catch (IOException | InvalidDDFFileException e) { } catch (IOException | InvalidDDFFileException e) {
log.error("Could not parse the XML file [{}]", streamName, e); log.error("Could not parse the XML file [{}]", streamName, e);

3
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mVersionedModelProvider.java

@ -154,8 +154,7 @@ public class LwM2mVersionedModelProvider implements LwM2mModelProvider {
Optional<TbResource> tbResource = context.getTransportResourceCache().get(this.tenantId, LWM2M_MODEL, key); Optional<TbResource> tbResource = context.getTransportResourceCache().get(this.tenantId, LWM2M_MODEL, key);
return tbResource.map(resource -> helper.parseFromXmlToObjectModel( return tbResource.map(resource -> helper.parseFromXmlToObjectModel(
Base64.getDecoder().decode(resource.getData()), Base64.getDecoder().decode(resource.getData()),
key + ".xml", key + ".xml")).orElse(null);
new DefaultDDFFileValidator())).orElse(null);
} }
} }
} }

2
common/transport/mqtt/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard.common</groupId> <groupId>org.thingsboard.common</groupId>
<version>3.5.2-SNAPSHOT</version> <version>3.6.0-SNAPSHOT</version>
<artifactId>transport</artifactId> <artifactId>transport</artifactId>
</parent> </parent>
<groupId>org.thingsboard.common.transport</groupId> <groupId>org.thingsboard.common.transport</groupId>

2
common/transport/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>3.5.2-SNAPSHOT</version> <version>3.6.0-SNAPSHOT</version>
<artifactId>common</artifactId> <artifactId>common</artifactId>
</parent> </parent>
<groupId>org.thingsboard.common</groupId> <groupId>org.thingsboard.common</groupId>

2
common/transport/snmp/pom.xml

@ -21,7 +21,7 @@
<parent> <parent>
<groupId>org.thingsboard.common</groupId> <groupId>org.thingsboard.common</groupId>
<version>3.5.2-SNAPSHOT</version> <version>3.6.0-SNAPSHOT</version>
<artifactId>transport</artifactId> <artifactId>transport</artifactId>
</parent> </parent>

2
common/transport/transport-api/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard.common</groupId> <groupId>org.thingsboard.common</groupId>
<version>3.5.2-SNAPSHOT</version> <version>3.6.0-SNAPSHOT</version>
<artifactId>transport</artifactId> <artifactId>transport</artifactId>
</parent> </parent>
<groupId>org.thingsboard.common.transport</groupId> <groupId>org.thingsboard.common.transport</groupId>

2
common/util/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>3.5.2-SNAPSHOT</version> <version>3.6.0-SNAPSHOT</version>
<artifactId>common</artifactId> <artifactId>common</artifactId>
</parent> </parent>
<groupId>org.thingsboard.common</groupId> <groupId>org.thingsboard.common</groupId>

2
common/version-control/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>3.5.2-SNAPSHOT</version> <version>3.6.0-SNAPSHOT</version>
<artifactId>common</artifactId> <artifactId>common</artifactId>
</parent> </parent>
<groupId>org.thingsboard.common</groupId> <groupId>org.thingsboard.common</groupId>

2
dao/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>3.5.2-SNAPSHOT</version> <version>3.6.0-SNAPSHOT</version>
<artifactId>thingsboard</artifactId> <artifactId>thingsboard</artifactId>
</parent> </parent>
<artifactId>dao</artifactId> <artifactId>dao</artifactId>

8
dao/src/main/java/org/thingsboard/server/dao/DaoUtil.java

@ -109,6 +109,14 @@ public abstract class DaoUtil {
return ids; return ids;
} }
public static <I> I toEntityId(UUID uuid, Function<UUID, I> creator) {
if (uuid != null) {
return creator.apply(uuid);
} else {
return null;
}
}
public static <T> void processInBatches(Function<PageLink, PageData<T>> finder, int batchSize, Consumer<T> processor) { public static <T> void processInBatches(Function<PageLink, PageData<T>> finder, int batchSize, Consumer<T> processor) {
processBatches(finder, batchSize, batch -> batch.getData().forEach(processor)); processBatches(finder, batchSize, batch -> batch.getData().forEach(processor));
} }

6
dao/src/main/java/org/thingsboard/server/dao/asset/AssetProfileServiceImpl.java

@ -273,6 +273,12 @@ public class AssetProfileServiceImpl extends AbstractCachedEntityService<AssetPr
return Optional.ofNullable(findAssetProfileById(tenantId, new AssetProfileId(entityId.getId()))); return Optional.ofNullable(findAssetProfileById(tenantId, new AssetProfileId(entityId.getId())));
} }
@Override
@Transactional
public void deleteEntity(TenantId tenantId, EntityId id) {
deleteAssetProfile(tenantId, (AssetProfileId) id);
}
@Override @Override
public EntityType getEntityType() { public EntityType getEntityType() {
return EntityType.ASSET_PROFILE; return EntityType.ASSET_PROFILE;

6
dao/src/main/java/org/thingsboard/server/dao/asset/BaseAssetService.java

@ -450,6 +450,12 @@ public class BaseAssetService extends AbstractCachedEntityService<AssetCacheKey,
return assetDao.countByTenantId(tenantId); return assetDao.countByTenantId(tenantId);
} }
@Override
@Transactional
public void deleteEntity(TenantId tenantId, EntityId id) {
deleteAsset(tenantId, (AssetId) id);
}
@Override @Override
public EntityType getEntityType() { public EntityType getEntityType() {
return EntityType.ASSET; return EntityType.ASSET;

6
dao/src/main/java/org/thingsboard/server/dao/customer/CustomerServiceImpl.java

@ -197,6 +197,12 @@ public class CustomerServiceImpl extends AbstractEntityService implements Custom
return Optional.ofNullable(findCustomerById(tenantId, new CustomerId(entityId.getId()))); return Optional.ofNullable(findCustomerById(tenantId, new CustomerId(entityId.getId())));
} }
@Transactional
@Override
public void deleteEntity(TenantId tenantId, EntityId id) {
deleteCustomer(tenantId, (CustomerId) id);
}
@Override @Override
public long countByTenantId(TenantId tenantId) { public long countByTenantId(TenantId tenantId) {
return customerDao.countByTenantId(tenantId); return customerDao.countByTenantId(tenantId);

6
dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardServiceImpl.java

@ -366,6 +366,12 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb
return dashboardDao.countByTenantId(tenantId); return dashboardDao.countByTenantId(tenantId);
} }
@Override
@Transactional
public void deleteEntity(TenantId tenantId, EntityId id) {
deleteDashboard(tenantId, (DashboardId) id);
}
@Override @Override
public EntityType getEntityType() { public EntityType getEntityType() {
return EntityType.DASHBOARD; return EntityType.DASHBOARD;

6
dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java

@ -328,6 +328,12 @@ public class DeviceProfileServiceImpl extends AbstractCachedEntityService<Device
return Optional.ofNullable(findDeviceProfileById(tenantId, new DeviceProfileId(entityId.getId()))); return Optional.ofNullable(findDeviceProfileById(tenantId, new DeviceProfileId(entityId.getId())));
} }
@Override
@Transactional
public void deleteEntity(TenantId tenantId, EntityId id) {
deleteDeviceProfile(tenantId, (DeviceProfileId) id);
}
@Override @Override
public EntityType getEntityType() { public EntityType getEntityType() {
return EntityType.DEVICE_PROFILE; return EntityType.DEVICE_PROFILE;

6
dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java

@ -638,6 +638,12 @@ public class DeviceServiceImpl extends AbstractCachedEntityService<DeviceCacheKe
return deviceDao.countByTenantId(tenantId); return deviceDao.countByTenantId(tenantId);
} }
@Override
@Transactional
public void deleteEntity(TenantId tenantId, EntityId id) {
deleteDevice(tenantId, (DeviceId) id);
}
private PaginatedRemover<TenantId, Device> tenantDevicesRemover = private PaginatedRemover<TenantId, Device> tenantDevicesRemover =
new PaginatedRemover<>() { new PaginatedRemover<>() {

7
dao/src/main/java/org/thingsboard/server/dao/model/BaseSqlEntity.java

@ -22,6 +22,7 @@ import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UUIDBased; import org.thingsboard.server.common.data.id.UUIDBased;
import org.thingsboard.server.dao.DaoUtil;
import javax.persistence.Column; import javax.persistence.Column;
import javax.persistence.Id; import javax.persistence.Id;
@ -85,11 +86,7 @@ public abstract class BaseSqlEntity<D> implements BaseEntity<D> {
} }
protected static <I> I getEntityId(UUID uuid, Function<UUID, I> creator) { protected static <I> I getEntityId(UUID uuid, Function<UUID, I> creator) {
if (uuid != null) { return DaoUtil.toEntityId(uuid, creator);
return creator.apply(uuid);
} else {
return null;
}
} }
protected static TenantId getTenantId(UUID uuid) { protected static TenantId getTenantId(UUID uuid) {

17
dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractDeviceEntity.java

@ -32,7 +32,6 @@ import org.thingsboard.server.common.data.id.OtaPackageId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.dao.model.BaseSqlEntity; import org.thingsboard.server.dao.model.BaseSqlEntity;
import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.dao.model.SearchTextEntity;
import org.thingsboard.server.dao.util.mapping.JsonBinaryType; import org.thingsboard.server.dao.util.mapping.JsonBinaryType;
import org.thingsboard.server.dao.util.mapping.JsonStringType; import org.thingsboard.server.dao.util.mapping.JsonStringType;
@ -47,7 +46,7 @@ import java.util.UUID;
@TypeDef(name = "jsonb", typeClass = JsonBinaryType.class) @TypeDef(name = "jsonb", typeClass = JsonBinaryType.class)
}) })
@MappedSuperclass @MappedSuperclass
public abstract class AbstractDeviceEntity<T extends Device> extends BaseSqlEntity<T> implements SearchTextEntity<T> { public abstract class AbstractDeviceEntity<T extends Device> extends BaseSqlEntity<T> {
@Column(name = ModelConstants.DEVICE_TENANT_ID_PROPERTY, columnDefinition = "uuid") @Column(name = ModelConstants.DEVICE_TENANT_ID_PROPERTY, columnDefinition = "uuid")
private UUID tenantId; private UUID tenantId;
@ -64,9 +63,6 @@ public abstract class AbstractDeviceEntity<T extends Device> extends BaseSqlEnti
@Column(name = ModelConstants.DEVICE_LABEL_PROPERTY) @Column(name = ModelConstants.DEVICE_LABEL_PROPERTY)
private String label; private String label;
@Column(name = ModelConstants.SEARCH_TEXT_PROPERTY)
private String searchText;
@Type(type = "json") @Type(type = "json")
@Column(name = ModelConstants.DEVICE_ADDITIONAL_INFO_PROPERTY) @Column(name = ModelConstants.DEVICE_ADDITIONAL_INFO_PROPERTY)
private JsonNode additionalInfo; private JsonNode additionalInfo;
@ -131,23 +127,12 @@ public abstract class AbstractDeviceEntity<T extends Device> extends BaseSqlEnti
this.type = deviceEntity.getType(); this.type = deviceEntity.getType();
this.name = deviceEntity.getName(); this.name = deviceEntity.getName();
this.label = deviceEntity.getLabel(); this.label = deviceEntity.getLabel();
this.searchText = deviceEntity.getSearchText();
this.additionalInfo = deviceEntity.getAdditionalInfo(); this.additionalInfo = deviceEntity.getAdditionalInfo();
this.firmwareId = deviceEntity.getFirmwareId(); this.firmwareId = deviceEntity.getFirmwareId();
this.softwareId = deviceEntity.getSoftwareId(); this.softwareId = deviceEntity.getSoftwareId();
this.externalId = deviceEntity.getExternalId(); this.externalId = deviceEntity.getExternalId();
} }
@Override
public String getSearchTextSource() {
return name;
}
@Override
public void setSearchText(String searchText) {
this.searchText = searchText;
}
protected Device toDevice() { protected Device toDevice() {
Device device = new Device(new DeviceId(getUuid())); Device device = new Device(new DeviceId(getUuid()));
device.setCreatedTime(createdTime); device.setCreatedTime(createdTime);

6
dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationRuleEntity.java

@ -72,6 +72,9 @@ public class NotificationRuleEntity extends BaseSqlEntity<NotificationRule> {
@Column(name = ModelConstants.NOTIFICATION_RULE_ADDITIONAL_CONFIG_PROPERTY) @Column(name = ModelConstants.NOTIFICATION_RULE_ADDITIONAL_CONFIG_PROPERTY)
private JsonNode additionalConfig; private JsonNode additionalConfig;
@Column(name = ModelConstants.EXTERNAL_ID_PROPERTY)
private UUID externalId;
public NotificationRuleEntity() {} public NotificationRuleEntity() {}
public NotificationRuleEntity(NotificationRule notificationRule) { public NotificationRuleEntity(NotificationRule notificationRule) {
@ -85,6 +88,7 @@ public class NotificationRuleEntity extends BaseSqlEntity<NotificationRule> {
setTriggerConfig(toJson(notificationRule.getTriggerConfig())); setTriggerConfig(toJson(notificationRule.getTriggerConfig()));
setRecipientsConfig(toJson(notificationRule.getRecipientsConfig())); setRecipientsConfig(toJson(notificationRule.getRecipientsConfig()));
setAdditionalConfig(toJson(notificationRule.getAdditionalConfig())); setAdditionalConfig(toJson(notificationRule.getAdditionalConfig()));
setExternalId(getUuid(notificationRule.getExternalId()));
} }
public NotificationRuleEntity(NotificationRuleEntity other) { public NotificationRuleEntity(NotificationRuleEntity other) {
@ -98,6 +102,7 @@ public class NotificationRuleEntity extends BaseSqlEntity<NotificationRule> {
this.triggerConfig = other.triggerConfig; this.triggerConfig = other.triggerConfig;
this.recipientsConfig = other.recipientsConfig; this.recipientsConfig = other.recipientsConfig;
this.additionalConfig = other.additionalConfig; this.additionalConfig = other.additionalConfig;
this.externalId = other.externalId;
} }
@Override @Override
@ -113,6 +118,7 @@ public class NotificationRuleEntity extends BaseSqlEntity<NotificationRule> {
notificationRule.setTriggerConfig(fromJson(triggerConfig, NotificationRuleTriggerConfig.class)); notificationRule.setTriggerConfig(fromJson(triggerConfig, NotificationRuleTriggerConfig.class));
notificationRule.setRecipientsConfig(fromJson(recipientsConfig, NotificationRuleRecipientsConfig.class)); notificationRule.setRecipientsConfig(fromJson(recipientsConfig, NotificationRuleRecipientsConfig.class));
notificationRule.setAdditionalConfig(fromJson(additionalConfig, NotificationRuleConfig.class)); notificationRule.setAdditionalConfig(fromJson(additionalConfig, NotificationRuleConfig.class));
notificationRule.setExternalId(getEntityId(externalId, NotificationRuleId::new));
return notificationRule; return notificationRule;
} }

5
dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationTargetEntity.java

@ -49,6 +49,9 @@ public class NotificationTargetEntity extends BaseSqlEntity<NotificationTarget>
@Column(name = ModelConstants.NOTIFICATION_TARGET_CONFIGURATION_PROPERTY, nullable = false) @Column(name = ModelConstants.NOTIFICATION_TARGET_CONFIGURATION_PROPERTY, nullable = false)
private JsonNode configuration; private JsonNode configuration;
@Column(name = ModelConstants.EXTERNAL_ID_PROPERTY)
private UUID externalId;
public NotificationTargetEntity() {} public NotificationTargetEntity() {}
public NotificationTargetEntity(NotificationTarget notificationTarget) { public NotificationTargetEntity(NotificationTarget notificationTarget) {
@ -57,6 +60,7 @@ public class NotificationTargetEntity extends BaseSqlEntity<NotificationTarget>
setTenantId(getTenantUuid(notificationTarget.getTenantId())); setTenantId(getTenantUuid(notificationTarget.getTenantId()));
setName(notificationTarget.getName()); setName(notificationTarget.getName());
setConfiguration(toJson(notificationTarget.getConfiguration())); setConfiguration(toJson(notificationTarget.getConfiguration()));
setExternalId(getUuid(notificationTarget.getExternalId()));
} }
@Override @Override
@ -67,6 +71,7 @@ public class NotificationTargetEntity extends BaseSqlEntity<NotificationTarget>
notificationTarget.setTenantId(getTenantId(tenantId)); notificationTarget.setTenantId(getTenantId(tenantId));
notificationTarget.setName(name); notificationTarget.setName(name);
notificationTarget.setConfiguration(fromJson(configuration, NotificationTargetConfig.class)); notificationTarget.setConfiguration(fromJson(configuration, NotificationTargetConfig.class));
notificationTarget.setExternalId(getEntityId(externalId, NotificationTargetId::new));
return notificationTarget; return notificationTarget;
} }

5
dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationTemplateEntity.java

@ -56,6 +56,9 @@ public class NotificationTemplateEntity extends BaseSqlEntity<NotificationTempla
@Column(name = ModelConstants.NOTIFICATION_TEMPLATE_CONFIGURATION_PROPERTY, nullable = false) @Column(name = ModelConstants.NOTIFICATION_TEMPLATE_CONFIGURATION_PROPERTY, nullable = false)
private JsonNode configuration; private JsonNode configuration;
@Column(name = ModelConstants.EXTERNAL_ID_PROPERTY)
private UUID externalId;
public NotificationTemplateEntity() {} public NotificationTemplateEntity() {}
public NotificationTemplateEntity(NotificationTemplate notificationTemplate) { public NotificationTemplateEntity(NotificationTemplate notificationTemplate) {
@ -65,6 +68,7 @@ public class NotificationTemplateEntity extends BaseSqlEntity<NotificationTempla
setName(notificationTemplate.getName()); setName(notificationTemplate.getName());
setNotificationType(notificationTemplate.getNotificationType()); setNotificationType(notificationTemplate.getNotificationType());
setConfiguration(toJson(notificationTemplate.getConfiguration())); setConfiguration(toJson(notificationTemplate.getConfiguration()));
setExternalId(getUuid(notificationTemplate.getExternalId()));
} }
@Override @Override
@ -76,6 +80,7 @@ public class NotificationTemplateEntity extends BaseSqlEntity<NotificationTempla
notificationTemplate.setName(name); notificationTemplate.setName(name);
notificationTemplate.setNotificationType(notificationType); notificationTemplate.setNotificationType(notificationType);
notificationTemplate.setConfiguration(fromJson(configuration, NotificationTemplateConfig.class)); notificationTemplate.setConfiguration(fromJson(configuration, NotificationTemplateConfig.class));
notificationTemplate.setExternalId(getEntityId(externalId, NotificationTemplateId::new));
return notificationTemplate; return notificationTemplate;
} }

6
dao/src/main/java/org/thingsboard/server/dao/nosql/CassandraBufferedRateReadExecutor.java

@ -24,10 +24,10 @@ import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.thingsboard.server.common.stats.StatsFactory; import org.thingsboard.server.common.stats.StatsFactory;
import org.thingsboard.server.dao.entity.EntityService; import org.thingsboard.server.dao.entity.EntityService;
import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import org.thingsboard.server.dao.util.AbstractBufferedRateExecutor; import org.thingsboard.server.dao.util.AbstractBufferedRateExecutor;
import org.thingsboard.server.dao.util.AsyncTaskContext; import org.thingsboard.server.dao.util.AsyncTaskContext;
import org.thingsboard.server.dao.util.NoSqlAnyDao; import org.thingsboard.server.dao.util.NoSqlAnyDao;
import org.thingsboard.server.dao.util.limits.RateLimitService;
import javax.annotation.PreDestroy; import javax.annotation.PreDestroy;
@ -52,9 +52,9 @@ public class CassandraBufferedRateReadExecutor extends AbstractBufferedRateExecu
@Value("${cassandra.query.print_queries_freq:0}") int printQueriesFreq, @Value("${cassandra.query.print_queries_freq:0}") int printQueriesFreq,
@Autowired StatsFactory statsFactory, @Autowired StatsFactory statsFactory,
@Autowired EntityService entityService, @Autowired EntityService entityService,
@Autowired TbTenantProfileCache tenantProfileCache) { @Autowired RateLimitService rateLimitService) {
super(queueLimit, concurrencyLimit, maxWaitTime, dispatcherThreads, callbackThreads, pollMs, printQueriesFreq, statsFactory, super(queueLimit, concurrencyLimit, maxWaitTime, dispatcherThreads, callbackThreads, pollMs, printQueriesFreq, statsFactory,
entityService, tenantProfileCache, printTenantNames); entityService, rateLimitService, printTenantNames);
} }
@Scheduled(fixedDelayString = "${cassandra.query.rate_limit_print_interval_ms}") @Scheduled(fixedDelayString = "${cassandra.query.rate_limit_print_interval_ms}")

6
dao/src/main/java/org/thingsboard/server/dao/nosql/CassandraBufferedRateWriteExecutor.java

@ -24,10 +24,10 @@ import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.thingsboard.server.common.stats.StatsFactory; import org.thingsboard.server.common.stats.StatsFactory;
import org.thingsboard.server.dao.entity.EntityService; import org.thingsboard.server.dao.entity.EntityService;
import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import org.thingsboard.server.dao.util.AbstractBufferedRateExecutor; import org.thingsboard.server.dao.util.AbstractBufferedRateExecutor;
import org.thingsboard.server.dao.util.AsyncTaskContext; import org.thingsboard.server.dao.util.AsyncTaskContext;
import org.thingsboard.server.dao.util.NoSqlAnyDao; import org.thingsboard.server.dao.util.NoSqlAnyDao;
import org.thingsboard.server.dao.util.limits.RateLimitService;
import javax.annotation.PreDestroy; import javax.annotation.PreDestroy;
@ -52,9 +52,9 @@ public class CassandraBufferedRateWriteExecutor extends AbstractBufferedRateExec
@Value("${cassandra.query.print_queries_freq:0}") int printQueriesFreq, @Value("${cassandra.query.print_queries_freq:0}") int printQueriesFreq,
@Autowired StatsFactory statsFactory, @Autowired StatsFactory statsFactory,
@Autowired EntityService entityService, @Autowired EntityService entityService,
@Autowired TbTenantProfileCache tenantProfileCache) { @Autowired RateLimitService rateLimitService) {
super(queueLimit, concurrencyLimit, maxWaitTime, dispatcherThreads, callbackThreads, pollMs, printQueriesFreq, statsFactory, super(queueLimit, concurrencyLimit, maxWaitTime, dispatcherThreads, callbackThreads, pollMs, printQueriesFreq, statsFactory,
entityService, tenantProfileCache, printTenantNames); entityService, rateLimitService, printTenantNames);
} }
@Scheduled(fixedDelayString = "${cassandra.query.rate_limit_print_interval_ms}") @Scheduled(fixedDelayString = "${cassandra.query.rate_limit_print_interval_ms}")

5
dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationRuleService.java

@ -98,6 +98,11 @@ public class DefaultNotificationRuleService extends AbstractEntityService implem
return Optional.ofNullable(findNotificationRuleById(tenantId, new NotificationRuleId(entityId.getId()))); return Optional.ofNullable(findNotificationRuleById(tenantId, new NotificationRuleId(entityId.getId())));
} }
@Override
public void deleteEntity(TenantId tenantId, EntityId id) {
deleteNotificationRuleById(tenantId, (NotificationRuleId) id);
}
@Override @Override
public EntityType getEntityType() { public EntityType getEntityType() {
return EntityType.NOTIFICATION_RULE; return EntityType.NOTIFICATION_RULE;

5
dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationTargetService.java

@ -202,6 +202,11 @@ public class DefaultNotificationTargetService extends AbstractEntityService impl
return Optional.ofNullable(findNotificationTargetById(tenantId, new NotificationTargetId(entityId.getId()))); return Optional.ofNullable(findNotificationTargetById(tenantId, new NotificationTargetId(entityId.getId())));
} }
@Override
public void deleteEntity(TenantId tenantId, EntityId id) {
deleteNotificationTargetById(tenantId, (NotificationTargetId) id);
}
@Override @Override
public EntityType getEntityType() { public EntityType getEntityType() {
return EntityType.NOTIFICATION_TARGET; return EntityType.NOTIFICATION_TARGET;

5
dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationTemplateService.java

@ -94,6 +94,11 @@ public class DefaultNotificationTemplateService extends AbstractEntityService im
return Optional.ofNullable(findNotificationTemplateById(tenantId, new NotificationTemplateId(entityId.getId()))); return Optional.ofNullable(findNotificationTemplateById(tenantId, new NotificationTemplateId(entityId.getId())));
} }
@Override
public void deleteEntity(TenantId tenantId, EntityId id) {
deleteNotificationTemplateById(tenantId, (NotificationTemplateId) id);
}
@Override @Override
public EntityType getEntityType() { public EntityType getEntityType() {
return EntityType.NOTIFICATION_TEMPLATE; return EntityType.NOTIFICATION_TEMPLATE;

3
dao/src/main/java/org/thingsboard/server/dao/notification/NotificationRuleDao.java

@ -24,10 +24,11 @@ import org.thingsboard.server.common.data.notification.rule.trigger.Notification
import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.dao.Dao; import org.thingsboard.server.dao.Dao;
import org.thingsboard.server.dao.ExportableEntityDao;
import java.util.List; import java.util.List;
public interface NotificationRuleDao extends Dao<NotificationRule> { public interface NotificationRuleDao extends Dao<NotificationRule>, ExportableEntityDao<NotificationRuleId, NotificationRule> {
PageData<NotificationRule> findByTenantIdAndPageLink(TenantId tenantId, PageLink pageLink); PageData<NotificationRule> findByTenantIdAndPageLink(TenantId tenantId, PageLink pageLink);

3
dao/src/main/java/org/thingsboard/server/dao/notification/NotificationTargetDao.java

@ -22,11 +22,12 @@ import org.thingsboard.server.common.data.notification.targets.NotificationTarge
import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.dao.Dao; import org.thingsboard.server.dao.Dao;
import org.thingsboard.server.dao.ExportableEntityDao;
import org.thingsboard.server.dao.TenantEntityDao; import org.thingsboard.server.dao.TenantEntityDao;
import java.util.List; import java.util.List;
public interface NotificationTargetDao extends Dao<NotificationTarget>, TenantEntityDao { public interface NotificationTargetDao extends Dao<NotificationTarget>, TenantEntityDao, ExportableEntityDao<NotificationTargetId, NotificationTarget> {
PageData<NotificationTarget> findByTenantIdAndPageLink(TenantId tenantId, PageLink pageLink); PageData<NotificationTarget> findByTenantIdAndPageLink(TenantId tenantId, PageLink pageLink);

4
dao/src/main/java/org/thingsboard/server/dao/notification/NotificationTemplateDao.java

@ -15,16 +15,18 @@
*/ */
package org.thingsboard.server.dao.notification; package org.thingsboard.server.dao.notification;
import org.thingsboard.server.common.data.id.NotificationTemplateId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.notification.NotificationType; import org.thingsboard.server.common.data.notification.NotificationType;
import org.thingsboard.server.common.data.notification.template.NotificationTemplate; import org.thingsboard.server.common.data.notification.template.NotificationTemplate;
import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.dao.Dao; import org.thingsboard.server.dao.Dao;
import org.thingsboard.server.dao.ExportableEntityDao;
import java.util.List; import java.util.List;
public interface NotificationTemplateDao extends Dao<NotificationTemplate> { public interface NotificationTemplateDao extends Dao<NotificationTemplate>, ExportableEntityDao<NotificationTemplateId, NotificationTemplate> {
PageData<NotificationTemplate> findByTenantIdAndNotificationTypesAndPageLink(TenantId tenantId, List<NotificationType> notificationTypes, PageLink pageLink); PageData<NotificationTemplate> findByTenantIdAndNotificationTypesAndPageLink(TenantId tenantId, List<NotificationType> notificationTypes, PageLink pageLink);

6
dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java

@ -754,6 +754,12 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC
return ruleChainDao.countByTenantId(tenantId); return ruleChainDao.countByTenantId(tenantId);
} }
@Override
@Transactional
public void deleteEntity(TenantId tenantId, EntityId id) {
deleteRuleChainById(tenantId, (RuleChainId) id);
}
@Override @Override
public EntityType getEntityType() { public EntityType getEntityType() {
return EntityType.RULE_CHAIN; return EntityType.RULE_CHAIN;

2
dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDao.java

@ -348,7 +348,7 @@ public class JpaAlarmDao extends JpaAbstractDao<AlarmEntity, Alarm> implements A
@Override @Override
public AlarmApiCallResult clearAlarm(TenantId tenantId, AlarmId id, long clearTs, JsonNode details) { public AlarmApiCallResult clearAlarm(TenantId tenantId, AlarmId id, long clearTs, JsonNode details) {
return toAlarmApiResult(alarmRepository.clearAlarm(tenantId.getId(), id.getId(), clearTs, getDetailsAsString(details))); return toAlarmApiResult(alarmRepository.clearAlarm(tenantId.getId(), id.getId(), clearTs, details != null ? getDetailsAsString(details) : null));
} }
@Override @Override

42
dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceRepository.java

@ -35,27 +35,30 @@ public interface DeviceRepository extends JpaRepository<DeviceEntity, UUID>, Exp
@Query("SELECT d FROM DeviceEntity d WHERE d.tenantId = :tenantId " + @Query("SELECT d FROM DeviceEntity d WHERE d.tenantId = :tenantId " +
"AND d.customerId = :customerId " + "AND d.customerId = :customerId " +
"AND LOWER(d.searchText) LIKE LOWER(CONCAT('%', :searchText, '%'))") "AND (LOWER(d.name) LIKE LOWER(CONCAT('%', :textSearch, '%')) " +
"OR LOWER(d.label) LIKE LOWER(CONCAT('%', :textSearch, '%')))")
Page<DeviceEntity> findByTenantIdAndCustomerId(@Param("tenantId") UUID tenantId, Page<DeviceEntity> findByTenantIdAndCustomerId(@Param("tenantId") UUID tenantId,
@Param("customerId") UUID customerId, @Param("customerId") UUID customerId,
@Param("searchText") String searchText, @Param("textSearch") String textSearch,
Pageable pageable); Pageable pageable);
@Query("SELECT d FROM DeviceEntity d WHERE d.tenantId = :tenantId " + @Query("SELECT d FROM DeviceEntity d WHERE d.tenantId = :tenantId " +
"AND d.deviceProfileId = :profileId " + "AND d.deviceProfileId = :profileId " +
"AND LOWER(d.searchText) LIKE LOWER(CONCAT('%', :searchText, '%'))") "AND (LOWER(d.name) LIKE LOWER(CONCAT('%', :textSearch, '%')) " +
"OR LOWER(d.label) LIKE LOWER(CONCAT('%', :textSearch, '%')))")
Page<DeviceEntity> findByTenantIdAndProfileId(@Param("tenantId") UUID tenantId, Page<DeviceEntity> findByTenantIdAndProfileId(@Param("tenantId") UUID tenantId,
@Param("profileId") UUID profileId, @Param("profileId") UUID profileId,
@Param("searchText") String searchText, @Param("textSearch") String textSearch,
Pageable pageable); Pageable pageable);
@Query("SELECT d FROM DeviceInfoEntity d " + @Query("SELECT d FROM DeviceInfoEntity d " +
"WHERE d.tenantId = :tenantId " + "WHERE d.tenantId = :tenantId " +
"AND d.customerId = :customerId " + "AND d.customerId = :customerId " +
"AND LOWER(d.searchText) LIKE LOWER(CONCAT('%', :searchText, '%'))") "AND (LOWER(d.name) LIKE LOWER(CONCAT('%', :textSearch, '%')) " +
"OR LOWER(d.label) LIKE LOWER(CONCAT('%', :textSearch, '%')))")
Page<DeviceInfoEntity> findDeviceInfosByTenantIdAndCustomerId(@Param("tenantId") UUID tenantId, Page<DeviceInfoEntity> findDeviceInfosByTenantIdAndCustomerId(@Param("tenantId") UUID tenantId,
@Param("customerId") UUID customerId, @Param("customerId") UUID customerId,
@Param("searchText") String searchText, @Param("textSearch") String textSearch,
Pageable pageable); Pageable pageable);
@Query("SELECT d FROM DeviceEntity d WHERE d.tenantId = :tenantId") @Query("SELECT d FROM DeviceEntity d WHERE d.tenantId = :tenantId")
@ -63,14 +66,16 @@ public interface DeviceRepository extends JpaRepository<DeviceEntity, UUID>, Exp
Pageable pageable); Pageable pageable);
@Query("SELECT d FROM DeviceEntity d WHERE d.tenantId = :tenantId " + @Query("SELECT d FROM DeviceEntity d WHERE d.tenantId = :tenantId " +
"AND LOWER(d.searchText) LIKE LOWER(CONCAT('%', :textSearch, '%'))") "AND (LOWER(d.name) LIKE LOWER(CONCAT('%', :textSearch, '%')) " +
"OR LOWER(d.label) LIKE LOWER(CONCAT('%', :textSearch, '%')))")
Page<DeviceEntity> findByTenantId(@Param("tenantId") UUID tenantId, Page<DeviceEntity> findByTenantId(@Param("tenantId") UUID tenantId,
@Param("textSearch") String textSearch, @Param("textSearch") String textSearch,
Pageable pageable); Pageable pageable);
@Query("SELECT d FROM DeviceEntity d WHERE d.tenantId = :tenantId " + @Query("SELECT d FROM DeviceEntity d WHERE d.tenantId = :tenantId " +
"AND d.type = :type " + "AND d.type = :type " +
"AND LOWER(d.searchText) LIKE LOWER(CONCAT('%', :textSearch, '%'))") "AND (LOWER(d.name) LIKE LOWER(CONCAT('%', :textSearch, '%')) " +
"OR LOWER(d.label) LIKE LOWER(CONCAT('%', :textSearch, '%')))")
Page<DeviceEntity> findByTenantIdAndType(@Param("tenantId") UUID tenantId, Page<DeviceEntity> findByTenantIdAndType(@Param("tenantId") UUID tenantId,
@Param("type") String type, @Param("type") String type,
@Param("textSearch") String textSearch, @Param("textSearch") String textSearch,
@ -79,7 +84,8 @@ public interface DeviceRepository extends JpaRepository<DeviceEntity, UUID>, Exp
@Query("SELECT d FROM DeviceEntity d WHERE d.tenantId = :tenantId " + @Query("SELECT d FROM DeviceEntity d WHERE d.tenantId = :tenantId " +
"AND d.deviceProfileId = :deviceProfileId " + "AND d.deviceProfileId = :deviceProfileId " +
"AND d.firmwareId = null " + "AND d.firmwareId = null " +
"AND LOWER(d.searchText) LIKE LOWER(CONCAT('%', :textSearch, '%'))") "AND (LOWER(d.name) LIKE LOWER(CONCAT('%', :textSearch, '%')) " +
"OR LOWER(d.label) LIKE LOWER(CONCAT('%', :textSearch, '%')))")
Page<DeviceEntity> findByTenantIdAndTypeAndFirmwareIdIsNull(@Param("tenantId") UUID tenantId, Page<DeviceEntity> findByTenantIdAndTypeAndFirmwareIdIsNull(@Param("tenantId") UUID tenantId,
@Param("deviceProfileId") UUID deviceProfileId, @Param("deviceProfileId") UUID deviceProfileId,
@Param("textSearch") String textSearch, @Param("textSearch") String textSearch,
@ -88,7 +94,8 @@ public interface DeviceRepository extends JpaRepository<DeviceEntity, UUID>, Exp
@Query("SELECT d FROM DeviceEntity d WHERE d.tenantId = :tenantId " + @Query("SELECT d FROM DeviceEntity d WHERE d.tenantId = :tenantId " +
"AND d.deviceProfileId = :deviceProfileId " + "AND d.deviceProfileId = :deviceProfileId " +
"AND d.softwareId = null " + "AND d.softwareId = null " +
"AND LOWER(d.searchText) LIKE LOWER(CONCAT('%', :textSearch, '%'))") "AND (LOWER(d.name) LIKE LOWER(CONCAT('%', :textSearch, '%')) " +
"OR LOWER(d.label) LIKE LOWER(CONCAT('%', :textSearch, '%')))")
Page<DeviceEntity> findByTenantIdAndTypeAndSoftwareIdIsNull(@Param("tenantId") UUID tenantId, Page<DeviceEntity> findByTenantIdAndTypeAndSoftwareIdIsNull(@Param("tenantId") UUID tenantId,
@Param("deviceProfileId") UUID deviceProfileId, @Param("deviceProfileId") UUID deviceProfileId,
@Param("textSearch") String textSearch, @Param("textSearch") String textSearch,
@ -109,7 +116,8 @@ public interface DeviceRepository extends JpaRepository<DeviceEntity, UUID>, Exp
@Query("SELECT d FROM DeviceEntity d WHERE d.tenantId = :tenantId " + @Query("SELECT d FROM DeviceEntity d WHERE d.tenantId = :tenantId " +
"AND d.customerId = :customerId " + "AND d.customerId = :customerId " +
"AND d.type = :type " + "AND d.type = :type " +
"AND LOWER(d.searchText) LIKE LOWER(CONCAT('%', :textSearch, '%'))") "AND (LOWER(d.name) LIKE LOWER(CONCAT('%', :textSearch, '%')) " +
"OR LOWER(d.label) LIKE LOWER(CONCAT('%', :textSearch, '%')))")
Page<DeviceEntity> findByTenantIdAndCustomerIdAndType(@Param("tenantId") UUID tenantId, Page<DeviceEntity> findByTenantIdAndCustomerIdAndType(@Param("tenantId") UUID tenantId,
@Param("customerId") UUID customerId, @Param("customerId") UUID customerId,
@Param("type") String type, @Param("type") String type,
@ -123,7 +131,7 @@ public interface DeviceRepository extends JpaRepository<DeviceEntity, UUID>, Exp
"AND ((:deviceType) IS NULL OR d.type = :deviceType) " + "AND ((:deviceType) IS NULL OR d.type = :deviceType) " +
"AND (:deviceProfileId IS NULL OR d.deviceProfileId = uuid(:deviceProfileId)) " + "AND (:deviceProfileId IS NULL OR d.deviceProfileId = uuid(:deviceProfileId)) " +
"AND ((:filterByActive) IS FALSE OR d.active = :deviceActive) " + "AND ((:filterByActive) IS FALSE OR d.active = :deviceActive) " +
"AND (LOWER(d.searchText) LIKE LOWER(CONCAT('%', :textSearch, '%')) " + "AND (LOWER(d.name) LIKE LOWER(CONCAT('%', :textSearch, '%')) " +
"OR LOWER(d.label) LIKE LOWER(CONCAT('%', :textSearch, '%')) " + "OR LOWER(d.label) LIKE LOWER(CONCAT('%', :textSearch, '%')) " +
"OR LOWER(d.type) LIKE LOWER(CONCAT('%', :textSearch, '%')) " + "OR LOWER(d.type) LIKE LOWER(CONCAT('%', :textSearch, '%')) " +
"OR LOWER(d.customerTitle) LIKE LOWER(CONCAT('%', :textSearch, '%')))") "OR LOWER(d.customerTitle) LIKE LOWER(CONCAT('%', :textSearch, '%')))")
@ -155,21 +163,23 @@ public interface DeviceRepository extends JpaRepository<DeviceEntity, UUID>, Exp
@Query("SELECT d FROM DeviceEntity d, RelationEntity re WHERE d.tenantId = :tenantId " + @Query("SELECT d FROM DeviceEntity d, RelationEntity re WHERE d.tenantId = :tenantId " +
"AND d.id = re.toId AND re.toType = 'DEVICE' AND re.relationTypeGroup = 'EDGE' " + "AND d.id = re.toId AND re.toType = 'DEVICE' AND re.relationTypeGroup = 'EDGE' " +
"AND re.relationType = 'Contains' AND re.fromId = :edgeId AND re.fromType = 'EDGE' " + "AND re.relationType = 'Contains' AND re.fromId = :edgeId AND re.fromType = 'EDGE' " +
"AND LOWER(d.searchText) LIKE LOWER(CONCAT('%', :searchText, '%'))") "AND (LOWER(d.name) LIKE LOWER(CONCAT('%', :textSearch, '%')) " +
"OR LOWER(d.label) LIKE LOWER(CONCAT('%', :textSearch, '%')))")
Page<DeviceEntity> findByTenantIdAndEdgeId(@Param("tenantId") UUID tenantId, Page<DeviceEntity> findByTenantIdAndEdgeId(@Param("tenantId") UUID tenantId,
@Param("edgeId") UUID edgeId, @Param("edgeId") UUID edgeId,
@Param("searchText") String searchText, @Param("textSearch") String textSearch,
Pageable pageable); Pageable pageable);
@Query("SELECT d FROM DeviceEntity d, RelationEntity re WHERE d.tenantId = :tenantId " + @Query("SELECT d FROM DeviceEntity d, RelationEntity re WHERE d.tenantId = :tenantId " +
"AND d.id = re.toId AND re.toType = 'DEVICE' AND re.relationTypeGroup = 'EDGE' " + "AND d.id = re.toId AND re.toType = 'DEVICE' AND re.relationTypeGroup = 'EDGE' " +
"AND re.relationType = 'Contains' AND re.fromId = :edgeId AND re.fromType = 'EDGE' " + "AND re.relationType = 'Contains' AND re.fromId = :edgeId AND re.fromType = 'EDGE' " +
"AND d.type = :type " + "AND d.type = :type " +
"AND LOWER(d.searchText) LIKE LOWER(CONCAT('%', :searchText, '%'))") "AND (LOWER(d.name) LIKE LOWER(CONCAT('%', :textSearch, '%')) " +
"OR LOWER(d.label) LIKE LOWER(CONCAT('%', :textSearch, '%')))")
Page<DeviceEntity> findByTenantIdAndEdgeIdAndType(@Param("tenantId") UUID tenantId, Page<DeviceEntity> findByTenantIdAndEdgeIdAndType(@Param("tenantId") UUID tenantId,
@Param("edgeId") UUID edgeId, @Param("edgeId") UUID edgeId,
@Param("type") String type, @Param("type") String type,
@Param("searchText") String searchText, @Param("textSearch") String textSearch,
Pageable pageable); Pageable pageable);
/** /**

4
dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceDao.java

@ -40,7 +40,7 @@ import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.dao.DaoUtil; import org.thingsboard.server.dao.DaoUtil;
import org.thingsboard.server.dao.device.DeviceDao; import org.thingsboard.server.dao.device.DeviceDao;
import org.thingsboard.server.dao.model.sql.DeviceEntity; import org.thingsboard.server.dao.model.sql.DeviceEntity;
import org.thingsboard.server.dao.sql.JpaAbstractSearchTextDao; import org.thingsboard.server.dao.sql.JpaAbstractDao;
import org.thingsboard.server.dao.util.SqlDao; import org.thingsboard.server.dao.util.SqlDao;
import java.util.ArrayList; import java.util.ArrayList;
@ -56,7 +56,7 @@ import java.util.UUID;
@Component @Component
@SqlDao @SqlDao
@Slf4j @Slf4j
public class JpaDeviceDao extends JpaAbstractSearchTextDao<DeviceEntity, Device> implements DeviceDao { public class JpaDeviceDao extends JpaAbstractDao<DeviceEntity, Device> implements DeviceDao {
@Autowired @Autowired
private DeviceRepository deviceRepository; private DeviceRepository deviceRepository;

20
dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationRuleDao.java

@ -82,6 +82,26 @@ public class JpaNotificationRuleDao extends JpaAbstractDao<NotificationRuleEntit
notificationRuleRepository.deleteByTenantId(tenantId.getId()); notificationRuleRepository.deleteByTenantId(tenantId.getId());
} }
@Override
public NotificationRule findByTenantIdAndExternalId(UUID tenantId, UUID externalId) {
return DaoUtil.getData(notificationRuleRepository.findByTenantIdAndExternalId(tenantId, externalId));
}
@Override
public NotificationRule findByTenantIdAndName(UUID tenantId, String name) {
return DaoUtil.getData(notificationRuleRepository.findByTenantIdAndName(tenantId, name));
}
@Override
public PageData<NotificationRule> findByTenantId(UUID tenantId, PageLink pageLink) {
return DaoUtil.toPageData(notificationRuleRepository.findByTenantId(tenantId, DaoUtil.toPageable(pageLink)));
}
@Override
public NotificationRuleId getExternalIdByInternal(NotificationRuleId internalId) {
return DaoUtil.toEntityId(notificationRuleRepository.getExternalIdByInternal(internalId.getId()), NotificationRuleId::new);
}
@Override @Override
protected Class<NotificationRuleEntity> getEntityClass() { protected Class<NotificationRuleEntity> getEntityClass() {
return NotificationRuleEntity.class; return NotificationRuleEntity.class;

20
dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationTargetDao.java

@ -76,6 +76,26 @@ public class JpaNotificationTargetDao extends JpaAbstractDao<NotificationTargetE
return notificationTargetRepository.countByTenantId(tenantId.getId()); return notificationTargetRepository.countByTenantId(tenantId.getId());
} }
@Override
public NotificationTarget findByTenantIdAndExternalId(UUID tenantId, UUID externalId) {
return DaoUtil.getData(notificationTargetRepository.findByTenantIdAndExternalId(tenantId, externalId));
}
@Override
public NotificationTarget findByTenantIdAndName(UUID tenantId, String name) {
return DaoUtil.getData(notificationTargetRepository.findByTenantIdAndName(tenantId, name));
}
@Override
public PageData<NotificationTarget> findByTenantId(UUID tenantId, PageLink pageLink) {
return DaoUtil.toPageData(notificationTargetRepository.findByTenantId(tenantId, DaoUtil.toPageable(pageLink)));
}
@Override
public NotificationTargetId getExternalIdByInternal(NotificationTargetId internalId) {
return DaoUtil.toEntityId(notificationTargetRepository.getExternalIdByInternal(internalId.getId()), NotificationTargetId::new);
}
@Override @Override
protected Class<NotificationTargetEntity> getEntityClass() { protected Class<NotificationTargetEntity> getEntityClass() {
return NotificationTargetEntity.class; return NotificationTargetEntity.class;

21
dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationTemplateDao.java

@ -20,6 +20,7 @@ import lombok.RequiredArgsConstructor;
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.id.NotificationTemplateId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.notification.NotificationType; import org.thingsboard.server.common.data.notification.NotificationType;
import org.thingsboard.server.common.data.notification.template.NotificationTemplate; import org.thingsboard.server.common.data.notification.template.NotificationTemplate;
@ -57,6 +58,26 @@ public class JpaNotificationTemplateDao extends JpaAbstractDao<NotificationTempl
notificationTemplateRepository.deleteByTenantId(tenantId.getId()); notificationTemplateRepository.deleteByTenantId(tenantId.getId());
} }
@Override
public NotificationTemplate findByTenantIdAndExternalId(UUID tenantId, UUID externalId) {
return DaoUtil.getData(notificationTemplateRepository.findByTenantIdAndExternalId(tenantId, externalId));
}
@Override
public NotificationTemplate findByTenantIdAndName(UUID tenantId, String name) {
return DaoUtil.getData(notificationTemplateRepository.findByTenantIdAndName(tenantId, name));
}
@Override
public PageData<NotificationTemplate> findByTenantId(UUID tenantId, PageLink pageLink) {
return DaoUtil.toPageData(notificationTemplateRepository.findByTenantId(tenantId, DaoUtil.toPageable(pageLink)));
}
@Override
public NotificationTemplateId getExternalIdByInternal(NotificationTemplateId internalId) {
return DaoUtil.toEntityId(notificationTemplateRepository.getExternalIdByInternal(internalId.getId()), NotificationTemplateId::new);
}
@Override @Override
protected JpaRepository<NotificationTemplateEntity, UUID> getRepository() { protected JpaRepository<NotificationTemplateEntity, UUID> getRepository() {
return notificationTemplateRepository; return notificationTemplateRepository;

10
dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationRuleRepository.java

@ -23,6 +23,7 @@ import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository; import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType; import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType;
import org.thingsboard.server.dao.ExportableEntityRepository;
import org.thingsboard.server.dao.model.sql.NotificationRuleEntity; import org.thingsboard.server.dao.model.sql.NotificationRuleEntity;
import org.thingsboard.server.dao.model.sql.NotificationRuleInfoEntity; import org.thingsboard.server.dao.model.sql.NotificationRuleInfoEntity;
@ -30,7 +31,7 @@ import java.util.List;
import java.util.UUID; import java.util.UUID;
@Repository @Repository
public interface NotificationRuleRepository extends JpaRepository<NotificationRuleEntity, UUID> { public interface NotificationRuleRepository extends JpaRepository<NotificationRuleEntity, UUID>, ExportableEntityRepository<NotificationRuleEntity> {
String RULE_INFO_QUERY = "SELECT new org.thingsboard.server.dao.model.sql.NotificationRuleInfoEntity(r, t.name, t.configuration) " + String RULE_INFO_QUERY = "SELECT new org.thingsboard.server.dao.model.sql.NotificationRuleInfoEntity(r, t.name, t.configuration) " +
"FROM NotificationRuleEntity r INNER JOIN NotificationTemplateEntity t ON r.templateId = t.id"; "FROM NotificationRuleEntity r INNER JOIN NotificationTemplateEntity t ON r.templateId = t.id";
@ -59,4 +60,11 @@ public interface NotificationRuleRepository extends JpaRepository<NotificationRu
@Transactional @Transactional
void deleteByTenantId(UUID tenantId); void deleteByTenantId(UUID tenantId);
NotificationRuleEntity findByTenantIdAndName(UUID tenantId, String name);
Page<NotificationRuleEntity> findByTenantId(UUID tenantId, Pageable pageable);
@Query("SELECT externalId FROM NotificationRuleEntity WHERE id = :id")
UUID getExternalIdByInternal(@Param("id") UUID internalId);
} }

10
dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationTargetRepository.java

@ -22,13 +22,14 @@ import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param; import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository; import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import org.thingsboard.server.dao.ExportableEntityRepository;
import org.thingsboard.server.dao.model.sql.NotificationTargetEntity; import org.thingsboard.server.dao.model.sql.NotificationTargetEntity;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
@Repository @Repository
public interface NotificationTargetRepository extends JpaRepository<NotificationTargetEntity, UUID> { public interface NotificationTargetRepository extends JpaRepository<NotificationTargetEntity, UUID>, ExportableEntityRepository<NotificationTargetEntity> {
@Query("SELECT t FROM NotificationTargetEntity t WHERE t.tenantId = :tenantId " + @Query("SELECT t FROM NotificationTargetEntity t WHERE t.tenantId = :tenantId " +
"AND (:searchText = '' OR lower(t.name) LIKE lower(concat('%', :searchText, '%')))") "AND (:searchText = '' OR lower(t.name) LIKE lower(concat('%', :searchText, '%')))")
@ -52,4 +53,11 @@ public interface NotificationTargetRepository extends JpaRepository<Notification
long countByTenantId(UUID tenantId); long countByTenantId(UUID tenantId);
NotificationTargetEntity findByTenantIdAndName(UUID tenantId, String name);
Page<NotificationTargetEntity> findByTenantId(UUID tenantId, Pageable pageable);
@Query("SELECT externalId FROM NotificationTargetEntity WHERE id = :id")
UUID getExternalIdByInternal(@Param("id") UUID internalId);
} }

10
dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationTemplateRepository.java

@ -23,13 +23,14 @@ import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository; import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import org.thingsboard.server.common.data.notification.NotificationType; import org.thingsboard.server.common.data.notification.NotificationType;
import org.thingsboard.server.dao.ExportableEntityRepository;
import org.thingsboard.server.dao.model.sql.NotificationTemplateEntity; import org.thingsboard.server.dao.model.sql.NotificationTemplateEntity;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
@Repository @Repository
public interface NotificationTemplateRepository extends JpaRepository<NotificationTemplateEntity, UUID> { public interface NotificationTemplateRepository extends JpaRepository<NotificationTemplateEntity, UUID>, ExportableEntityRepository<NotificationTemplateEntity> {
@Query("SELECT t FROM NotificationTemplateEntity t WHERE t.tenantId = :tenantId AND " + @Query("SELECT t FROM NotificationTemplateEntity t WHERE t.tenantId = :tenantId AND " +
"t.notificationType IN :notificationTypes " + "t.notificationType IN :notificationTypes " +
@ -43,4 +44,11 @@ public interface NotificationTemplateRepository extends JpaRepository<Notificati
@Transactional @Transactional
void deleteByTenantId(UUID tenantId); void deleteByTenantId(UUID tenantId);
NotificationTemplateEntity findByTenantIdAndName(UUID tenantId, String name);
Page<NotificationTemplateEntity> findByTenantId(UUID tenantId, Pageable pageable);
@Query("SELECT externalId FROM NotificationTemplateEntity WHERE id = :id")
UUID getExternalIdByInternal(@Param("id") UUID internalId);
} }

44
dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultEntityQueryRepository.java

@ -17,6 +17,7 @@ package org.thingsboard.server.dao.sql.query;
import lombok.Getter; import lombok.Getter;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.stereotype.Repository; import org.springframework.stereotype.Repository;
@ -66,6 +67,7 @@ import java.util.stream.Collectors;
@Slf4j @Slf4j
public class DefaultEntityQueryRepository implements EntityQueryRepository { public class DefaultEntityQueryRepository implements EntityQueryRepository {
private static final Map<EntityType, String> entityTableMap = new HashMap<>(); private static final Map<EntityType, String> entityTableMap = new HashMap<>();
private static final Map<EntityType, String> entityNameColumns = new HashMap<>();
private static final String SELECT_PHONE = " CASE WHEN entity.entity_type = 'TENANT' THEN (select phone from tenant where id = entity_id)" + private static final String SELECT_PHONE = " CASE WHEN entity.entity_type = 'TENANT' THEN (select phone from tenant where id = entity_id)" +
" WHEN entity.entity_type = 'CUSTOMER' THEN (select phone from customer where id = entity_id) END as phone"; " WHEN entity.entity_type = 'CUSTOMER' THEN (select phone from customer where id = entity_id) END as phone";
private static final String SELECT_ZIP = " CASE WHEN entity.entity_type = 'TENANT' THEN (select zip from tenant where id = entity_id)" + private static final String SELECT_ZIP = " CASE WHEN entity.entity_type = 'TENANT' THEN (select zip from tenant where id = entity_id)" +
@ -242,6 +244,24 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository {
entityTableMap.put(EntityType.DEVICE_PROFILE, "device_profile"); entityTableMap.put(EntityType.DEVICE_PROFILE, "device_profile");
entityTableMap.put(EntityType.ASSET_PROFILE, "asset_profile"); entityTableMap.put(EntityType.ASSET_PROFILE, "asset_profile");
entityTableMap.put(EntityType.TENANT_PROFILE, "tenant_profile"); entityTableMap.put(EntityType.TENANT_PROFILE, "tenant_profile");
entityNameColumns.put(EntityType.DEVICE, "name");
entityNameColumns.put(EntityType.CUSTOMER, "title");
entityNameColumns.put(EntityType.DASHBOARD, "title");
entityNameColumns.put(EntityType.RULE_CHAIN, "name");
entityNameColumns.put(EntityType.RULE_NODE, "name");
entityNameColumns.put(EntityType.OTA_PACKAGE, "title");
entityNameColumns.put(EntityType.ASSET_PROFILE, "name");
entityNameColumns.put(EntityType.ASSET, "name");
entityNameColumns.put(EntityType.DEVICE_PROFILE, "name");
entityNameColumns.put(EntityType.USER, "email");
entityNameColumns.put(EntityType.TENANT_PROFILE, "name");
entityNameColumns.put(EntityType.TENANT, "title");
entityNameColumns.put(EntityType.WIDGETS_BUNDLE, "title");
entityNameColumns.put(EntityType.ENTITY_VIEW, "name");
entityNameColumns.put(EntityType.TB_RESOURCE, "search_text");
entityNameColumns.put(EntityType.EDGE, "name");
entityNameColumns.put(EntityType.QUEUE, "name");
} }
public static EntityType[] RELATION_QUERY_ENTITY_TYPES = new EntityType[]{ public static EntityType[] RELATION_QUERY_ENTITY_TYPES = new EntityType[]{
@ -807,33 +827,38 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository {
private String entityNameQuery(QueryContext ctx, EntityNameFilter filter) { private String entityNameQuery(QueryContext ctx, EntityNameFilter filter) {
ctx.addStringParameter("entity_filter_name_filter", filter.getEntityNameFilter()); ctx.addStringParameter("entity_filter_name_filter", filter.getEntityNameFilter());
String nameColumn = getNameColumn(filter.getEntityType());
if (filter.getEntityNameFilter().startsWith("%") || filter.getEntityNameFilter().endsWith("%")) { if (filter.getEntityNameFilter().startsWith("%") || filter.getEntityNameFilter().endsWith("%")) {
return "lower(e.search_text) like lower(:entity_filter_name_filter)"; return String.format("lower(e.%s) like lower(:entity_filter_name_filter)", nameColumn);
} }
return "lower(e.search_text) like lower(concat(:entity_filter_name_filter, '%%'))"; return String.format("lower(e.%s) like lower(concat(:entity_filter_name_filter, '%%'))", nameColumn);
} }
private String typeQuery(QueryContext ctx, EntityFilter filter) { private String typeQuery(QueryContext ctx, EntityFilter filter) {
List<String> types; List<String> types;
String name; String name;
String nameColumn;
switch (filter.getType()) { switch (filter.getType()) {
case ASSET_TYPE: case ASSET_TYPE:
types = ((AssetTypeFilter) filter).getAssetTypes(); types = ((AssetTypeFilter) filter).getAssetTypes();
name = ((AssetTypeFilter) filter).getAssetNameFilter(); name = ((AssetTypeFilter) filter).getAssetNameFilter();
nameColumn = getNameColumn(EntityType.ASSET);
break; break;
case DEVICE_TYPE: case DEVICE_TYPE:
types = ((DeviceTypeFilter) filter).getDeviceTypes(); types = ((DeviceTypeFilter) filter).getDeviceTypes();
name = ((DeviceTypeFilter) filter).getDeviceNameFilter(); name = ((DeviceTypeFilter) filter).getDeviceNameFilter();
nameColumn = getNameColumn(EntityType.DEVICE);
break; break;
case ENTITY_VIEW_TYPE: case ENTITY_VIEW_TYPE:
types = ((EntityViewTypeFilter) filter).getEntityViewTypes(); types = ((EntityViewTypeFilter) filter).getEntityViewTypes();
name = ((EntityViewTypeFilter) filter).getEntityViewNameFilter(); name = ((EntityViewTypeFilter) filter).getEntityViewNameFilter();
nameColumn = getNameColumn(EntityType.ENTITY_VIEW);
break; break;
case EDGE_TYPE: case EDGE_TYPE:
types = ((EdgeTypeFilter) filter).getEdgeTypes(); types = ((EdgeTypeFilter) filter).getEdgeTypes();
name = ((EdgeTypeFilter) filter).getEdgeNameFilter(); name = ((EdgeTypeFilter) filter).getEdgeNameFilter();
nameColumn = getNameColumn(EntityType.EDGE);
break; break;
default: default:
throw new RuntimeException("Not supported!"); throw new RuntimeException("Not supported!");
@ -843,14 +868,23 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository {
if (!StringUtils.isEmpty(name)) { if (!StringUtils.isEmpty(name)) {
ctx.addStringParameter("entity_filter_type_query_name", name); ctx.addStringParameter("entity_filter_type_query_name", name);
if (name.startsWith("%") || name.endsWith("%")) { if (name.startsWith("%") || name.endsWith("%")) {
return typesFilter + " and lower(e.search_text) like lower(:entity_filter_type_query_name)"; return typesFilter + " and lower(e." + nameColumn + ") like lower(:entity_filter_type_query_name)";
} }
return typesFilter + " and lower(e.search_text) like lower(concat(:entity_filter_type_query_name, '%%'))"; return typesFilter + " and lower(e." + nameColumn + ") like lower(concat(:entity_filter_type_query_name, '%%'))";
} else { } else {
return typesFilter; return typesFilter;
} }
} }
private String getNameColumn(EntityType entityType) {
String nameColumn = entityNameColumns.get(entityType);
if (nameColumn == null) {
log.error("Name column is not defined in the entityNameColumns map for entity type {}.", entityType);
throw new RuntimeException("Name column is not defined for entity type: " + entityType);
}
return nameColumn;
}
public static EntityType resolveEntityType(EntityFilter entityFilter) { public static EntityType resolveEntityType(EntityFilter entityFilter) {
switch (entityFilter.getType()) { switch (entityFilter.getType()) {
case SINGLE_ENTITY: case SINGLE_ENTITY:

44
dao/src/main/java/org/thingsboard/server/dao/util/AbstractBufferedRateExecutor.java

@ -30,24 +30,21 @@ import com.google.common.util.concurrent.SettableFuture;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.thingsboard.common.util.ThingsBoardExecutors; import org.thingsboard.common.util.ThingsBoardExecutors;
import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.msg.tools.TbRateLimits;
import org.thingsboard.server.common.stats.DefaultCounter; import org.thingsboard.server.common.stats.DefaultCounter;
import org.thingsboard.server.common.stats.StatsCounter; import org.thingsboard.server.common.stats.StatsCounter;
import org.thingsboard.server.common.stats.StatsFactory; import org.thingsboard.server.common.stats.StatsFactory;
import org.thingsboard.server.common.stats.StatsType; import org.thingsboard.server.common.stats.StatsType;
import org.thingsboard.server.dao.entity.EntityService; import org.thingsboard.server.dao.entity.EntityService;
import org.thingsboard.server.dao.nosql.CassandraStatementTask; import org.thingsboard.server.dao.nosql.CassandraStatementTask;
import org.thingsboard.server.dao.tenant.TbTenantProfileCache; import org.thingsboard.server.dao.util.limits.LimitedApi;
import org.thingsboard.server.dao.util.limits.RateLimitService;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.BlockingQueue; import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingDeque; import java.util.concurrent.LinkedBlockingDeque;
@ -73,7 +70,6 @@ public abstract class AbstractBufferedRateExecutor<T extends AsyncTask, F extend
private final ScheduledExecutorService timeoutExecutor; private final ScheduledExecutorService timeoutExecutor;
private final int concurrencyLimit; private final int concurrencyLimit;
private final int printQueriesFreq; private final int printQueriesFreq;
private final ConcurrentMap<TenantId, TbRateLimits> perTenantLimits = new ConcurrentHashMap<>();
private final AtomicInteger printQueriesIdx = new AtomicInteger(0); private final AtomicInteger printQueriesIdx = new AtomicInteger(0);
@ -81,14 +77,14 @@ public abstract class AbstractBufferedRateExecutor<T extends AsyncTask, F extend
protected final BufferedRateExecutorStats stats; protected final BufferedRateExecutorStats stats;
private final EntityService entityService; private final EntityService entityService;
private final TbTenantProfileCache tenantProfileCache; private final RateLimitService rateLimitService;
private final boolean printTenantNames; private final boolean printTenantNames;
private final Map<TenantId, String> tenantNamesCache = new HashMap<>(); private final Map<TenantId, String> tenantNamesCache = new HashMap<>();
public AbstractBufferedRateExecutor(int queueLimit, int concurrencyLimit, long maxWaitTime, int dispatcherThreads, public AbstractBufferedRateExecutor(int queueLimit, int concurrencyLimit, long maxWaitTime, int dispatcherThreads,
int callbackThreads, long pollMs, int printQueriesFreq, StatsFactory statsFactory, int callbackThreads, long pollMs, int printQueriesFreq, StatsFactory statsFactory,
EntityService entityService, TbTenantProfileCache tenantProfileCache, boolean printTenantNames) { EntityService entityService, RateLimitService rateLimitService, boolean printTenantNames) {
this.maxWaitTime = maxWaitTime; this.maxWaitTime = maxWaitTime;
this.pollMs = pollMs; this.pollMs = pollMs;
this.concurrencyLimit = concurrencyLimit; this.concurrencyLimit = concurrencyLimit;
@ -102,7 +98,7 @@ public abstract class AbstractBufferedRateExecutor<T extends AsyncTask, F extend
this.concurrencyLevel = statsFactory.createGauge(concurrencyLevelKey, new AtomicInteger(0)); this.concurrencyLevel = statsFactory.createGauge(concurrencyLevelKey, new AtomicInteger(0));
this.entityService = entityService; this.entityService = entityService;
this.tenantProfileCache = tenantProfileCache; this.rateLimitService = rateLimitService;
this.printTenantNames = printTenantNames; this.printTenantNames = printTenantNames;
for (int i = 0; i < dispatcherThreads; i++) { for (int i = 0; i < dispatcherThreads; i++) {
@ -116,28 +112,16 @@ public abstract class AbstractBufferedRateExecutor<T extends AsyncTask, F extend
F result = wrap(task, settableFuture); F result = wrap(task, settableFuture);
boolean perTenantLimitReached = false; boolean perTenantLimitReached = false;
TenantId tenantId = task.getTenantId();
var tenantProfileConfiguration = if (tenantId != null && !tenantId.isSysTenantId()) {
(task.getTenantId() != null && !TenantId.SYS_TENANT_ID.equals(task.getTenantId())) if (!rateLimitService.checkRateLimit(LimitedApi.CASSANDRA_QUERIES, tenantId)) {
? tenantProfileCache.get(task.getTenantId()).getDefaultProfileConfiguration() stats.incrementRateLimitedTenant(tenantId);
: null; stats.getTotalRateLimited().increment();
if (tenantProfileConfiguration != null && settableFuture.setException(new TenantRateLimitException());
StringUtils.isNotEmpty(tenantProfileConfiguration.getCassandraQueryTenantRateLimitsConfiguration())) { perTenantLimitReached = true;
if (task.getTenantId() == null) {
log.info("Invalid task received: {}", task);
} else if (!task.getTenantId().isNullUid()) {
TbRateLimits rateLimits = perTenantLimits.computeIfAbsent(
task.getTenantId(), id -> new TbRateLimits(tenantProfileConfiguration.getCassandraQueryTenantRateLimitsConfiguration())
);
if (!rateLimits.tryConsume()) {
stats.incrementRateLimitedTenant(task.getTenantId());
stats.getTotalRateLimited().increment();
settableFuture.setException(new TenantRateLimitException());
perTenantLimitReached = true;
}
} }
} else if (!TenantId.SYS_TENANT_ID.equals(task.getTenantId())) { } else if (tenantId == null) {
perTenantLimits.remove(task.getTenantId()); log.info("Invalid task received: {}", task);
} }
if (!perTenantLimitReached) { if (!perTenantLimitReached) {

58
application/src/main/java/org/thingsboard/server/service/apiusage/limits/DefaultRateLimitService.java → dao/src/main/java/org/thingsboard/server/dao/util/limits/DefaultRateLimitService.java

@ -13,83 +13,95 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.thingsboard.server.service.apiusage.limits; package org.thingsboard.server.dao.util.limits;
import com.github.benmanes.caffeine.cache.Cache; import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine; import com.github.benmanes.caffeine.cache.Caffeine;
import lombok.Data; import lombok.Data;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.TenantProfile;
import org.thingsboard.server.common.data.exception.TenantProfileNotFoundException;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.msg.tools.TbRateLimits; import org.thingsboard.server.common.msg.tools.TbRateLimits;
import org.thingsboard.server.dao.tenant.TbTenantProfileCache; import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import javax.annotation.PostConstruct;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@Service @Service
@RequiredArgsConstructor
@Slf4j @Slf4j
public class DefaultRateLimitService implements RateLimitService { public class DefaultRateLimitService implements RateLimitService {
private final TbTenantProfileCache tenantProfileCache; private final TbTenantProfileCache tenantProfileCache;
@Value("${cache.rateLimits.timeToLiveInMinutes:60}")
private int rateLimitsTtl;
@Value("${cache.rateLimits.maxSize:100000}")
private int rateLimitsCacheMaxSize;
private Cache<RateLimitKey, TbRateLimits> rateLimits; public DefaultRateLimitService(TbTenantProfileCache tenantProfileCache,
@Value("${cache.rateLimits.timeToLiveInMinutes:120}") int rateLimitsTtl,
@PostConstruct @Value("${cache.rateLimits.maxSize:200000}") int rateLimitsCacheMaxSize) {
private void init() { this.tenantProfileCache = tenantProfileCache;
rateLimits = Caffeine.newBuilder() this.rateLimits = Caffeine.newBuilder()
.expireAfterAccess(rateLimitsTtl, TimeUnit.MINUTES) .expireAfterAccess(rateLimitsTtl, TimeUnit.MINUTES)
.maximumSize(rateLimitsCacheMaxSize) .maximumSize(rateLimitsCacheMaxSize)
.build(); .build();
} }
private final Cache<RateLimitKey, TbRateLimits> rateLimits;
@Override @Override
public boolean checkRateLimit(LimitedApi api, TenantId tenantId) { public boolean checkRateLimit(LimitedApi api, TenantId tenantId) {
return checkRateLimit(api, tenantId, tenantId); return checkRateLimit(api, tenantId, tenantId);
} }
@Override @Override
public boolean checkRateLimit(LimitedApi api, TenantId tenantId, EntityId entityId) { public boolean checkRateLimit(LimitedApi api, TenantId tenantId, Object level) {
if (tenantId.isSysTenantId()) { if (tenantId.isSysTenantId()) {
return true; return true;
} }
RateLimitKey key = new RateLimitKey(api, entityId); TenantProfile tenantProfile = tenantProfileCache.get(tenantId);
if (tenantProfile == null) {
throw new TenantProfileNotFoundException(tenantId);
}
String rateLimitConfig = tenantProfile.getProfileConfiguration()
.map(profileConfiguration -> api.getLimitConfig(profileConfiguration, level))
.orElse(null);
return checkRateLimit(api, level, rateLimitConfig);
}
String rateLimitConfig = tenantProfileCache.get(tenantId).getProfileConfiguration() @Override
.map(api::getLimitConfig).orElse(null); public boolean checkRateLimit(LimitedApi api, Object level, String rateLimitConfig) {
RateLimitKey key = new RateLimitKey(api, level);
if (StringUtils.isEmpty(rateLimitConfig)) { if (StringUtils.isEmpty(rateLimitConfig)) {
rateLimits.invalidate(key); rateLimits.invalidate(key);
return true; return true;
} }
log.trace("[{}] Checking rate limit for {} ({})", entityId, api, rateLimitConfig); log.trace("[{}] Checking rate limit for {} ({})", level, api, rateLimitConfig);
TbRateLimits rateLimit = rateLimits.asMap().compute(key, (k, limit) -> { TbRateLimits rateLimit = rateLimits.asMap().compute(key, (k, limit) -> {
if (limit == null || !limit.getConfiguration().equals(rateLimitConfig)) { if (limit == null || !limit.getConfiguration().equals(rateLimitConfig)) {
limit = new TbRateLimits(rateLimitConfig); limit = new TbRateLimits(rateLimitConfig, api.isRefillRateLimitIntervally());
log.trace("[{}] Created new rate limit bucket for {} ({})", entityId, api, rateLimitConfig); log.trace("[{}] Created new rate limit bucket for {} ({})", level, api, rateLimitConfig);
} }
return limit; return limit;
}); });
boolean success = rateLimit.tryConsume(); boolean success = rateLimit.tryConsume();
if (!success) { if (!success) {
log.debug("[{}] Rate limit exceeded for {} ({})", entityId, api, rateLimitConfig); log.debug("[{}] Rate limit exceeded for {} ({})", level, api, rateLimitConfig);
} }
return success; return success;
} }
@Override
public void cleanUp(LimitedApi api, Object level) {
RateLimitKey key = new RateLimitKey(api, level);
rateLimits.invalidate(key);
}
@Data(staticConstructor = "of") @Data(staticConstructor = "of")
private static class RateLimitKey { private static class RateLimitKey {
private final LimitedApi api; private final LimitedApi api;
private final EntityId entityId; private final Object level;
} }
} }

67
dao/src/main/java/org/thingsboard/server/dao/util/limits/LimitedApi.java

@ -0,0 +1,67 @@
/**
* 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.dao.util.limits;
import lombok.Getter;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
import java.util.function.BiFunction;
import java.util.function.Function;
public enum LimitedApi {
ENTITY_EXPORT(DefaultTenantProfileConfiguration::getTenantEntityExportRateLimit),
ENTITY_IMPORT(DefaultTenantProfileConfiguration::getTenantEntityImportRateLimit),
NOTIFICATION_REQUESTS(DefaultTenantProfileConfiguration::getTenantNotificationRequestsRateLimit),
NOTIFICATION_REQUESTS_PER_RULE(DefaultTenantProfileConfiguration::getTenantNotificationRequestsPerRuleRateLimit),
REST_REQUESTS((profileConfiguration, level) -> ((EntityId) level).getEntityType() == EntityType.TENANT ?
profileConfiguration.getTenantServerRestLimitsConfiguration() :
profileConfiguration.getCustomerServerRestLimitsConfiguration()),
WS_UPDATES_PER_SESSION(DefaultTenantProfileConfiguration::getWsUpdatesPerSessionRateLimit),
CASSANDRA_QUERIES(DefaultTenantProfileConfiguration::getCassandraQueryTenantRateLimitsConfiguration),
PASSWORD_RESET(true),
TWO_FA_VERIFICATION_CODE_SEND(true),
TWO_FA_VERIFICATION_CODE_CHECK(true);
private final BiFunction<DefaultTenantProfileConfiguration, Object, String> configExtractor;
@Getter
private final boolean refillRateLimitIntervally;
LimitedApi(Function<DefaultTenantProfileConfiguration, String> configExtractor) {
this((profileConfiguration, level) -> configExtractor.apply(profileConfiguration));
}
LimitedApi(BiFunction<DefaultTenantProfileConfiguration, Object, String> configExtractor) {
this.configExtractor = configExtractor;
this.refillRateLimitIntervally = false;
}
LimitedApi(boolean refillRateLimitIntervally) {
this.configExtractor = null;
this.refillRateLimitIntervally = refillRateLimitIntervally;
}
public String getLimitConfig(DefaultTenantProfileConfiguration profileConfiguration, Object level) {
if (configExtractor != null) {
return configExtractor.apply(profileConfiguration, level);
} else {
throw new IllegalArgumentException("No tenant profile config for " + name() + " rate limits");
}
}
}

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save