Browse Source

Merge branch 'develop/3.6' of github.com:thingsboard/thingsboard into feature/java-17

pull/8460/head
YevhenBondarenko 3 years ago
parent
commit
c29e0dbf75
  1. 8
      application/src/main/data/upgrade/3.5.0/schema_update.sql
  2. 54
      application/src/main/data/upgrade/3.5.1/schema_update.sql
  3. 64
      application/src/main/java/org/thingsboard/server/config/RateLimitProcessingFilter.java
  4. 17
      application/src/main/java/org/thingsboard/server/controller/AuthController.java
  5. 37
      application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketHandler.java
  6. 17
      application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java
  7. 37
      application/src/main/java/org/thingsboard/server/service/apiusage/limits/LimitedApi.java
  8. 21
      application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java
  9. 3
      application/src/main/java/org/thingsboard/server/service/install/update/DefaultCacheCleanupService.java
  10. 4
      application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationCenter.java
  11. 4
      application/src/main/java/org/thingsboard/server/service/notification/rule/DefaultNotificationRuleProcessor.java
  12. 4
      application/src/main/java/org/thingsboard/server/service/resource/DefaultTbResourceService.java
  13. 52
      application/src/main/java/org/thingsboard/server/service/security/auth/mfa/DefaultTwoFactorAuthService.java
  14. 25
      application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/CookieUtils.java
  15. 7
      application/src/main/java/org/thingsboard/server/service/sync/ie/DefaultEntitiesExportImportService.java
  16. 58
      application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/DefaultExportableEntitiesService.java
  17. 11
      application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/BaseEntityExportService.java
  18. 103
      application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/NotificationRuleExportService.java
  19. 55
      application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/NotificationTargetExportService.java
  20. 42
      application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/NotificationTemplateExportService.java
  21. 150
      application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/NotificationRuleImportService.java
  22. 109
      application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/NotificationTargetImportService.java
  23. 72
      application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/NotificationTemplateImportService.java
  24. 3
      application/src/main/java/org/thingsboard/server/utils/LwM2mObjectModelUtils.java
  25. 4
      application/src/main/resources/thingsboard.yml
  26. 110
      application/src/test/java/org/thingsboard/server/service/limits/RateLimitServiceTest.java
  27. 9
      application/src/test/java/org/thingsboard/server/service/notification/NotificationRuleApiTest.java
  28. 85
      application/src/test/java/org/thingsboard/server/service/resource/sql/BaseTbResourceServiceTest.java
  29. 66
      application/src/test/java/org/thingsboard/server/service/security/auth/oauth2/HttpCookieOAuth2AuthorizationRequestRepositoryTest.java
  30. 4
      common/dao-api/src/main/java/org/thingsboard/server/dao/entity/EntityDaoService.java
  31. 5
      common/data/pom.xml
  32. 108
      common/data/src/main/java/org/thingsboard/server/common/data/BaseDataWithAdditionalInfo.java
  33. 7
      common/data/src/main/java/org/thingsboard/server/common/data/Device.java
  34. 31
      common/data/src/main/java/org/thingsboard/server/common/data/exception/TenantProfileNotFoundException.java
  35. 6
      common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/NotificationRule.java
  36. 16
      common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/NotificationTarget.java
  37. 17
      common/data/src/main/java/org/thingsboard/server/common/data/notification/template/NotificationTemplate.java
  38. 9
      common/data/src/main/java/org/thingsboard/server/common/data/notification/template/NotificationTemplateConfig.java
  39. 8
      common/data/src/main/java/org/thingsboard/server/common/data/sync/JsonTbEntity.java
  40. 272
      common/data/src/main/java/org/thingsboard/server/common/data/util/TbDDFFileParser.java
  41. 5
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportServerHelper.java
  42. 3
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mVersionedModelProvider.java
  43. 8
      dao/src/main/java/org/thingsboard/server/dao/DaoUtil.java
  44. 6
      dao/src/main/java/org/thingsboard/server/dao/asset/AssetProfileServiceImpl.java
  45. 6
      dao/src/main/java/org/thingsboard/server/dao/asset/BaseAssetService.java
  46. 6
      dao/src/main/java/org/thingsboard/server/dao/customer/CustomerServiceImpl.java
  47. 6
      dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardServiceImpl.java
  48. 6
      dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java
  49. 6
      dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java
  50. 7
      dao/src/main/java/org/thingsboard/server/dao/model/BaseSqlEntity.java
  51. 17
      dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractDeviceEntity.java
  52. 6
      dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationRuleEntity.java
  53. 5
      dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationTargetEntity.java
  54. 5
      dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationTemplateEntity.java
  55. 6
      dao/src/main/java/org/thingsboard/server/dao/nosql/CassandraBufferedRateReadExecutor.java
  56. 6
      dao/src/main/java/org/thingsboard/server/dao/nosql/CassandraBufferedRateWriteExecutor.java
  57. 5
      dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationRuleService.java
  58. 5
      dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationTargetService.java
  59. 5
      dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationTemplateService.java
  60. 3
      dao/src/main/java/org/thingsboard/server/dao/notification/NotificationRuleDao.java
  61. 3
      dao/src/main/java/org/thingsboard/server/dao/notification/NotificationTargetDao.java
  62. 4
      dao/src/main/java/org/thingsboard/server/dao/notification/NotificationTemplateDao.java
  63. 6
      dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java
  64. 2
      dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDao.java
  65. 42
      dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceRepository.java
  66. 4
      dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceDao.java
  67. 20
      dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationRuleDao.java
  68. 20
      dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationTargetDao.java
  69. 21
      dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationTemplateDao.java
  70. 10
      dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationRuleRepository.java
  71. 10
      dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationTargetRepository.java
  72. 10
      dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationTemplateRepository.java
  73. 44
      dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultEntityQueryRepository.java
  74. 44
      dao/src/main/java/org/thingsboard/server/dao/util/AbstractBufferedRateExecutor.java
  75. 58
      dao/src/main/java/org/thingsboard/server/dao/util/limits/DefaultRateLimitService.java
  76. 67
      dao/src/main/java/org/thingsboard/server/dao/util/limits/LimitedApi.java
  77. 9
      dao/src/main/java/org/thingsboard/server/dao/util/limits/RateLimitService.java
  78. 5
      dao/src/main/java/org/thingsboard/server/dao/widget/WidgetsBundleServiceImpl.java
  79. 13
      dao/src/main/resources/sql/schema-entities.sql
  80. 6
      dao/src/main/resources/sql/schema-views-and-functions.sql
  81. 30
      dao/src/test/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDaoTest.java
  82. 7
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/TestRestClient.java
  83. 24
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/base/AbstractDriverBaseTest.java
  84. 2
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/pages/CustomerPageElements.java
  85. 74
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/pages/DevicePageElements.java
  86. 42
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/pages/DevicePageHelper.java
  87. 42
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tabs/AssignDeviceTabElements.java
  88. 30
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tabs/AssignDeviceTabHelper.java
  89. 97
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tabs/CreateDeviceTabElements.java
  90. 58
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tabs/CreateDeviceTabHelper.java
  91. 8
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/devicessmoke/AbstractDeviceTest.java
  92. 193
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/devicessmoke/AssignToCustomerTest.java
  93. 97
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/devicessmoke/CreateDeviceTest.java
  94. 14
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/devicessmoke/DeleteSeveralDevicesTest.java
  95. 2
      msa/monitoring/pom.xml
  96. 2
      msa/tb/pom.xml
  97. 3
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesByNameAndTypeLoader.java
  98. 30
      ui-ngx/package.json
  99. 20
      ui-ngx/patches/@angular+core+15.2.9.patch
  100. 8
      ui-ngx/src/app/core/http/entity.service.ts

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

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

5
common/data/pom.xml

@ -112,6 +112,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);
}
}

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

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");
}
}
}

9
application/src/main/java/org/thingsboard/server/service/apiusage/limits/RateLimitService.java → dao/src/main/java/org/thingsboard/server/dao/util/limits/RateLimitService.java

@ -13,15 +13,18 @@
* 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 org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
public interface RateLimitService { public interface RateLimitService {
boolean checkRateLimit(LimitedApi api, TenantId tenantId); boolean checkRateLimit(LimitedApi api, TenantId tenantId);
boolean checkRateLimit(LimitedApi api, TenantId tenantId, EntityId entityId); boolean checkRateLimit(LimitedApi api, TenantId tenantId, Object level);
boolean checkRateLimit(LimitedApi api, Object level, String rateLimitConfig);
void cleanUp(LimitedApi api, Object level);
} }

5
dao/src/main/java/org/thingsboard/server/dao/widget/WidgetsBundleServiceImpl.java

@ -160,6 +160,11 @@ public class WidgetsBundleServiceImpl implements WidgetsBundleService {
return Optional.ofNullable(findWidgetsBundleById(tenantId, new WidgetsBundleId(entityId.getId()))); return Optional.ofNullable(findWidgetsBundleById(tenantId, new WidgetsBundleId(entityId.getId())));
} }
@Override
public void deleteEntity(TenantId tenantId, EntityId id) {
deleteWidgetsBundle(tenantId, (WidgetsBundleId) id);
}
@Override @Override
public EntityType getEntityType() { public EntityType getEntityType() {
return EntityType.WIDGETS_BUNDLE; return EntityType.WIDGETS_BUNDLE;

13
dao/src/main/resources/sql/schema-entities.sql

@ -340,7 +340,6 @@ CREATE TABLE IF NOT EXISTS device (
type varchar(255), type varchar(255),
name varchar(255), name varchar(255),
label varchar(255), label varchar(255),
search_text varchar(255),
tenant_id uuid, tenant_id uuid,
firmware_id uuid, firmware_id uuid,
software_id uuid, software_id uuid,
@ -797,7 +796,9 @@ CREATE TABLE IF NOT EXISTS notification_target (
tenant_id UUID NOT NULL, tenant_id UUID NOT NULL,
name VARCHAR(255) NOT NULL, name VARCHAR(255) NOT NULL,
configuration VARCHAR(10000) NOT NULL, configuration VARCHAR(10000) NOT NULL,
CONSTRAINT uq_notification_target_name UNIQUE (tenant_id, name) external_id UUID,
CONSTRAINT uq_notification_target_name UNIQUE (tenant_id, name),
CONSTRAINT uq_notification_target_external_id UNIQUE (tenant_id, external_id)
); );
CREATE TABLE IF NOT EXISTS notification_template ( CREATE TABLE IF NOT EXISTS notification_template (
@ -807,7 +808,9 @@ CREATE TABLE IF NOT EXISTS notification_template (
name VARCHAR(255) NOT NULL, name VARCHAR(255) NOT NULL,
notification_type VARCHAR(50) NOT NULL, notification_type VARCHAR(50) NOT NULL,
configuration VARCHAR(10000000) NOT NULL, configuration VARCHAR(10000000) NOT NULL,
CONSTRAINT uq_notification_template_name UNIQUE (tenant_id, name) external_id UUID,
CONSTRAINT uq_notification_template_name UNIQUE (tenant_id, name),
CONSTRAINT uq_notification_template_external_id UNIQUE (tenant_id, external_id)
); );
CREATE TABLE IF NOT EXISTS notification_rule ( CREATE TABLE IF NOT EXISTS notification_rule (
@ -821,7 +824,9 @@ CREATE TABLE IF NOT EXISTS notification_rule (
trigger_config VARCHAR(1000) NOT NULL, trigger_config VARCHAR(1000) NOT NULL,
recipients_config VARCHAR(10000) NOT NULL, recipients_config VARCHAR(10000) NOT NULL,
additional_config VARCHAR(255), additional_config VARCHAR(255),
CONSTRAINT uq_notification_rule_name UNIQUE (tenant_id, name) external_id UUID,
CONSTRAINT uq_notification_rule_name UNIQUE (tenant_id, name),
CONSTRAINT uq_notification_rule_external_id UNIQUE (tenant_id, external_id)
); );
CREATE TABLE IF NOT EXISTS notification_request ( CREATE TABLE IF NOT EXISTS notification_request (

6
dao/src/main/resources/sql/schema-views-and-functions.sql

@ -226,7 +226,11 @@ BEGIN
END IF; END IF;
IF NOT(existing.cleared) THEN IF NOT(existing.cleared) THEN
cleared = TRUE; cleared = TRUE;
UPDATE alarm a SET cleared = true, clear_ts = a_ts, additional_info = a_details WHERE a.id = a_id AND a.tenant_id = t_id; IF a_details IS NULL THEN
UPDATE alarm a SET cleared = true, clear_ts = a_ts WHERE a.id = a_id AND a.tenant_id = t_id;
ELSE
UPDATE alarm a SET cleared = true, clear_ts = a_ts, additional_info = a_details WHERE a.id = a_id AND a.tenant_id = t_id;
END IF;
END IF; END IF;
SELECT * INTO result FROM alarm_info a WHERE a.id = a_id AND a.tenant_id = t_id; SELECT * INTO result FROM alarm_info a WHERE a.id = a_id AND a.tenant_id = t_id;
RETURN json_build_object('success', true, 'cleared', cleared, 'alarm', row_to_json(result))::text; RETURN json_build_object('success', true, 'cleared', cleared, 'alarm', row_to_json(result))::text;

30
dao/src/test/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDaoTest.java

@ -202,7 +202,34 @@ public class JpaAlarmDaoTest extends AbstractJpaDaoTest {
@Test @Test
public void testClearAlarmProcedure() { public void testClearAlarmProcedure() {
UUID tenantId = UUID.randomUUID(); UUID tenantId = UUID.randomUUID();
; UUID originator1Id = UUID.fromString("d4b68f41-3e96-11e7-a884-898080180d6b");
UUID alarm1Id = UUID.fromString("d4b68f43-3e96-11e7-a884-898080180d6b");
Alarm alarm = saveAlarm(alarm1Id, tenantId, originator1Id, "TEST_ALARM");
long clearTs = System.currentTimeMillis();
var details = JacksonUtil.newObjectNode().put("test", 123);
AlarmApiCallResult result = alarmDao.clearAlarm(alarm.getTenantId(), alarm.getId(), clearTs, details);
AlarmInfo afterSave = alarmDao.findAlarmInfoById(alarm.getTenantId(), alarm.getUuidId());
assertNotNull(result);
assertTrue(result.isSuccessful());
assertTrue(result.isCleared());
assertNotNull(result.getAlarm());
assertEquals(afterSave, result.getAlarm());
assertEquals(clearTs, result.getAlarm().getClearTs());
assertTrue(result.getAlarm().isCleared());
assertEquals(details, result.getAlarm().getDetails());
result = alarmDao.clearAlarm(alarm.getTenantId(), alarm.getId(), clearTs + 1, JacksonUtil.newObjectNode());
assertNotNull(result);
assertNotNull(result.getAlarm());
assertEquals(afterSave, result.getAlarm());
assertTrue(result.isSuccessful());
assertFalse(result.isCleared());
assertEquals(clearTs, result.getAlarm().getClearTs());
assertTrue(result.getAlarm().isCleared());
}
@Test
public void testClearAlarmWithoutDetailsProcedure() {
UUID tenantId = UUID.randomUUID();
UUID originator1Id = UUID.fromString("d4b68f41-3e96-11e7-a884-898080180d6b"); UUID originator1Id = UUID.fromString("d4b68f41-3e96-11e7-a884-898080180d6b");
UUID alarm1Id = UUID.fromString("d4b68f43-3e96-11e7-a884-898080180d6b"); UUID alarm1Id = UUID.fromString("d4b68f43-3e96-11e7-a884-898080180d6b");
Alarm alarm = saveAlarm(alarm1Id, tenantId, originator1Id, "TEST_ALARM"); Alarm alarm = saveAlarm(alarm1Id, tenantId, originator1Id, "TEST_ALARM");
@ -216,6 +243,7 @@ public class JpaAlarmDaoTest extends AbstractJpaDaoTest {
assertEquals(afterSave, result.getAlarm()); assertEquals(afterSave, result.getAlarm());
assertEquals(clearTs, result.getAlarm().getClearTs()); assertEquals(clearTs, result.getAlarm().getClearTs());
assertTrue(result.getAlarm().isCleared()); assertTrue(result.getAlarm().isCleared());
assertEquals(alarm.getDetails(), result.getAlarm().getDetails());
result = alarmDao.clearAlarm(alarm.getTenantId(), alarm.getId(), clearTs + 1, JacksonUtil.newObjectNode()); result = alarmDao.clearAlarm(alarm.getTenantId(), alarm.getId(), clearTs + 1, JacksonUtil.newObjectNode());
assertNotNull(result); assertNotNull(result);
assertNotNull(result.getAlarm()); assertNotNull(result.getAlarm());

7
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/TestRestClient.java

@ -520,4 +520,11 @@ public class TestRestClient {
.then() .then()
.statusCode(HTTP_OK); .statusCode(HTTP_OK);
} }
public void setDevicePublic(DeviceId deviceId) {
given().spec(requestSpec)
.post("/api/customer/public/device/{deviceId}", deviceId.getId())
.then()
.statusCode(HTTP_OK);
}
} }

24
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/base/AbstractDriverBaseTest.java

@ -73,7 +73,7 @@ abstract public class AbstractDriverBaseTest extends AbstractContainerTest {
private static final int WIDTH = 1680; private static final int WIDTH = 1680;
private static final int HEIGHT = 1050; private static final int HEIGHT = 1050;
private static final String REMOTE_WEBDRIVER_HOST = "http://localhost:4444"; private static final String REMOTE_WEBDRIVER_HOST = "http://localhost:4444";
protected final PageLink pageLink = new PageLink(10); protected final PageLink pageLink = new PageLink(30);
private final ContainerTestSuite instance = ContainerTestSuite.getInstance(); private final ContainerTestSuite instance = ContainerTestSuite.getInstance();
private JavascriptExecutor js; private JavascriptExecutor js;
public static final long WAIT_TIMEOUT = TimeUnit.SECONDS.toMillis(10); public static final long WAIT_TIMEOUT = TimeUnit.SECONDS.toMillis(10);
@ -253,6 +253,13 @@ abstract public class AbstractDriverBaseTest extends AbstractContainerTest {
} }
} }
public void deleteCustomerByName(String customerName) {
Customer customer = getCustomerByName(customerName);
if (customer != null) {
testRestClient.deleteCustomer(customer.getId());
}
}
public void deleteDeviceById(DeviceId deviceId) { public void deleteDeviceById(DeviceId deviceId) {
if (deviceId != null) { if (deviceId != null) {
testRestClient.deleteDevice(deviceId); testRestClient.deleteDevice(deviceId);
@ -283,4 +290,19 @@ abstract public class AbstractDriverBaseTest extends AbstractContainerTest {
testRestClient.deleteDevice(device.getId()); testRestClient.deleteDevice(device.getId());
} }
} }
public void deleteDeviceProfileByTitle(String deviceProfileTitle) {
DeviceProfile deviceProfile = getDeviceProfileByName(deviceProfileTitle);
if (deviceProfile != null) {
testRestClient.deleteDeviseProfile(deviceProfile.getId());
}
}
public void assertInvisibilityOfElement(WebElement element) {
try {
new WebDriverWait(driver, duration).until(ExpectedConditions.invisibilityOf(element));
} catch (WebDriverException e) {
fail("Element " + element.toString() + " stay visible");
}
}
} }

2
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/pages/CustomerPageElements.java

@ -58,7 +58,7 @@ public class CustomerPageElements extends OtherPageElementsHelper {
private static final String ASSIGNED_BTN = "//button[@type='submit']"; private static final String ASSIGNED_BTN = "//button[@type='submit']";
private static final String HIDE_HOME_DASHBOARD_TOOLBAR = "//mat-checkbox[@formcontrolname='homeDashboardHideToolbar']//label"; private static final String HIDE_HOME_DASHBOARD_TOOLBAR = "//mat-checkbox[@formcontrolname='homeDashboardHideToolbar']//label";
private static final String FILTER_BTN = "//tb-filters-edit"; private static final String FILTER_BTN = "//tb-filters-edit";
private static final String TIME_BTN = "//tb-timewindow"; private static final String TIME_BTN = "//tb-timewindow[not(@hidelabel)]";
private static final String CUSTOMER_ICON_HEADER = "//tb-breadcrumb//span[contains(text(),'Customer %s')]"; private static final String CUSTOMER_ICON_HEADER = "//tb-breadcrumb//span[contains(text(),'Customer %s')]";
private static final String CUSTOMER_USER_ICON_HEADER = "Users"; private static final String CUSTOMER_USER_ICON_HEADER = "Users";
private static final String CUSTOMER_ASSETS_ICON_HEADER = "Assets"; private static final String CUSTOMER_ASSETS_ICON_HEADER = "Assets";

74
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/pages/DevicePageElements.java

@ -33,26 +33,26 @@ public class DevicePageElements extends OtherPageElementsHelper {
private static final String CLOSE_DEVICE_DETAILS_VIEW = "//header//mat-icon[contains(text(),'close')]/parent::button"; private static final String CLOSE_DEVICE_DETAILS_VIEW = "//header//mat-icon[contains(text(),'close')]/parent::button";
private static final String SUBMIT_ASSIGN_TO_CUSTOMER_BTN = "//button[@type='submit']"; private static final String SUBMIT_ASSIGN_TO_CUSTOMER_BTN = "//button[@type='submit']";
private static final String ADD_DEVICE_BTN = "//mat-icon[text() = 'insert_drive_file']/parent::button"; private static final String ADD_DEVICE_BTN = "//mat-icon[text() = 'insert_drive_file']/parent::button";
private static final String CREATE_DEVICE_NAME_FIELD = "//tb-device-wizard//input[@formcontrolname='name']";
private static final String HEADER_NAME_VIEW = "//header//div[@class='tb-details-title']/span"; private static final String HEADER_NAME_VIEW = "//header//div[@class='tb-details-title']/span";
private static final String DESCRIPTION_FIELD_CREATE_VIEW = "//tb-device-wizard//textarea[@formcontrolname='description']";
private static final String ADD_DEVICE_VIEW = "//tb-device-wizard"; private static final String ADD_DEVICE_VIEW = "//tb-device-wizard";
private static final String DELETE_BTN_DETAILS_TAB = "//span[contains(text(),'Delete device')]/parent::button"; private static final String DELETE_BTN_DETAILS_TAB = "//span[contains(text(),'Delete device')]/parent::button";
private static final String CHECKBOX_GATEWAY_EDIT = "//mat-checkbox[@formcontrolname='gateway']//label"; private static final String CHECKBOX_GATEWAY_EDIT = "//mat-checkbox[@formcontrolname='gateway']//label";
private static final String CHECKBOX_GATEWAY_CREATE = "//tb-device-wizard//mat-checkbox[@formcontrolname='gateway']//label";
private static final String CHECKBOX_OVERWRITE_ACTIVITY_TIME_EDIT = "//mat-checkbox[@formcontrolname='overwriteActivityTime']//label"; private static final String CHECKBOX_OVERWRITE_ACTIVITY_TIME_EDIT = "//mat-checkbox[@formcontrolname='overwriteActivityTime']//label";
private static final String CHECKBOX_OVERWRITE_ACTIVITY_TIME_CREATE = "//tb-device-wizard//mat-checkbox[@formcontrolname='overwriteActivityTime']//label";
private static final String CHECKBOX_GATEWAY_DETAILS = "//mat-checkbox[@formcontrolname='gateway']//input"; private static final String CHECKBOX_GATEWAY_DETAILS = "//mat-checkbox[@formcontrolname='gateway']//input";
private static final String CHECKBOX_GATEWAY_PAGE = DEVICE + "/ancestor::mat-row//mat-cell[contains(@class,'cdk-column-gateway')]//mat-icon[text() = 'check_box']"; private static final String CHECKBOX_GATEWAY_PAGE = DEVICE + "/ancestor::mat-row//mat-cell[contains(@class,'cdk-column-gateway')]//mat-icon[text() = 'check_box']";
private static final String CHECKBOX_OVERWRITE_ACTIVITY_TIME_DETAILS = "//mat-checkbox[@formcontrolname='overwriteActivityTime']//input"; private static final String CHECKBOX_OVERWRITE_ACTIVITY_TIME_DETAILS = "//mat-checkbox[@formcontrolname='overwriteActivityTime']//input";
private static final String CLEAR_PROFILE_FIELD_BTN = "//button[@aria-label='Clear']"; private static final String CLEAR_PROFILE_FIELD_BTN = "//button[@aria-label='Clear']";
private static final String DEVICE_PROFILE_REDIRECTED_BTN = "//a[@aria-label='Open device profile']"; private static final String DEVICE_PROFILE_REDIRECTED_BTN = "//a[@aria-label='Open device profile']";
private static final String DEVICE_LABEL_FIELD_CREATE = "//tb-device-wizard//input[@formcontrolname='label']";
private static final String DEVICE_LABEL_PAGE = DEVICE + "/ancestor::mat-row//mat-cell[contains(@class,'cdk-column-label')]/span"; private static final String DEVICE_LABEL_PAGE = DEVICE + "/ancestor::mat-row//mat-cell[contains(@class,'cdk-column-label')]/span";
private static final String DEVICE_CUSTOMER_PAGE = DEVICE + "/ancestor::mat-row//mat-cell[contains(@class,'cdk-column-customerTitle')]/span"; private static final String DEVICE_CUSTOMER_PAGE = DEVICE + "/ancestor::mat-row//mat-cell[contains(@class,'cdk-column-customerTitle')]/span";
private static final String CUSTOMER_OPTION_BNT = "//div[text() = 'Customer']/ancestor::mat-step-header";
private static final String ASSIGN_ON_CUSTOMER_FIELD = "//input[@formcontrolname='entity']";
private static final String DEVICE_LABEL_EDIT = "//input[@formcontrolname='label']"; private static final String DEVICE_LABEL_EDIT = "//input[@formcontrolname='label']";
private static final String DEVICE_DEVICE_PROFILE_PAGE = DEVICE + "/ancestor::mat-row//mat-cell[contains(@class,'cdk-column-deviceProfileName')]/span";
protected static final String ASSIGN_BTN = ENTITY + "/ancestor::mat-row//mat-icon[contains(text(),'assignment_ind')]/ancestor::button";
protected static final String UNASSIGN_BTN = ENTITY + "/ancestor::mat-row//mat-icon[contains(text(),' assignment_return')]/ancestor::button";
protected static final String ASSIGN_BTN_DETAILS_TAB = "//span[contains(text(),'Assign to customer')]/parent::button";
protected static final String UNASSIGN_BTN_DETAILS_TAB = "//span[contains(text(),'Unassign from customer')]/parent::button";
protected static final String ASSIGNED_FIELD_DETAILS_TAB = "//mat-label[text() = 'Assigned to customer']/parent::label/parent::div/input";
protected static final String ASSIGN_MARKED_DEVICE_BTN = "//mat-icon[text() = 'assignment_ind']/parent::button";
public WebElement device(String deviceName) { public WebElement device(String deviceName) {
return waitUntilElementToBeClickable(String.format(DEVICE, deviceName)); return waitUntilElementToBeClickable(String.format(DEVICE, deviceName));
@ -90,18 +90,10 @@ public class DevicePageElements extends OtherPageElementsHelper {
return waitUntilElementToBeClickable(ADD_DEVICE_BTN); return waitUntilElementToBeClickable(ADD_DEVICE_BTN);
} }
public WebElement nameField() {
return waitUntilElementToBeClickable(CREATE_DEVICE_NAME_FIELD);
}
public WebElement headerNameView() { public WebElement headerNameView() {
return waitUntilVisibilityOfElementLocated(HEADER_NAME_VIEW); return waitUntilVisibilityOfElementLocated(HEADER_NAME_VIEW);
} }
public WebElement descriptionFieldCreateField() {
return waitUntilElementToBeClickable(DESCRIPTION_FIELD_CREATE_VIEW);
}
public WebElement addDeviceView() { public WebElement addDeviceView() {
return waitUntilPresenceOfElementLocated(ADD_DEVICE_VIEW); return waitUntilPresenceOfElementLocated(ADD_DEVICE_VIEW);
} }
@ -114,18 +106,10 @@ public class DevicePageElements extends OtherPageElementsHelper {
return waitUntilElementToBeClickable(CHECKBOX_GATEWAY_EDIT); return waitUntilElementToBeClickable(CHECKBOX_GATEWAY_EDIT);
} }
public WebElement checkboxGatewayCreate() {
return waitUntilElementToBeClickable(CHECKBOX_GATEWAY_CREATE);
}
public WebElement checkboxOverwriteActivityTimeEdit() { public WebElement checkboxOverwriteActivityTimeEdit() {
return waitUntilElementToBeClickable(CHECKBOX_OVERWRITE_ACTIVITY_TIME_EDIT); return waitUntilElementToBeClickable(CHECKBOX_OVERWRITE_ACTIVITY_TIME_EDIT);
} }
public WebElement checkboxOverwriteActivityTimeCreate() {
return waitUntilElementToBeClickable(CHECKBOX_OVERWRITE_ACTIVITY_TIME_CREATE);
}
public WebElement checkboxGatewayDetailsTab() { public WebElement checkboxGatewayDetailsTab() {
return waitUntilPresenceOfElementLocated(CHECKBOX_GATEWAY_DETAILS); return waitUntilPresenceOfElementLocated(CHECKBOX_GATEWAY_DETAILS);
} }
@ -146,22 +130,10 @@ public class DevicePageElements extends OtherPageElementsHelper {
return waitUntilElementToBeClickable(DEVICE_PROFILE_REDIRECTED_BTN); return waitUntilElementToBeClickable(DEVICE_PROFILE_REDIRECTED_BTN);
} }
public WebElement deviceLabelFieldCreate() {
return waitUntilElementToBeClickable(DEVICE_LABEL_FIELD_CREATE);
}
public WebElement deviceLabelOnPage(String deviceName) { public WebElement deviceLabelOnPage(String deviceName) {
return waitUntilVisibilityOfElementLocated(String.format(DEVICE_LABEL_PAGE, deviceName)); return waitUntilVisibilityOfElementLocated(String.format(DEVICE_LABEL_PAGE, deviceName));
} }
public WebElement customerOptionBtn() {
return waitUntilElementToBeClickable(CUSTOMER_OPTION_BNT);
}
public WebElement assignOnCustomerField() {
return waitUntilElementToBeClickable(ASSIGN_ON_CUSTOMER_FIELD);
}
public WebElement deviceCustomerOnPage(String deviceName) { public WebElement deviceCustomerOnPage(String deviceName) {
return waitUntilVisibilityOfElementLocated(String.format(DEVICE_CUSTOMER_PAGE, deviceName)); return waitUntilVisibilityOfElementLocated(String.format(DEVICE_CUSTOMER_PAGE, deviceName));
} }
@ -173,4 +145,36 @@ public class DevicePageElements extends OtherPageElementsHelper {
public WebElement deviceLabelDetailsField() { public WebElement deviceLabelDetailsField() {
return waitUntilVisibilityOfElementLocated(DEVICE_LABEL_EDIT); return waitUntilVisibilityOfElementLocated(DEVICE_LABEL_EDIT);
} }
public WebElement deviceDeviceProfileOnPage(String deviceProfileTitle) {
return waitUntilVisibilityOfElementLocated(String.format(DEVICE_DEVICE_PROFILE_PAGE, deviceProfileTitle));
}
public WebElement assignBtn(String deviceName) {
return waitUntilElementToBeClickable(String.format(ASSIGN_BTN, deviceName));
}
public WebElement assignBtnVisible(String deviceName) {
return waitUntilVisibilityOfElementLocated(String.format(ASSIGN_BTN, deviceName));
}
public WebElement unassignBtn(String deviceName) {
return waitUntilElementToBeClickable(String.format(UNASSIGN_BTN, deviceName));
}
public WebElement assignBtnDetailsTab() {
return waitUntilElementToBeClickable(ASSIGN_BTN_DETAILS_TAB);
}
public WebElement unassignBtnDetailsTab() {
return waitUntilElementToBeClickable(UNASSIGN_BTN_DETAILS_TAB);
}
public WebElement assignFieldDetailsTab() {
return waitUntilVisibilityOfElementLocated(ASSIGNED_FIELD_DETAILS_TAB);
}
public WebElement assignMarkedDeviceBtn() {
return waitUntilVisibilityOfElementLocated(ASSIGN_MARKED_DEVICE_BTN);
}
} }

42
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/pages/DevicePageHelper.java

@ -43,18 +43,6 @@ public class DevicePageHelper extends DevicePageElements {
addDeviceBtn().click(); addDeviceBtn().click();
} }
public void enterName(String deviceName) {
enterText(nameField(), deviceName);
}
public void enterDescription(String description) {
enterText(descriptionFieldCreateField(), description);
}
public void enterLabel(String label) {
enterText(deviceLabelFieldCreate(), label);
}
public void deleteDeviceByRightSideBtn(String deviceName) { public void deleteDeviceByRightSideBtn(String deviceName) {
deleteBtn(deviceName).click(); deleteBtn(deviceName).click();
warningPopUpYesBtn().click(); warningPopUpYesBtn().click();
@ -87,10 +75,30 @@ public class DevicePageHelper extends DevicePageElements {
entityFromDropdown(deviceProfileName).click(); entityFromDropdown(deviceProfileName).click();
} }
public void assignOnCustomer(String customerTitle) { public void unassignedDeviceByRightSideBtn(String deviceName) {
customerOptionBtn().click(); unassignBtn(deviceName).click();
assignOnCustomerField().click(); warningPopUpYesBtn().click();
entityFromList(customerTitle).click(); }
sleep(2); //waiting for the action to count
public void unassignedDeviceFromDetailsTab() {
unassignBtnDetailsTab().click();
warningPopUpYesBtn().click();
}
public void selectDevices(String... deviceNames) {
for (String deviceName : deviceNames) {
checkBox(deviceName).click();
}
}
public void assignSelectedDevices(String... deviceNames) {
selectDevices(deviceNames);
assignMarkedDeviceBtn().click();
}
public void deleteSelectedDevices(String... deviceNames) {
selectDevices(deviceNames);
deleteSelectedBtn().click();
warningPopUpYesBtn().click();
} }
} }

42
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tabs/AssignDeviceTabElements.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.msa.ui.tabs;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.thingsboard.server.msa.ui.base.AbstractBasePage;
public class AssignDeviceTabElements extends AbstractBasePage {
public AssignDeviceTabElements(WebDriver driver) {
super(driver);
}
private static final String ASSIGN_ON_CUSTOMER_FIELD = "//input[@formcontrolname='entity']";
private static final String CUSTOMER_FROM_DROPDOWN = "//div[@role='listbox']/mat-option//span[contains(text(),'%s')]";
private static final String ASSIGN_BTN = "//button[@type='submit']";
public WebElement assignOnCustomerField() {
return waitUntilElementToBeClickable(ASSIGN_ON_CUSTOMER_FIELD);
}
public WebElement customerFromDropDown(String entityName) {
return waitUntilVisibilityOfElementLocated(String.format(CUSTOMER_FROM_DROPDOWN, entityName));
}
public WebElement assignBtn() {
return waitUntilElementToBeClickable(ASSIGN_BTN);
}
}

30
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tabs/AssignDeviceTabHelper.java

@ -0,0 +1,30 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.msa.ui.tabs;
import org.openqa.selenium.WebDriver;
public class AssignDeviceTabHelper extends AssignDeviceTabElements {
public AssignDeviceTabHelper(WebDriver driver) {
super(driver);
}
public void assignOnCustomer(String customerTitle) {
assignOnCustomerField().click();
customerFromDropDown(customerTitle).click();
assignBtn().click();
}
}

97
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tabs/CreateDeviceTabElements.java

@ -0,0 +1,97 @@
/**
* 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.msa.ui.tabs;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.thingsboard.server.msa.ui.base.AbstractBasePage;
public class CreateDeviceTabElements extends AbstractBasePage {
public CreateDeviceTabElements(WebDriver driver) {
super(driver);
}
private static final String CREATE_DEVICE_NAME_FIELD = "//tb-device-wizard//input[@formcontrolname='name']";
private static final String CREATE_NEW_DEVICE_PROFILE_RADIO_BTN = "//span[text() = 'Create new device profile']/ancestor::mat-radio-button";
private static final String SELECT_EXISTING_DEVICE_PROFILE_RADIO_BTN = "//span[text() = 'Select existing device profile']/ancestor::mat-radio-button";
private static final String DEVICE_PROFILE_TITLE_FIELD = "//input[@formcontrolname='newDeviceProfileTitle']";
private static final String ADD_BTN = "//span[text() = 'Add']";
private static final String CLEAR_PROFILE_FIELD_BTN = "//button[@aria-label='Clear']";
private static final String ENTITY_FROM_DROPDOWN = "//div[@role = 'listbox']//span[text() = '%s']";
private static final String ASSIGN_ON_CUSTOMER_FIELD = "//input[@formcontrolname='entity']";
private static final String CUSTOMER_OPTION_BNT = "//div[text() = 'Customer']/ancestor::mat-step-header";
private static final String CUSTOMER_FROM_DROPDOWN = "//div[@role='listbox']/mat-option//span[contains(text(),'%s')]";
private static final String DEVICE_LABEL_FIELD = "//tb-device-wizard//input[@formcontrolname='label']";
private static final String CHECKBOX_GATEWAY = "//tb-device-wizard//mat-checkbox[@formcontrolname='gateway']//label";
private static final String CHECKBOX_OVERWRITE_ACTIVITY_TIME = "//tb-device-wizard//mat-checkbox[@formcontrolname='overwriteActivityTime']//label";
private static final String DESCRIPTION_FIELD = "//tb-device-wizard//textarea[@formcontrolname='description']";
public WebElement nameField() {
return waitUntilElementToBeClickable(CREATE_DEVICE_NAME_FIELD);
}
public WebElement createNewDeviceProfileRadioBtn() {
return waitUntilElementToBeClickable(CREATE_NEW_DEVICE_PROFILE_RADIO_BTN);
}
public WebElement selectExistingDeviceProfileRadioBtn() {
return waitUntilElementToBeClickable(SELECT_EXISTING_DEVICE_PROFILE_RADIO_BTN);
}
public WebElement deviceProfileTitleField() {
return waitUntilElementToBeClickable(DEVICE_PROFILE_TITLE_FIELD);
}
public WebElement addBtn() {
return waitUntilElementToBeClickable(ADD_BTN);
}
public WebElement clearProfileFieldBtn() {
return waitUntilElementToBeClickable(CLEAR_PROFILE_FIELD_BTN);
}
public WebElement entityFromDropdown(String customerTitle) {
return waitUntilElementToBeClickable(String.format(ENTITY_FROM_DROPDOWN, customerTitle));
}
public WebElement assignOnCustomerField() {
return waitUntilElementToBeClickable(ASSIGN_ON_CUSTOMER_FIELD);
}
public WebElement customerOptionBtn() {
return waitUntilElementToBeClickable(CUSTOMER_OPTION_BNT);
}
public WebElement customerFromDropDown(String entityName) {
return waitUntilVisibilityOfElementLocated(String.format(CUSTOMER_FROM_DROPDOWN, entityName));
}
public WebElement deviceLabelField() {
return waitUntilElementToBeClickable(DEVICE_LABEL_FIELD);
}
public WebElement checkboxGateway() {
return waitUntilElementToBeClickable(CHECKBOX_GATEWAY);
}
public WebElement checkboxOverwriteActivityTime() {
return waitUntilElementToBeClickable(CHECKBOX_OVERWRITE_ACTIVITY_TIME);
}
public WebElement descriptionField() {
return waitUntilElementToBeClickable(DESCRIPTION_FIELD);
}
}

58
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tabs/CreateDeviceTabHelper.java

@ -0,0 +1,58 @@
/**
* 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.msa.ui.tabs;
import org.openqa.selenium.WebDriver;
public class CreateDeviceTabHelper extends CreateDeviceTabElements {
public CreateDeviceTabHelper(WebDriver driver) {
super(driver);
}
public void enterName(String deviceName) {
enterText(nameField(), deviceName);
}
public void createNewDeviceProfile(String deviceProfileTitle) {
if (!createNewDeviceProfileRadioBtn().getAttribute("class").contains("checked")) {
createNewDeviceProfileRadioBtn().click();
}
deviceProfileTitleField().sendKeys(deviceProfileTitle);
}
public void changeDeviceProfile(String deviceProfileName) {
if (!selectExistingDeviceProfileRadioBtn().getAttribute("class").contains("checked")) {
selectExistingDeviceProfileRadioBtn().click();
}
clearProfileFieldBtn().click();
entityFromDropdown(deviceProfileName).click();
}
public void assignOnCustomer(String customerTitle) {
customerOptionBtn().click();
assignOnCustomerField().click();
customerFromDropDown(customerTitle).click();
sleep(2); //waiting for the action to count
}
public void enterLabel(String label) {
enterText(deviceLabelField(), label);
}
public void enterDescription(String description) {
enterText(descriptionField(), description);
}
}

8
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/devicessmoke/AbstractDeviceTest.java

@ -22,24 +22,32 @@ import org.thingsboard.server.msa.ui.base.AbstractDriverBaseTest;
import org.thingsboard.server.msa.ui.pages.DevicePageHelper; import org.thingsboard.server.msa.ui.pages.DevicePageHelper;
import org.thingsboard.server.msa.ui.pages.LoginPageHelper; import org.thingsboard.server.msa.ui.pages.LoginPageHelper;
import org.thingsboard.server.msa.ui.pages.SideBarMenuViewHelper; import org.thingsboard.server.msa.ui.pages.SideBarMenuViewHelper;
import org.thingsboard.server.msa.ui.tabs.CreateDeviceTabHelper;
@Epic("Device smoke tests") @Epic("Device smoke tests")
abstract public class AbstractDeviceTest extends AbstractDriverBaseTest { abstract public class AbstractDeviceTest extends AbstractDriverBaseTest {
protected SideBarMenuViewHelper sideBarMenuView; protected SideBarMenuViewHelper sideBarMenuView;
protected DevicePageHelper devicePage; protected DevicePageHelper devicePage;
protected CreateDeviceTabHelper createDeviceTab;
protected String deviceName; protected String deviceName;
protected String deviceProfileTitle;
@BeforeClass @BeforeClass
public void login() { public void login() {
new LoginPageHelper(driver).authorizationTenant(); new LoginPageHelper(driver).authorizationTenant();
sideBarMenuView = new SideBarMenuViewHelper(driver); sideBarMenuView = new SideBarMenuViewHelper(driver);
devicePage = new DevicePageHelper(driver); devicePage = new DevicePageHelper(driver);
createDeviceTab = new CreateDeviceTabHelper(driver);
} }
@AfterMethod @AfterMethod
public void delete() { public void delete() {
deleteDeviceByName(deviceName); deleteDeviceByName(deviceName);
deviceName = null; deviceName = null;
if (deviceProfileTitle != null) {
deleteDeviceProfileByTitle(deviceProfileTitle);
deviceProfileTitle = null;
}
} }
} }

193
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/devicessmoke/AssignToCustomerTest.java

@ -0,0 +1,193 @@
/**
* 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.msa.ui.tests.devicessmoke;
import io.qameta.allure.Description;
import io.qameta.allure.Feature;
import org.openqa.selenium.WebElement;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.msa.ui.pages.CustomerPageHelper;
import org.thingsboard.server.msa.ui.tabs.AssignDeviceTabHelper;
import org.thingsboard.server.msa.ui.utils.EntityPrototypes;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
import static org.thingsboard.server.msa.ui.base.AbstractBasePage.random;
import static org.thingsboard.server.msa.ui.utils.Const.ENTITY_NAME;
@Feature("Assign to customer")
public class AssignToCustomerTest extends AbstractDeviceTest {
private AssignDeviceTabHelper assignDeviceTab;
private CustomerPageHelper customerPage;
private CustomerId customerId;
private Device device;
private Device device1;
private String customerName;
@BeforeClass
public void create() {
assignDeviceTab = new AssignDeviceTabHelper(driver);
customerPage = new CustomerPageHelper(driver);
Customer customer = testRestClient.postCustomer(EntityPrototypes.defaultCustomerPrototype(ENTITY_NAME + random()));
customerId = customer.getId();
customerName = customer.getName();
device1 = testRestClient.postDevice("", EntityPrototypes.defaultDevicePrototype("Device " + random()));
}
@AfterClass
public void deleteCustomer() {
deleteCustomerById(customerId);
deleteCustomerByName("Public");
deleteDeviceByName(device1.getName());
}
@BeforeMethod
public void createDevice() {
device = testRestClient.postDevice("", EntityPrototypes.defaultDevicePrototype(ENTITY_NAME));
deviceName = device.getName();
}
@Test(groups = "smoke")
@Description("Assign to customer by right side of device btn")
public void assignToCustomerByRightSideBtn() {
sideBarMenuView.goToDevicesPage();
devicePage.assignBtn(deviceName).click();
assignDeviceTab.assignOnCustomer(customerName);
assertIsDisplayed(devicePage.deviceCustomerOnPage(deviceName));
assertThat(devicePage.deviceCustomerOnPage(deviceName).getText())
.as("Customer added correctly").isEqualTo(customerName);
sideBarMenuView.customerBtn().click();
customerPage.manageCustomersDevicesBtn(customerName).click();
assertIsDisplayed(devicePage.device(deviceName));
}
@Test(groups = "smoke")
@Description("Assign to customer by 'Assign to customer' btn on details tab")
public void assignToCustomerFromDetailsTab() {
sideBarMenuView.goToDevicesPage();
devicePage.device(deviceName).click();
devicePage.assignBtnDetailsTab().click();
assignDeviceTab.assignOnCustomer(customerName);
String customerInAssignedField = devicePage.assignFieldDetailsTab().getAttribute("value");
devicePage.closeDeviceDetailsViewBtn().click();
assertIsDisplayed(devicePage.deviceCustomerOnPage(deviceName));
assertThat(devicePage.deviceCustomerOnPage(deviceName).getText())
.as("Customer added correctly").isEqualTo(customerName);
assertThat(customerInAssignedField)
.as("Customer in details tab added correctly").isEqualTo(customerName);
sideBarMenuView.customerBtn().click();
customerPage.manageCustomersDevicesBtn(customerName).click();
assertIsDisplayed(devicePage.device(deviceName));
}
@Test(groups = "smoke")
@Description("Assign marked device by btn on the top")
public void assignToCustomerMarkedDevice() {
sideBarMenuView.goToDevicesPage();
devicePage.assignSelectedDevices(deviceName);
assignDeviceTab.assignOnCustomer(customerName);
assertIsDisplayed(devicePage.deviceCustomerOnPage(deviceName));
assertThat(devicePage.deviceCustomerOnPage(deviceName).getText())
.as("Customer added correctly").isEqualTo(customerName);
sideBarMenuView.customerBtn().click();
customerPage.manageCustomersDevicesBtn(customerName).click();
assertIsDisplayed(devicePage.device(deviceName));
}
@Test(groups = "smoke")
@Description("Unassign from customer by right side of device btn")
public void unassignedFromCustomerByRightSideBtn() {
device.setCustomerId(customerId);
testRestClient.postDevice("", device);
sideBarMenuView.goToDevicesPage();
WebElement element = devicePage.deviceCustomerOnPage(deviceName);
devicePage.unassignedDeviceByRightSideBtn(deviceName);
assertInvisibilityOfElement(element);
sideBarMenuView.customerBtn().click();
customerPage.manageCustomersDevicesBtn(customerName).click();
devicePage.assertEntityIsNotPresent(deviceName);
}
@Test(groups = "smoke")
@Description("Unassign from customer by 'Unassign from customer' btn on details tab")
public void unassignedFromCustomerFromDetailsTab() {
device.setCustomerId(customerId);
testRestClient.postDevice("", device);
sideBarMenuView.goToDevicesPage();
WebElement customerInColumn = devicePage.deviceCustomerOnPage(deviceName);
devicePage.device(deviceName).click();
WebElement assignFieldDetailsTab = devicePage.assignFieldDetailsTab();
devicePage.unassignedDeviceFromDetailsTab();
assertInvisibilityOfElement(customerInColumn);
assertInvisibilityOfElement(assignFieldDetailsTab);
devicePage.closeDeviceDetailsViewBtn().click();
sideBarMenuView.customerBtn().click();
customerPage.manageCustomersDevicesBtn(customerName).click();
devicePage.assertEntityIsNotPresent(deviceName);
}
@Test(groups = "smoke")
@Description("Can't assign device on several customer")
public void assignToSeveralCustomer() {
device.setCustomerId(customerId);
testRestClient.postDevice("", device);
sideBarMenuView.goToDevicesPage();
assertIsDisable(devicePage.assignBtnVisible(deviceName));
}
@Test(groups = "smoke")
@Description("Can't assign public device")
public void assignPublicDevice() {
testRestClient.setDevicePublic(device.getId());
sideBarMenuView.goToDevicesPage();
assertIsDisable(devicePage.assignBtnVisible(deviceName));
}
@Test(groups = "smoke")
@Description("Assign several devices by btn on the top")
public void assignSeveralDevices() {
sideBarMenuView.goToDevicesPage();
devicePage.assignSelectedDevices(deviceName, device1.getName());
assignDeviceTab.assignOnCustomer(customerName);
assertIsDisplayed(devicePage.deviceCustomerOnPage(deviceName));
assertThat(devicePage.deviceCustomerOnPage(deviceName).getText())
.as("Customer added correctly").isEqualTo(customerName);
assertThat(devicePage.deviceCustomerOnPage(device1.getName()).getText())
.as("Customer added correctly").isEqualTo(customerName);
sideBarMenuView.customerBtn().click();
customerPage.manageCustomersDevicesBtn(customerName).click();
List.of(deviceName, device1.getName()).
forEach(d -> assertIsDisplayed(devicePage.device(d)));
}
}

97
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/devicessmoke/CreateDeviceTest.java

@ -19,6 +19,7 @@ import io.qameta.allure.Description;
import io.qameta.allure.Feature; import io.qameta.allure.Feature;
import org.testng.annotations.Test; import org.testng.annotations.Test;
import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.msa.ui.pages.ProfilesPageElements;
import org.thingsboard.server.msa.ui.utils.EntityPrototypes; import org.thingsboard.server.msa.ui.utils.EntityPrototypes;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
@ -39,8 +40,8 @@ public class CreateDeviceTest extends AbstractDeviceTest {
sideBarMenuView.goToDevicesPage(); sideBarMenuView.goToDevicesPage();
devicePage.openCreateDeviceView(); devicePage.openCreateDeviceView();
devicePage.enterName(deviceName); createDeviceTab.enterName(deviceName);
devicePage.addBtnC().click(); createDeviceTab.addBtn().click();
devicePage.refreshBtn().click(); devicePage.refreshBtn().click();
assertIsDisplayed(devicePage.entity(deviceName)); assertIsDisplayed(devicePage.entity(deviceName));
@ -53,9 +54,9 @@ public class CreateDeviceTest extends AbstractDeviceTest {
sideBarMenuView.goToDevicesPage(); sideBarMenuView.goToDevicesPage();
devicePage.openCreateDeviceView(); devicePage.openCreateDeviceView();
devicePage.enterName(deviceName); createDeviceTab.enterName(deviceName);
devicePage.enterDescription(deviceName); createDeviceTab.enterDescription(deviceName);
devicePage.addBtnC().click(); createDeviceTab.addBtn().click();
devicePage.refreshBtn().click(); devicePage.refreshBtn().click();
devicePage.entity(deviceName).click(); devicePage.entity(deviceName).click();
devicePage.setHeaderName(); devicePage.setHeaderName();
@ -70,8 +71,8 @@ public class CreateDeviceTest extends AbstractDeviceTest {
public void createDeviceWithoutName() { public void createDeviceWithoutName() {
sideBarMenuView.goToDevicesPage(); sideBarMenuView.goToDevicesPage();
devicePage.openCreateDeviceView(); devicePage.openCreateDeviceView();
devicePage.nameField().click(); createDeviceTab.nameField().click();
devicePage.addBtnC().click(); createDeviceTab.addBtn().click();
assertIsDisplayed(devicePage.addDeviceView()); assertIsDisplayed(devicePage.addDeviceView());
assertThat(devicePage.errorMessage().getText()).as("Text of warning message").isEqualTo(NAME_IS_REQUIRED_MESSAGE); assertThat(devicePage.errorMessage().getText()).as("Text of warning message").isEqualTo(NAME_IS_REQUIRED_MESSAGE);
@ -82,8 +83,8 @@ public class CreateDeviceTest extends AbstractDeviceTest {
public void createDeviceWithOnlySpace() { public void createDeviceWithOnlySpace() {
sideBarMenuView.goToDevicesPage(); sideBarMenuView.goToDevicesPage();
devicePage.openCreateDeviceView(); devicePage.openCreateDeviceView();
devicePage.enterName(" "); createDeviceTab.enterName(" ");
devicePage.addBtnC().click(); createDeviceTab.addBtn().click();
assertIsDisplayed(devicePage.warningMessage()); assertIsDisplayed(devicePage.warningMessage());
assertThat(devicePage.warningMessage().getText()).as("Text of warning message").isEqualTo(EMPTY_DEVICE_MESSAGE); assertThat(devicePage.warningMessage().getText()).as("Text of warning message").isEqualTo(EMPTY_DEVICE_MESSAGE);
@ -98,8 +99,8 @@ public class CreateDeviceTest extends AbstractDeviceTest {
sideBarMenuView.goToDevicesPage(); sideBarMenuView.goToDevicesPage();
devicePage.openCreateDeviceView(); devicePage.openCreateDeviceView();
devicePage.enterName(deviceName); createDeviceTab.enterName(deviceName);
devicePage.addBtnC().click(); createDeviceTab.addBtn().click();
assertIsDisplayed(devicePage.warningMessage()); assertIsDisplayed(devicePage.warningMessage());
assertThat(devicePage.warningMessage().getText()).as("Text of warning message").isEqualTo(SAME_NAME_WARNING_DEVICE_MESSAGE); assertThat(devicePage.warningMessage().getText()).as("Text of warning message").isEqualTo(SAME_NAME_WARNING_DEVICE_MESSAGE);
@ -113,8 +114,8 @@ public class CreateDeviceTest extends AbstractDeviceTest {
sideBarMenuView.goToDevicesPage(); sideBarMenuView.goToDevicesPage();
devicePage.openCreateDeviceView(); devicePage.openCreateDeviceView();
devicePage.enterName(deviceName); createDeviceTab.enterName(deviceName);
devicePage.addBtnC().click(); createDeviceTab.addBtn().click();
assertIsDisplayed(devicePage.entity(deviceName)); assertIsDisplayed(devicePage.entity(deviceName));
} }
@ -126,9 +127,9 @@ public class CreateDeviceTest extends AbstractDeviceTest {
sideBarMenuView.goToDevicesPage(); sideBarMenuView.goToDevicesPage();
devicePage.openCreateDeviceView(); devicePage.openCreateDeviceView();
devicePage.enterName(deviceName); createDeviceTab.enterName(deviceName);
devicePage.clearProfileFieldBtn().click(); createDeviceTab.clearProfileFieldBtn().click();
devicePage.addBtnC().click(); createDeviceTab.addBtn().click();
assertIsDisplayed(devicePage.errorMessage()); assertIsDisplayed(devicePage.errorMessage());
assertThat(devicePage.errorMessage().getText()).as("Text of warning message").isEqualTo(DEVICE_PROFILE_IS_REQUIRED_MESSAGE); assertThat(devicePage.errorMessage().getText()).as("Text of warning message").isEqualTo(DEVICE_PROFILE_IS_REQUIRED_MESSAGE);
@ -142,9 +143,9 @@ public class CreateDeviceTest extends AbstractDeviceTest {
sideBarMenuView.goToDevicesPage(); sideBarMenuView.goToDevicesPage();
devicePage.openCreateDeviceView(); devicePage.openCreateDeviceView();
devicePage.enterName(deviceName); createDeviceTab.enterName(deviceName);
devicePage.checkboxGatewayCreate().click(); createDeviceTab.checkboxGateway().click();
devicePage.addBtnC().click(); createDeviceTab.addBtn().click();
assertIsDisplayed(devicePage.device(deviceName)); assertIsDisplayed(devicePage.device(deviceName));
assertIsDisplayed(devicePage.checkboxGatewayPage(deviceName)); assertIsDisplayed(devicePage.checkboxGatewayPage(deviceName));
@ -157,10 +158,10 @@ public class CreateDeviceTest extends AbstractDeviceTest {
sideBarMenuView.goToDevicesPage(); sideBarMenuView.goToDevicesPage();
devicePage.openCreateDeviceView(); devicePage.openCreateDeviceView();
devicePage.enterName(deviceName); createDeviceTab.enterName(deviceName);
devicePage.checkboxGatewayCreate().click(); createDeviceTab.checkboxGateway().click();
devicePage.checkboxOverwriteActivityTimeCreate().click(); createDeviceTab.checkboxOverwriteActivityTime().click();
devicePage.addBtnC().click(); createDeviceTab.addBtn().click();
devicePage.device(deviceName).click(); devicePage.device(deviceName).click();
assertThat(devicePage.checkboxOverwriteActivityTimeDetails().getAttribute("class").contains("selected")) assertThat(devicePage.checkboxOverwriteActivityTimeDetails().getAttribute("class").contains("selected"))
@ -175,9 +176,9 @@ public class CreateDeviceTest extends AbstractDeviceTest {
sideBarMenuView.goToDevicesPage(); sideBarMenuView.goToDevicesPage();
devicePage.openCreateDeviceView(); devicePage.openCreateDeviceView();
devicePage.enterName(deviceName); createDeviceTab.enterName(deviceName);
devicePage.enterLabel(deviceLabel); createDeviceTab.enterLabel(deviceLabel);
devicePage.addBtnC().click(); createDeviceTab.addBtn().click();
assertIsDisplayed(devicePage.deviceLabelOnPage(deviceName)); assertIsDisplayed(devicePage.deviceLabelOnPage(deviceName));
assertThat(devicePage.deviceLabelOnPage(deviceName).getText()).as("Label added correctly").isEqualTo(deviceLabel); assertThat(devicePage.deviceLabelOnPage(deviceName).getText()).as("Label added correctly").isEqualTo(deviceLabel);
@ -191,9 +192,9 @@ public class CreateDeviceTest extends AbstractDeviceTest {
sideBarMenuView.goToDevicesPage(); sideBarMenuView.goToDevicesPage();
devicePage.openCreateDeviceView(); devicePage.openCreateDeviceView();
devicePage.enterName(deviceName); createDeviceTab.enterName(deviceName);
devicePage.assignOnCustomer(customer); createDeviceTab.assignOnCustomer(customer);
devicePage.addBtnC().click(); createDeviceTab.addBtn().click();
assertIsDisplayed(devicePage.deviceCustomerOnPage(deviceName)); assertIsDisplayed(devicePage.deviceCustomerOnPage(deviceName));
assertThat(devicePage.deviceCustomerOnPage(deviceName).getText()) assertThat(devicePage.deviceCustomerOnPage(deviceName).getText())
@ -211,4 +212,42 @@ public class CreateDeviceTest extends AbstractDeviceTest {
assertThat(urlContains(urlPath)).as("Redirected URL contains " + urlPath).isTrue(); assertThat(urlContains(urlPath)).as("Redirected URL contains " + urlPath).isTrue();
} }
@Test(groups = "smoke")
@Description("Create new device profile from create device")
public void createNewDeviceProfile() {
ProfilesPageElements profilesPage = new ProfilesPageElements(driver);
deviceName = ENTITY_NAME + random();
deviceProfileTitle = ENTITY_NAME + random();
sideBarMenuView.goToDevicesPage();
devicePage.openCreateDeviceView();
createDeviceTab.enterName(deviceName);
createDeviceTab.createNewDeviceProfile(deviceProfileTitle);
createDeviceTab.addBtn().click();
devicePage.refreshBtn().click();
String deviceProfileColumn = devicePage.deviceDeviceProfileOnPage(deviceName).getText();
sideBarMenuView.openDeviceProfiles();
assertThat(deviceProfileColumn).as("Profile changed correctly").isEqualTo(deviceProfileTitle);
assertIsDisplayed(profilesPage.entity(deviceProfileTitle));
}
@Test(groups = "smoke")
@Description("Add device with changed device profile (from default to another)")
public void createDeviceWithChangedProfile() {
deviceName = ENTITY_NAME + random();
deviceProfileTitle = ENTITY_NAME + random();
testRestClient.postDeviceProfile(EntityPrototypes.defaultDeviceProfile(deviceProfileTitle));
sideBarMenuView.goToDevicesPage();
devicePage.openCreateDeviceView();
createDeviceTab.enterName(deviceName);
createDeviceTab.changeDeviceProfile(deviceProfileTitle);
createDeviceTab.addBtn().click();
devicePage.refreshBtn().click();
assertThat(devicePage.deviceDeviceProfileOnPage(deviceName).getText())
.as("Profile changed correctly").isEqualTo(deviceProfileTitle);
}
} }

14
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/devicessmoke/DeleteSeveralDevicesTest.java

@ -23,6 +23,8 @@ import org.testng.annotations.Test;
import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.msa.ui.utils.EntityPrototypes; import org.thingsboard.server.msa.ui.utils.EntityPrototypes;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.thingsboard.server.msa.ui.utils.Const.ENTITY_NAME; import static org.thingsboard.server.msa.ui.utils.Const.ENTITY_NAME;
@ -51,11 +53,11 @@ public class DeleteSeveralDevicesTest extends AbstractDeviceTest {
"that appears at the top") "that appears at the top")
public void deleteSeveralDevicesByTopBtn() { public void deleteSeveralDevicesByTopBtn() {
sideBarMenuView.goToDevicesPage(); sideBarMenuView.goToDevicesPage();
devicePage.deleteSelected(2); devicePage.deleteSelectedDevices(deviceName1, deviceName2);
devicePage.refreshBtn().click(); devicePage.refreshBtn().click();
devicePage.assertEntityIsNotPresent(deviceName1); List.of(deviceName1, deviceName2)
devicePage.assertEntityIsNotPresent(deviceName2); .forEach(d -> devicePage.assertEntityIsNotPresent(d));
} }
@Test(groups = "smoke") @Test(groups = "smoke")
@ -76,9 +78,9 @@ public class DeleteSeveralDevicesTest extends AbstractDeviceTest {
"that appears at the top without refresh") "that appears at the top without refresh")
public void deleteSeveralWithoutRefresh() { public void deleteSeveralWithoutRefresh() {
sideBarMenuView.goToDevicesPage(); sideBarMenuView.goToDevicesPage();
devicePage.deleteSelected(2); devicePage.deleteSelectedDevices(deviceName1, deviceName2);
devicePage.assertEntityIsNotPresent(deviceName1); List.of(deviceName1, deviceName2)
devicePage.assertEntityIsNotPresent(deviceName2); .forEach(d -> devicePage.assertEntityIsNotPresent(d));
} }
} }

2
msa/monitoring/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.1-SNAPSHOT</version> <version>3.6.0-SNAPSHOT</version>
<artifactId>msa</artifactId> <artifactId>msa</artifactId>
</parent> </parent>

2
msa/tb/pom.xml

@ -38,7 +38,7 @@
<tb-postgres.docker.name>tb-postgres</tb-postgres.docker.name> <tb-postgres.docker.name>tb-postgres</tb-postgres.docker.name>
<tb-cassandra.docker.name>tb-cassandra</tb-cassandra.docker.name> <tb-cassandra.docker.name>tb-cassandra</tb-cassandra.docker.name>
<pkg.installFolder>/usr/share/${pkg.name}</pkg.installFolder> <pkg.installFolder>/usr/share/${pkg.name}</pkg.installFolder>
<pkg.upgradeVersion>3.5.0</pkg.upgradeVersion> <pkg.upgradeVersion>3.5.2</pkg.upgradeVersion>
</properties> </properties>
<dependencies> <dependencies>

3
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesByNameAndTypeLoader.java

@ -16,6 +16,7 @@
package org.thingsboard.rule.engine.util; package org.thingsboard.rule.engine.util;
import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbContext;
import org.thingsboard.server.common.data.BaseData;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.SearchTextBasedWithAdditionalInfo; import org.thingsboard.server.common.data.SearchTextBasedWithAdditionalInfo;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;
@ -32,7 +33,7 @@ public class EntitiesByNameAndTypeLoader {
EntityType.USER); EntityType.USER);
public static EntityId findEntityId(TbContext ctx, EntityType entityType, String entityName) { public static EntityId findEntityId(TbContext ctx, EntityType entityType, String entityName) {
SearchTextBasedWithAdditionalInfo<? extends EntityId> targetEntity; BaseData<? extends EntityId> targetEntity;
switch (entityType) { switch (entityType) {
case DEVICE: case DEVICE:
targetEntity = ctx.getDeviceService().findDeviceByTenantIdAndName(ctx.getTenantId(), entityName); targetEntity = ctx.getDeviceService().findDeviceByTenantIdAndName(ctx.getTenantId(), entityName);

30
ui-ngx/package.json

@ -14,17 +14,17 @@
}, },
"private": true, "private": true,
"dependencies": { "dependencies": {
"@angular/animations": "^15.2.5", "@angular/animations": "^15.2.9",
"@angular/cdk": "^15.2.5", "@angular/cdk": "^15.2.9",
"@angular/common": "^15.2.5", "@angular/common": "^15.2.9",
"@angular/compiler": "^15.2.5", "@angular/compiler": "^15.2.9",
"@angular/core": "^15.2.5", "@angular/core": "^15.2.9",
"@angular/flex-layout": "^15.0.0-beta.42", "@angular/flex-layout": "^15.0.0-beta.42",
"@angular/forms": "^15.2.5", "@angular/forms": "^15.2.9",
"@angular/material": "^15.2.5", "@angular/material": "^15.2.9",
"@angular/platform-browser": "^15.2.5", "@angular/platform-browser": "^15.2.9",
"@angular/platform-browser-dynamic": "^15.2.5", "@angular/platform-browser-dynamic": "^15.2.9",
"@angular/router": "^15.2.5", "@angular/router": "^15.2.9",
"@auth0/angular-jwt": "^5.1.2", "@auth0/angular-jwt": "^5.1.2",
"@date-io/core": "1.3.7", "@date-io/core": "1.3.7",
"@date-io/date-fns": "1.3.7", "@date-io/date-fns": "1.3.7",
@ -108,16 +108,16 @@
}, },
"devDependencies": { "devDependencies": {
"@angular-builders/custom-webpack": "~15.0.0", "@angular-builders/custom-webpack": "~15.0.0",
"@angular-devkit/build-angular": "^15.2.4", "@angular-devkit/build-angular": "^15.2.8",
"@angular-eslint/builder": "15.2.1", "@angular-eslint/builder": "15.2.1",
"@angular-eslint/eslint-plugin": "15.2.1", "@angular-eslint/eslint-plugin": "15.2.1",
"@angular-eslint/eslint-plugin-template": "15.2.1", "@angular-eslint/eslint-plugin-template": "15.2.1",
"@angular-eslint/schematics": "15.2.1", "@angular-eslint/schematics": "15.2.1",
"@angular-eslint/template-parser": "15.2.1", "@angular-eslint/template-parser": "15.2.1",
"@angular/cli": "^15.2.4", "@angular/cli": "^15.2.8",
"@angular/compiler-cli": "^15.2.5", "@angular/compiler-cli": "^15.2.9",
"@angular/language-service": "^15.2.5", "@angular/language-service": "^15.2.9",
"@ngtools/webpack": "^15.1.6", "@ngtools/webpack": "15.2.1",
"@types/ace-diff": "^2.1.1", "@types/ace-diff": "^2.1.1",
"@types/canvas-gauges": "^2.1.4", "@types/canvas-gauges": "^2.1.4",
"@types/flot": "^0.0.32", "@types/flot": "^0.0.32",

20
ui-ngx/patches/@angular+core+15.2.9.patch

@ -0,0 +1,20 @@
diff --git a/node_modules/@angular/core/fesm2020/core.mjs b/node_modules/@angular/core/fesm2020/core.mjs
index 3e93015..9efcb96 100755
--- a/node_modules/@angular/core/fesm2020/core.mjs
+++ b/node_modules/@angular/core/fesm2020/core.mjs
@@ -11053,13 +11053,13 @@ function findDirectiveDefMatches(tView, tNode) {
if (isNodeMatchingSelectorList(tNode, def.selectors, /* isProjectionMode */ false)) {
matches || (matches = []);
if (isComponentDef(def)) {
- if (ngDevMode) {
+ // if (ngDevMode) {
assertTNodeType(tNode, 2 /* TNodeType.Element */, `"${tNode.value}" tags cannot be used as component hosts. ` +
`Please use a different tag to activate the ${stringify(def.type)} component.`);
if (isComponentHost(tNode)) {
throwMultipleComponentError(tNode, matches.find(isComponentDef).type, def.type);
}
- }
+ // }
// Components are inserted at the front of the matches array so that their lifecycle
// hooks run before any directive lifecycle hooks. This appears to be for ViewEngine
// compatibility. This logic doesn't make sense with host directives, because it

8
ui-ngx/src/app/core/http/entity.service.ts

@ -422,6 +422,14 @@ export class EntityService {
pageLink.sortOrder.property = 'name'; pageLink.sortOrder.property = 'name';
entitiesObservable = this.notificationService.getNotificationTargets(pageLink, subType as NotificationType, config); entitiesObservable = this.notificationService.getNotificationTargets(pageLink, subType as NotificationType, config);
break; break;
case EntityType.NOTIFICATION_TEMPLATE:
pageLink.sortOrder.property = 'name';
entitiesObservable = this.notificationService.getNotificationTemplates(pageLink, subType as NotificationType, config);
break;
case EntityType.NOTIFICATION_RULE:
pageLink.sortOrder.property = 'name';
entitiesObservable = this.notificationService.getNotificationRules(pageLink, config);
break;
} }
return entitiesObservable; return entitiesObservable;
} }

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

Loading…
Cancel
Save