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.
--
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;
import lombok.RequiredArgsConstructor;
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.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.id.CustomerId;
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.data.EntityType;
import org.thingsboard.server.common.data.exception.TenantProfileNotFoundException;
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.dao.util.limits.LimitedApi;
import org.thingsboard.server.dao.util.limits.RateLimitService;
import org.thingsboard.server.service.security.model.SecurityUser;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
@Slf4j
@Component
@RequiredArgsConstructor
public class RateLimitProcessingFilter extends OncePerRequestFilter {
@Autowired
private ThingsboardErrorResponseHandler errorResponseHandler;
@Autowired
@Lazy
private TbTenantProfileCache tenantProfileCache;
private final ConcurrentMap<TenantId, TbRateLimits> perTenantLimits = new ConcurrentHashMap<>();
private final ConcurrentMap<CustomerId, TbRateLimits> perCustomerLimits = new ConcurrentHashMap<>();
private final ThingsboardErrorResponseHandler errorResponseHandler;
private final RateLimitService rateLimitService;
@Override
public void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
SecurityUser user = getCurrentUser();
if (user != null && !user.isSystemAdmin()) {
var profile = tenantProfileCache.get(user.getTenantId());
if (profile == null) {
try {
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());
errorResponseHandler.handle(new BadCredentialsException("Failed to lookup tenant profile"), response);
return;
}
var profileConfiguration = profile.getDefaultProfileConfiguration();
if (!checkRateLimits(user.getTenantId(), profileConfiguration.getTenantServerRestLimitsConfiguration(), perTenantLimits, response)) {
return;
}
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;
}
}
@ -90,23 +79,8 @@ public class RateLimitProcessingFilter extends OncePerRequestFilter {
return false;
}
private <I extends EntityId> boolean checkRateLimits(I ownerId, String rateLimitConfig, Map<I, TbRateLimits> rateLimitsMap, ServletResponse response) {
if (StringUtils.isNotEmpty(rateLimitConfig)) {
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;
private void rateLimitExceeded(EntityType type, HttpServletResponse response) {
errorResponseHandler.handle(new TbRateLimitsException(type), response);
}
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.ThingsboardException;
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.event.UserCredentialsInvalidationEvent;
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.SecuritySettings;
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.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.model.ActivateUserRequest;
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 java.net.URI;
import java.net.URISyntaxException;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
@RestController
@TbCoreComponent
@ -77,12 +74,11 @@ public class AuthController extends BaseController {
@Value("${server.rest.rate_limits.reset_password_per_user:5:3600}")
private String defaultLimitsConfiguration;
private final ConcurrentMap<UserId, TbRateLimits> resetPasswordRateLimits = new ConcurrentHashMap<>();
private final BCryptPasswordEncoder passwordEncoder;
private final JwtTokenFactory tokenFactory;
private final MailService mailService;
private final SystemSecurityService systemSecurityService;
private final AuditLogService auditLogService;
private final RateLimitService rateLimitService;
private final ApplicationEventPublisher eventPublisher;
@ -210,8 +206,7 @@ public class AuthController extends BaseController {
UserCredentials userCredentials = userService.findUserCredentialsByResetToken(TenantId.SYS_TENANT_ID, resetToken);
if (userCredentials != null) {
TbRateLimits tbRateLimits = getTbRateLimits(userCredentials.getUserId());
if (!tbRateLimits.tryConsume()) {
if (!rateLimitService.checkRateLimit(LimitedApi.PASSWORD_RESET, userCredentials.getUserId(), defaultLimitsConfiguration)) {
return ResponseEntity.status(HttpStatus.TOO_MANY_REQUESTS).build();
}
try {
@ -314,8 +309,4 @@ public class AuthController extends BaseController {
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.adapter.NativeWebSocketSession;
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.exception.ThingsboardErrorCode;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
import org.thingsboard.server.common.msg.tools.TbRateLimits;
import org.thingsboard.server.config.WebSocketConfiguration;
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.service.security.model.SecurityUser;
import org.thingsboard.server.service.security.model.UserPrincipal;
@ -80,6 +80,9 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements WebSocke
@Autowired
private TbTenantProfileCache tenantProfileCache;
@Autowired
private RateLimitService rateLimitService;
@Value("${server.ws.send_timeout:5000}")
private long sendTimeout;
@Value("${server.ws.ping_timeout:30000}")
@ -88,7 +91,6 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements WebSocke
private int wsMaxQueueMessagesPerSession;
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<CustomerId, Set<String>> customerSessionsMap = new ConcurrentHashMap<>();
@ -331,24 +333,17 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements WebSocke
if (internalId != null) {
SessionMetaData sessionMd = internalSessionMap.get(internalId);
if (sessionMd != null) {
var tenantProfileConfiguration = getTenantProfileConfiguration(sessionRef);
if (tenantProfileConfiguration != null) {
if (StringUtils.isNotEmpty(tenantProfileConfiguration.getWsUpdatesPerSessionRateLimit())) {
TbRateLimits rateLimits = perSessionUpdateLimits.computeIfAbsent(sessionRef.getSessionId(), sid -> new TbRateLimits(tenantProfileConfiguration.getWsUpdatesPerSessionRateLimit()));
if (!rateLimits.tryConsume()) {
if (blacklistedSessions.putIfAbsent(externalId, sessionRef) == null) {
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());
TenantId tenantId = sessionRef.getSecurityCtx().getTenantId();
if (!rateLimitService.checkRateLimit(LimitedApi.WS_UPDATES_PER_SESSION, tenantId, (Object) sessionRef.getSessionId())) {
if (blacklistedSessions.putIfAbsent(externalId, sessionRef) == null) {
log.info("[{}][{}][{}] Failed to process session update. Max session updates limit reached"
, tenantId, 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.", tenantId, sessionRef.getSecurityCtx().getId(), externalId);
blacklistedSessions.remove(externalId);
}
sessionMd.sendMsg(msg);
} else {
@ -464,7 +459,7 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements WebSocke
if (tenantProfileConfiguration == null) return;
String sessionId = session.getId();
perSessionUpdateLimits.remove(sessionRef.getSessionId());
rateLimitService.cleanUp(LimitedApi.WS_UPDATES_PER_SESSION, sessionRef.getSessionId());
blacklistedSessions.remove(sessionRef.getSessionId());
if (tenantProfileConfiguration.getMaxWsSessionsPerTenant() > 0) {
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":
log.info("Upgrading ThingsBoard from version 3.5.0 to 3.5.1 ...");
databaseEntitiesUpgradeService.upgradeDatabase("3.5.0");
entityDatabaseSchemaService.createOrUpdateViewsAndFunctions();
entityDatabaseSchemaService.createOrUpdateDeviceInfoView(persistToTelemetry);
log.info("Updating system data...");
systemDataLoaderService.updateSystemWidgets();
installScripts.loadSystemLwm2mResources();
case "3.5.1":
log.info("Upgrading ThingsBoard from version 3.5.1 to 3.5.2 ...");
databaseEntitiesUpgradeService.upgradeDatabase("3.5.1");
//TODO DON'T FORGET to update switch statement in the CacheCleanupService if you need to clear the cache
break;
//TODO update CacheCleanupService on the next version upgrade
default:
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!");

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);
}
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:
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;
case "3.4.4":
log.info("Clearing cache to upgrade from version 3.4.4 to 3.5.0");
clearCacheByName("deviceProfiles");
clearAll();
break;
default:
//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.discovery.NotificationsTopicService;
import org.thingsboard.server.queue.provider.TbQueueProducerProvider;
import org.thingsboard.server.service.apiusage.limits.LimitedApi;
import org.thingsboard.server.service.apiusage.limits.RateLimitService;
import org.thingsboard.server.dao.util.limits.LimitedApi;
import org.thingsboard.server.dao.util.limits.RateLimitService;
import org.thingsboard.server.service.executors.DbCallbackExecutorService;
import org.thingsboard.server.service.executors.NotificationExecutorService;
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.queue.ServiceType;
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.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.notification.rule.cache.NotificationRulesCache;
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;
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.thingsboard.server.common.data.EntityType;
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 {
private final ResourceService resourceService;
private final DDFFileParser ddfFileParser;
public DefaultTbResourceService(ResourceService resourceService) {
this.resourceService = resourceService;
this.ddfFileParser = new DDFFileParser(new DefaultDDFFileValidator());
}
@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;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.tuple.Pair;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.LockedException;
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.provider.TwoFaProviderConfig;
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.util.limits.LimitedApi;
import org.thingsboard.server.dao.util.limits.RateLimitService;
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.provider.TwoFaProvider;
@ -41,8 +43,6 @@ import java.util.Collection;
import java.util.EnumMap;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
@Service
@RequiredArgsConstructor
@ -52,14 +52,13 @@ public class DefaultTwoFactorAuthService implements TwoFactorAuthService {
private final TwoFaConfigManager configManager;
private final SystemSecurityService systemSecurityService;
private final UserService userService;
private final RateLimitService rateLimitService;
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 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 final ConcurrentMap<UserId, ConcurrentMap<TwoFaProviderType, TbRateLimits>> verificationCodeSendingRateLimits = new ConcurrentHashMap<>();
private final ConcurrentMap<UserId, ConcurrentMap<TwoFaProviderType, TbRateLimits>> verificationCodeCheckingRateLimits = new ConcurrentHashMap<>();
private static final ThingsboardException TOO_MANY_REQUESTS_ERROR = new ThingsboardException("Too many requests", ThingsboardErrorCode.TOO_MANY_REQUESTS);
@Override
public boolean isTwoFaEnabled(TenantId tenantId, UserId userId) {
@ -91,7 +90,10 @@ public class DefaultTwoFactorAuthService implements TwoFactorAuthService {
if (minVerificationCodeSendPeriod != null && minVerificationCodeSendPeriod > 4) {
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())
@ -116,7 +118,10 @@ public class DefaultTwoFactorAuthService implements TwoFactorAuthService {
PlatformTwoFaSettings twoFaSettings = configManager.getPlatformTwoFaSettings(user.getTenantId(), true)
.orElseThrow(() -> PROVIDER_NOT_CONFIGURED_ERROR);
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())
.orElseThrow(() -> PROVIDER_NOT_CONFIGURED_ERROR);
@ -131,43 +136,28 @@ public class DefaultTwoFactorAuthService implements TwoFactorAuthService {
try {
systemSecurityService.validateTwoFaVerification(user, verificationSuccess, twoFaSettings);
} catch (LockedException e) {
verificationCodeCheckingRateLimits.remove(user.getId());
verificationCodeSendingRateLimits.remove(user.getId());
cleanUpRateLimits(user.getId());
throw new ThingsboardException(e.getMessage(), ThingsboardErrorCode.AUTHENTICATION);
}
if (verificationSuccess) {
verificationCodeCheckingRateLimits.remove(user.getId());
verificationCodeSendingRateLimits.remove(user.getId());
cleanUpRateLimits(user.getId());
}
}
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
public TwoFaAccountConfig generateNewAccountConfig(User user, TwoFaProviderType providerType) throws ThingsboardException {
TwoFaProviderConfig providerConfig = getTwoFaProviderConfig(user.getTenantId(), providerType);
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 {
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;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.SerializationUtils;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
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.Optional;
@Slf4j
public class CookieUtils {
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) {
return cls.cast(SerializationUtils.deserialize(
Base64.getUrlDecoder().decode(cookie.getValue())));
byte[] decodedBytes = 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.relation.RelationService;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.apiusage.limits.LimitedApi;
import org.thingsboard.server.service.apiusage.limits.RateLimitService;
import org.thingsboard.server.dao.util.limits.LimitedApi;
import org.thingsboard.server.dao.util.limits.RateLimitService;
import org.thingsboard.server.service.entitiy.TbNotificationEntityService;
import org.thingsboard.server.service.sync.ie.exporting.EntityExportService;
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(
EntityType.CUSTOMER, EntityType.ASSET_PROFILE, EntityType.ASSET, EntityType.RULE_CHAIN,
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.ExportableEntity;
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.HasId;
import org.thingsboard.server.common.data.id.RuleChainId;
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.PageLink;
import org.thingsboard.server.dao.Dao;
import org.thingsboard.server.dao.ExportableEntityDao;
import org.thingsboard.server.dao.asset.AssetProfileService;
import org.thingsboard.server.dao.asset.AssetService;
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.dao.entity.EntityDaoService;
import org.thingsboard.server.dao.entity.EntityServiceRegistry;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.security.permission.AccessControlService;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.function.BiConsumer;
@Service
@TbCoreComponent
@ -60,11 +45,10 @@ import java.util.function.BiConsumer;
public class DefaultExportableEntitiesService implements ExportableEntitiesService {
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;
@Override
public <E extends ExportableEntity<I>, I extends EntityId> E findEntityByTenantIdAndExternalId(TenantId tenantId, I externalId) {
EntityType entityType = externalId.getEntityType();
@ -152,11 +136,11 @@ public class DefaultExportableEntitiesService implements ExportableEntitiesServi
@Override
public <I extends EntityId> void removeById(TenantId tenantId, I id) {
EntityType entityType = id.getEntityType();
BiConsumer<TenantId, EntityId> entityRemover = removers.get(entityType);
if (entityRemover == null) {
EntityDaoService entityService = entityServiceRegistry.getServiceByEntityType(entityType);
if (entityService == null) {
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) {
@ -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.service.sync.vc.data.EntitiesExportCtx;
import java.util.Collection;
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> {
@ -47,4 +51,11 @@ public abstract class BaseEntityExportService<I extends EntityId, E extends Expo
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.LwM2mObject;
import org.thingsboard.server.common.data.lwm2m.LwM2mResourceObserve;
import org.thingsboard.server.common.data.util.TbDDFFileParser;
import org.thingsboard.server.dao.exception.DataValidationException;
import java.io.ByteArrayInputStream;
@ -41,7 +42,7 @@ import static org.thingsboard.server.common.data.lwm2m.LwM2mConstants.LWM2M_SEPA
@Slf4j
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 {
try {

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

@ -495,8 +495,8 @@ cache:
timeToLiveInMinutes: "${CACHE_SPECS_NOTIFICATION_RULES_TTL:30}"
maxSize: "${CACHE_SPECS_NOTIFICATION_RULES_MAX_SIZE:1000}"
rateLimits:
timeToLiveInMinutes: "${CACHE_SPECS_RATE_LIMITS_TTL:60}"
maxSize: "${CACHE_SPECS_RATE_LIMITS_MAX_SIZE:100000}"
timeToLiveInMinutes: "${CACHE_SPECS_RATE_LIMITS_TTL:120}"
maxSize: "${CACHE_SPECS_RATE_LIMITS_MAX_SIZE:200000}"
#Disable this because it is not required.
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.rule.RuleChainService;
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.service.apiusage.limits.LimitedApi;
import org.thingsboard.server.service.apiusage.limits.RateLimitService;
import org.thingsboard.server.service.telemetry.AlarmSubscriptionService;
import java.util.ArrayList;
@ -480,7 +480,7 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest {
assertThat(getMyNotifications(false, 100)).size().isZero();
createDevice("Device 1", "default", "111");
await().atMost(15, TimeUnit.SECONDS)
await().atMost(30, TimeUnit.SECONDS)
.untilAsserted(() -> {
assertThat(getMyNotifications(false, 100)).size().isEqualTo(1);
});
@ -494,9 +494,10 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest {
rule.setEnabled(true);
saveNotificationRule(rule);
TimeUnit.SECONDS.sleep(2); // for rule update event to reach rules cache
createDevice("Device 3", "default", "333");
await().atMost(15, TimeUnit.SECONDS)
await().atMost(30, TimeUnit.SECONDS)
.untilAsserted(() -> {
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 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;
@DaoSqlTest
@ -75,6 +77,32 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest {
"</Object>\n" +
"</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 IdComparator<TbResourceInfo> idComparator = new IdComparator<>();
@ -126,11 +154,11 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest {
loginTenantAdmin();
Assert.assertEquals(0, resourceService.sumDataSizeByTenantId(tenantId));
assertEquals(0, resourceService.sumDataSizeByTenantId(tenantId));
createResource("test", DEFAULT_FILE_NAME);
Assert.assertEquals(1, resourceService.sumDataSizeByTenantId(tenantId));
assertEquals(1, resourceService.sumDataSizeByTenantId(tenantId));
try {
assertThatThrownBy(() -> createResource("test1", 1 + DEFAULT_FILE_NAME))
@ -145,19 +173,19 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest {
@Test
public void sumDataSizeByTenantId() throws Exception {
Assert.assertEquals(0, resourceService.sumDataSizeByTenantId(tenantId));
assertEquals(0, resourceService.sumDataSizeByTenantId(tenantId));
createResource("test", DEFAULT_FILE_NAME);
Assert.assertEquals(1, resourceService.sumDataSizeByTenantId(tenantId));
assertEquals(1, resourceService.sumDataSizeByTenantId(tenantId));
int maxSumDataSize = 8;
for (int i = 2; i <= maxSumDataSize; i++) {
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 {
@ -184,16 +212,16 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest {
Assert.assertNotNull(savedResource);
Assert.assertNotNull(savedResource.getId());
Assert.assertTrue(savedResource.getCreatedTime() > 0);
Assert.assertEquals(resource.getTenantId(), savedResource.getTenantId());
Assert.assertEquals(resource.getTitle(), savedResource.getTitle());
Assert.assertEquals(resource.getResourceKey(), savedResource.getResourceKey());
Assert.assertEquals(resource.getData(), savedResource.getData());
assertEquals(resource.getTenantId(), savedResource.getTenantId());
assertEquals(resource.getTitle(), savedResource.getTitle());
assertEquals(resource.getResourceKey(), savedResource.getResourceKey());
assertEquals(resource.getData(), savedResource.getData());
savedResource.setTitle("My new resource");
resourceService.save(savedResource);
TbResource foundResource = resourceService.findResourceById(tenantId, savedResource.getId());
Assert.assertEquals(foundResource.getTitle(), savedResource.getTitle());
assertEquals(foundResource.getTitle(), savedResource.getTitle());
resourceService.delete(savedResource, null);
}
@ -211,10 +239,10 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest {
Assert.assertNotNull(savedResource);
Assert.assertNotNull(savedResource.getId());
Assert.assertTrue(savedResource.getCreatedTime() > 0);
Assert.assertEquals(resource.getTenantId(), savedResource.getTenantId());
Assert.assertEquals("My first resource id=0 v1.0", savedResource.getTitle());
Assert.assertEquals("0_1.0", savedResource.getResourceKey());
Assert.assertEquals(resource.getData(), savedResource.getData());
assertEquals(resource.getTenantId(), savedResource.getTenantId());
assertEquals("My first resource id=0 v1.0", savedResource.getTitle());
assertEquals("0_1.0", savedResource.getResourceKey());
assertEquals(resource.getData(), savedResource.getData());
resourceService.delete(savedResource, null);
}
@ -228,7 +256,7 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest {
resource.setData("Test Data");
TbResource savedResource = resourceService.save(resource);
Assert.assertEquals(TenantId.SYS_TENANT_ID, savedResource.getTenantId());
assertEquals(TenantId.SYS_TENANT_ID, savedResource.getTenantId());
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
public void testFindResourceById() throws Exception {
TbResource resource = new TbResource();
@ -296,7 +339,7 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest {
TbResource foundResource = resourceService.findResourceById(tenantId, savedResource.getId());
Assert.assertNotNull(foundResource);
Assert.assertEquals(savedResource, foundResource);
assertEquals(savedResource, foundResource);
resourceService.delete(savedResource, null);
}
@ -312,7 +355,7 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest {
TbResource foundResource = resourceService.getResource(tenantId, savedResource.getResourceType(), savedResource.getResourceKey());
Assert.assertNotNull(foundResource);
Assert.assertEquals(savedResource, foundResource);
assertEquals(savedResource, foundResource);
resourceService.delete(savedResource, null);
}
@ -366,7 +409,7 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest {
Collections.sort(resources, idComparator);
Collections.sort(loadedResources, idComparator);
Assert.assertEquals(resources, loadedResources);
assertEquals(resources, loadedResources);
resourceService.deleteResourcesByTenantId(tenantId);
@ -427,14 +470,14 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest {
Collections.sort(resources, idComparator);
Collections.sort(loadedResources, idComparator);
Assert.assertEquals(resources, loadedResources);
assertEquals(resources, loadedResources);
resourceService.deleteResourcesByTenantId(tenantId);
pageLink = new PageLink(100);
pageData = resourceService.findAllTenantResourcesByTenantId(tenantId, pageLink);
Assert.assertFalse(pageData.hasNext());
Assert.assertEquals(pageData.getData().size(), 100);
assertEquals(pageData.getData().size(), 100);
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());
}
default void deleteEntity(TenantId tenantId, EntityId id) {
throw new IllegalArgumentException(getEntityType().getNormalName() + " deletion not supported");
}
EntityType getEntityType();
}

5
common/data/pom.xml

@ -112,6 +112,11 @@
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java-util</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.leshan</groupId>
<artifactId>leshan-core</artifactId>
<scope>compile</scope>
</dependency>
</dependencies>
<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
@EqualsAndHashCode(callSuper = true)
@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;
@ -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.")
public OtaPackageId getFirmwareId() {
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.NoArgsConstructor;
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.HasTenantId;
import org.thingsboard.server.common.data.id.NotificationRuleId;
@ -39,7 +40,7 @@ import java.io.Serializable;
@Data
@NoArgsConstructor
@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;
@NotBlank
@ -61,6 +62,8 @@ public class NotificationRule extends BaseData<NotificationRuleId> implements Ha
private NotificationRuleConfig additionalConfig;
private NotificationRuleId externalId;
public NotificationRule(NotificationRule other) {
super(other);
this.tenantId = other.tenantId;
@ -71,6 +74,7 @@ public class NotificationRule extends BaseData<NotificationRuleId> implements Ha
this.triggerConfig = other.triggerConfig;
this.recipientsConfig = other.recipientsConfig;
this.additionalConfig = other.additionalConfig;
this.externalId = other.externalId;
}
@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.EqualsAndHashCode;
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.HasTenantId;
import org.thingsboard.server.common.data.id.NotificationTargetId;
@ -31,7 +32,7 @@ import javax.validation.constraints.NotNull;
@Data
@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;
@NotBlank
@ -42,4 +43,17 @@ public class NotificationTarget extends BaseData<NotificationTargetId> implement
@Valid
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.EqualsAndHashCode;
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.HasTenantId;
import org.thingsboard.server.common.data.id.NotificationTemplateId;
@ -32,7 +33,7 @@ import javax.validation.constraints.NotNull;
@Data
@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;
@NoXss
@ -46,4 +47,18 @@ public class NotificationTemplate extends BaseData<NotificationTemplateId> imple
@NotNull
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.constraints.NotEmpty;
import java.util.HashMap;
import java.util.Map;
@Data
@ -29,4 +30,12 @@ public class NotificationTemplateConfig {
@NotEmpty
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.asset.Asset;
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.widget.WidgetsBundle;
@ -48,7 +51,10 @@ import java.lang.annotation.Target;
@Type(name = "DASHBOARD", value = Dashboard.class),
@Type(name = "CUSTOMER", value = Customer.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)
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.node.codec.CodecException;
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.auth.ValidateDeviceCredentialsResponse;
import org.thingsboard.server.gen.transport.TransportProtos;
@ -142,9 +143,9 @@ public class LwM2mTransportServerHelper {
.build();
}
public ObjectModel parseFromXmlToObjectModel(byte[] xmlByte, String streamName, DefaultDDFFileValidator ddfValidator) {
public ObjectModel parseFromXmlToObjectModel(byte[] xmlByte, String streamName) {
try {
DDFFileParser ddfFileParser = new DDFFileParser(ddfValidator);
TbDDFFileParser ddfFileParser = new TbDDFFileParser();
return ddfFileParser.parse(new ByteArrayInputStream(xmlByte), streamName).get(0);
} catch (IOException | InvalidDDFFileException 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);
return tbResource.map(resource -> helper.parseFromXmlToObjectModel(
Base64.getDecoder().decode(resource.getData()),
key + ".xml",
new DefaultDDFFileValidator())).orElse(null);
key + ".xml")).orElse(null);
}
}
}

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

@ -109,6 +109,14 @@ public abstract class DaoUtil {
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) {
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())));
}
@Override
@Transactional
public void deleteEntity(TenantId tenantId, EntityId id) {
deleteAssetProfile(tenantId, (AssetProfileId) id);
}
@Override
public EntityType getEntityType() {
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);
}
@Override
@Transactional
public void deleteEntity(TenantId tenantId, EntityId id) {
deleteAsset(tenantId, (AssetId) id);
}
@Override
public EntityType getEntityType() {
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())));
}
@Transactional
@Override
public void deleteEntity(TenantId tenantId, EntityId id) {
deleteCustomer(tenantId, (CustomerId) id);
}
@Override
public long countByTenantId(TenantId 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);
}
@Override
@Transactional
public void deleteEntity(TenantId tenantId, EntityId id) {
deleteDashboard(tenantId, (DashboardId) id);
}
@Override
public EntityType getEntityType() {
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())));
}
@Override
@Transactional
public void deleteEntity(TenantId tenantId, EntityId id) {
deleteDeviceProfile(tenantId, (DeviceProfileId) id);
}
@Override
public EntityType getEntityType() {
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);
}
@Override
@Transactional
public void deleteEntity(TenantId tenantId, EntityId id) {
deleteDevice(tenantId, (DeviceId) id);
}
private PaginatedRemover<TenantId, Device> tenantDevicesRemover =
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.TenantId;
import org.thingsboard.server.common.data.id.UUIDBased;
import org.thingsboard.server.dao.DaoUtil;
import javax.persistence.Column;
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) {
if (uuid != null) {
return creator.apply(uuid);
} else {
return null;
}
return DaoUtil.toEntityId(uuid, creator);
}
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.dao.model.BaseSqlEntity;
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.JsonStringType;
@ -47,7 +46,7 @@ import java.util.UUID;
@TypeDef(name = "jsonb", typeClass = JsonBinaryType.class)
})
@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")
private UUID tenantId;
@ -64,9 +63,6 @@ public abstract class AbstractDeviceEntity<T extends Device> extends BaseSqlEnti
@Column(name = ModelConstants.DEVICE_LABEL_PROPERTY)
private String label;
@Column(name = ModelConstants.SEARCH_TEXT_PROPERTY)
private String searchText;
@Type(type = "json")
@Column(name = ModelConstants.DEVICE_ADDITIONAL_INFO_PROPERTY)
private JsonNode additionalInfo;
@ -131,23 +127,12 @@ public abstract class AbstractDeviceEntity<T extends Device> extends BaseSqlEnti
this.type = deviceEntity.getType();
this.name = deviceEntity.getName();
this.label = deviceEntity.getLabel();
this.searchText = deviceEntity.getSearchText();
this.additionalInfo = deviceEntity.getAdditionalInfo();
this.firmwareId = deviceEntity.getFirmwareId();
this.softwareId = deviceEntity.getSoftwareId();
this.externalId = deviceEntity.getExternalId();
}
@Override
public String getSearchTextSource() {
return name;
}
@Override
public void setSearchText(String searchText) {
this.searchText = searchText;
}
protected Device toDevice() {
Device device = new Device(new DeviceId(getUuid()));
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)
private JsonNode additionalConfig;
@Column(name = ModelConstants.EXTERNAL_ID_PROPERTY)
private UUID externalId;
public NotificationRuleEntity() {}
public NotificationRuleEntity(NotificationRule notificationRule) {
@ -85,6 +88,7 @@ public class NotificationRuleEntity extends BaseSqlEntity<NotificationRule> {
setTriggerConfig(toJson(notificationRule.getTriggerConfig()));
setRecipientsConfig(toJson(notificationRule.getRecipientsConfig()));
setAdditionalConfig(toJson(notificationRule.getAdditionalConfig()));
setExternalId(getUuid(notificationRule.getExternalId()));
}
public NotificationRuleEntity(NotificationRuleEntity other) {
@ -98,6 +102,7 @@ public class NotificationRuleEntity extends BaseSqlEntity<NotificationRule> {
this.triggerConfig = other.triggerConfig;
this.recipientsConfig = other.recipientsConfig;
this.additionalConfig = other.additionalConfig;
this.externalId = other.externalId;
}
@Override
@ -113,6 +118,7 @@ public class NotificationRuleEntity extends BaseSqlEntity<NotificationRule> {
notificationRule.setTriggerConfig(fromJson(triggerConfig, NotificationRuleTriggerConfig.class));
notificationRule.setRecipientsConfig(fromJson(recipientsConfig, NotificationRuleRecipientsConfig.class));
notificationRule.setAdditionalConfig(fromJson(additionalConfig, NotificationRuleConfig.class));
notificationRule.setExternalId(getEntityId(externalId, NotificationRuleId::new));
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)
private JsonNode configuration;
@Column(name = ModelConstants.EXTERNAL_ID_PROPERTY)
private UUID externalId;
public NotificationTargetEntity() {}
public NotificationTargetEntity(NotificationTarget notificationTarget) {
@ -57,6 +60,7 @@ public class NotificationTargetEntity extends BaseSqlEntity<NotificationTarget>
setTenantId(getTenantUuid(notificationTarget.getTenantId()));
setName(notificationTarget.getName());
setConfiguration(toJson(notificationTarget.getConfiguration()));
setExternalId(getUuid(notificationTarget.getExternalId()));
}
@Override
@ -67,6 +71,7 @@ public class NotificationTargetEntity extends BaseSqlEntity<NotificationTarget>
notificationTarget.setTenantId(getTenantId(tenantId));
notificationTarget.setName(name);
notificationTarget.setConfiguration(fromJson(configuration, NotificationTargetConfig.class));
notificationTarget.setExternalId(getEntityId(externalId, NotificationTargetId::new));
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)
private JsonNode configuration;
@Column(name = ModelConstants.EXTERNAL_ID_PROPERTY)
private UUID externalId;
public NotificationTemplateEntity() {}
public NotificationTemplateEntity(NotificationTemplate notificationTemplate) {
@ -65,6 +68,7 @@ public class NotificationTemplateEntity extends BaseSqlEntity<NotificationTempla
setName(notificationTemplate.getName());
setNotificationType(notificationTemplate.getNotificationType());
setConfiguration(toJson(notificationTemplate.getConfiguration()));
setExternalId(getUuid(notificationTemplate.getExternalId()));
}
@Override
@ -76,6 +80,7 @@ public class NotificationTemplateEntity extends BaseSqlEntity<NotificationTempla
notificationTemplate.setName(name);
notificationTemplate.setNotificationType(notificationType);
notificationTemplate.setConfiguration(fromJson(configuration, NotificationTemplateConfig.class));
notificationTemplate.setExternalId(getEntityId(externalId, NotificationTemplateId::new));
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.thingsboard.server.common.stats.StatsFactory;
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.AsyncTaskContext;
import org.thingsboard.server.dao.util.NoSqlAnyDao;
import org.thingsboard.server.dao.util.limits.RateLimitService;
import javax.annotation.PreDestroy;
@ -52,9 +52,9 @@ public class CassandraBufferedRateReadExecutor extends AbstractBufferedRateExecu
@Value("${cassandra.query.print_queries_freq:0}") int printQueriesFreq,
@Autowired StatsFactory statsFactory,
@Autowired EntityService entityService,
@Autowired TbTenantProfileCache tenantProfileCache) {
@Autowired RateLimitService rateLimitService) {
super(queueLimit, concurrencyLimit, maxWaitTime, dispatcherThreads, callbackThreads, pollMs, printQueriesFreq, statsFactory,
entityService, tenantProfileCache, printTenantNames);
entityService, rateLimitService, printTenantNames);
}
@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.thingsboard.server.common.stats.StatsFactory;
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.AsyncTaskContext;
import org.thingsboard.server.dao.util.NoSqlAnyDao;
import org.thingsboard.server.dao.util.limits.RateLimitService;
import javax.annotation.PreDestroy;
@ -52,9 +52,9 @@ public class CassandraBufferedRateWriteExecutor extends AbstractBufferedRateExec
@Value("${cassandra.query.print_queries_freq:0}") int printQueriesFreq,
@Autowired StatsFactory statsFactory,
@Autowired EntityService entityService,
@Autowired TbTenantProfileCache tenantProfileCache) {
@Autowired RateLimitService rateLimitService) {
super(queueLimit, concurrencyLimit, maxWaitTime, dispatcherThreads, callbackThreads, pollMs, printQueriesFreq, statsFactory,
entityService, tenantProfileCache, printTenantNames);
entityService, rateLimitService, printTenantNames);
}
@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())));
}
@Override
public void deleteEntity(TenantId tenantId, EntityId id) {
deleteNotificationRuleById(tenantId, (NotificationRuleId) id);
}
@Override
public EntityType getEntityType() {
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())));
}
@Override
public void deleteEntity(TenantId tenantId, EntityId id) {
deleteNotificationTargetById(tenantId, (NotificationTargetId) id);
}
@Override
public EntityType getEntityType() {
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())));
}
@Override
public void deleteEntity(TenantId tenantId, EntityId id) {
deleteNotificationTemplateById(tenantId, (NotificationTemplateId) id);
}
@Override
public EntityType getEntityType() {
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.PageLink;
import org.thingsboard.server.dao.Dao;
import org.thingsboard.server.dao.ExportableEntityDao;
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);

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.PageLink;
import org.thingsboard.server.dao.Dao;
import org.thingsboard.server.dao.ExportableEntityDao;
import org.thingsboard.server.dao.TenantEntityDao;
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);

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

@ -15,16 +15,18 @@
*/
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.notification.NotificationType;
import org.thingsboard.server.common.data.notification.template.NotificationTemplate;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.dao.Dao;
import org.thingsboard.server.dao.ExportableEntityDao;
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);

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);
}
@Override
@Transactional
public void deleteEntity(TenantId tenantId, EntityId id) {
deleteRuleChainById(tenantId, (RuleChainId) id);
}
@Override
public EntityType getEntityType() {
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
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

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 " +
"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,
@Param("customerId") UUID customerId,
@Param("searchText") String searchText,
@Param("textSearch") String textSearch,
Pageable pageable);
@Query("SELECT d FROM DeviceEntity d WHERE d.tenantId = :tenantId " +
"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,
@Param("profileId") UUID profileId,
@Param("searchText") String searchText,
@Param("textSearch") String textSearch,
Pageable pageable);
@Query("SELECT d FROM DeviceInfoEntity d " +
"WHERE d.tenantId = :tenantId " +
"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,
@Param("customerId") UUID customerId,
@Param("searchText") String searchText,
@Param("textSearch") String textSearch,
Pageable pageable);
@Query("SELECT d FROM DeviceEntity d WHERE d.tenantId = :tenantId")
@ -63,14 +66,16 @@ public interface DeviceRepository extends JpaRepository<DeviceEntity, UUID>, Exp
Pageable pageable);
@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,
@Param("textSearch") String textSearch,
Pageable pageable);
@Query("SELECT d FROM DeviceEntity d WHERE d.tenantId = :tenantId " +
"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,
@Param("type") String type,
@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 " +
"AND d.deviceProfileId = :deviceProfileId " +
"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,
@Param("deviceProfileId") UUID deviceProfileId,
@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 " +
"AND d.deviceProfileId = :deviceProfileId " +
"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,
@Param("deviceProfileId") UUID deviceProfileId,
@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 " +
"AND d.customerId = :customerId " +
"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,
@Param("customerId") UUID customerId,
@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 (:deviceProfileId IS NULL OR d.deviceProfileId = uuid(:deviceProfileId)) " +
"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.type) 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 " +
"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 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,
@Param("edgeId") UUID edgeId,
@Param("searchText") String searchText,
@Param("textSearch") String textSearch,
Pageable pageable);
@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 re.relationType = 'Contains' AND re.fromId = :edgeId AND re.fromType = 'EDGE' " +
"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,
@Param("edgeId") UUID edgeId,
@Param("type") String type,
@Param("searchText") String searchText,
@Param("textSearch") String textSearch,
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.device.DeviceDao;
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 java.util.ArrayList;
@ -56,7 +56,7 @@ import java.util.UUID;
@Component
@SqlDao
@Slf4j
public class JpaDeviceDao extends JpaAbstractSearchTextDao<DeviceEntity, Device> implements DeviceDao {
public class JpaDeviceDao extends JpaAbstractDao<DeviceEntity, Device> implements DeviceDao {
@Autowired
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());
}
@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
protected Class<NotificationRuleEntity> getEntityClass() {
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());
}
@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
protected Class<NotificationTargetEntity> getEntityClass() {
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.stereotype.Component;
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.notification.NotificationType;
import org.thingsboard.server.common.data.notification.template.NotificationTemplate;
@ -57,6 +58,26 @@ public class JpaNotificationTemplateDao extends JpaAbstractDao<NotificationTempl
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
protected JpaRepository<NotificationTemplateEntity, UUID> getRepository() {
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.transaction.annotation.Transactional;
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.NotificationRuleInfoEntity;
@ -30,7 +31,7 @@ import java.util.List;
import java.util.UUID;
@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) " +
"FROM NotificationRuleEntity r INNER JOIN NotificationTemplateEntity t ON r.templateId = t.id";
@ -59,4 +60,11 @@ public interface NotificationRuleRepository extends JpaRepository<NotificationRu
@Transactional
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.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
import org.thingsboard.server.dao.ExportableEntityRepository;
import org.thingsboard.server.dao.model.sql.NotificationTargetEntity;
import java.util.List;
import java.util.UUID;
@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 " +
"AND (:searchText = '' OR lower(t.name) LIKE lower(concat('%', :searchText, '%')))")
@ -52,4 +53,11 @@ public interface NotificationTargetRepository extends JpaRepository<Notification
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.transaction.annotation.Transactional;
import org.thingsboard.server.common.data.notification.NotificationType;
import org.thingsboard.server.dao.ExportableEntityRepository;
import org.thingsboard.server.dao.model.sql.NotificationTemplateEntity;
import java.util.List;
import java.util.UUID;
@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 " +
"t.notificationType IN :notificationTypes " +
@ -43,4 +44,11 @@ public interface NotificationTemplateRepository extends JpaRepository<Notificati
@Transactional
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.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.stereotype.Repository;
@ -66,6 +67,7 @@ import java.util.stream.Collectors;
@Slf4j
public class DefaultEntityQueryRepository implements EntityQueryRepository {
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)" +
" 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)" +
@ -242,6 +244,24 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository {
entityTableMap.put(EntityType.DEVICE_PROFILE, "device_profile");
entityTableMap.put(EntityType.ASSET_PROFILE, "asset_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[]{
@ -807,33 +827,38 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository {
private String entityNameQuery(QueryContext ctx, EntityNameFilter filter) {
ctx.addStringParameter("entity_filter_name_filter", filter.getEntityNameFilter());
String nameColumn = getNameColumn(filter.getEntityType());
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) {
List<String> types;
String name;
String nameColumn;
switch (filter.getType()) {
case ASSET_TYPE:
types = ((AssetTypeFilter) filter).getAssetTypes();
name = ((AssetTypeFilter) filter).getAssetNameFilter();
nameColumn = getNameColumn(EntityType.ASSET);
break;
case DEVICE_TYPE:
types = ((DeviceTypeFilter) filter).getDeviceTypes();
name = ((DeviceTypeFilter) filter).getDeviceNameFilter();
nameColumn = getNameColumn(EntityType.DEVICE);
break;
case ENTITY_VIEW_TYPE:
types = ((EntityViewTypeFilter) filter).getEntityViewTypes();
name = ((EntityViewTypeFilter) filter).getEntityViewNameFilter();
nameColumn = getNameColumn(EntityType.ENTITY_VIEW);
break;
case EDGE_TYPE:
types = ((EdgeTypeFilter) filter).getEdgeTypes();
name = ((EdgeTypeFilter) filter).getEdgeNameFilter();
nameColumn = getNameColumn(EntityType.EDGE);
break;
default:
throw new RuntimeException("Not supported!");
@ -843,14 +868,23 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository {
if (!StringUtils.isEmpty(name)) {
ctx.addStringParameter("entity_filter_type_query_name", name);
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 {
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) {
switch (entityFilter.getType()) {
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 org.thingsboard.common.util.ThingsBoardExecutors;
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.msg.tools.TbRateLimits;
import org.thingsboard.server.common.stats.DefaultCounter;
import org.thingsboard.server.common.stats.StatsCounter;
import org.thingsboard.server.common.stats.StatsFactory;
import org.thingsboard.server.common.stats.StatsType;
import org.thingsboard.server.dao.entity.EntityService;
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 java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingDeque;
@ -73,7 +70,6 @@ public abstract class AbstractBufferedRateExecutor<T extends AsyncTask, F extend
private final ScheduledExecutorService timeoutExecutor;
private final int concurrencyLimit;
private final int printQueriesFreq;
private final ConcurrentMap<TenantId, TbRateLimits> perTenantLimits = new ConcurrentHashMap<>();
private final AtomicInteger printQueriesIdx = new AtomicInteger(0);
@ -81,14 +77,14 @@ public abstract class AbstractBufferedRateExecutor<T extends AsyncTask, F extend
protected final BufferedRateExecutorStats stats;
private final EntityService entityService;
private final TbTenantProfileCache tenantProfileCache;
private final RateLimitService rateLimitService;
private final boolean printTenantNames;
private final Map<TenantId, String> tenantNamesCache = new HashMap<>();
public AbstractBufferedRateExecutor(int queueLimit, int concurrencyLimit, long maxWaitTime, int dispatcherThreads,
int callbackThreads, long pollMs, int printQueriesFreq, StatsFactory statsFactory,
EntityService entityService, TbTenantProfileCache tenantProfileCache, boolean printTenantNames) {
EntityService entityService, RateLimitService rateLimitService, boolean printTenantNames) {
this.maxWaitTime = maxWaitTime;
this.pollMs = pollMs;
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.entityService = entityService;
this.tenantProfileCache = tenantProfileCache;
this.rateLimitService = rateLimitService;
this.printTenantNames = printTenantNames;
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);
boolean perTenantLimitReached = false;
var tenantProfileConfiguration =
(task.getTenantId() != null && !TenantId.SYS_TENANT_ID.equals(task.getTenantId()))
? tenantProfileCache.get(task.getTenantId()).getDefaultProfileConfiguration()
: null;
if (tenantProfileConfiguration != null &&
StringUtils.isNotEmpty(tenantProfileConfiguration.getCassandraQueryTenantRateLimitsConfiguration())) {
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;
}
TenantId tenantId = task.getTenantId();
if (tenantId != null && !tenantId.isSysTenantId()) {
if (!rateLimitService.checkRateLimit(LimitedApi.CASSANDRA_QUERIES, tenantId)) {
stats.incrementRateLimitedTenant(tenantId);
stats.getTotalRateLimited().increment();
settableFuture.setException(new TenantRateLimitException());
perTenantLimitReached = true;
}
} else if (!TenantId.SYS_TENANT_ID.equals(task.getTenantId())) {
perTenantLimits.remove(task.getTenantId());
} else if (tenantId == null) {
log.info("Invalid task received: {}", task);
}
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
* 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.Caffeine;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
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.msg.tools.TbRateLimits;
import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import javax.annotation.PostConstruct;
import java.util.concurrent.TimeUnit;
@Service
@RequiredArgsConstructor
@Slf4j
public class DefaultRateLimitService implements RateLimitService {
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;
@PostConstruct
private void init() {
rateLimits = Caffeine.newBuilder()
public DefaultRateLimitService(TbTenantProfileCache tenantProfileCache,
@Value("${cache.rateLimits.timeToLiveInMinutes:120}") int rateLimitsTtl,
@Value("${cache.rateLimits.maxSize:200000}") int rateLimitsCacheMaxSize) {
this.tenantProfileCache = tenantProfileCache;
this.rateLimits = Caffeine.newBuilder()
.expireAfterAccess(rateLimitsTtl, TimeUnit.MINUTES)
.maximumSize(rateLimitsCacheMaxSize)
.build();
}
private final Cache<RateLimitKey, TbRateLimits> rateLimits;
@Override
public boolean checkRateLimit(LimitedApi api, TenantId tenantId) {
return checkRateLimit(api, tenantId, tenantId);
}
@Override
public boolean checkRateLimit(LimitedApi api, TenantId tenantId, EntityId entityId) {
public boolean checkRateLimit(LimitedApi api, TenantId tenantId, Object level) {
if (tenantId.isSysTenantId()) {
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()
.map(api::getLimitConfig).orElse(null);
@Override
public boolean checkRateLimit(LimitedApi api, Object level, String rateLimitConfig) {
RateLimitKey key = new RateLimitKey(api, level);
if (StringUtils.isEmpty(rateLimitConfig)) {
rateLimits.invalidate(key);
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) -> {
if (limit == null || !limit.getConfiguration().equals(rateLimitConfig)) {
limit = new TbRateLimits(rateLimitConfig);
log.trace("[{}] Created new rate limit bucket for {} ({})", entityId, api, rateLimitConfig);
limit = new TbRateLimits(rateLimitConfig, api.isRefillRateLimitIntervally());
log.trace("[{}] Created new rate limit bucket for {} ({})", level, api, rateLimitConfig);
}
return limit;
});
boolean success = rateLimit.tryConsume();
if (!success) {
log.debug("[{}] Rate limit exceeded for {} ({})", entityId, api, rateLimitConfig);
log.debug("[{}] Rate limit exceeded for {} ({})", level, api, rateLimitConfig);
}
return success;
}
@Override
public void cleanUp(LimitedApi api, Object level) {
RateLimitKey key = new RateLimitKey(api, level);
rateLimits.invalidate(key);
}
@Data(staticConstructor = "of")
private static class RateLimitKey {
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
* 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;
public interface RateLimitService {
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())));
}
@Override
public void deleteEntity(TenantId tenantId, EntityId id) {
deleteWidgetsBundle(tenantId, (WidgetsBundleId) id);
}
@Override
public EntityType getEntityType() {
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),
name varchar(255),
label varchar(255),
search_text varchar(255),
tenant_id uuid,
firmware_id uuid,
software_id uuid,
@ -797,7 +796,9 @@ CREATE TABLE IF NOT EXISTS notification_target (
tenant_id UUID NOT NULL,
name VARCHAR(255) 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 (
@ -807,7 +808,9 @@ CREATE TABLE IF NOT EXISTS notification_template (
name VARCHAR(255) NOT NULL,
notification_type VARCHAR(50) 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 (
@ -821,7 +824,9 @@ CREATE TABLE IF NOT EXISTS notification_rule (
trigger_config VARCHAR(1000) NOT NULL,
recipients_config VARCHAR(10000) NOT NULL,
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 (

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

@ -226,7 +226,11 @@ BEGIN
END IF;
IF NOT(existing.cleared) THEN
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;
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;

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

@ -202,7 +202,34 @@ public class JpaAlarmDaoTest extends AbstractJpaDaoTest {
@Test
public void testClearAlarmProcedure() {
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 alarm1Id = UUID.fromString("d4b68f43-3e96-11e7-a884-898080180d6b");
Alarm alarm = saveAlarm(alarm1Id, tenantId, originator1Id, "TEST_ALARM");
@ -216,6 +243,7 @@ public class JpaAlarmDaoTest extends AbstractJpaDaoTest {
assertEquals(afterSave, result.getAlarm());
assertEquals(clearTs, result.getAlarm().getClearTs());
assertTrue(result.getAlarm().isCleared());
assertEquals(alarm.getDetails(), result.getAlarm().getDetails());
result = alarmDao.clearAlarm(alarm.getTenantId(), alarm.getId(), clearTs + 1, JacksonUtil.newObjectNode());
assertNotNull(result);
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()
.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 HEIGHT = 1050;
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 JavascriptExecutor js;
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) {
if (deviceId != null) {
testRestClient.deleteDevice(deviceId);
@ -283,4 +290,19 @@ abstract public class AbstractDriverBaseTest extends AbstractContainerTest {
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 HIDE_HOME_DASHBOARD_TOOLBAR = "//mat-checkbox[@formcontrolname='homeDashboardHideToolbar']//label";
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_USER_ICON_HEADER = "Users";
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 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 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 DESCRIPTION_FIELD_CREATE_VIEW = "//tb-device-wizard//textarea[@formcontrolname='description']";
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 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_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_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 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_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_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_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) {
return waitUntilElementToBeClickable(String.format(DEVICE, deviceName));
@ -90,18 +90,10 @@ public class DevicePageElements extends OtherPageElementsHelper {
return waitUntilElementToBeClickable(ADD_DEVICE_BTN);
}
public WebElement nameField() {
return waitUntilElementToBeClickable(CREATE_DEVICE_NAME_FIELD);
}
public WebElement headerNameView() {
return waitUntilVisibilityOfElementLocated(HEADER_NAME_VIEW);
}
public WebElement descriptionFieldCreateField() {
return waitUntilElementToBeClickable(DESCRIPTION_FIELD_CREATE_VIEW);
}
public WebElement addDeviceView() {
return waitUntilPresenceOfElementLocated(ADD_DEVICE_VIEW);
}
@ -114,18 +106,10 @@ public class DevicePageElements extends OtherPageElementsHelper {
return waitUntilElementToBeClickable(CHECKBOX_GATEWAY_EDIT);
}
public WebElement checkboxGatewayCreate() {
return waitUntilElementToBeClickable(CHECKBOX_GATEWAY_CREATE);
}
public WebElement checkboxOverwriteActivityTimeEdit() {
return waitUntilElementToBeClickable(CHECKBOX_OVERWRITE_ACTIVITY_TIME_EDIT);
}
public WebElement checkboxOverwriteActivityTimeCreate() {
return waitUntilElementToBeClickable(CHECKBOX_OVERWRITE_ACTIVITY_TIME_CREATE);
}
public WebElement checkboxGatewayDetailsTab() {
return waitUntilPresenceOfElementLocated(CHECKBOX_GATEWAY_DETAILS);
}
@ -146,22 +130,10 @@ public class DevicePageElements extends OtherPageElementsHelper {
return waitUntilElementToBeClickable(DEVICE_PROFILE_REDIRECTED_BTN);
}
public WebElement deviceLabelFieldCreate() {
return waitUntilElementToBeClickable(DEVICE_LABEL_FIELD_CREATE);
}
public WebElement deviceLabelOnPage(String 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) {
return waitUntilVisibilityOfElementLocated(String.format(DEVICE_CUSTOMER_PAGE, deviceName));
}
@ -173,4 +145,36 @@ public class DevicePageElements extends OtherPageElementsHelper {
public WebElement deviceLabelDetailsField() {
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();
}
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) {
deleteBtn(deviceName).click();
warningPopUpYesBtn().click();
@ -87,10 +75,30 @@ public class DevicePageHelper extends DevicePageElements {
entityFromDropdown(deviceProfileName).click();
}
public void assignOnCustomer(String customerTitle) {
customerOptionBtn().click();
assignOnCustomerField().click();
entityFromList(customerTitle).click();
sleep(2); //waiting for the action to count
public void unassignedDeviceByRightSideBtn(String deviceName) {
unassignBtn(deviceName).click();
warningPopUpYesBtn().click();
}
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.LoginPageHelper;
import org.thingsboard.server.msa.ui.pages.SideBarMenuViewHelper;
import org.thingsboard.server.msa.ui.tabs.CreateDeviceTabHelper;
@Epic("Device smoke tests")
abstract public class AbstractDeviceTest extends AbstractDriverBaseTest {
protected SideBarMenuViewHelper sideBarMenuView;
protected DevicePageHelper devicePage;
protected CreateDeviceTabHelper createDeviceTab;
protected String deviceName;
protected String deviceProfileTitle;
@BeforeClass
public void login() {
new LoginPageHelper(driver).authorizationTenant();
sideBarMenuView = new SideBarMenuViewHelper(driver);
devicePage = new DevicePageHelper(driver);
createDeviceTab = new CreateDeviceTabHelper(driver);
}
@AfterMethod
public void delete() {
deleteDeviceByName(deviceName);
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 org.testng.annotations.Test;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.msa.ui.pages.ProfilesPageElements;
import org.thingsboard.server.msa.ui.utils.EntityPrototypes;
import static org.assertj.core.api.Assertions.assertThat;
@ -39,8 +40,8 @@ public class CreateDeviceTest extends AbstractDeviceTest {
sideBarMenuView.goToDevicesPage();
devicePage.openCreateDeviceView();
devicePage.enterName(deviceName);
devicePage.addBtnC().click();
createDeviceTab.enterName(deviceName);
createDeviceTab.addBtn().click();
devicePage.refreshBtn().click();
assertIsDisplayed(devicePage.entity(deviceName));
@ -53,9 +54,9 @@ public class CreateDeviceTest extends AbstractDeviceTest {
sideBarMenuView.goToDevicesPage();
devicePage.openCreateDeviceView();
devicePage.enterName(deviceName);
devicePage.enterDescription(deviceName);
devicePage.addBtnC().click();
createDeviceTab.enterName(deviceName);
createDeviceTab.enterDescription(deviceName);
createDeviceTab.addBtn().click();
devicePage.refreshBtn().click();
devicePage.entity(deviceName).click();
devicePage.setHeaderName();
@ -70,8 +71,8 @@ public class CreateDeviceTest extends AbstractDeviceTest {
public void createDeviceWithoutName() {
sideBarMenuView.goToDevicesPage();
devicePage.openCreateDeviceView();
devicePage.nameField().click();
devicePage.addBtnC().click();
createDeviceTab.nameField().click();
createDeviceTab.addBtn().click();
assertIsDisplayed(devicePage.addDeviceView());
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() {
sideBarMenuView.goToDevicesPage();
devicePage.openCreateDeviceView();
devicePage.enterName(" ");
devicePage.addBtnC().click();
createDeviceTab.enterName(" ");
createDeviceTab.addBtn().click();
assertIsDisplayed(devicePage.warningMessage());
assertThat(devicePage.warningMessage().getText()).as("Text of warning message").isEqualTo(EMPTY_DEVICE_MESSAGE);
@ -98,8 +99,8 @@ public class CreateDeviceTest extends AbstractDeviceTest {
sideBarMenuView.goToDevicesPage();
devicePage.openCreateDeviceView();
devicePage.enterName(deviceName);
devicePage.addBtnC().click();
createDeviceTab.enterName(deviceName);
createDeviceTab.addBtn().click();
assertIsDisplayed(devicePage.warningMessage());
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();
devicePage.openCreateDeviceView();
devicePage.enterName(deviceName);
devicePage.addBtnC().click();
createDeviceTab.enterName(deviceName);
createDeviceTab.addBtn().click();
assertIsDisplayed(devicePage.entity(deviceName));
}
@ -126,9 +127,9 @@ public class CreateDeviceTest extends AbstractDeviceTest {
sideBarMenuView.goToDevicesPage();
devicePage.openCreateDeviceView();
devicePage.enterName(deviceName);
devicePage.clearProfileFieldBtn().click();
devicePage.addBtnC().click();
createDeviceTab.enterName(deviceName);
createDeviceTab.clearProfileFieldBtn().click();
createDeviceTab.addBtn().click();
assertIsDisplayed(devicePage.errorMessage());
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();
devicePage.openCreateDeviceView();
devicePage.enterName(deviceName);
devicePage.checkboxGatewayCreate().click();
devicePage.addBtnC().click();
createDeviceTab.enterName(deviceName);
createDeviceTab.checkboxGateway().click();
createDeviceTab.addBtn().click();
assertIsDisplayed(devicePage.device(deviceName));
assertIsDisplayed(devicePage.checkboxGatewayPage(deviceName));
@ -157,10 +158,10 @@ public class CreateDeviceTest extends AbstractDeviceTest {
sideBarMenuView.goToDevicesPage();
devicePage.openCreateDeviceView();
devicePage.enterName(deviceName);
devicePage.checkboxGatewayCreate().click();
devicePage.checkboxOverwriteActivityTimeCreate().click();
devicePage.addBtnC().click();
createDeviceTab.enterName(deviceName);
createDeviceTab.checkboxGateway().click();
createDeviceTab.checkboxOverwriteActivityTime().click();
createDeviceTab.addBtn().click();
devicePage.device(deviceName).click();
assertThat(devicePage.checkboxOverwriteActivityTimeDetails().getAttribute("class").contains("selected"))
@ -175,9 +176,9 @@ public class CreateDeviceTest extends AbstractDeviceTest {
sideBarMenuView.goToDevicesPage();
devicePage.openCreateDeviceView();
devicePage.enterName(deviceName);
devicePage.enterLabel(deviceLabel);
devicePage.addBtnC().click();
createDeviceTab.enterName(deviceName);
createDeviceTab.enterLabel(deviceLabel);
createDeviceTab.addBtn().click();
assertIsDisplayed(devicePage.deviceLabelOnPage(deviceName));
assertThat(devicePage.deviceLabelOnPage(deviceName).getText()).as("Label added correctly").isEqualTo(deviceLabel);
@ -191,9 +192,9 @@ public class CreateDeviceTest extends AbstractDeviceTest {
sideBarMenuView.goToDevicesPage();
devicePage.openCreateDeviceView();
devicePage.enterName(deviceName);
devicePage.assignOnCustomer(customer);
devicePage.addBtnC().click();
createDeviceTab.enterName(deviceName);
createDeviceTab.assignOnCustomer(customer);
createDeviceTab.addBtn().click();
assertIsDisplayed(devicePage.deviceCustomerOnPage(deviceName));
assertThat(devicePage.deviceCustomerOnPage(deviceName).getText())
@ -211,4 +212,42 @@ public class CreateDeviceTest extends AbstractDeviceTest {
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.msa.ui.utils.EntityPrototypes;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
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")
public void deleteSeveralDevicesByTopBtn() {
sideBarMenuView.goToDevicesPage();
devicePage.deleteSelected(2);
devicePage.deleteSelectedDevices(deviceName1, deviceName2);
devicePage.refreshBtn().click();
devicePage.assertEntityIsNotPresent(deviceName1);
devicePage.assertEntityIsNotPresent(deviceName2);
List.of(deviceName1, deviceName2)
.forEach(d -> devicePage.assertEntityIsNotPresent(d));
}
@Test(groups = "smoke")
@ -76,9 +78,9 @@ public class DeleteSeveralDevicesTest extends AbstractDeviceTest {
"that appears at the top without refresh")
public void deleteSeveralWithoutRefresh() {
sideBarMenuView.goToDevicesPage();
devicePage.deleteSelected(2);
devicePage.deleteSelectedDevices(deviceName1, deviceName2);
devicePage.assertEntityIsNotPresent(deviceName1);
devicePage.assertEntityIsNotPresent(deviceName2);
List.of(deviceName1, deviceName2)
.forEach(d -> devicePage.assertEntityIsNotPresent(d));
}
}

2
msa/monitoring/pom.xml

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

2
msa/tb/pom.xml

@ -38,7 +38,7 @@
<tb-postgres.docker.name>tb-postgres</tb-postgres.docker.name>
<tb-cassandra.docker.name>tb-cassandra</tb-cassandra.docker.name>
<pkg.installFolder>/usr/share/${pkg.name}</pkg.installFolder>
<pkg.upgradeVersion>3.5.0</pkg.upgradeVersion>
<pkg.upgradeVersion>3.5.2</pkg.upgradeVersion>
</properties>
<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;
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.SearchTextBasedWithAdditionalInfo;
import org.thingsboard.server.common.data.id.EntityId;
@ -32,7 +33,7 @@ public class EntitiesByNameAndTypeLoader {
EntityType.USER);
public static EntityId findEntityId(TbContext ctx, EntityType entityType, String entityName) {
SearchTextBasedWithAdditionalInfo<? extends EntityId> targetEntity;
BaseData<? extends EntityId> targetEntity;
switch (entityType) {
case DEVICE:
targetEntity = ctx.getDeviceService().findDeviceByTenantIdAndName(ctx.getTenantId(), entityName);

30
ui-ngx/package.json

@ -14,17 +14,17 @@
},
"private": true,
"dependencies": {
"@angular/animations": "^15.2.5",
"@angular/cdk": "^15.2.5",
"@angular/common": "^15.2.5",
"@angular/compiler": "^15.2.5",
"@angular/core": "^15.2.5",
"@angular/animations": "^15.2.9",
"@angular/cdk": "^15.2.9",
"@angular/common": "^15.2.9",
"@angular/compiler": "^15.2.9",
"@angular/core": "^15.2.9",
"@angular/flex-layout": "^15.0.0-beta.42",
"@angular/forms": "^15.2.5",
"@angular/material": "^15.2.5",
"@angular/platform-browser": "^15.2.5",
"@angular/platform-browser-dynamic": "^15.2.5",
"@angular/router": "^15.2.5",
"@angular/forms": "^15.2.9",
"@angular/material": "^15.2.9",
"@angular/platform-browser": "^15.2.9",
"@angular/platform-browser-dynamic": "^15.2.9",
"@angular/router": "^15.2.9",
"@auth0/angular-jwt": "^5.1.2",
"@date-io/core": "1.3.7",
"@date-io/date-fns": "1.3.7",
@ -108,16 +108,16 @@
},
"devDependencies": {
"@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/eslint-plugin": "15.2.1",
"@angular-eslint/eslint-plugin-template": "15.2.1",
"@angular-eslint/schematics": "15.2.1",
"@angular-eslint/template-parser": "15.2.1",
"@angular/cli": "^15.2.4",
"@angular/compiler-cli": "^15.2.5",
"@angular/language-service": "^15.2.5",
"@ngtools/webpack": "^15.1.6",
"@angular/cli": "^15.2.8",
"@angular/compiler-cli": "^15.2.9",
"@angular/language-service": "^15.2.9",
"@ngtools/webpack": "15.2.1",
"@types/ace-diff": "^2.1.1",
"@types/canvas-gauges": "^2.1.4",
"@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';
entitiesObservable = this.notificationService.getNotificationTargets(pageLink, subType as NotificationType, config);
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;
}

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

Loading…
Cancel
Save