Browse Source

Merge branch 'master' into feature/prod-1586_add_quename_to_generator_node

# Conflicts:
#	rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNodeConfiguration.java
pull/7895/head
AndriiD 4 years ago
parent
commit
fdf7f117f7
  1. 88
      .github/release.yml
  2. 2
      application/pom.xml
  3. 8
      application/src/main/data/json/tenant/device_profile/rule_chain_template.json
  4. 13
      application/src/main/data/json/tenant/edge_management/rule_chains/edge_root_rule_chain.json
  5. 8
      application/src/main/data/json/tenant/rule_chains/root_rule_chain.json
  6. 5
      application/src/main/data/upgrade/3.4.1/schema_update.sql
  7. 1
      application/src/main/java/org/thingsboard/server/ThingsboardInstallApplication.java
  8. 14
      application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java
  9. 28
      application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java
  10. 49
      application/src/main/java/org/thingsboard/server/controller/AdminController.java
  11. 6
      application/src/main/java/org/thingsboard/server/controller/AuthController.java
  12. 9
      application/src/main/java/org/thingsboard/server/controller/BaseController.java
  13. 11
      application/src/main/java/org/thingsboard/server/controller/ControllerConstants.java
  14. 28
      application/src/main/java/org/thingsboard/server/controller/RuleChainController.java
  15. 23
      application/src/main/java/org/thingsboard/server/controller/TelemetryController.java
  16. 6
      application/src/main/java/org/thingsboard/server/controller/TwoFactorAuthController.java
  17. 6
      application/src/main/java/org/thingsboard/server/controller/UserController.java
  18. 47
      application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketHandler.java
  19. 1
      application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java
  20. 9
      application/src/main/java/org/thingsboard/server/service/device/DeviceBulkImportService.java
  21. 8
      application/src/main/java/org/thingsboard/server/service/edge/DefaultEdgeNotificationService.java
  22. 54
      application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java
  23. 3
      application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeSessionState.java
  24. 4
      application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/RuleChainMsgConstructor.java
  25. 12
      application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/RuleChainsEdgeEventFetcher.java
  26. 13
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/RuleChainEdgeProcessor.java
  27. 13
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/TelemetryEdgeProcessor.java
  28. 3
      application/src/main/java/org/thingsboard/server/service/edge/rpc/sync/DefaultEdgeRequestsService.java
  29. 5
      application/src/main/java/org/thingsboard/server/service/entitiy/asset/DefaultTbAssetService.java
  30. 9
      application/src/main/java/org/thingsboard/server/service/entitiy/device/DefaultTbDeviceService.java
  31. 3
      application/src/main/java/org/thingsboard/server/service/entitiy/edge/DefaultTbEdgeService.java
  32. 15
      application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java
  33. 4
      application/src/main/java/org/thingsboard/server/service/install/SystemDataLoaderService.java
  34. 4
      application/src/main/java/org/thingsboard/server/service/install/update/DefaultCacheCleanupService.java
  35. 1
      application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java
  36. 9
      application/src/main/java/org/thingsboard/server/service/ota/DefaultOtaPackageStateService.java
  37. 38
      application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java
  38. 8
      application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java
  39. 3
      application/src/main/java/org/thingsboard/server/service/queue/DefaultTbRuleEngineConsumerService.java
  40. 23
      application/src/main/java/org/thingsboard/server/service/queue/processing/AbstractConsumerService.java
  41. 8
      application/src/main/java/org/thingsboard/server/service/script/RuleNodeTbelScriptEngine.java
  42. 162
      application/src/main/java/org/thingsboard/server/service/security/auth/jwt/settings/DefaultJwtSettingsService.java
  43. 69
      application/src/main/java/org/thingsboard/server/service/security/auth/jwt/settings/DefaultJwtSettingsValidator.java
  44. 39
      application/src/main/java/org/thingsboard/server/service/security/auth/jwt/settings/InstallJwtSettingsValidator.java
  45. 35
      application/src/main/java/org/thingsboard/server/service/security/auth/jwt/settings/JwtSettingsService.java
  46. 23
      application/src/main/java/org/thingsboard/server/service/security/auth/jwt/settings/JwtSettingsValidator.java
  47. 17
      application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java
  48. 4
      application/src/main/java/org/thingsboard/server/service/security/auth/rest/RestAwareAuthenticationSuccessHandler.java
  49. 32
      application/src/main/java/org/thingsboard/server/service/security/model/token/JwtTokenFactory.java
  50. 2
      application/src/main/java/org/thingsboard/server/service/session/DefaultDeviceSessionCacheService.java
  51. 10
      application/src/main/java/org/thingsboard/server/service/subscription/DefaultSubscriptionManagerService.java
  52. 10
      application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbEntityDataSubscriptionService.java
  53. 2
      application/src/main/java/org/thingsboard/server/service/subscription/SubscriptionManagerService.java
  54. 5
      application/src/main/java/org/thingsboard/server/service/subscription/TbAbstractDataSubCtx.java
  55. 6
      application/src/main/java/org/thingsboard/server/service/subscription/TbAlarmDataSubCtx.java
  56. 6
      application/src/main/java/org/thingsboard/server/service/subscription/TbEntityDataSubCtx.java
  57. 3
      application/src/main/java/org/thingsboard/server/service/subscription/TbSubscriptionUtils.java
  58. 2
      application/src/main/java/org/thingsboard/server/service/sync/ie/importing/csv/AbstractBulkImportService.java
  59. 18
      application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java
  60. 12
      application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetryWebSocketService.java
  61. 2
      application/src/main/java/org/thingsboard/server/service/telemetry/InternalTelemetryService.java
  62. 40
      application/src/main/resources/thingsboard.yml
  63. 2
      application/src/test/java/org/thingsboard/server/controller/AbstractNotifyEntityTest.java
  64. 7
      application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java
  65. 122
      application/src/test/java/org/thingsboard/server/controller/BaseAdminControllerTest.java
  66. 16
      application/src/test/java/org/thingsboard/server/controller/BaseAuditLogControllerTest.java
  67. 116
      application/src/test/java/org/thingsboard/server/controller/BaseDeviceControllerTest.java
  68. 14
      application/src/test/java/org/thingsboard/server/controller/BaseEdgeControllerTest.java
  69. 22
      application/src/test/java/org/thingsboard/server/controller/BaseEntityQueryControllerTest.java
  70. 31
      application/src/test/java/org/thingsboard/server/controller/BaseRuleChainControllerTest.java
  71. 12
      application/src/test/java/org/thingsboard/server/controller/BaseUserControllerTest.java
  72. 38
      application/src/test/java/org/thingsboard/server/controller/BaseWebsocketApiTest.java
  73. 19
      application/src/test/java/org/thingsboard/server/controller/TbTestWebSocketClient.java
  74. 4
      application/src/test/java/org/thingsboard/server/controller/TwoFactorAuthTest.java
  75. 25
      application/src/test/java/org/thingsboard/server/edge/BaseDeviceEdgeTest.java
  76. 45
      application/src/test/java/org/thingsboard/server/edge/BaseRuleChainEdgeTest.java
  77. 61
      application/src/test/java/org/thingsboard/server/edge/BaseTelemetryEdgeTest.java
  78. 6
      application/src/test/java/org/thingsboard/server/edge/imitator/EdgeImitator.java
  79. 247
      application/src/test/java/org/thingsboard/server/service/queue/DefaultTbClusterServiceTest.java
  80. 2
      application/src/test/java/org/thingsboard/server/service/script/NashornJsInvokeServiceTest.java
  81. 107
      application/src/test/java/org/thingsboard/server/service/script/TbelInvokeServiceTest.java
  82. 10
      application/src/test/java/org/thingsboard/server/service/security/auth/JwtTokenFactoryTest.java
  83. 68
      application/src/test/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandlerTest.java
  84. 2
      application/src/test/resources/logback-test.xml
  85. 2
      common/actor/pom.xml
  86. 2
      common/cache/pom.xml
  87. 2
      common/cluster-api/pom.xml
  88. 1
      common/cluster-api/src/main/proto/queue.proto
  89. 2
      common/coap-server/pom.xml
  90. 2
      common/dao-api/pom.xml
  91. 2
      common/data/pom.xml
  92. 1
      common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java
  93. 19
      common/data/src/main/java/org/thingsboard/server/common/data/EdgeUtils.java
  94. 2
      common/data/src/main/java/org/thingsboard/server/common/data/SearchTextBasedWithAdditionalInfo.java
  95. 2
      common/data/src/main/java/org/thingsboard/server/common/data/alarm/Alarm.java
  96. 8
      common/data/src/main/java/org/thingsboard/server/common/data/kv/BaseReadTsKvQuery.java
  97. 6
      common/data/src/main/java/org/thingsboard/server/common/data/kv/BaseTsKvQuery.java
  98. 1
      common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleChainMetaData.java
  99. 2
      common/data/src/main/java/org/thingsboard/server/common/data/script/ScriptLanguage.java
  100. 9
      common/data/src/main/java/org/thingsboard/server/common/data/security/model/JwtPair.java

88
.github/release.yml

@ -0,0 +1,88 @@
#
# Copyright © 2016-2022 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.
#
changelog:
exclude:
labels:
- Ignore for release
categories:
- title: 'Major Core & Rule Engine'
labels:
- 'Major Core'
- 'Major Rule Engine'
exclude:
labels:
- 'Bug'
- title: 'Major UI'
labels:
- 'Major UI'
exclude:
labels:
- 'Bug'
- title: 'Major Transport'
labels:
- 'Major Transport'
exclude:
labels:
- 'Bug'
- title: 'Major Edge'
labels:
- 'Major Edge'
exclude:
labels:
- 'Bug'
- title: 'Core & Rule Engine'
labels:
- 'Core'
- 'Rule Engine'
exclude:
labels:
- 'Bug'
- title: 'UI'
labels:
- 'UI'
exclude:
labels:
- 'Bug'
- title: 'Transport'
labels:
- 'Transport'
exclude:
labels:
- 'Bug'
- title: 'Edge'
labels:
- 'Edge'
exclude:
labels:
- 'Bug'
- title: 'Bug: Core & Rule Engine'
labels:
- 'Core'
- 'Rule Engine'
- 'Bug'
- title: 'Bug: UI'
labels:
- 'UI'
- 'Bug'
- title: 'Bug: Transport'
labels:
- 'Transport'
- 'Bug'
- title: 'Bug: Edge'
labels:
- 'Edge'
- 'Bug'

2
application/pom.xml

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

8
application/src/main/data/json/tenant/device_profile/rule_chain_template.json

@ -57,9 +57,9 @@
"name": "Log RPC from Device",
"debugMode": false,
"configuration": {
"scriptLang": "MVEL",
"scriptLang": "TBEL",
"jsScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);",
"mvelScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);"
"tbelScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);"
}
},
{
@ -71,9 +71,9 @@
"name": "Log Other",
"debugMode": false,
"configuration": {
"scriptLang": "MVEL",
"scriptLang": "TBEL",
"jsScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);",
"mvelScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);"
"tbelScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);"
}
},
{

13
application/src/main/data/json/tenant/edge_management/rule_chains/edge_root_rule_chain.json

@ -70,9 +70,9 @@
"name": "Log RPC from Device",
"debugMode": false,
"configuration": {
"scriptLang": "MVEL",
"scriptLang": "TBEL",
"jsScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);",
"mvelScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);"
"tbelScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);"
}
},
{
@ -84,9 +84,9 @@
"name": "Log Other",
"debugMode": false,
"configuration": {
"scriptLang": "MVEL",
"scriptLang": "TBEL",
"jsScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);",
"mvelScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);"
"tbelScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);"
}
},
{
@ -174,11 +174,6 @@
"fromIndex": 3,
"toIndex": 7,
"type": "Timeseries Updated"
},
{
"fromIndex": 4,
"toIndex": 7,
"type": "Success"
}
],
"ruleChainConnections": null

8
application/src/main/data/json/tenant/rule_chains/root_rule_chain.json

@ -57,9 +57,9 @@
"name": "Log RPC from Device",
"debugMode": false,
"configuration": {
"scriptLang": "MVEL",
"scriptLang": "TBEL",
"jsScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);",
"mvelScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);"
"tbelScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);"
}
},
{
@ -71,9 +71,9 @@
"name": "Log Other",
"debugMode": false,
"configuration": {
"scriptLang": "MVEL",
"scriptLang": "TBEL",
"jsScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);",
"mvelScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);"
"tbelScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);"
}
},
{

5
application/src/main/data/upgrade/3.4.1/schema_update.sql

@ -22,6 +22,8 @@ $$
-- in case of running the upgrade script a second time:
IF NOT (SELECT exists(SELECT FROM pg_tables WHERE tablename = 'old_audit_log')) THEN
ALTER TABLE audit_log RENAME TO old_audit_log;
CREATE INDEX IF NOT EXISTS idx_old_audit_log_created_time ON old_audit_log(created_time);
ALTER INDEX IF EXISTS idx_audit_log_tenant_id_and_created_time RENAME TO idx_old_audit_log_tenant_id_and_created_time;
FOR table_partition IN SELECT tablename AS name, split_part(tablename, '_', 3) AS partition_ts
@ -51,6 +53,7 @@ CREATE TABLE IF NOT EXISTS audit_log (
action_failure_details varchar(1000000)
) PARTITION BY RANGE (created_time);
CREATE INDEX IF NOT EXISTS idx_audit_log_tenant_id_and_created_time ON audit_log(tenant_id, created_time DESC);
CREATE INDEX IF NOT EXISTS idx_audit_log_id ON audit_log(id);
CREATE OR REPLACE PROCEDURE migrate_audit_logs(IN start_time_ms BIGINT, IN end_time_ms BIGINT, IN partition_size_ms BIGINT)
LANGUAGE plpgsql AS
@ -85,6 +88,7 @@ $$
-- in case of running the upgrade script a second time:
IF NOT (SELECT exists(SELECT FROM pg_tables WHERE tablename = 'old_edge_event')) THEN
ALTER TABLE edge_event RENAME TO old_edge_event;
CREATE INDEX IF NOT EXISTS idx_old_edge_event_created_time_tmp ON old_edge_event(created_time);
ALTER INDEX IF EXISTS idx_edge_event_tenant_id_and_created_time RENAME TO idx_old_edge_event_tenant_id_and_created_time;
FOR table_partition IN SELECT tablename AS name, split_part(tablename, '_', 3) AS partition_ts
@ -111,6 +115,7 @@ CREATE TABLE IF NOT EXISTS edge_event (
ts bigint NOT NULL
) PARTITION BY RANGE (created_time);
CREATE INDEX IF NOT EXISTS idx_edge_event_tenant_id_and_created_time ON edge_event(tenant_id, created_time DESC);
CREATE INDEX IF NOT EXISTS idx_edge_event_id ON edge_event(id);
CREATE OR REPLACE PROCEDURE migrate_edge_event(IN start_time_ms BIGINT, IN end_time_ms BIGINT, IN partition_size_ms BIGINT)
LANGUAGE plpgsql AS

1
application/src/main/java/org/thingsboard/server/ThingsboardInstallApplication.java

@ -29,6 +29,7 @@ import java.util.Arrays;
@ComponentScan({"org.thingsboard.server.install",
"org.thingsboard.server.service.component",
"org.thingsboard.server.service.install",
"org.thingsboard.server.service.security.auth.jwt.settings",
"org.thingsboard.server.dao",
"org.thingsboard.server.common.stats",
"org.thingsboard.server.common.transport.config.ssl",

14
application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java

@ -33,7 +33,7 @@ import org.thingsboard.rule.engine.api.MailService;
import org.thingsboard.rule.engine.api.SmsService;
import org.thingsboard.rule.engine.api.sms.SmsSenderFactory;
import org.thingsboard.script.api.js.JsInvokeService;
import org.thingsboard.script.api.mvel.MvelInvokeService;
import org.thingsboard.script.api.tbel.TbelInvokeService;
import org.thingsboard.server.actors.service.ActorService;
import org.thingsboard.server.actors.tenant.DebugTbRateLimits;
import org.thingsboard.server.cluster.TbClusterService;
@ -50,6 +50,7 @@ import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
import org.thingsboard.server.common.msg.tools.TbRateLimits;
import org.thingsboard.server.common.stats.TbApiUsageReportClient;
import org.thingsboard.server.dao.asset.AssetProfileService;
import org.thingsboard.server.dao.asset.AssetService;
import org.thingsboard.server.dao.attributes.AttributesService;
import org.thingsboard.server.dao.audit.AuditLogService;
@ -58,6 +59,7 @@ import org.thingsboard.server.dao.customer.CustomerService;
import org.thingsboard.server.dao.dashboard.DashboardService;
import org.thingsboard.server.dao.device.ClaimDevicesService;
import org.thingsboard.server.dao.device.DeviceCredentialsService;
import org.thingsboard.server.dao.device.DeviceProfileService;
import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.dao.edge.EdgeEventService;
import org.thingsboard.server.dao.edge.EdgeService;
@ -176,6 +178,14 @@ public class ActorSystemContext {
@Getter
private DeviceService deviceService;
@Autowired
@Getter
private DeviceProfileService deviceProfileService;
@Autowired
@Getter
private AssetProfileService assetProfileService;
@Autowired
@Getter
private DeviceCredentialsService deviceCredentialsService;
@ -274,7 +284,7 @@ public class ActorSystemContext {
@Autowired(required = false)
@Getter
private MvelInvokeService mvelInvokeService;
private TbelInvokeService tbelInvokeService;
@Autowired
@Getter

28
application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java

@ -71,12 +71,14 @@ import org.thingsboard.server.common.msg.TbMsgMetaData;
import org.thingsboard.server.common.msg.TbMsgProcessingStackItem;
import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
import org.thingsboard.server.dao.asset.AssetProfileService;
import org.thingsboard.server.dao.asset.AssetService;
import org.thingsboard.server.dao.attributes.AttributesService;
import org.thingsboard.server.dao.cassandra.CassandraCluster;
import org.thingsboard.server.dao.customer.CustomerService;
import org.thingsboard.server.dao.dashboard.DashboardService;
import org.thingsboard.server.dao.device.DeviceCredentialsService;
import org.thingsboard.server.dao.device.DeviceProfileService;
import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.dao.edge.EdgeEventService;
import org.thingsboard.server.dao.edge.EdgeService;
@ -97,7 +99,7 @@ import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.queue.TbQueueCallback;
import org.thingsboard.server.queue.TbQueueMsgMetadata;
import org.thingsboard.server.service.script.RuleNodeJsScriptEngine;
import org.thingsboard.server.service.script.RuleNodeMvelScriptEngine;
import org.thingsboard.server.service.script.RuleNodeTbelScriptEngine;
import java.util.Collections;
import java.util.List;
@ -474,11 +476,11 @@ class DefaultTbContext implements TbContext {
return new RuleNodeJsScriptEngine(getTenantId(), mainCtx.getJsInvokeService(), script, argNames);
}
private ScriptEngine createMvelScriptEngine(String script, String... argNames) {
if (mainCtx.getMvelInvokeService() == null) {
throw new RuntimeException("MVEL execution is disabled!");
private ScriptEngine createTbelScriptEngine(String script, String... argNames) {
if (mainCtx.getTbelInvokeService() == null) {
throw new RuntimeException("TBEL execution is disabled!");
}
return new RuleNodeMvelScriptEngine(getTenantId(), mainCtx.getMvelInvokeService(), script, argNames);
return new RuleNodeTbelScriptEngine(getTenantId(), mainCtx.getTbelInvokeService(), script, argNames);
}
@Override
@ -492,11 +494,11 @@ class DefaultTbContext implements TbContext {
switch (scriptLang) {
case JS:
return createJsScriptEngine(script, argNames);
case MVEL:
case TBEL:
if (Arrays.isNullOrEmpty(argNames)) {
return createMvelScriptEngine(script, "msg", "metadata", "msgType");
return createTbelScriptEngine(script, "msg", "metadata", "msgType");
} else {
return createMvelScriptEngine(script, argNames);
return createTbelScriptEngine(script, argNames);
}
default:
throw new RuntimeException("Unsupported script language: " + scriptLang.name());
@ -559,6 +561,16 @@ class DefaultTbContext implements TbContext {
return mainCtx.getDeviceService();
}
@Override
public DeviceProfileService getDeviceProfileService() {
return mainCtx.getDeviceProfileService();
}
@Override
public AssetProfileService getAssetProfileService() {
return mainCtx.getAssetProfileService();
}
@Override
public DeviceCredentialsService getDeviceCredentialsService() {
return mainCtx.getDeviceCredentialsService();

49
application/src/main/java/org/thingsboard/server/controller/AdminController.java

@ -22,7 +22,9 @@ import com.google.common.util.concurrent.MoreExecutors;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.context.request.async.DeferredResult;
@ -37,8 +39,13 @@ import org.thingsboard.server.common.data.sms.config.TestSmsRequest;
import org.thingsboard.server.common.data.sync.vc.AutoCommitSettings;
import org.thingsboard.server.common.data.sync.vc.RepositorySettings;
import org.thingsboard.server.common.data.sync.vc.RepositorySettingsInfo;
import org.thingsboard.server.common.data.security.model.JwtSettings;
import org.thingsboard.server.service.security.auth.jwt.settings.JwtSettingsService;
import org.thingsboard.server.dao.settings.AdminSettingsService;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.common.data.security.model.JwtPair;
import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.service.security.model.token.JwtTokenFactory;
import org.thingsboard.server.service.security.permission.Operation;
import org.thingsboard.server.service.security.permission.Resource;
import org.thingsboard.server.service.security.system.SystemSecurityService;
@ -65,6 +72,14 @@ public class AdminController extends BaseController {
@Autowired
private SystemSecurityService systemSecurityService;
@Lazy
@Autowired
private JwtSettingsService jwtSettingsService;
@Lazy
@Autowired
private JwtTokenFactory tokenFactory;
@Autowired
private EntitiesVersionControlService versionControlService;
@ -152,6 +167,40 @@ public class AdminController extends BaseController {
}
}
@ApiOperation(value = "Get the JWT Settings object (getJwtSettings)",
notes = "Get the JWT Settings object that contains JWT token policy, etc. " + SYSTEM_AUTHORITY_PARAGRAPH,
produces = MediaType.APPLICATION_JSON_VALUE)
@PreAuthorize("hasAuthority('SYS_ADMIN')")
@RequestMapping(value = "/jwtSettings", method = RequestMethod.GET)
@ResponseBody
public JwtSettings getJwtSettings() throws ThingsboardException {
try {
accessControlService.checkPermission(getCurrentUser(), Resource.ADMIN_SETTINGS, Operation.READ);
return checkNotNull(jwtSettingsService.getJwtSettings());
} catch (Exception e) {
throw handleException(e);
}
}
@ApiOperation(value = "Update JWT Settings (saveJwtSettings)",
notes = "Updates the JWT Settings object that contains JWT token policy, etc. The tokenSigningKey field is a Base64 encoded string." + SYSTEM_AUTHORITY_PARAGRAPH,
produces = MediaType.APPLICATION_JSON_VALUE)
@PreAuthorize("hasAuthority('SYS_ADMIN')")
@RequestMapping(value = "/jwtSettings", method = RequestMethod.POST)
@ResponseBody
public JwtPair saveJwtSettings(
@ApiParam(value = "A JSON value representing the JWT Settings.")
@RequestBody JwtSettings jwtSettings) throws ThingsboardException {
try {
SecurityUser securityUser = getCurrentUser();
accessControlService.checkPermission(securityUser, Resource.ADMIN_SETTINGS, Operation.WRITE);
checkNotNull(jwtSettingsService.saveJwtSettings(jwtSettings));
return tokenFactory.createTokenPair(securityUser);
} catch (Exception e) {
throw handleException(e);
}
}
@ApiOperation(value = "Send test email (sendTestMail)",
notes = "Attempts to send test email to the System Administrator User using Mail Settings provided as a parameter. " +
"You may change the 'To' email in the user profile of the System Administrator. " + SYSTEM_AUTHORITY_PARAGRAPH)

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

@ -51,7 +51,7 @@ import org.thingsboard.server.queue.util.TbCoreComponent;
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;
import org.thingsboard.server.service.security.model.JwtTokenPair;
import org.thingsboard.server.common.data.security.model.JwtPair;
import org.thingsboard.server.service.security.model.ResetPasswordEmailRequest;
import org.thingsboard.server.service.security.model.ResetPasswordRequest;
import org.thingsboard.server.service.security.model.SecurityUser;
@ -236,7 +236,7 @@ public class AuthController extends BaseController {
@RequestMapping(value = "/noauth/activate", method = RequestMethod.POST)
@ResponseStatus(value = HttpStatus.OK)
@ResponseBody
public JwtTokenPair activateUser(
public JwtPair activateUser(
@ApiParam(value = "Activate user request.")
@RequestBody ActivateUserRequest activateRequest,
@RequestParam(required = false, defaultValue = "true") boolean sendActivationMail,
@ -278,7 +278,7 @@ public class AuthController extends BaseController {
@RequestMapping(value = "/noauth/resetPassword", method = RequestMethod.POST)
@ResponseStatus(value = HttpStatus.OK)
@ResponseBody
public JwtTokenPair resetPassword(
public JwtPair resetPassword(
@ApiParam(value = "Reset password request.")
@RequestBody ResetPasswordRequest resetPasswordRequest,
HttpServletRequest request) throws ThingsboardException {

9
application/src/main/java/org/thingsboard/server/controller/BaseController.java

@ -120,6 +120,7 @@ import org.thingsboard.server.dao.queue.QueueService;
import org.thingsboard.server.dao.relation.RelationService;
import org.thingsboard.server.dao.rpc.RpcService;
import org.thingsboard.server.dao.rule.RuleChainService;
import org.thingsboard.server.dao.service.Validator;
import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import org.thingsboard.server.dao.tenant.TenantProfileService;
import org.thingsboard.server.dao.tenant.TenantService;
@ -131,7 +132,6 @@ import org.thingsboard.server.queue.discovery.PartitionService;
import org.thingsboard.server.queue.provider.TbQueueProducerProvider;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.component.ComponentDiscoveryService;
import org.thingsboard.server.service.edge.EdgeNotificationService;
import org.thingsboard.server.service.edge.rpc.EdgeRpcService;
import org.thingsboard.server.service.entitiy.TbNotificationEntityService;
import org.thingsboard.server.service.ota.OtaPackageStateService;
@ -417,9 +417,12 @@ public abstract class BaseController {
}
PageLink createPageLink(int pageSize, int page, String textSearch, String sortProperty, String sortOrder) throws ThingsboardException {
if (!StringUtils.isEmpty(sortProperty)) {
if (StringUtils.isNotEmpty(sortProperty)) {
if (!Validator.isValidProperty(sortProperty)) {
throw new IllegalArgumentException("Invalid sort property");
}
SortOrder.Direction direction = SortOrder.Direction.ASC;
if (!StringUtils.isEmpty(sortOrder)) {
if (StringUtils.isNotEmpty(sortOrder)) {
try {
direction = SortOrder.Direction.valueOf(sortOrder.toUpperCase());
} catch (IllegalArgumentException e) {

11
application/src/main/java/org/thingsboard/server/controller/ControllerConstants.java

@ -1433,7 +1433,8 @@ public class ControllerConstants {
protected static final String ATTRIBUTES_SCOPE_DESCRIPTION = "A string value representing the attributes scope. For example, 'SERVER_SCOPE'.";
protected static final String ATTRIBUTES_KEYS_DESCRIPTION = "A string value representing the comma-separated list of attributes keys. For example, 'active,inactivityAlarmTime'.";
protected static final String ATTRIBUTES_SCOPE_ALLOWED_VALUES = "SERVER_SCOPE, CLIENT_SCOPE, SHARED_SCOPE";
protected static final String ATTRIBUTES_SAVE_SCOPE_ALLOWED_VALUES = "SERVER_SCOPE, SHARED_SCOPE";
protected static final String ATTRIBUTES_SCOPE_ALLOWED_VALUES = ATTRIBUTES_SAVE_SCOPE_ALLOWED_VALUES + ", CLIENT_SCOPE";
protected static final String ATTRIBUTES_JSON_REQUEST_DESCRIPTION = "A string value representing the json object. For example, '{\"key\":\"value\"}'. See API call description for more details.";
protected static final String TELEMETRY_KEYS_BASE_DESCRIPTION = "A string value representing the comma-separated list of telemetry keys.";
@ -1459,10 +1460,12 @@ public class ControllerConstants {
protected static final String SAVE_ENTITY_TIMESERIES_STATUS_INTERNAL_SERVER_ERROR = "The exception was thrown during processing the request. " +
"Platform creates an audit log event about entity timeseries updates with action type 'TIMESERIES_UPDATED' that includes an error stacktrace.";
protected static final String ENTITY_ATTRIBUTE_SCOPES = " List of possible attribute scopes depends on the entity type: " +
protected static final String ENTITY_ATTRIBUTE_SCOPES_TEMPLATE = " List of possible attribute scopes depends on the entity type: " +
"\n\n * SERVER_SCOPE - supported for all entity types;" +
"\n * CLIENT_SCOPE - supported for devices;" +
"\n * SHARED_SCOPE - supported for devices. "+ "\n\n";
"\n * SHARED_SCOPE - supported for devices";
protected static final String ENTITY_SAVE_ATTRIBUTE_SCOPES = ENTITY_ATTRIBUTE_SCOPES_TEMPLATE + ".\n\n";
protected static final String ENTITY_GET_ATTRIBUTE_SCOPES = ENTITY_ATTRIBUTE_SCOPES_TEMPLATE +
";\n * CLIENT_SCOPE - supported for devices. " + "\n\n";
protected static final String ATTRIBUTE_DATA_EXAMPLE = "[\n" +
" {\"key\": \"stringAttributeKey\", \"value\": \"value\", \"lastUpdateTs\": 1609459200000},\n" +

28
application/src/main/java/org/thingsboard/server/controller/RuleChainController.java

@ -38,7 +38,7 @@ import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import org.thingsboard.rule.engine.api.ScriptEngine;
import org.thingsboard.script.api.js.JsInvokeService;
import org.thingsboard.script.api.mvel.MvelInvokeService;
import org.thingsboard.script.api.tbel.TbelInvokeService;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.actors.tenant.DebugTbRateLimits;
import org.thingsboard.server.common.data.EventInfo;
@ -69,7 +69,7 @@ import org.thingsboard.server.dao.event.EventService;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.rule.TbRuleChainService;
import org.thingsboard.server.service.script.RuleNodeJsScriptEngine;
import org.thingsboard.server.service.script.RuleNodeMvelScriptEngine;
import org.thingsboard.server.service.script.RuleNodeTbelScriptEngine;
import org.thingsboard.server.service.security.permission.Operation;
import org.thingsboard.server.service.security.permission.Resource;
@ -146,7 +146,7 @@ public class RuleChainController extends BaseController {
private JsInvokeService jsInvokeService;
@Autowired(required = false)
private MvelInvokeService mvelInvokeService;
private TbelInvokeService tbelInvokeService;
@Autowired(required = false)
private ActorSystemContext actorContext;
@ -154,8 +154,8 @@ public class RuleChainController extends BaseController {
@Value("${actors.rule.chain.debug_mode_rate_limits_per_tenant.enabled}")
private boolean debugPerTenantEnabled;
@Value("${mvel.enabled:true}")
private boolean mvelEnabled;
@Value("${tbel.enabled:true}")
private boolean tbelEnabled;
@ApiOperation(value = "Get Rule Chain (getRuleChainById)",
notes = "Fetch the Rule Chain object based on the provided Rule Chain Id. " + RULE_CHAIN_DESCRIPTION + TENANT_AUTHORITY_PARAGRAPH)
@ -378,13 +378,13 @@ public class RuleChainController extends BaseController {
}
}
@ApiOperation(value = "Is MVEL script executor enabled",
notes = "Returns 'True' if the MVEL script execution is enabled" + TENANT_AUTHORITY_PARAGRAPH)
@ApiOperation(value = "Is TBEL script executor enabled",
notes = "Returns 'True' if the TBEL script execution is enabled" + TENANT_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/ruleChain/mvelEnabled", method = RequestMethod.GET)
@RequestMapping(value = "/ruleChain/tbelEnabled", method = RequestMethod.GET)
@ResponseBody
public Boolean isMvelEnabled() {
return mvelEnabled;
public Boolean isTbelEnabled() {
return tbelEnabled;
}
@ApiOperation(value = "Test Script function",
@ -393,7 +393,7 @@ public class RuleChainController extends BaseController {
@RequestMapping(value = "/ruleChain/testScript", method = RequestMethod.POST)
@ResponseBody
public JsonNode testScript(
@ApiParam(value = "Script language: JS or MVEL")
@ApiParam(value = "Script language: JS or TBEL")
@RequestParam(required = false) ScriptLanguage scriptLang,
@ApiParam(value = "Test JS request. See API call description above.")
@RequestBody JsonNode inputParams) throws ThingsboardException {
@ -418,10 +418,10 @@ public class RuleChainController extends BaseController {
if (ScriptLanguage.JS.equals(scriptLang)) {
engine = new RuleNodeJsScriptEngine(getTenantId(), jsInvokeService, script, argNames);
} else {
if (mvelInvokeService == null) {
throw new IllegalArgumentException("MVEL script engine is disabled!");
if (tbelInvokeService == null) {
throw new IllegalArgumentException("TBEL script engine is disabled!");
}
engine = new RuleNodeMvelScriptEngine(getTenantId(), mvelInvokeService, script, argNames);
engine = new RuleNodeTbelScriptEngine(getTenantId(), tbelInvokeService, script, argNames);
}
TbMsg inMsg = TbMsg.newMsg(msgType, null, new TbMsgMetaData(metadata), TbMsgDataType.JSON, data);
switch (scriptType) {

23
application/src/main/java/org/thingsboard/server/controller/TelemetryController.java

@ -58,7 +58,6 @@ import org.thingsboard.server.common.data.id.EntityIdFactory;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UUIDBased;
import org.thingsboard.server.common.data.kv.Aggregation;
import org.thingsboard.server.common.data.kv.AttributeKey;
import org.thingsboard.server.common.data.kv.AttributeKvEntry;
import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry;
import org.thingsboard.server.common.data.kv.BaseDeleteTsKvQuery;
@ -92,11 +91,9 @@ import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
@ -104,13 +101,15 @@ import java.util.stream.Collectors;
import static org.thingsboard.server.controller.ControllerConstants.ATTRIBUTES_JSON_REQUEST_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.ATTRIBUTES_KEYS_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.ATTRIBUTES_SAVE_SCOPE_ALLOWED_VALUES;
import static org.thingsboard.server.controller.ControllerConstants.ATTRIBUTES_SCOPE_ALLOWED_VALUES;
import static org.thingsboard.server.controller.ControllerConstants.ATTRIBUTES_SCOPE_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.ATTRIBUTE_DATA_EXAMPLE;
import static org.thingsboard.server.controller.ControllerConstants.DEVICE_ID;
import static org.thingsboard.server.controller.ControllerConstants.DEVICE_ID_PARAM_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.ENTITY_ATTRIBUTE_SCOPES;
import static org.thingsboard.server.controller.ControllerConstants.ENTITY_GET_ATTRIBUTE_SCOPES;
import static org.thingsboard.server.controller.ControllerConstants.ENTITY_ID_PARAM_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.ENTITY_SAVE_ATTRIBUTE_SCOPES;
import static org.thingsboard.server.controller.ControllerConstants.ENTITY_TYPE_PARAM_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.INVALID_ENTITY_ID_OR_ENTITY_TYPE_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.INVALID_STRUCTURE_OF_THE_REQUEST;
@ -241,7 +240,7 @@ public class TelemetryController extends BaseController {
@ApiOperation(value = "Get attributes by scope (getAttributesByScope)",
notes = "Returns all attributes of a specified scope that belong to specified entity." +
ENTITY_ATTRIBUTE_SCOPES +
ENTITY_GET_ATTRIBUTE_SCOPES +
"Use optional 'keys' parameter to return specific attributes."
+ "\n Example of the result: \n\n"
+ MARKDOWN_CODE_BLOCK_START
@ -383,7 +382,7 @@ public class TelemetryController extends BaseController {
@ResponseBody
public DeferredResult<ResponseEntity> saveDeviceAttributes(
@ApiParam(value = DEVICE_ID_PARAM_DESCRIPTION, required = true) @PathVariable("deviceId") String deviceIdStr,
@ApiParam(value = ATTRIBUTES_SCOPE_DESCRIPTION, allowableValues = ATTRIBUTES_SCOPE_ALLOWED_VALUES, required = true) @PathVariable("scope") String scope,
@ApiParam(value = ATTRIBUTES_SCOPE_DESCRIPTION, allowableValues = ATTRIBUTES_SAVE_SCOPE_ALLOWED_VALUES, required = true) @PathVariable("scope") String scope,
@ApiParam(value = ATTRIBUTES_JSON_REQUEST_DESCRIPTION, required = true) @RequestBody JsonNode request) throws ThingsboardException {
try {
EntityId entityId = EntityIdFactory.getByTypeAndUuid(EntityType.DEVICE, deviceIdStr);
@ -395,7 +394,7 @@ public class TelemetryController extends BaseController {
@ApiOperation(value = "Save entity attributes (saveEntityAttributesV1)",
notes = "Creates or updates the entity attributes based on Entity Id and the specified attribute scope. " +
ENTITY_ATTRIBUTE_SCOPES +
ENTITY_SAVE_ATTRIBUTE_SCOPES +
SAVE_ATTRIBUTES_REQUEST_PAYLOAD
+ INVALID_ENTITY_ID_OR_ENTITY_TYPE_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH,
produces = MediaType.APPLICATION_JSON_VALUE)
@ -411,7 +410,7 @@ public class TelemetryController extends BaseController {
public DeferredResult<ResponseEntity> saveEntityAttributesV1(
@ApiParam(value = ENTITY_TYPE_PARAM_DESCRIPTION, required = true, defaultValue = "DEVICE") @PathVariable("entityType") String entityType,
@ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr,
@ApiParam(value = ATTRIBUTES_SCOPE_DESCRIPTION, allowableValues = ATTRIBUTES_SCOPE_ALLOWED_VALUES) @PathVariable("scope") String scope,
@ApiParam(value = ATTRIBUTES_SCOPE_DESCRIPTION, allowableValues = ATTRIBUTES_SAVE_SCOPE_ALLOWED_VALUES) @PathVariable("scope") String scope,
@ApiParam(value = ATTRIBUTES_JSON_REQUEST_DESCRIPTION, required = true) @RequestBody JsonNode request) throws ThingsboardException {
try {
EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr);
@ -423,7 +422,7 @@ public class TelemetryController extends BaseController {
@ApiOperation(value = "Save entity attributes (saveEntityAttributesV2)",
notes = "Creates or updates the entity attributes based on Entity Id and the specified attribute scope. " +
ENTITY_ATTRIBUTE_SCOPES +
ENTITY_SAVE_ATTRIBUTE_SCOPES +
SAVE_ATTRIBUTES_REQUEST_PAYLOAD
+ INVALID_ENTITY_ID_OR_ENTITY_TYPE_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH,
produces = MediaType.APPLICATION_JSON_VALUE)
@ -439,7 +438,7 @@ public class TelemetryController extends BaseController {
public DeferredResult<ResponseEntity> saveEntityAttributesV2(
@ApiParam(value = ENTITY_TYPE_PARAM_DESCRIPTION, required = true, defaultValue = "DEVICE") @PathVariable("entityType") String entityType,
@ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr,
@ApiParam(value = ATTRIBUTES_SCOPE_DESCRIPTION, allowableValues = ATTRIBUTES_SCOPE_ALLOWED_VALUES, required = true) @PathVariable("scope") String scope,
@ApiParam(value = ATTRIBUTES_SCOPE_DESCRIPTION, allowableValues = ATTRIBUTES_SAVE_SCOPE_ALLOWED_VALUES, required = true) @PathVariable("scope") String scope,
@ApiParam(value = ATTRIBUTES_JSON_REQUEST_DESCRIPTION, required = true) @RequestBody JsonNode request) throws ThingsboardException {
try {
EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr);
@ -661,10 +660,8 @@ public class TelemetryController extends BaseController {
logAttributesDeleted(user, entityId, scope, keys, null);
if (entityIdSrc.getEntityType().equals(EntityType.DEVICE)) {
DeviceId deviceId = new DeviceId(entityId.getId());
Set<AttributeKey> keysToNotify = new HashSet<>();
keys.forEach(key -> keysToNotify.add(new AttributeKey(scope, key)));
tbClusterService.pushMsgToCore(DeviceAttributesEventNotificationMsg.onDelete(
user.getTenantId(), deviceId, keysToNotify), null);
user.getTenantId(), deviceId, scope, keys), null);
}
result.setResult(new ResponseEntity<>(HttpStatus.OK));
}

6
application/src/main/java/org/thingsboard/server/controller/TwoFactorAuthController.java

@ -39,7 +39,7 @@ import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.security.auth.mfa.TwoFactorAuthService;
import org.thingsboard.server.service.security.auth.mfa.config.TwoFaConfigManager;
import org.thingsboard.server.service.security.auth.rest.RestAuthenticationDetails;
import org.thingsboard.server.service.security.model.JwtTokenPair;
import org.thingsboard.server.common.data.security.model.JwtPair;
import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.service.security.model.token.JwtTokenFactory;
import org.thingsboard.server.service.security.system.SystemSecurityService;
@ -87,8 +87,8 @@ public class TwoFactorAuthController extends BaseController {
"and Too Many Requests error if rate limits are exceeded.")
@PostMapping("/verification/check")
@PreAuthorize("hasAuthority('PRE_VERIFICATION_TOKEN')")
public JwtTokenPair checkTwoFaVerificationCode(@RequestParam TwoFaProviderType providerType,
@RequestParam String verificationCode, HttpServletRequest servletRequest) throws Exception {
public JwtPair checkTwoFaVerificationCode(@RequestParam TwoFaProviderType providerType,
@RequestParam String verificationCode, HttpServletRequest servletRequest) throws Exception {
SecurityUser user = getCurrentUser();
boolean verificationSuccess = twoFactorAuthService.checkVerificationCode(user, providerType, verificationCode, true);
if (verificationSuccess) {

6
application/src/main/java/org/thingsboard/server/controller/UserController.java

@ -46,7 +46,7 @@ import org.thingsboard.server.common.data.security.UserCredentials;
import org.thingsboard.server.common.data.security.event.UserCredentialsInvalidationEvent;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.entitiy.user.TbUserService;
import org.thingsboard.server.service.security.model.JwtTokenPair;
import org.thingsboard.server.common.data.security.model.JwtPair;
import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.service.security.model.UserPrincipal;
import org.thingsboard.server.service.security.model.token.JwtTokenFactory;
@ -145,7 +145,7 @@ public class UserController extends BaseController {
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
@RequestMapping(value = "/user/{userId}/token", method = RequestMethod.GET)
@ResponseBody
public JwtTokenPair getUserToken(
public JwtPair getUserToken(
@ApiParam(value = USER_ID_PARAM_DESCRIPTION)
@PathVariable(USER_ID) String strUserId) throws ThingsboardException {
checkParameter(USER_ID, strUserId);
@ -182,7 +182,7 @@ public class UserController extends BaseController {
@RequestBody User user,
@ApiParam(value = "Send activation email (or use activation link)", defaultValue = "true")
@RequestParam(required = false, defaultValue = "true") boolean sendActivationMail, HttpServletRequest request) throws ThingsboardException {
if (Authority.TENANT_ADMIN.equals(getCurrentUser().getAuthority())) {
if (!Authority.SYS_ADMIN.equals(getCurrentUser().getAuthority())) {
user.setTenantId(getCurrentUser().getTenantId());
}
checkEntity(user.getId(), user, Resource.USER);

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

@ -28,10 +28,12 @@ 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;
@ -50,6 +52,7 @@ import javax.websocket.Session;
import java.io.IOException;
import java.net.URI;
import java.security.InvalidParameterException;
import java.util.Optional;
import java.util.Queue;
import java.util.Set;
import java.util.UUID;
@ -136,8 +139,9 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements Telemetr
if (!checkLimits(session, sessionRef)) {
return;
}
var tenantProfileConfiguration = tenantProfileCache.get(sessionRef.getSecurityCtx().getTenantId()).getDefaultProfileConfiguration();
internalSessionMap.put(internalSessionId, new SessionMetaData(session, sessionRef, tenantProfileConfiguration.getWsMsgQueueLimitPerSession() > 0 ?
var tenantProfileConfiguration = getTenantProfileConfiguration(sessionRef);
internalSessionMap.put(internalSessionId, new SessionMetaData(session, sessionRef,
tenantProfileConfiguration != null && tenantProfileConfiguration.getWsMsgQueueLimitPerSession() > 0 ?
tenantProfileConfiguration.getWsMsgQueueLimitPerSession() : 500));
externalSessionMap.put(externalSessionId, internalSessionId);
@ -316,22 +320,24 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements Telemetr
if (internalId != null) {
SessionMetaData sessionMd = internalSessionMap.get(internalId);
if (sessionMd != null) {
var tenantProfileConfiguration = tenantProfileCache.get(sessionRef.getSecurityCtx().getTenantId()).getDefaultProfileConfiguration();
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!\"}");
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);
}
return;
} else {
log.debug("[{}][{}][{}] Session is no longer blacklisted.", sessionRef.getSecurityCtx().getTenantId(), sessionRef.getSecurityCtx().getId(), externalId);
blacklistedSessions.remove(externalId);
perSessionUpdateLimits.remove(sessionRef.getSessionId());
}
} else {
perSessionUpdateLimits.remove(sessionRef.getSessionId());
}
sessionMd.sendMsg(msg);
} else {
@ -376,8 +382,7 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements Telemetr
}
private boolean checkLimits(WebSocketSession session, TelemetryWebSocketSessionRef sessionRef) throws Exception {
var tenantProfileConfiguration =
tenantProfileCache.get(sessionRef.getSecurityCtx().getTenantId()).getDefaultProfileConfiguration();
var tenantProfileConfiguration = getTenantProfileConfiguration(sessionRef);
if (tenantProfileConfiguration == null) {
return true;
}
@ -444,7 +449,8 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements Telemetr
}
private void cleanupLimits(WebSocketSession session, TelemetryWebSocketSessionRef sessionRef) {
var tenantProfileConfiguration = tenantProfileCache.get(sessionRef.getSecurityCtx().getTenantId()).getDefaultProfileConfiguration();
var tenantProfileConfiguration = getTenantProfileConfiguration(sessionRef);
if (tenantProfileConfiguration == null) return;
String sessionId = session.getId();
perSessionUpdateLimits.remove(sessionRef.getSessionId());
@ -477,4 +483,9 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements Telemetr
}
}
private DefaultTenantProfileConfiguration getTenantProfileConfiguration(TelemetryWebSocketSessionRef sessionRef) {
return Optional.ofNullable(tenantProfileCache.get(sessionRef.getSecurityCtx().getTenantId()))
.map(TenantProfile::getDefaultProfileConfiguration).orElse(null);
}
}

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

@ -273,6 +273,7 @@ public class ThingsboardInstallService {
systemDataLoaderService.createSysAdmin();
systemDataLoaderService.createDefaultTenantProfiles();
systemDataLoaderService.createAdminSettings();
systemDataLoaderService.createRandomJwtSettings();
systemDataLoaderService.loadSystemWidgets();
systemDataLoaderService.createOAuth2Templates();
systemDataLoaderService.createQueues();

9
application/src/main/java/org/thingsboard/server/service/device/DeviceBulkImportService.java

@ -40,6 +40,7 @@ import org.thingsboard.server.common.data.device.profile.DisabledDeviceProfilePr
import org.thingsboard.server.common.data.device.profile.Lwm2mDeviceProfileTransportConfiguration;
import org.thingsboard.server.common.data.device.profile.lwm2m.OtherConfiguration;
import org.thingsboard.server.common.data.device.profile.lwm2m.TelemetryMappingConfiguration;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.security.DeviceCredentials;
import org.thingsboard.server.common.data.security.DeviceCredentialsType;
@ -104,7 +105,7 @@ public class DeviceBulkImportService extends AbstractBulkImportService<Device> {
protected Device saveEntity(SecurityUser user, Device entity, Map<BulkImportColumnType, String> fields) {
DeviceCredentials deviceCredentials;
try {
deviceCredentials = createDeviceCredentials(fields);
deviceCredentials = createDeviceCredentials(entity.getTenantId(), entity.getId(), fields);
deviceCredentialsService.formatCredentials(deviceCredentials);
} catch (Exception e) {
throw new DeviceCredentialsValidationException("Invalid device credentials: " + e.getMessage());
@ -136,7 +137,7 @@ public class DeviceBulkImportService extends AbstractBulkImportService<Device> {
}
@SneakyThrows
private DeviceCredentials createDeviceCredentials(Map<BulkImportColumnType, String> fields) {
private DeviceCredentials createDeviceCredentials(TenantId tenantId, DeviceId deviceId, Map<BulkImportColumnType, String> fields) {
DeviceCredentials credentials = new DeviceCredentials();
if (fields.containsKey(BulkImportColumnType.LWM2M_CLIENT_ENDPOINT)) {
credentials.setCredentialsType(DeviceCredentialsType.LWM2M_CREDENTIALS);
@ -147,7 +148,9 @@ public class DeviceBulkImportService extends AbstractBulkImportService<Device> {
} else if (CollectionUtils.containsAny(fields.keySet(), EnumSet.of(BulkImportColumnType.MQTT_CLIENT_ID, BulkImportColumnType.MQTT_USER_NAME, BulkImportColumnType.MQTT_PASSWORD))) {
credentials.setCredentialsType(DeviceCredentialsType.MQTT_BASIC);
setUpBasicMqttCredentials(fields, credentials);
} else {
} else if (deviceId != null && !fields.containsKey(BulkImportColumnType.ACCESS_TOKEN)) {
credentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(tenantId, deviceId);
} else {
credentials.setCredentialsType(DeviceCredentialsType.ACCESS_TOKEN);
setUpAccessTokenCredentials(fields, credentials);
}

8
application/src/main/java/org/thingsboard/server/service/edge/DefaultEdgeNotificationService.java

@ -16,6 +16,7 @@
package org.thingsboard.server.service.edge;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
@ -23,6 +24,7 @@ import lombok.extern.slf4j.Slf4j;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.common.data.EdgeUtils;
@ -67,6 +69,8 @@ import java.util.concurrent.Executors;
@Slf4j
public class DefaultEdgeNotificationService implements EdgeNotificationService {
public static final String EDGE_IS_ROOT_BODY_KEY = "isRoot";
@Autowired
private EdgeService edgeService;
@ -142,7 +146,9 @@ public class DefaultEdgeNotificationService implements EdgeNotificationService {
public Edge setEdgeRootRuleChain(TenantId tenantId, Edge edge, RuleChainId ruleChainId) throws Exception {
edge.setRootRuleChainId(ruleChainId);
Edge savedEdge = edgeService.saveEdge(edge);
saveEdgeEvent(tenantId, edge.getId(), EdgeEventType.RULE_CHAIN, EdgeEventActionType.UPDATED, ruleChainId, null).get();
ObjectNode isRootBody = JacksonUtil.OBJECT_MAPPER.createObjectNode();
isRootBody.put(EDGE_IS_ROOT_BODY_KEY, Boolean.TRUE);
saveEdgeEvent(tenantId, edge.getId(), EdgeEventType.RULE_CHAIN, EdgeEventActionType.UPDATED, ruleChainId, isRootBody).get();
return savedEdge;
}

54
application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java

@ -187,10 +187,7 @@ public final class EdgeGrpcSession implements Closeable {
public void startSyncProcess(TenantId tenantId, EdgeId edgeId, boolean fullSync) {
log.trace("[{}][{}] Staring edge sync process", tenantId, edgeId);
syncCompleted = false;
if (sessionState.getSendDownlinkMsgsFuture() != null && sessionState.getSendDownlinkMsgsFuture().isDone()) {
String errorMsg = String.format("[%s][%s] Sync process started. General processing interrupted!", tenantId, edgeId);
sessionState.getSendDownlinkMsgsFuture().setException(new RuntimeException(errorMsg));
}
interruptGeneralProcessingOnSync(tenantId, edgeId);
doSync(new EdgeSyncCursor(ctx, edge, fullSync));
}
@ -244,7 +241,7 @@ public final class EdgeGrpcSession implements Closeable {
@Override
public void onFailure(Throwable t) {
String errorMsg = t.getMessage() != null ? t.getMessage() : "";
String errorMsg = EdgeUtils.createErrorMsgFromRootCauseAndStackTrace(t);
UplinkResponseMsg uplinkResponseMsg = UplinkResponseMsg.newBuilder()
.setUplinkMsgId(uplinkMsg.getUplinkMsgId())
.setSuccess(false).setErrorMsg(errorMsg).build();
@ -265,10 +262,7 @@ public final class EdgeGrpcSession implements Closeable {
}
if (sessionState.getPendingMsgsMap().isEmpty()) {
log.debug("[{}] Pending msgs map is empty. Stopping current iteration", edge.getRoutingKey());
if (sessionState.getScheduledSendDownlinkTask() != null) {
sessionState.getScheduledSendDownlinkTask().cancel(false);
}
sessionState.getSendDownlinkMsgsFuture().set(null);
stopCurrentSendDownlinkMsgsTask(null);
}
} catch (Exception e) {
log.error("[{}] Can't process downlink response message [{}]", this.sessionId, msg, e);
@ -391,15 +385,14 @@ public final class EdgeGrpcSession implements Closeable {
}
private ListenableFuture<Void> sendDownlinkMsgsPack(List<DownlinkMsg> downlinkMsgsPack) {
if (sessionState.getSendDownlinkMsgsFuture() != null && !sessionState.getSendDownlinkMsgsFuture().isDone()) {
String errorMsg = "[" + this.sessionId + "] Previous send downlink future was not properly completed, stopping it now";
log.error(errorMsg);
sessionState.getSendDownlinkMsgsFuture().setException(new RuntimeException(errorMsg));
}
interruptPreviousSendDownlinkMsgsTask();
sessionState.setSendDownlinkMsgsFuture(SettableFuture.create());
sessionState.getPendingMsgsMap().clear();
downlinkMsgsPack.forEach(msg -> sessionState.getPendingMsgsMap().put(msg.getDownlinkMsgId(), msg));
scheduleDownlinkMsgsPackSend(1);
return sessionState.getSendDownlinkMsgsFuture();
}
@ -422,13 +415,13 @@ public final class EdgeGrpcSession implements Closeable {
} else {
log.warn("[{}] Failed to deliver the batch after {} attempts. Next messages are going to be discarded {}",
this.sessionId, MAX_DOWNLINK_ATTEMPTS, copy);
sessionState.getSendDownlinkMsgsFuture().set(null);
stopCurrentSendDownlinkMsgsTask(null);
}
} else {
sessionState.getSendDownlinkMsgsFuture().set(null);
stopCurrentSendDownlinkMsgsTask(null);
}
} catch (Exception e) {
sessionState.getSendDownlinkMsgsFuture().setException(e);
stopCurrentSendDownlinkMsgsTask(e);
}
};
@ -531,7 +524,7 @@ public final class EdgeGrpcSession implements Closeable {
case CUSTOMER:
return ctx.getCustomerProcessor().convertCustomerEventToDownlink(edgeEvent);
case RULE_CHAIN:
return ctx.getRuleChainProcessor().convertRuleChainEventToDownlink(edge, edgeEvent);
return ctx.getRuleChainProcessor().convertRuleChainEventToDownlink(edgeEvent);
case RULE_CHAIN_METADATA:
return ctx.getRuleChainProcessor().convertRuleChainMetadataEventToDownlink(edgeEvent, this.edgeVersion);
case ALARM:
@ -673,4 +666,29 @@ public final class EdgeGrpcSession implements Closeable {
log.debug("[{}] Failed to close output stream: {}", sessionId, e.getMessage());
}
}
private void interruptPreviousSendDownlinkMsgsTask() {
String msg = String.format("[%s] Previous send downlink future was not properly completed, stopping it now!", this.sessionId);
stopCurrentSendDownlinkMsgsTask(new RuntimeException(msg));
}
private void interruptGeneralProcessingOnSync(TenantId tenantId, EdgeId edgeId) {
String msg = String.format("[%s][%s] Sync process started. General processing interrupted!", tenantId, edgeId);
stopCurrentSendDownlinkMsgsTask(new RuntimeException(msg));
}
public void stopCurrentSendDownlinkMsgsTask(Exception e) {
if (sessionState.getSendDownlinkMsgsFuture() != null && !sessionState.getSendDownlinkMsgsFuture().isDone()) {
if (e != null) {
log.warn(e.getMessage(), e);
sessionState.getSendDownlinkMsgsFuture().setException(e);
} else {
sessionState.getSendDownlinkMsgsFuture().set(null);
}
}
if (sessionState.getScheduledSendDownlinkTask() != null) {
sessionState.getScheduledSendDownlinkTask().cancel(true);
}
}
}

3
application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeSessionState.java

@ -19,6 +19,7 @@ import com.google.common.util.concurrent.SettableFuture;
import lombok.Data;
import org.thingsboard.server.gen.edge.v1.DownlinkMsg;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.ScheduledFuture;
@ -26,7 +27,7 @@ import java.util.concurrent.ScheduledFuture;
@Data
public class EdgeSessionState {
private final Map<Integer, DownlinkMsg> pendingMsgsMap = new LinkedHashMap<>();
private final Map<Integer, DownlinkMsg> pendingMsgsMap = Collections.synchronizedMap(new LinkedHashMap<>());
private SettableFuture<Void> sendDownlinkMsgsFuture;
private ScheduledFuture<?> scheduledSendDownlinkTask;
}

4
application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/RuleChainMsgConstructor.java

@ -35,13 +35,13 @@ import org.thingsboard.server.service.edge.rpc.constructor.rule.RuleChainMetadat
@TbCoreComponent
public class RuleChainMsgConstructor {
public RuleChainUpdateMsg constructRuleChainUpdatedMsg(RuleChainId edgeRootRuleChainId, UpdateMsgType msgType, RuleChain ruleChain) {
public RuleChainUpdateMsg constructRuleChainUpdatedMsg(UpdateMsgType msgType, RuleChain ruleChain, boolean isRoot) {
RuleChainUpdateMsg.Builder builder = RuleChainUpdateMsg.newBuilder()
.setMsgType(msgType)
.setIdMSB(ruleChain.getId().getId().getMostSignificantBits())
.setIdLSB(ruleChain.getId().getId().getLeastSignificantBits())
.setName(ruleChain.getName())
.setRoot(ruleChain.getId().equals(edgeRootRuleChainId))
.setRoot(isRoot)
.setDebugMode(ruleChain.isDebugMode())
.setConfiguration(JacksonUtil.toString(ruleChain.getConfiguration()));
if (ruleChain.getFirstRuleNodeId() != null) {

12
application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/RuleChainsEdgeEventFetcher.java

@ -15,8 +15,10 @@
*/
package org.thingsboard.server.service.edge.rpc.fetch;
import com.fasterxml.jackson.databind.node.ObjectNode;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.EdgeUtils;
import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.edge.EdgeEvent;
@ -28,6 +30,8 @@ import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.dao.rule.RuleChainService;
import static org.thingsboard.server.service.edge.DefaultEdgeNotificationService.EDGE_IS_ROOT_BODY_KEY;
@Slf4j
@AllArgsConstructor
public class RuleChainsEdgeEventFetcher extends BasePageableEdgeEventFetcher<RuleChain> {
@ -41,7 +45,13 @@ public class RuleChainsEdgeEventFetcher extends BasePageableEdgeEventFetcher<Rul
@Override
EdgeEvent constructEdgeEvent(TenantId tenantId, Edge edge, RuleChain ruleChain) {
ObjectNode isRootBody = JacksonUtil.OBJECT_MAPPER.createObjectNode();
boolean isRoot = false;
try {
isRoot = ruleChain.getId().equals(edge.getRootRuleChainId());
} catch (Exception ignored) {}
isRootBody.put(EDGE_IS_ROOT_BODY_KEY, isRoot);
return EdgeUtils.constructEdgeEvent(tenantId, edge.getId(), EdgeEventType.RULE_CHAIN,
EdgeEventActionType.ADDED, ruleChain.getId(), null);
EdgeEventActionType.ADDED, ruleChain.getId(), isRootBody);
}
}

13
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/RuleChainEdgeProcessor.java

@ -19,7 +19,6 @@ import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.EdgeUtils;
import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.edge.EdgeEvent;
import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.id.TenantId;
@ -33,12 +32,14 @@ import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.queue.util.TbCoreComponent;
import static org.thingsboard.server.service.edge.DefaultEdgeNotificationService.EDGE_IS_ROOT_BODY_KEY;
@Component
@Slf4j
@TbCoreComponent
public class RuleChainEdgeProcessor extends BaseEdgeProcessor {
public DownlinkMsg convertRuleChainEventToDownlink(Edge edge, EdgeEvent edgeEvent) {
public DownlinkMsg convertRuleChainEventToDownlink(EdgeEvent edgeEvent) {
RuleChainId ruleChainId = new RuleChainId(edgeEvent.getEntityId());
DownlinkMsg downlinkMsg = null;
switch (edgeEvent.getAction()) {
@ -47,9 +48,15 @@ public class RuleChainEdgeProcessor extends BaseEdgeProcessor {
case ASSIGNED_TO_EDGE:
RuleChain ruleChain = ruleChainService.findRuleChainById(edgeEvent.getTenantId(), ruleChainId);
if (ruleChain != null) {
boolean isRoot = false;
if (edgeEvent.getBody() != null && edgeEvent.getBody().get(EDGE_IS_ROOT_BODY_KEY) != null) {
try {
isRoot = Boolean.parseBoolean(edgeEvent.getBody().get(EDGE_IS_ROOT_BODY_KEY).asText());
} catch (Exception ignored) {}
}
UpdateMsgType msgType = getUpdateMsgType(edgeEvent.getAction());
RuleChainUpdateMsg ruleChainUpdateMsg =
ruleChainMsgConstructor.constructRuleChainUpdatedMsg(edge.getRootRuleChainId(), msgType, ruleChain);
ruleChainMsgConstructor.constructRuleChainUpdatedMsg(msgType, ruleChain, isRoot);
downlinkMsg = DownlinkMsg.newBuilder()
.setDownlinkMsgId(EdgeUtils.nextPositiveInt())
.addRuleChainUpdateMsg(ruleChainUpdateMsg)

13
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/TelemetryEdgeProcessor.java

@ -48,7 +48,7 @@ import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.EntityViewId;
import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.kv.AttributeKey;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.kv.AttributeKvEntry;
import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.TbMsgMetaData;
@ -71,9 +71,7 @@ import org.thingsboard.server.queue.util.TbCoreComponent;
import javax.annotation.Nullable;
import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@Component
@Slf4j
@ -287,12 +285,8 @@ public class TelemetryEdgeProcessor extends BaseEdgeProcessor {
List<String> attributeNames = attributeDeleteMsg.getAttributeNamesList();
attributesService.removeAll(tenantId, entityId, scope, attributeNames);
if (EntityType.DEVICE.name().equals(entityType)) {
Set<AttributeKey> attributeKeys = new HashSet<>();
for (String attributeName : attributeNames) {
attributeKeys.add(new AttributeKey(scope, attributeName));
}
tbClusterService.pushMsgToCore(DeviceAttributesEventNotificationMsg.onDelete(
tenantId, (DeviceId) entityId, attributeKeys), new TbQueueCallback() {
tenantId, (DeviceId) entityId, scope, attributeNames), new TbQueueCallback() {
@Override
public void onSuccess(TbQueueMsgMetadata metadata) {
futureToSet.set(null);
@ -329,6 +323,9 @@ public class TelemetryEdgeProcessor extends BaseEdgeProcessor {
case CUSTOMER:
entityId = new CustomerId(edgeEvent.getEntityId());
break;
case USER:
entityId = new UserId(edgeEvent.getEntityId());
break;
case EDGE:
entityId = new EdgeId(edgeEvent.getEntityId());
break;

3
application/src/main/java/org/thingsboard/server/service/edge/rpc/sync/DefaultEdgeRequestsService.java

@ -165,7 +165,8 @@ public class DefaultEdgeRequestsService implements EdgeRequestsService {
Map<String, Object> entityData = new HashMap<>();
ObjectNode attributes = JacksonUtil.OBJECT_MAPPER.createObjectNode();
for (AttributeKvEntry attr : ssAttributes) {
if (DefaultDeviceStateService.PERSISTENT_ATTRIBUTES.contains(attr.getKey())) {
if (DefaultDeviceStateService.PERSISTENT_ATTRIBUTES.contains(attr.getKey())
&& !DefaultDeviceStateService.INACTIVITY_TIMEOUT.equals(attr.getKey())) {
continue;
}
if (attr.getDataType() == DataType.BOOLEAN && attr.getBooleanValue().isPresent()) {

5
application/src/main/java/org/thingsboard/server/service/entitiy/asset/DefaultTbAssetService.java

@ -31,6 +31,7 @@ import org.thingsboard.server.common.data.id.AssetId;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.EdgeId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
import org.thingsboard.server.dao.asset.AssetService;
import org.thingsboard.server.service.entitiy.AbstractTbEntityService;
import org.thingsboard.server.service.profile.TbAssetProfileCache;
@ -63,6 +64,8 @@ public class DefaultTbAssetService extends AbstractTbEntityService implements Tb
autoCommit(user, savedAsset.getId());
notificationEntityService.notifyCreateOrUpdateEntity(tenantId, savedAsset.getId(), savedAsset,
asset.getCustomerId(), actionType, user);
tbClusterService.broadcastEntityStateChangeEvent(tenantId, savedAsset.getId(),
asset.getId() == null ? ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED);
return savedAsset;
} catch (Exception e) {
notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.ASSET), asset, actionType, user, e);
@ -79,7 +82,7 @@ public class DefaultTbAssetService extends AbstractTbEntityService implements Tb
assetService.deleteAsset(tenantId, assetId);
notificationEntityService.notifyDeleteEntity(tenantId, assetId, asset, asset.getCustomerId(),
ActionType.DELETED, relatedEdgeIds, user, assetId.toString());
tbClusterService.broadcastEntityStateChangeEvent(tenantId, assetId, ComponentLifecycleEvent.DELETED);
return removeAlarmsByEntityId(tenantId, assetId);
} catch (Exception e) {
notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.ASSET), ActionType.DELETED, user, e,

9
application/src/main/java/org/thingsboard/server/service/entitiy/device/DefaultTbDeviceService.java

@ -76,17 +76,18 @@ public class DefaultTbDeviceService extends AbstractTbEntityService implements T
@Override
public Device saveDeviceWithCredentials(Device device, DeviceCredentials credentials, User user) throws ThingsboardException {
ActionType actionType = device.getId() == null ? ActionType.ADDED : ActionType.UPDATED;
boolean isCreate = device.getId() == null;
ActionType actionType = isCreate ? ActionType.ADDED : ActionType.UPDATED;
TenantId tenantId = device.getTenantId();
try {
Device oldDevice = isCreate ? null : deviceService.findDeviceById(tenantId, device.getId());
Device savedDevice = checkNotNull(deviceService.saveDeviceWithCredentials(device, credentials));
notificationEntityService.notifyCreateOrUpdateDevice(tenantId, savedDevice.getId(), savedDevice.getCustomerId(),
savedDevice, device, actionType, user);
savedDevice, oldDevice, actionType, user);
return savedDevice;
} catch (Exception e) {
notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.DEVICE), device,
actionType, user, e);
notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.DEVICE), device, actionType, user, e);
throw e;
}
}

3
application/src/main/java/org/thingsboard/server/service/entitiy/edge/DefaultTbEdgeService.java

@ -48,6 +48,9 @@ public class DefaultTbEdgeService extends AbstractTbEntityService implements TbE
ActionType actionType = edge.getId() == null ? ActionType.ADDED : ActionType.UPDATED;
TenantId tenantId = edge.getTenantId();
try {
if (actionType == ActionType.ADDED && edge.getRootRuleChainId() == null) {
edge.setRootRuleChainId(edgeTemplateRootRuleChain.getId());
}
Edge savedEdge = checkNotNull(edgeService.saveEdge(edge));
EdgeId edgeId = savedEdge.getId();

15
application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java

@ -82,6 +82,7 @@ import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileCon
import org.thingsboard.server.common.data.tenant.profile.TenantProfileData;
import org.thingsboard.server.common.data.tenant.profile.TenantProfileQueueConfiguration;
import org.thingsboard.server.common.data.widget.WidgetsBundle;
import org.thingsboard.server.service.security.auth.jwt.settings.JwtSettingsService;
import org.thingsboard.server.dao.attributes.AttributesService;
import org.thingsboard.server.dao.customer.CustomerService;
import org.thingsboard.server.dao.device.DeviceCredentialsService;
@ -167,6 +168,9 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
@Autowired
private QueueService queueService;
@Autowired
private JwtSettingsService jwtSettingsService;
@Bean
protected BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
@ -263,6 +267,16 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
adminSettingsService.saveAdminSettings(TenantId.SYS_TENANT_ID, mailSettings);
}
@Override
public void createRandomJwtSettings() throws Exception {
jwtSettingsService.createRandomJwtSettings();
}
@Override
public void saveLegacyYmlSettings() throws Exception {
jwtSettingsService.saveLegacyYmlSettings();
}
@Override
public void createOAuth2Templates() throws Exception {
installScripts.createOAuth2Templates();
@ -656,4 +670,5 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
queueService.saveQueue(sequentialByOriginatorQueue);
}
}
}

4
application/src/main/java/org/thingsboard/server/service/install/SystemDataLoaderService.java

@ -23,6 +23,10 @@ public interface SystemDataLoaderService {
void createAdminSettings() throws Exception;
void createRandomJwtSettings() throws Exception;
void saveLegacyYmlSettings() throws Exception;
void createOAuth2Templates() throws Exception;
void loadSystemWidgets() throws Exception;

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

@ -78,6 +78,10 @@ public class DefaultCacheCleanupService implements CacheCleanupService {
clearCacheByName("assets");
clearCacheByName("repositorySettings");
break;
case "3.4.2":
log.info("Clearing cache to upgrade from version 3.4.2 to 3.4.3 ...");
clearCacheByName("repositorySettings");
break;
default:
//Do nothing, since cache cleanup is optional.
}

1
application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java

@ -186,6 +186,7 @@ public class DefaultDataUpdateService implements DataUpdateService {
break;
case "3.4.1":
log.info("Updating data from version 3.4.1 to 3.4.2 ...");
systemDataLoaderService.saveLegacyYmlSettings();
boolean skipAuditLogsMigration = getEnv("TB_SKIP_AUDIT_LOGS_MIGRATION", false);
if (!skipAuditLogsMigration) {
log.info("Starting audit logs migration. Can be skipped with TB_SKIP_AUDIT_LOGS_MIGRATION env variable set to true");

9
application/src/main/java/org/thingsboard/server/service/ota/DefaultOtaPackageStateService.java

@ -181,10 +181,11 @@ public class DefaultOtaPackageStateService implements OtaPackageStateService {
private void update(TenantId tenantId, DeviceProfile deviceProfile, OtaPackageType otaPackageType) {
Consumer<Device> updateConsumer;
OtaPackageId packageId = OtaPackageUtil.getOtaPackageId(deviceProfile, otaPackageType);
if (deviceProfile.getFirmwareId() != null) {
if (packageId != null) {
long ts = System.currentTimeMillis();
updateConsumer = d -> send(d.getTenantId(), d.getId(), deviceProfile.getFirmwareId(), ts, otaPackageType);
updateConsumer = d -> send(d.getTenantId(), d.getId(), packageId, ts, otaPackageType);
} else {
updateConsumer = d -> remove(d, otaPackageType);
}
@ -360,9 +361,7 @@ public class DefaultOtaPackageStateService implements OtaPackageStateService {
@Override
public void onSuccess(@Nullable Void tmp) {
log.trace("[{}] Success remove target {} attributes!", device.getId(), otaPackageType);
Set<AttributeKey> keysToNotify = new HashSet<>();
attributesKeys.forEach(key -> keysToNotify.add(new AttributeKey(DataConstants.SHARED_SCOPE, key)));
tbClusterService.pushMsgToCore(DeviceAttributesEventNotificationMsg.onDelete(device.getTenantId(), device.getId(), keysToNotify), null);
tbClusterService.pushMsgToCore(DeviceAttributesEventNotificationMsg.onDelete(device.getTenantId(), device.getId(), DataConstants.SHARED_SCOPE, attributesKeys), null);
}
@Override

38
application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java

@ -88,10 +88,8 @@ public class DefaultTbClusterService implements TbClusterService {
@Value("${cluster.stats.enabled:false}")
private boolean statsEnabled;
@Value("${edges.enabled}")
@Value("${edges.enabled:true}")
protected boolean edgesEnabled;
@Value("${service.type:monolith}")
private String serviceType;
private final AtomicInteger toCoreMsgs = new AtomicInteger(0);
private final AtomicInteger toCoreNfs = new AtomicInteger(0);
@ -584,25 +582,27 @@ public class DefaultTbClusterService implements TbClusterService {
}
private void doSendQueueNotifications(ToRuleEngineNotificationMsg ruleEngineMsg, ToCoreNotificationMsg coreMsg, ToTransportMsg transportMsg) {
Set<TransportProtos.ServiceInfo> tbRuleEngineServices = partitionService.getAllServices(ServiceType.TB_RULE_ENGINE);
for (TransportProtos.ServiceInfo ruleEngineService : tbRuleEngineServices) {
TopicPartitionInfo tpi = notificationsTopicService.getNotificationsTopic(ServiceType.TB_RULE_ENGINE, ruleEngineService.getServiceId());
Set<String> tbRuleEngineServices = partitionService.getAllServiceIds(ServiceType.TB_RULE_ENGINE);
Set<String> tbCoreServices = partitionService.getAllServiceIds(ServiceType.TB_CORE);
Set<String> tbTransportServices = partitionService.getAllServiceIds(ServiceType.TB_TRANSPORT);
// No need to push notifications twice
tbTransportServices.removeAll(tbCoreServices);
tbCoreServices.removeAll(tbRuleEngineServices);
for (String ruleEngineServiceId : tbRuleEngineServices) {
TopicPartitionInfo tpi = notificationsTopicService.getNotificationsTopic(ServiceType.TB_RULE_ENGINE, ruleEngineServiceId);
producerProvider.getRuleEngineNotificationsMsgProducer().send(tpi, new TbProtoQueueMsg<>(UUID.randomUUID(), ruleEngineMsg), null);
toRuleEngineNfs.incrementAndGet();
}
if (!serviceType.equals("monolith")) {
Set<TransportProtos.ServiceInfo> tbCoreServices = partitionService.getAllServices(ServiceType.TB_CORE);
for (TransportProtos.ServiceInfo coreService : tbCoreServices) {
TopicPartitionInfo tpi = notificationsTopicService.getNotificationsTopic(ServiceType.TB_CORE, coreService.getServiceId());
producerProvider.getTbCoreNotificationsMsgProducer().send(tpi, new TbProtoQueueMsg<>(UUID.randomUUID(), coreMsg), null);
toCoreNfs.incrementAndGet();
}
Set<TransportProtos.ServiceInfo> tbTransportServices = partitionService.getAllServices(ServiceType.TB_TRANSPORT);
for (TransportProtos.ServiceInfo transportService : tbTransportServices) {
TopicPartitionInfo tpi = notificationsTopicService.getNotificationsTopic(ServiceType.TB_TRANSPORT, transportService.getServiceId());
producerProvider.getTransportNotificationsMsgProducer().send(tpi, new TbProtoQueueMsg<>(UUID.randomUUID(), transportMsg), null);
toTransportNfs.incrementAndGet();
}
for (String coreServiceId : tbCoreServices) {
TopicPartitionInfo tpi = notificationsTopicService.getNotificationsTopic(ServiceType.TB_CORE, coreServiceId);
producerProvider.getTbCoreNotificationsMsgProducer().send(tpi, new TbProtoQueueMsg<>(UUID.randomUUID(), coreMsg), null);
toCoreNfs.incrementAndGet();
}
for (String transportServiceId : tbTransportServices) {
TopicPartitionInfo tpi = notificationsTopicService.getNotificationsTopic(ServiceType.TB_TRANSPORT, transportServiceId);
producerProvider.getTransportNotificationsMsgProducer().send(tpi, new TbProtoQueueMsg<>(UUID.randomUUID(), transportMsg), null);
toTransportNfs.incrementAndGet();
}
}
}

8
application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java

@ -35,6 +35,7 @@ import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.common.msg.queue.TbCallback;
import org.thingsboard.server.common.msg.rpc.FromDeviceRpcResponse;
import org.thingsboard.server.common.stats.StatsFactory;
import org.thingsboard.server.service.security.auth.jwt.settings.JwtSettingsService;
import org.thingsboard.server.queue.util.DataDecodingEncodingService;
import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import org.thingsboard.server.gen.transport.TransportProtos;
@ -143,8 +144,9 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore
EdgeNotificationService edgeNotificationService,
OtaPackageStateService firmwareStateService,
GitVersionControlQueueService vcQueueService,
PartitionService partitionService) {
super(actorContext, encodingService, tenantProfileCache, deviceProfileCache, assetProfileCache, apiUsageStateService, partitionService, tbCoreQueueFactory.createToCoreNotificationsMsgConsumer());
PartitionService partitionService,
Optional<JwtSettingsService> jwtSettingsService) {
super(actorContext, encodingService, tenantProfileCache, deviceProfileCache, assetProfileCache, apiUsageStateService, partitionService, tbCoreQueueFactory.createToCoreNotificationsMsgConsumer(), jwtSettingsService);
this.mainConsumer = tbCoreQueueFactory.createToCoreMsgConsumer();
this.usageStatsConsumer = tbCoreQueueFactory.createToUsageStatsServiceMsgConsumer();
this.firmwareStatesConsumer = tbCoreQueueFactory.createToOtaPackageStateServiceMsgConsumer();
@ -488,7 +490,7 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore
subscriptionManagerService.onAttributesDelete(
TenantId.fromUUID(new UUID(proto.getTenantIdMSB(), proto.getTenantIdLSB())),
TbSubscriptionUtils.toEntityId(proto.getEntityType(), proto.getEntityIdMSB(), proto.getEntityIdLSB()),
proto.getScope(), proto.getKeysList(), callback);
proto.getScope(), proto.getKeysList(), proto.getNotifyDevice(), callback);
} else if (msg.hasTsDelete()) {
TbTimeSeriesDeleteProto proto = msg.getTsDelete();
subscriptionManagerService.onTimeSeriesDelete(

3
application/src/main/java/org/thingsboard/server/service/queue/DefaultTbRuleEngineConsumerService.java

@ -70,6 +70,7 @@ import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
@ -126,7 +127,7 @@ public class DefaultTbRuleEngineConsumerService extends AbstractConsumerService<
TbTenantProfileCache tenantProfileCache,
TbApiUsageStateService apiUsageStateService,
PartitionService partitionService, TbServiceInfoProvider serviceInfoProvider, QueueService queueService) {
super(actorContext, encodingService, tenantProfileCache, deviceProfileCache, assetProfileCache, apiUsageStateService, partitionService, tbRuleEngineQueueFactory.createToRuleEngineNotificationsMsgConsumer());
super(actorContext, encodingService, tenantProfileCache, deviceProfileCache, assetProfileCache, apiUsageStateService, partitionService, tbRuleEngineQueueFactory.createToRuleEngineNotificationsMsgConsumer(), Optional.empty());
this.statisticsService = statisticsService;
this.tbRuleEngineQueueFactory = tbRuleEngineQueueFactory;
this.submitStrategyFactory = submitStrategyFactory;

23
application/src/main/java/org/thingsboard/server/service/queue/processing/AbstractConsumerService.java

@ -33,6 +33,7 @@ import org.thingsboard.server.common.msg.TbActorMsg;
import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg;
import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.common.msg.queue.TbCallback;
import org.thingsboard.server.service.security.auth.jwt.settings.JwtSettingsService;
import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import org.thingsboard.server.queue.TbQueueConsumer;
import org.thingsboard.server.queue.common.TbProtoQueueMsg;
@ -76,11 +77,13 @@ public abstract class AbstractConsumerService<N extends com.google.protobuf.Gene
protected final PartitionService partitionService;
protected final TbQueueConsumer<TbProtoQueueMsg<N>> nfConsumer;
protected final Optional<JwtSettingsService> jwtSettingsService;
public AbstractConsumerService(ActorSystemContext actorContext, DataDecodingEncodingService encodingService,
TbTenantProfileCache tenantProfileCache, TbDeviceProfileCache deviceProfileCache,
TbAssetProfileCache assetProfileCache, TbApiUsageStateService apiUsageStateService,
PartitionService partitionService, TbQueueConsumer<TbProtoQueueMsg<N>> nfConsumer) {
PartitionService partitionService, TbQueueConsumer<TbProtoQueueMsg<N>> nfConsumer, Optional<JwtSettingsService> jwtSettingsService) {
this.actorContext = actorContext;
this.encodingService = encodingService;
this.tenantProfileCache = tenantProfileCache;
@ -89,6 +92,7 @@ public abstract class AbstractConsumerService<N extends com.google.protobuf.Gene
this.apiUsageStateService = apiUsageStateService;
this.partitionService = partitionService;
this.nfConsumer = nfConsumer;
this.jwtSettingsService = jwtSettingsService;
}
public void init(String mainConsumerThreadName, String nfConsumerThreadName) {
@ -172,12 +176,17 @@ public abstract class AbstractConsumerService<N extends com.google.protobuf.Gene
apiUsageStateService.onTenantProfileUpdate(tenantProfileId);
}
} else if (EntityType.TENANT.equals(componentLifecycleMsg.getEntityId().getEntityType())) {
tenantProfileCache.evict(componentLifecycleMsg.getTenantId());
partitionService.removeTenant(componentLifecycleMsg.getTenantId());
if (componentLifecycleMsg.getEvent().equals(ComponentLifecycleEvent.UPDATED)) {
apiUsageStateService.onTenantUpdate(componentLifecycleMsg.getTenantId());
} else if (componentLifecycleMsg.getEvent().equals(ComponentLifecycleEvent.DELETED)) {
apiUsageStateService.onTenantDelete((TenantId) componentLifecycleMsg.getEntityId());
if (TenantId.SYS_TENANT_ID.equals(componentLifecycleMsg.getTenantId())) {
jwtSettingsService.ifPresent(JwtSettingsService::reloadJwtSettings);
return;
} else {
tenantProfileCache.evict(componentLifecycleMsg.getTenantId());
partitionService.removeTenant(componentLifecycleMsg.getTenantId());
if (componentLifecycleMsg.getEvent().equals(ComponentLifecycleEvent.UPDATED)) {
apiUsageStateService.onTenantUpdate(componentLifecycleMsg.getTenantId());
} else if (componentLifecycleMsg.getEvent().equals(ComponentLifecycleEvent.DELETED)) {
apiUsageStateService.onTenantDelete((TenantId) componentLifecycleMsg.getEntityId());
}
}
} else if (EntityType.DEVICE_PROFILE.equals(componentLifecycleMsg.getEntityId().getEntityType())) {
deviceProfileCache.evict(componentLifecycleMsg.getTenantId(), new DeviceProfileId(componentLifecycleMsg.getEntityId().getId()));

8
application/src/main/java/org/thingsboard/server/service/script/RuleNodeMvelScriptEngine.java → application/src/main/java/org/thingsboard/server/service/script/RuleNodeTbelScriptEngine.java

@ -23,7 +23,7 @@ import com.google.common.util.concurrent.MoreExecutors;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.script.api.RuleNodeScriptFactory;
import org.thingsboard.script.api.mvel.MvelInvokeService;
import org.thingsboard.script.api.tbel.TbelInvokeService;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.msg.TbMsg;
@ -42,9 +42,9 @@ import java.util.stream.Collectors;
@Slf4j
public class RuleNodeMvelScriptEngine extends RuleNodeScriptEngine<MvelInvokeService, Object> {
public class RuleNodeTbelScriptEngine extends RuleNodeScriptEngine<TbelInvokeService, Object> {
public RuleNodeMvelScriptEngine(TenantId tenantId, MvelInvokeService scriptInvokeService, String script, String... argNames) {
public RuleNodeTbelScriptEngine(TenantId tenantId, TbelInvokeService scriptInvokeService, String script, String... argNames) {
super(tenantId, scriptInvokeService, script, argNames);
}
@ -64,7 +64,7 @@ public class RuleNodeMvelScriptEngine extends RuleNodeScriptEngine<MvelInvokeSer
List<TbMsg> res = new ArrayList<>();
for (Object resObject : (Collection) result) {
if (resObject instanceof Map) {
res.add(unbindMsg((Map) result, msg));
res.add(unbindMsg((Map) resObject, msg));
} else {
return wrongResultType(resObject);
}

162
application/src/main/java/org/thingsboard/server/service/security/auth/jwt/settings/DefaultJwtSettingsService.java

@ -0,0 +1,162 @@
/**
* Copyright © 2016-2022 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.jwt.settings;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RandomStringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.common.data.AdminSettings;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
import org.thingsboard.server.common.data.security.model.JwtSettings;
import org.thingsboard.server.dao.settings.AdminSettingsService;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.Objects;
import java.util.Optional;
@Service
@RequiredArgsConstructor
@Slf4j
public class DefaultJwtSettingsService implements JwtSettingsService {
@Lazy
private final AdminSettingsService adminSettingsService;
@Lazy
private final Optional<TbClusterService> tbClusterService;
private final JwtSettingsValidator jwtSettingsValidator;
@Value("${security.jwt.tokenExpirationTime:9000}")
private Integer tokenExpirationTime;
@Value("${security.jwt.refreshTokenExpTime:604800}")
private Integer refreshTokenExpTime;
@Value("${security.jwt.tokenIssuer:thingsboard.io}")
private String tokenIssuer;
@Value("${security.jwt.tokenSigningKey:thingsboardDefaultSigningKey}")
private String tokenSigningKey;
private volatile JwtSettings jwtSettings = null; //lazy init
/**
* Create JWT admin settings is intended to be called from Install scripts only
*/
@Override
public void createRandomJwtSettings() {
if (getJwtSettingsFromDb() == null) {
log.info("Creating JWT admin settings...");
this.jwtSettings = getJwtSettingsFromYml();
if (isSigningKeyDefault(jwtSettings)) {
this.jwtSettings.setTokenSigningKey(Base64.getEncoder().encodeToString(
RandomStringUtils.randomAlphanumeric(64).getBytes(StandardCharsets.UTF_8)));
}
saveJwtSettings(jwtSettings);
} else {
log.info("Skip creating JWT admin settings because they already exist.");
}
}
/**
* Create JWT admin settings is intended to be called from Upgrade scripts only
*/
@Override
public void saveLegacyYmlSettings() {
log.info("Saving legacy JWT admin settings from YML...");
if (getJwtSettingsFromDb() == null) {
saveJwtSettings(getJwtSettingsFromYml());
}
}
@Override
public JwtSettings saveJwtSettings(JwtSettings jwtSettings) {
jwtSettingsValidator.validate(jwtSettings);
final AdminSettings adminJwtSettings = mapJwtToAdminSettings(jwtSettings);
final AdminSettings existedSettings = adminSettingsService.findAdminSettingsByKey(TenantId.SYS_TENANT_ID, ADMIN_SETTINGS_JWT_KEY);
if (existedSettings != null) {
adminJwtSettings.setId(existedSettings.getId());
}
log.info("Saving new JWT admin settings. From this moment, the JWT parameters from YAML and ENV will be ignored");
adminSettingsService.saveAdminSettings(TenantId.SYS_TENANT_ID, adminJwtSettings);
tbClusterService.ifPresent(cs -> cs.broadcastEntityStateChangeEvent(TenantId.SYS_TENANT_ID, TenantId.SYS_TENANT_ID, ComponentLifecycleEvent.UPDATED));
return reloadJwtSettings();
}
@Override
public JwtSettings reloadJwtSettings() {
return getJwtSettings(true);
}
@Override
public JwtSettings getJwtSettings() {
return getJwtSettings(false);
}
public JwtSettings getJwtSettings(boolean forceReload) {
if (this.jwtSettings == null || forceReload) {
synchronized (this) {
if (this.jwtSettings == null || forceReload) {
JwtSettings result = getJwtSettingsFromDb();
if (result == null) {
result = getJwtSettingsFromYml();
log.warn("Loading the JWT settings from YML since there are no settings in DB. Looks like the upgrade script was not applied.");
}
if (isSigningKeyDefault(result)) {
log.warn("WARNING: The platform is configured to use default JWT Signing Key. " +
"This is a security issue that needs to be resolved. Please change the JWT Signing Key using the Web UI. " +
"Navigate to \"System settings -> Security settings\" while logged in as a System Administrator.");
}
this.jwtSettings = result;
}
}
}
return this.jwtSettings;
}
private JwtSettings getJwtSettingsFromYml() {
return new JwtSettings(this.tokenExpirationTime, this.refreshTokenExpTime, this.tokenIssuer, this.tokenSigningKey);
}
private JwtSettings getJwtSettingsFromDb() {
AdminSettings adminJwtSettings = adminSettingsService.findAdminSettingsByKey(TenantId.SYS_TENANT_ID, ADMIN_SETTINGS_JWT_KEY);
return adminJwtSettings != null ? mapAdminToJwtSettings(adminJwtSettings) : null;
}
private JwtSettings mapAdminToJwtSettings(AdminSettings adminSettings) {
Objects.requireNonNull(adminSettings, "adminSettings for JWT is null");
return JacksonUtil.treeToValue(adminSettings.getJsonValue(), JwtSettings.class);
}
private AdminSettings mapJwtToAdminSettings(JwtSettings jwtSettings) {
Objects.requireNonNull(jwtSettings, "jwtSettings is null");
AdminSettings adminJwtSettings = new AdminSettings();
adminJwtSettings.setTenantId(TenantId.SYS_TENANT_ID);
adminJwtSettings.setKey(ADMIN_SETTINGS_JWT_KEY);
adminJwtSettings.setJsonValue(JacksonUtil.valueToTree(jwtSettings));
return adminJwtSettings;
}
private boolean isSigningKeyDefault(JwtSettings settings) {
return TOKEN_SIGNING_KEY_DEFAULT.equals(settings.getTokenSigningKey());
}
}

69
application/src/main/java/org/thingsboard/server/service/security/auth/jwt/settings/DefaultJwtSettingsValidator.java

@ -0,0 +1,69 @@
/**
* Copyright © 2016-2022 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.jwt.settings;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.RandomUtils;
import org.apache.commons.lang3.StringUtils;
import org.bouncycastle.util.Arrays;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.security.model.JwtSettings;
import org.thingsboard.server.dao.exception.DataValidationException;
import java.util.Base64;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
@Component
@RequiredArgsConstructor
public class DefaultJwtSettingsValidator implements JwtSettingsValidator {
@Override
public void validate(JwtSettings jwtSettings) {
if (StringUtils.isEmpty(jwtSettings.getTokenIssuer())) {
throw new DataValidationException("JWT token issuer should be specified!");
}
if (Optional.ofNullable(jwtSettings.getRefreshTokenExpTime()).orElse(0) <= TimeUnit.MINUTES.toSeconds(15)) {
throw new DataValidationException("JWT refresh token expiration time should be at least 15 minutes!");
}
if (Optional.ofNullable(jwtSettings.getTokenExpirationTime()).orElse(0) <= TimeUnit.MINUTES.toSeconds(1)) {
throw new DataValidationException("JWT token expiration time should be at least 1 minute!");
}
if (jwtSettings.getTokenExpirationTime() >= jwtSettings.getRefreshTokenExpTime()) {
throw new DataValidationException("JWT token expiration time should greater than JWT refresh token expiration time!");
}
if (StringUtils.isEmpty(jwtSettings.getTokenSigningKey())) {
throw new DataValidationException("JWT token signing key should be specified!");
}
byte[] decodedKey;
try {
decodedKey = Base64.getDecoder().decode(jwtSettings.getTokenSigningKey());
} catch (Exception e) {
throw new DataValidationException("JWT token signing key should be a valid Base64 encoded string! " + e.getMessage());
}
if (Arrays.isNullOrEmpty(decodedKey)) {
throw new DataValidationException("JWT token signing key should be non-empty after Base64 decoding!");
}
if (decodedKey.length * Byte.SIZE < 256 && !JwtSettingsService.TOKEN_SIGNING_KEY_DEFAULT.equals(jwtSettings.getTokenSigningKey())) {
throw new DataValidationException("JWT token signing key should be a Base64 encoded string representing at least 256 bits of data!");
}
System.arraycopy(decodedKey, 0, RandomUtils.nextBytes(decodedKey.length), 0, decodedKey.length); //secure memory
}
}

39
application/src/main/java/org/thingsboard/server/service/security/auth/jwt/settings/InstallJwtSettingsValidator.java

@ -0,0 +1,39 @@
/**
* Copyright © 2016-2022 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.jwt.settings;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Primary;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.security.model.JwtSettings;
/**
* During Install or upgrade the validation is suppressed to keep existing data
* */
@Primary
@Profile("install")
@Component
@RequiredArgsConstructor
public class InstallJwtSettingsValidator implements JwtSettingsValidator {
@Override
public void validate(JwtSettings jwtSettings) {
}
}

35
application/src/main/java/org/thingsboard/server/service/security/auth/jwt/settings/JwtSettingsService.java

@ -0,0 +1,35 @@
/**
* Copyright © 2016-2022 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.jwt.settings;
import org.thingsboard.server.common.data.security.model.JwtSettings;
public interface JwtSettingsService {
String ADMIN_SETTINGS_JWT_KEY = "jwt";
String TOKEN_SIGNING_KEY_DEFAULT = "thingsboardDefaultSigningKey";
JwtSettings getJwtSettings();
JwtSettings reloadJwtSettings();
void createRandomJwtSettings();
void saveLegacyYmlSettings();
JwtSettings saveJwtSettings(JwtSettings jwtSettings);
}

23
application/src/main/java/org/thingsboard/server/service/security/auth/jwt/settings/JwtSettingsValidator.java

@ -0,0 +1,23 @@
/**
* Copyright © 2016-2022 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.jwt.settings;
import org.thingsboard.server.common.data.security.model.JwtSettings;
public interface JwtSettingsValidator {
void validate(JwtSettings jwtSettings);
}

17
application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java

@ -32,7 +32,7 @@ import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.oauth2.OAuth2Registration;
import org.thingsboard.server.dao.oauth2.OAuth2Service;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.security.model.JwtTokenPair;
import org.thingsboard.server.common.data.security.model.JwtPair;
import org.thingsboard.server.service.security.auth.rest.RestAuthenticationDetails;
import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.service.security.model.token.JwtTokenFactory;
@ -104,10 +104,10 @@ public class Oauth2AuthenticationSuccessHandler extends SimpleUrlAuthenticationS
SecurityUser securityUser = mapper.getOrCreateUserByClientPrincipal(request, token, oAuth2AuthorizedClient.getAccessToken().getTokenValue(),
registration);
JwtTokenPair tokenPair = tokenFactory.createTokenPair(securityUser);
clearAuthenticationAttributes(request, response);
getRedirectStrategy().sendRedirect(request, response, baseUrl + "/?accessToken=" + tokenPair.getToken() + "&refreshToken=" + tokenPair.getRefreshToken());
JwtPair tokenPair = tokenFactory.createTokenPair(securityUser);
getRedirectStrategy().sendRedirect(request, response, getRedirectUrl(baseUrl, tokenPair));
systemSecurityService.logLoginAction(securityUser, new RestAuthenticationDetails(request), ActionType.LOGIN, registration.getName(), null);
} catch (Exception e) {
log.debug("Error occurred during processing authentication success result. " +
@ -128,4 +128,13 @@ public class Oauth2AuthenticationSuccessHandler extends SimpleUrlAuthenticationS
super.clearAuthenticationAttributes(request);
httpCookieOAuth2AuthorizationRequestRepository.removeAuthorizationRequestCookies(request, response);
}
String getRedirectUrl(String baseUrl, JwtPair tokenPair) {
if (baseUrl.indexOf("?") > 0) {
baseUrl += "&";
} else {
baseUrl += "/?";
}
return baseUrl + "accessToken=" + tokenPair.getToken() + "&refreshToken=" + tokenPair.getRefreshToken();
}
}

4
application/src/main/java/org/thingsboard/server/service/security/auth/rest/RestAwareAuthenticationSuccessHandler.java

@ -26,7 +26,7 @@ import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.service.security.auth.MfaAuthenticationToken;
import org.thingsboard.server.service.security.auth.mfa.config.TwoFaConfigManager;
import org.thingsboard.server.service.security.model.JwtTokenPair;
import org.thingsboard.server.common.data.security.model.JwtPair;
import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.service.security.model.token.JwtTokenFactory;
@ -49,7 +49,7 @@ public class RestAwareAuthenticationSuccessHandler implements AuthenticationSucc
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
SecurityUser securityUser = (SecurityUser) authentication.getPrincipal();
JwtTokenPair tokenPair = new JwtTokenPair();
JwtPair tokenPair = new JwtPair();
if (authentication instanceof MfaAuthenticationToken) {
int preVerificationTokenLifetime = twoFaConfigManager.getPlatformTwoFaSettings(securityUser.getTenantId(), true)

32
application/src/main/java/org/thingsboard/server/service/security/model/token/JwtTokenFactory.java

@ -24,8 +24,8 @@ import io.jsonwebtoken.MalformedJwtException;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.SignatureException;
import io.jsonwebtoken.UnsupportedJwtException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.stereotype.Component;
@ -35,9 +35,9 @@ import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.common.data.security.model.JwtToken;
import org.thingsboard.server.config.JwtSettings;
import org.thingsboard.server.service.security.auth.jwt.settings.JwtSettingsService;
import org.thingsboard.server.service.security.exception.JwtExpiredTokenException;
import org.thingsboard.server.service.security.model.JwtTokenPair;
import org.thingsboard.server.common.data.security.model.JwtPair;
import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.service.security.model.UserPrincipal;
@ -49,6 +49,7 @@ import java.util.UUID;
import java.util.stream.Collectors;
@Component
@RequiredArgsConstructor
@Slf4j
public class JwtTokenFactory {
@ -62,12 +63,7 @@ public class JwtTokenFactory {
private static final String CUSTOMER_ID = "customerId";
private static final String SESSION_ID = "sessionId";
private final JwtSettings settings;
@Autowired
public JwtTokenFactory(JwtSettings settings) {
this.settings = settings;
}
private final JwtSettingsService jwtSettingsService;
/**
* Factory method for issuing new JWT Tokens.
@ -80,7 +76,7 @@ public class JwtTokenFactory {
UserPrincipal principal = securityUser.getUserPrincipal();
JwtBuilder jwtBuilder = setUpToken(securityUser, securityUser.getAuthorities().stream()
.map(GrantedAuthority::getAuthority).collect(Collectors.toList()), settings.getTokenExpirationTime());
.map(GrantedAuthority::getAuthority).collect(Collectors.toList()), jwtSettingsService.getJwtSettings().getTokenExpirationTime());
jwtBuilder.claim(FIRST_NAME, securityUser.getFirstName())
.claim(LAST_NAME, securityUser.getLastName())
.claim(ENABLED, securityUser.isEnabled())
@ -142,7 +138,7 @@ public class JwtTokenFactory {
public JwtToken createRefreshToken(SecurityUser securityUser) {
UserPrincipal principal = securityUser.getUserPrincipal();
String token = setUpToken(securityUser, Collections.singletonList(Authority.REFRESH_TOKEN.name()), settings.getRefreshTokenExpTime())
String token = setUpToken(securityUser, Collections.singletonList(Authority.REFRESH_TOKEN.name()), jwtSettingsService.getJwtSettings().getRefreshTokenExpTime())
.claim(IS_PUBLIC, principal.getType() == UserPrincipal.Type.PUBLIC_ID)
.setId(UUID.randomUUID().toString()).compact();
@ -198,30 +194,30 @@ public class JwtTokenFactory {
return Jwts.builder()
.setClaims(claims)
.setIssuer(settings.getTokenIssuer())
.setIssuer(jwtSettingsService.getJwtSettings().getTokenIssuer())
.setIssuedAt(Date.from(currentTime.toInstant()))
.setExpiration(Date.from(currentTime.plusSeconds(expirationTime).toInstant()))
.signWith(SignatureAlgorithm.HS512, settings.getTokenSigningKey());
.signWith(SignatureAlgorithm.HS512, jwtSettingsService.getJwtSettings().getTokenSigningKey());
}
public Jws<Claims> parseTokenClaims(JwtToken token) {
try {
return Jwts.parser()
.setSigningKey(settings.getTokenSigningKey())
.setSigningKey(jwtSettingsService.getJwtSettings().getTokenSigningKey())
.parseClaimsJws(token.getToken());
} catch (UnsupportedJwtException | MalformedJwtException | IllegalArgumentException | SignatureException ex) {
} catch (UnsupportedJwtException | MalformedJwtException | IllegalArgumentException ex) {
log.debug("Invalid JWT Token", ex);
throw new BadCredentialsException("Invalid JWT token: ", ex);
} catch (ExpiredJwtException expiredEx) {
} catch (SignatureException | ExpiredJwtException expiredEx) {
log.debug("JWT Token is expired", expiredEx);
throw new JwtExpiredTokenException(token, "JWT Token expired", expiredEx);
}
}
public JwtTokenPair createTokenPair(SecurityUser securityUser) {
public JwtPair createTokenPair(SecurityUser securityUser) {
JwtToken accessToken = createAccessJwtToken(securityUser);
JwtToken refreshToken = createRefreshToken(securityUser);
return new JwtTokenPair(accessToken.getToken(), refreshToken.getToken());
return new JwtPair(accessToken.getToken(), refreshToken.getToken());
}
}

2
application/src/main/java/org/thingsboard/server/service/session/DefaultDeviceSessionCacheService.java

@ -52,7 +52,7 @@ public class DefaultDeviceSessionCacheService implements DeviceSessionCacheServi
@Override
public DeviceSessionsCacheEntry put(DeviceId deviceId, DeviceSessionsCacheEntry sessions) {
log.debug("[{}] Pushing session data to cache: {}", deviceId, sessions);
cache.putIfAbsent(deviceId, sessions);
cache.put(deviceId, sessions);
return sessions;
}
}

10
application/src/main/java/org/thingsboard/server/service/subscription/DefaultSubscriptionManagerService.java

@ -326,7 +326,7 @@ public class DefaultSubscriptionManagerService extends TbApplicationEventListene
}
@Override
public void onAttributesDelete(TenantId tenantId, EntityId entityId, String scope, List<String> keys, TbCallback callback) {
public void onAttributesDelete(TenantId tenantId, EntityId entityId, String scope, List<String> keys, boolean notifyDevice, TbCallback callback) {
onLocalTelemetrySubUpdate(entityId,
s -> {
if (TbSubscriptionType.ATTRIBUTES.equals(s.getType())) {
@ -349,7 +349,13 @@ public class DefaultSubscriptionManagerService extends TbApplicationEventListene
return subscriptionUpdate;
}, false);
if (entityId.getEntityType() == EntityType.DEVICE) {
deleteDeviceInactivityTimeout(tenantId, entityId, keys);
if (TbAttributeSubscriptionScope.SERVER_SCOPE.name().equalsIgnoreCase(scope)
|| TbAttributeSubscriptionScope.ANY_SCOPE.name().equalsIgnoreCase(scope)) {
deleteDeviceInactivityTimeout(tenantId, entityId, keys);
} else if (TbAttributeSubscriptionScope.SHARED_SCOPE.name().equalsIgnoreCase(scope) && notifyDevice) {
clusterService.pushMsgToCore(DeviceAttributesEventNotificationMsg.onDelete(tenantId,
new DeviceId(entityId.getId()), scope, keys), null);
}
}
callback.onSuccess();
}

10
application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbEntityDataSubscriptionService.java

@ -21,6 +21,7 @@ import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ArrayUtils;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
@ -562,8 +563,13 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc
if (queryResults != null) {
for (ReadTsKvQueryResult queryResult : queryResults) {
String queryKey = queriesKeys.get(queryResult.getQueryId());
entityData.getTimeseries().put(queryKey, queryResult.toTsValues());
lastTsMap.put(queryKey, queryResult.getLastEntryTs());
if (queryKey != null) {
entityData.getTimeseries().merge(queryKey, queryResult.toTsValues(), ArrayUtils::addAll);
lastTsMap.merge(queryKey, queryResult.getLastEntryTs(), Math::max);
} else {
log.warn("ReadTsKvQueryResult for {} {} has queryId not matching the initial query",
entityData.getEntityId().getEntityType(), entityData.getEntityId());
}
}
}
// Populate with empty values if no data found.

2
application/src/main/java/org/thingsboard/server/service/subscription/SubscriptionManagerService.java

@ -38,7 +38,7 @@ public interface SubscriptionManagerService extends ApplicationListener<Partitio
void onAttributesUpdate(TenantId tenantId, EntityId entityId, String scope, List<AttributeKvEntry> attributes, boolean notifyDevice, TbCallback callback);
void onAttributesDelete(TenantId tenantId, EntityId entityId, String scope, List<String> keys, TbCallback empty);
void onAttributesDelete(TenantId tenantId, EntityId entityId, String scope, List<String> keys, boolean notifyDevice, TbCallback empty);
void onTimeSeriesDelete(TenantId tenantId, EntityId entityId, List<String> keys, TbCallback callback);

5
application/src/main/java/org/thingsboard/server/service/subscription/TbAbstractDataSubCtx.java

@ -18,6 +18,7 @@ package org.thingsboard.server.service.subscription;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.kv.Aggregation;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.query.AbstractDataQuery;
import org.thingsboard.server.common.data.query.EntityData;
@ -197,6 +198,9 @@ public abstract class TbAbstractDataSubCtx<T extends AbstractDataQuery<? extends
entityData.getTimeseries().forEach((k, v) -> {
long ts = Arrays.stream(v).map(TsValue::getTs).max(Long::compareTo).orElse(0L);
log.trace("[{}][{}] Updating key: {} with ts: {}", serviceId, cmdId, k, ts);
if (!Aggregation.NONE.equals(getCurrentAggregation()) && ts < endTs) {
ts = endTs;
}
keyStates.put(k, ts);
});
}
@ -247,4 +251,5 @@ public abstract class TbAbstractDataSubCtx<T extends AbstractDataQuery<? extends
abstract void sendWsMsg(String sessionId, TelemetrySubscriptionUpdate subscriptionUpdate, EntityKeyType keyType, boolean resultToLatestValues);
protected abstract Aggregation getCurrentAggregation();
}

6
application/src/main/java/org/thingsboard/server/service/subscription/TbAlarmDataSubCtx.java

@ -23,6 +23,7 @@ import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmSearchStatus;
import org.thingsboard.server.common.data.id.AlarmId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.kv.Aggregation;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.query.AlarmData;
import org.thingsboard.server.common.data.query.AlarmDataPageLink;
@ -205,6 +206,11 @@ public class TbAlarmDataSubCtx extends TbAbstractDataSubCtx<AlarmDataQuery> {
}
}
@Override
protected Aggregation getCurrentAggregation() {
return Aggregation.NONE;
}
private void sendWsMsg(String sessionId, AlarmSubscriptionUpdate subscriptionUpdate) {
Alarm alarm = subscriptionUpdate.getAlarm();
AlarmId alarmId = alarm.getId();

6
application/src/main/java/org/thingsboard/server/service/subscription/TbEntityDataSubCtx.java

@ -19,6 +19,7 @@ import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.kv.Aggregation;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.query.EntityData;
import org.thingsboard.server.common.data.query.EntityDataQuery;
@ -86,6 +87,11 @@ public class TbEntityDataSubCtx extends TbAbstractDataSubCtx<EntityDataQuery> {
}
}
@Override
protected Aggregation getCurrentAggregation() {
return (this.curTsCmd == null || this.curTsCmd.getAgg() == null) ? Aggregation.NONE : this.curTsCmd.getAgg();
}
private void sendLatestWsMsg(EntityId entityId, String sessionId, TelemetrySubscriptionUpdate subscriptionUpdate, EntityKeyType keyType) {
Map<String, TsValue> latestUpdate = new HashMap<>();
subscriptionUpdate.getData().forEach((k, v) -> {

3
application/src/main/java/org/thingsboard/server/service/subscription/TbSubscriptionUtils.java

@ -236,7 +236,7 @@ public class TbSubscriptionUtils {
return ToCoreMsg.newBuilder().setToSubscriptionMgrMsg(msgBuilder.build()).build();
}
public static ToCoreMsg toAttributesDeleteProto(TenantId tenantId, EntityId entityId, String scope, List<String> keys) {
public static ToCoreMsg toAttributesDeleteProto(TenantId tenantId, EntityId entityId, String scope, List<String> keys, boolean notifyDevice) {
TbAttributeDeleteProto.Builder builder = TbAttributeDeleteProto.newBuilder();
builder.setEntityType(entityId.getEntityType().name());
builder.setEntityIdMSB(entityId.getId().getMostSignificantBits());
@ -245,6 +245,7 @@ public class TbSubscriptionUtils {
builder.setTenantIdLSB(tenantId.getId().getLeastSignificantBits());
builder.setScope(scope);
builder.addAllKeys(keys);
builder.setNotifyDevice(notifyDevice);
SubscriptionMgrMsgProto.Builder msgBuilder = SubscriptionMgrMsgProto.newBuilder();
msgBuilder.setAttrDelete(builder);

2
application/src/main/java/org/thingsboard/server/service/sync/ie/importing/csv/AbstractBulkImportService.java

@ -87,7 +87,7 @@ public abstract class AbstractBulkImportService<E extends HasId<? extends Entity
@Autowired
private EntityActionService entityActionService;
private static ThreadPoolExecutor executor;
private ThreadPoolExecutor executor;
@PostConstruct
private void initExecutor() {

18
application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java

@ -271,14 +271,20 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer
@Override
public void deleteAndNotify(TenantId tenantId, EntityId entityId, String scope, List<String> keys, FutureCallback<Void> callback) {
checkInternalEntity(entityId);
deleteAndNotifyInternal(tenantId, entityId, scope, keys, callback);
deleteAndNotifyInternal(tenantId, entityId, scope, keys, false, callback);
}
@Override
public void deleteAndNotifyInternal(TenantId tenantId, EntityId entityId, String scope, List<String> keys, FutureCallback<Void> callback) {
public void deleteAndNotify(TenantId tenantId, EntityId entityId, String scope, List<String> keys, boolean notifyDevice, FutureCallback<Void> callback) {
checkInternalEntity(entityId);
deleteAndNotifyInternal(tenantId, entityId, scope, keys, notifyDevice, callback);
}
@Override
public void deleteAndNotifyInternal(TenantId tenantId, EntityId entityId, String scope, List<String> keys, boolean notifyDevice, FutureCallback<Void> callback) {
ListenableFuture<List<String>> deleteFuture = attrService.removeAll(tenantId, entityId, scope, keys);
addVoidCallback(deleteFuture, callback);
addWsCallback(deleteFuture, success -> onAttributesDelete(tenantId, entityId, scope, keys));
addWsCallback(deleteFuture, success -> onAttributesDelete(tenantId, entityId, scope, keys, notifyDevice));
}
@Override
@ -382,16 +388,16 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer
}
}
private void onAttributesDelete(TenantId tenantId, EntityId entityId, String scope, List<String> keys) {
private void onAttributesDelete(TenantId tenantId, EntityId entityId, String scope, List<String> keys, boolean notifyDevice) {
TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, tenantId, entityId);
if (currentPartitions.contains(tpi)) {
if (subscriptionManagerService.isPresent()) {
subscriptionManagerService.get().onAttributesDelete(tenantId, entityId, scope, keys, TbCallback.EMPTY);
subscriptionManagerService.get().onAttributesDelete(tenantId, entityId, scope, keys, notifyDevice, TbCallback.EMPTY);
} else {
log.warn("Possible misconfiguration because subscriptionManagerService is null!");
}
} else {
TransportProtos.ToCoreMsg toCoreMsg = TbSubscriptionUtils.toAttributesDeleteProto(tenantId, entityId, scope, keys);
TransportProtos.ToCoreMsg toCoreMsg = TbSubscriptionUtils.toAttributesDeleteProto(tenantId, entityId, scope, keys, notifyDevice);
clusterService.pushMsgToCore(tpi, entityId.getId(), toCoreMsg, null);
}
}

12
application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetryWebSocketService.java

@ -31,6 +31,7 @@ import org.thingsboard.common.util.ThingsBoardExecutors;
import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.TenantProfile;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.EntityIdFactory;
@ -316,7 +317,7 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi
}
private void processSessionClose(TelemetryWebSocketSessionRef sessionRef) {
var tenantProfileConfiguration = tenantProfileCache.get(sessionRef.getSecurityCtx().getTenantId()).getDefaultProfileConfiguration();
var tenantProfileConfiguration = getTenantProfileConfiguration(sessionRef);
if (tenantProfileConfiguration != null) {
String sessionId = "[" + sessionRef.getSessionId() + "]";
@ -350,7 +351,8 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi
}
private boolean processSubscription(TelemetryWebSocketSessionRef sessionRef, SubscriptionCmd cmd) {
var tenantProfileConfiguration = (DefaultTenantProfileConfiguration) tenantProfileCache.get(sessionRef.getSecurityCtx().getTenantId()).getDefaultProfileConfiguration();
var tenantProfileConfiguration = getTenantProfileConfiguration(sessionRef);
if (tenantProfileConfiguration == null) return true;
String subId = "[" + sessionRef.getSessionId() + "]:[" + cmd.getCmdId() + "]";
try {
@ -932,4 +934,10 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi
private int getLimit(int limit) {
return limit == 0 ? DEFAULT_LIMIT : limit;
}
private DefaultTenantProfileConfiguration getTenantProfileConfiguration(TelemetryWebSocketSessionRef sessionRef) {
return Optional.ofNullable(tenantProfileCache.get(sessionRef.getSecurityCtx().getTenantId()))
.map(TenantProfile::getDefaultProfileConfiguration).orElse(null);
}
}

2
application/src/main/java/org/thingsboard/server/service/telemetry/InternalTelemetryService.java

@ -37,7 +37,7 @@ public interface InternalTelemetryService extends RuleEngineTelemetryService {
void saveLatestAndNotifyInternal(TenantId tenantId, EntityId entityId, List<TsKvEntry> ts, FutureCallback<Void> callback);
void deleteAndNotifyInternal(TenantId tenantId, EntityId entityId, String scope, List<String> keys, FutureCallback<Void> callback);
void deleteAndNotifyInternal(TenantId tenantId, EntityId entityId, String scope, List<String> keys, boolean notifyDevice, FutureCallback<Void> callback);
void deleteLatestInternal(TenantId tenantId, EntityId entityId, List<String> keys, FutureCallback<Void> callback);

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

@ -107,11 +107,11 @@ plugins:
# Security parameters
security:
# JWT Token parameters
jwt:
jwt: # Since 3.4.2 values are persisted to the database during install or upgrade. On Install, the key will be generated randomly if no custom value set. You can change it later from Web UI under SYS_ADMIN
tokenExpirationTime: "${JWT_TOKEN_EXPIRATION_TIME:9000}" # Number of seconds (2.5 hours)
refreshTokenExpTime: "${JWT_REFRESH_TOKEN_EXPIRATION_TIME:604800}" # Number of seconds (1 week).
tokenIssuer: "${JWT_TOKEN_ISSUER:thingsboard.io}"
tokenSigningKey: "${JWT_TOKEN_SIGNING_KEY:thingsboardDefaultSigningKey}"
tokenSigningKey: "${JWT_TOKEN_SIGNING_KEY:thingsboardDefaultSigningKey}" # Base64 encoded
# Enable/disable access to Tenant Administrators JWT token by System Administrator or Customer Users JWT token by Tenant Administrator
user_token_access_enabled: "${SECURITY_USER_TOKEN_ACCESS_ENABLED:true}"
# Enable/disable case-sensitive username login
@ -148,7 +148,7 @@ ui:
# Help parameters
help:
# Base url for UI help assets
base-url: "${UI_HELP_BASE_URL:https://raw.githubusercontent.com/thingsboard/thingsboard-ui-help/release-3.4.1}"
base-url: "${UI_HELP_BASE_URL:https://raw.githubusercontent.com/thingsboard/thingsboard-ui-help/release-3.4.3}"
database:
ts_max_intervals: "${DATABASE_TS_MAX_INTERVALS:700}" # Max number of DB queries generated by single API call to fetch telemetry records
@ -227,6 +227,7 @@ cassandra:
default_fetch_size: "${CASSANDRA_DEFAULT_FETCH_SIZE:2000}"
# Specify partitioning size for timestamp key-value storage. Example: MINUTES, HOURS, DAYS, MONTHS, INDEFINITE
ts_key_value_partitioning: "${TS_KV_PARTITIONING:MONTHS}"
use_ts_key_value_partitioning_on_read: "${USE_TS_KV_PARTITIONING_ON_READ:true}"
ts_key_value_partitions_max_cache_size: "${TS_KV_PARTITIONS_MAX_CACHE_SIZE:100000}"
ts_key_value_ttl: "${TS_KV_TTL:0}"
buffer_size: "${CASSANDRA_QUERY_BUFFER_SIZE:200000}"
@ -610,24 +611,25 @@ state:
defaultStateCheckIntervalInSec: "${DEFAULT_STATE_CHECK_INTERVAL:60}"
persistToTelemetry: "${PERSIST_STATE_TO_TELEMETRY:false}"
mvel:
enabled: "${MVEL_ENABLED:true}"
max_total_args_size: "${MVEL_MAX_TOTAL_ARGS_SIZE:100000}"
max_result_size: "${MVEL_MAX_RESULT_SIZE:300000}"
max_script_body_size: "${MVEL_MAX_SCRIPT_BODY_SIZE:50000}"
# Maximum allowed MVEL script execution memory
max_memory_limit_mb: "${MVEL_MAX_MEMORY_LIMIT_MB: 8}"
# Maximum allowed MVEL script execution errors before it will be blacklisted
max_errors: "${MVEL_MAX_ERRORS:3}"
# MVEL Eval max request timeout in milliseconds. 0 - no timeout
max_requests_timeout: "${MVEL_MAX_REQUEST_TIMEOUT:500}"
tbel:
enabled: "${TBEL_ENABLED:true}"
max_total_args_size: "${TBEL_MAX_TOTAL_ARGS_SIZE:100000}"
max_result_size: "${TBEL_MAX_RESULT_SIZE:300000}"
max_script_body_size: "${TBEL_MAX_SCRIPT_BODY_SIZE:50000}"
# Maximum allowed TBEL script execution memory
max_memory_limit_mb: "${TBEL_MAX_MEMORY_LIMIT_MB: 8}"
# Maximum allowed TBEL script execution errors before it will be blacklisted
max_errors: "${TBEL_MAX_ERRORS:3}"
# TBEL Eval max request timeout in milliseconds. 0 - no timeout
max_requests_timeout: "${TBEL_MAX_REQUEST_TIMEOUT:500}"
# Maximum time in seconds for black listed function to stay in the list.
max_black_list_duration_sec: "${MVEL_MAX_BLACKLIST_DURATION_SEC:60}"
max_black_list_duration_sec: "${TBEL_MAX_BLACKLIST_DURATION_SEC:60}"
# Specify thread pool size for javascript executor service
thread_pool_size: "${MVEL_THREAD_POOL_SIZE:50}"
thread_pool_size: "${TBEL_THREAD_POOL_SIZE:50}"
compiled_scripts_cache_size: "${TBEL_COMPILED_SCRIPTS_CACHE_SIZE:1000}"
stats:
enabled: "${TB_MVEL_STATS_ENABLED:false}"
print_interval_ms: "${TB_MVEL_STATS_PRINT_INTERVAL_MS:10000}"
enabled: "${TB_TBEL_STATS_ENABLED:false}"
print_interval_ms: "${TB_TBEL_STATS_PRINT_INTERVAL_MS:10000}"
js:
evaluator: "${JS_EVALUATOR:local}" # local/remote
@ -928,7 +930,7 @@ edges:
storage:
max_read_records_count: "${EDGES_STORAGE_MAX_READ_RECORDS_COUNT:50}"
no_read_records_sleep: "${EDGES_NO_READ_RECORDS_SLEEP:1000}"
sleep_between_batches: "${EDGES_SLEEP_BETWEEN_BATCHES:1000}"
sleep_between_batches: "${EDGES_SLEEP_BETWEEN_BATCHES:10000}"
scheduler_pool_size: "${EDGES_SCHEDULER_POOL_SIZE:1}"
send_scheduler_pool_size: "${EDGES_SEND_SCHEDULER_POOL_SIZE:1}"
grpc_callback_thread_pool_size: "${EDGES_GRPC_CALLBACK_POOL_SIZE:1}"

2
application/src/test/java/org/thingsboard/server/controller/AbstractNotifyEntityTest.java

@ -597,7 +597,7 @@ public abstract class AbstractNotifyEntityTest extends AbstractWebTest {
}
protected String msgErrorFieldLength(String fieldName) {
return "length of " + fieldName + " must be equal or less than 255";
return fieldName + " length must be equal or less than 255";
}
protected String msgErrorNoFound(String entityClassName, String assetIdStr) {

7
application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java

@ -88,6 +88,7 @@ import org.thingsboard.server.service.security.auth.jwt.RefreshTokenRequest;
import org.thingsboard.server.service.security.auth.rest.LoginRequest;
import java.io.IOException;
import java.lang.reflect.Field;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
@ -768,4 +769,10 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest {
throw new AssertionError("Unexpected status " + mvcResult.getResponse().getStatus());
}
protected <T> T getFieldValue(Object target, String fieldName) throws Exception {
Field field = target.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
return (T) field.get(target);
}
}

122
application/src/test/java/org/thingsboard/server/controller/BaseAdminControllerTest.java

@ -17,14 +17,21 @@ package org.thingsboard.server.controller;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RandomStringUtils;
import org.junit.Test;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.rule.engine.api.MailService;
import org.thingsboard.server.common.data.AdminSettings;
import org.thingsboard.server.common.data.security.model.JwtSettings;
import org.thingsboard.server.service.mail.DefaultMailService;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
@ -32,8 +39,9 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@Slf4j
public abstract class BaseAdminControllerTest extends AbstractControllerTest {
final JwtSettings defaultJwtSettings = new JwtSettings(9000, 604800, "thingsboard.io", "thingsboardDefaultSigningKey");
@Autowired
MailService mailService;
@ -45,67 +53,67 @@ public abstract class BaseAdminControllerTest extends AbstractControllerTest {
public void testFindAdminSettingsByKey() throws Exception {
loginSysAdmin();
doGet("/api/admin/settings/general")
.andExpect(status().isOk())
.andExpect(content().contentType(contentType))
.andExpect(jsonPath("$.id", notNullValue()))
.andExpect(jsonPath("$.key", is("general")))
.andExpect(jsonPath("$.jsonValue.baseUrl", is("http://localhost:8080")));
.andExpect(status().isOk())
.andExpect(content().contentType(contentType))
.andExpect(jsonPath("$.id", notNullValue()))
.andExpect(jsonPath("$.key", is("general")))
.andExpect(jsonPath("$.jsonValue.baseUrl", is("http://localhost:8080")));
doGet("/api/admin/settings/mail")
.andExpect(status().isOk())
.andExpect(content().contentType(contentType))
.andExpect(jsonPath("$.id", notNullValue()))
.andExpect(jsonPath("$.key", is("mail")))
.andExpect(jsonPath("$.jsonValue.smtpProtocol", is("smtp")))
.andExpect(jsonPath("$.jsonValue.smtpHost", is("localhost")))
.andExpect(jsonPath("$.jsonValue.smtpPort", is("25")));
.andExpect(status().isOk())
.andExpect(content().contentType(contentType))
.andExpect(jsonPath("$.id", notNullValue()))
.andExpect(jsonPath("$.key", is("mail")))
.andExpect(jsonPath("$.jsonValue.smtpProtocol", is("smtp")))
.andExpect(jsonPath("$.jsonValue.smtpHost", is("localhost")))
.andExpect(jsonPath("$.jsonValue.smtpPort", is("25")));
doGet("/api/admin/settings/unknown")
.andExpect(status().isNotFound());
.andExpect(status().isNotFound());
}
@Test
public void testSaveAdminSettings() throws Exception {
loginSysAdmin();
AdminSettings adminSettings = doGet("/api/admin/settings/general", AdminSettings.class);
AdminSettings adminSettings = doGet("/api/admin/settings/general", AdminSettings.class);
JsonNode jsonValue = adminSettings.getJsonValue();
((ObjectNode) jsonValue).put("baseUrl", "http://myhost.org");
adminSettings.setJsonValue(jsonValue);
doPost("/api/admin/settings", adminSettings).andExpect(status().isOk());
doGet("/api/admin/settings/general")
.andExpect(status().isOk())
.andExpect(content().contentType(contentType))
.andExpect(jsonPath("$.jsonValue.baseUrl", is("http://myhost.org")));
.andExpect(status().isOk())
.andExpect(content().contentType(contentType))
.andExpect(jsonPath("$.jsonValue.baseUrl", is("http://myhost.org")));
((ObjectNode) jsonValue).put("baseUrl", "http://localhost:8080");
adminSettings.setJsonValue(jsonValue);
doPost("/api/admin/settings", adminSettings)
.andExpect(status().isOk());
.andExpect(status().isOk());
}
@Test
public void testSaveAdminSettingsWithEmptyKey() throws Exception {
loginSysAdmin();
AdminSettings adminSettings = doGet("/api/admin/settings/mail", AdminSettings.class);
AdminSettings adminSettings = doGet("/api/admin/settings/mail", AdminSettings.class);
adminSettings.setKey(null);
doPost("/api/admin/settings", adminSettings)
.andExpect(status().isBadRequest())
.andExpect(statusReason(containsString("Key should be specified")));
.andExpect(status().isBadRequest())
.andExpect(statusReason(containsString("Key should be specified")));
}
@Test
public void testChangeAdminSettingsKey() throws Exception {
loginSysAdmin();
AdminSettings adminSettings = doGet("/api/admin/settings/mail", AdminSettings.class);
AdminSettings adminSettings = doGet("/api/admin/settings/mail", AdminSettings.class);
adminSettings.setKey("newKey");
doPost("/api/admin/settings", adminSettings)
.andExpect(status().isBadRequest())
.andExpect(statusReason(containsString("is prohibited")));
.andExpect(status().isBadRequest())
.andExpect(statusReason(containsString("is prohibited")));
}
@Test
@ -113,7 +121,7 @@ public abstract class BaseAdminControllerTest extends AbstractControllerTest {
loginSysAdmin();
AdminSettings adminSettings = doGet("/api/admin/settings/mail", AdminSettings.class);
doPost("/api/admin/settings/testMail", adminSettings)
.andExpect(status().isOk());
.andExpect(status().isOk());
}
@Test
@ -139,4 +147,48 @@ public abstract class BaseAdminControllerTest extends AbstractControllerTest {
doPost("/api/admin/settings/testMail", adminSettings).andExpect(status().is5xxServerError());
Mockito.doNothing().when(mailService).sendTestMail(Mockito.any(), Mockito.any());
}
void resetJwtSettingsToDefault() throws Exception {
loginSysAdmin();
doPost("/api/admin/jwtSettings", defaultJwtSettings).andExpect(status().isOk()); // jwt test scenarios are always started from
loginTenantAdmin();
}
@Test
public void testGetAndSaveDefaultJwtSettings() throws Exception {
JwtSettings jwtSettings;
loginSysAdmin();
jwtSettings = doGet("/api/admin/jwtSettings", JwtSettings.class);
assertThat(jwtSettings).isEqualTo(defaultJwtSettings);
doPost("/api/admin/jwtSettings", jwtSettings).andExpect(status().isOk());
jwtSettings = doGet("/api/admin/jwtSettings", JwtSettings.class);
assertThat(jwtSettings).isEqualTo(defaultJwtSettings);
resetJwtSettingsToDefault();
}
@Test
public void testCreateJwtSettings() throws Exception {
loginSysAdmin();
JwtSettings jwtSettings = doGet("/api/admin/jwtSettings", JwtSettings.class);
assertThat(jwtSettings).isEqualTo(defaultJwtSettings);
jwtSettings.setTokenSigningKey(Base64.getEncoder().encodeToString(
RandomStringUtils.randomAlphanumeric(256 / Byte.SIZE).getBytes(StandardCharsets.UTF_8)));
doPost("/api/admin/jwtSettings", jwtSettings).andExpect(status().isOk());
doGet("/api/admin/jwtSettings").andExpect(status().isUnauthorized()); //the old JWT token does not work after signing key was changed!
loginSysAdmin();
JwtSettings newJwtSettings = doGet("/api/admin/jwtSettings", JwtSettings.class);
assertThat(jwtSettings).isEqualTo(newJwtSettings);
resetJwtSettingsToDefault();
}
}

16
application/src/test/java/org/thingsboard/server/controller/BaseAuditLogControllerTest.java

@ -44,6 +44,7 @@ import java.util.List;
import java.util.concurrent.TimeUnit;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
@ -202,6 +203,21 @@ public abstract class BaseAuditLogControllerTest extends AbstractControllerTest
});
}
@Test
public void whenSavingAuditLogAndPartitionSaveErrorOccurred_thenSaveAuditLogAnyway() throws Exception {
// creating partition bigger than sql.audit_logs.partition_size
partitioningRepository.createPartitionIfNotExists("audit_log", System.currentTimeMillis(), TimeUnit.DAYS.toMillis(7));
List<Long> partitions = partitioningRepository.fetchPartitions("audit_log");
assertThat(partitions).size().isOne();
partitioningRepository.cleanupPartitionsCache("audit_log", System.currentTimeMillis(), 0);
assertDoesNotThrow(() -> {
// expecting partition overlap error on partition save
createAuditLog(ActionType.LOGIN, tenantAdminUserId);
});
assertThat(partitioningRepository.fetchPartitions("audit_log")).isEqualTo(partitions);
}
private AuditLog createAuditLog(ActionType actionType, EntityId entityId) {
AuditLog auditLog = new AuditLog();
auditLog.setTenantId(tenantId);

116
application/src/test/java/org/thingsboard/server/controller/BaseDeviceControllerTest.java

@ -38,6 +38,7 @@ import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.EntitySubtype;
import org.thingsboard.server.common.data.OtaPackageInfo;
import org.thingsboard.server.common.data.SaveDeviceWithCredentialsRequest;
import org.thingsboard.server.common.data.SaveOtaPackageInfoRequest;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.Tenant;
@ -55,6 +56,9 @@ import org.thingsboard.server.common.data.relation.RelationTypeGroup;
import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.common.data.security.DeviceCredentials;
import org.thingsboard.server.common.data.security.DeviceCredentialsType;
import org.thingsboard.server.common.data.sync.ie.importing.csv.BulkImportColumnType;
import org.thingsboard.server.common.data.sync.ie.importing.csv.BulkImportRequest;
import org.thingsboard.server.common.data.sync.ie.importing.csv.BulkImportResult;
import org.thingsboard.server.dao.device.DeviceDao;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.exception.DeviceCredentialsValidationException;
@ -180,6 +184,58 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
Assert.assertEquals(foundDevice.getName(), savedDevice.getName());
}
@Test
public void testSaveDeviceWithCredentials() throws Exception {
String testToken = "TEST_TOKEN";
Device device = new Device();
device.setName("My device");
device.setType("default");
DeviceCredentials deviceCredentials = new DeviceCredentials();
deviceCredentials.setCredentialsType(DeviceCredentialsType.ACCESS_TOKEN);
deviceCredentials.setCredentialsId(testToken);
SaveDeviceWithCredentialsRequest saveRequest = new SaveDeviceWithCredentialsRequest(device, deviceCredentials);
Mockito.reset(tbClusterService, auditLogService, gatewayNotificationsService);
Device savedDevice = readResponse(doPost("/api/device-with-credentials", saveRequest).andExpect(status().isOk()), Device.class);
Device oldDevice = new Device(savedDevice);
testNotifyEntityOneTimeMsgToEdgeServiceNever(savedDevice, savedDevice.getId(), savedDevice.getId(),
savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(),
ActionType.ADDED);
testNotificationUpdateGatewayNever();
Assert.assertNotNull(savedDevice);
Assert.assertNotNull(savedDevice.getId());
Assert.assertTrue(savedDevice.getCreatedTime() > 0);
Assert.assertEquals(savedTenant.getId(), savedDevice.getTenantId());
Assert.assertNotNull(savedDevice.getCustomerId());
Assert.assertEquals(NULL_UUID, savedDevice.getCustomerId().getId());
Assert.assertEquals(device.getName(), savedDevice.getName());
DeviceCredentials foundDeviceCredentials =
doGet("/api/device/" + savedDevice.getId().getId() + "/credentials", DeviceCredentials.class);
Assert.assertNotNull(foundDeviceCredentials);
Assert.assertNotNull(foundDeviceCredentials.getId());
Assert.assertEquals(savedDevice.getId(), foundDeviceCredentials.getDeviceId());
Assert.assertEquals(DeviceCredentialsType.ACCESS_TOKEN, foundDeviceCredentials.getCredentialsType());
Assert.assertEquals(testToken, foundDeviceCredentials.getCredentialsId());
Mockito.reset(tbClusterService, auditLogService, gatewayNotificationsService);
savedDevice.setName("My new device");
doPost("/api/device", savedDevice, Device.class);
testNotifyEntityAllOneTime(savedDevice, savedDevice.getId(), savedDevice.getId(), savedTenant.getId(),
tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.UPDATED);
testNotificationUpdateGatewayOneTime(savedDevice, oldDevice);
}
@Test
public void saveDeviceWithViolationOfValidation() throws Exception {
Device device = new Device();
@ -1235,6 +1291,66 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
testEntityDaoWithRelationsTransactionalException(deviceDao, savedTenant.getId(), deviceId, "/api/device/" + deviceId);
}
@Test
public void testBulkImportDeviceWithoutCredentials() throws Exception {
String deviceName = "some_device";
String deviceType = "some_type";
BulkImportRequest request = new BulkImportRequest();
request.setFile(String.format("NAME,TYPE\n%s,%s", deviceName, deviceType));
BulkImportRequest.Mapping mapping = new BulkImportRequest.Mapping();
BulkImportRequest.ColumnMapping name = new BulkImportRequest.ColumnMapping();
name.setType(BulkImportColumnType.NAME);
BulkImportRequest.ColumnMapping type = new BulkImportRequest.ColumnMapping();
type.setType(BulkImportColumnType.TYPE);
List<BulkImportRequest.ColumnMapping> columns = new ArrayList<>();
columns.add(name);
columns.add(type);
mapping.setColumns(columns);
mapping.setDelimiter(',');
mapping.setUpdate(true);
mapping.setHeader(true);
request.setMapping(mapping);
BulkImportResult<Device> deviceBulkImportResult = doPostWithTypedResponse("/api/device/bulk_import", request, new TypeReference<>() {});
Assert.assertEquals(1, deviceBulkImportResult.getCreated().get());
Assert.assertEquals(0, deviceBulkImportResult.getErrors().get());
Assert.assertEquals(0, deviceBulkImportResult.getUpdated().get());
Assert.assertTrue(deviceBulkImportResult.getErrorsList().isEmpty());
Device savedDevice = doGet("/api/tenant/devices?deviceName=" + deviceName, Device.class);
Assert.assertNotNull(savedDevice);
Assert.assertEquals(deviceName, savedDevice.getName());
Assert.assertEquals(deviceType, savedDevice.getType());
DeviceCredentials savedCredentials =
doGet("/api/device/" + savedDevice.getId().getId() + "/credentials", DeviceCredentials.class);
Assert.assertNotNull(savedCredentials);
Assert.assertNotNull(savedCredentials.getId());
Assert.assertEquals(savedDevice.getId(), savedCredentials.getDeviceId());
Assert.assertEquals(DeviceCredentialsType.ACCESS_TOKEN, savedCredentials.getCredentialsType());
Assert.assertNotNull(savedCredentials.getCredentialsId());
Assert.assertEquals(20, savedCredentials.getCredentialsId().length());
deviceBulkImportResult = doPostWithTypedResponse("/api/device/bulk_import", request, new TypeReference<>() {});
Assert.assertEquals(0, deviceBulkImportResult.getCreated().get());
Assert.assertEquals(0, deviceBulkImportResult.getErrors().get());
Assert.assertEquals(1, deviceBulkImportResult.getUpdated().get());
Assert.assertTrue(deviceBulkImportResult.getErrorsList().isEmpty());
Device updatedDevice = doGet("/api/device/" + savedDevice.getId().getId(), Device.class);
Assert.assertEquals(savedDevice, updatedDevice);
DeviceCredentials updatedCredentials =
doGet("/api/device/" + savedDevice.getId().getId() + "/credentials", DeviceCredentials.class);
Assert.assertEquals(savedCredentials, updatedCredentials);
}
private Device createDevice(String name) {
Device device = new Device();
device.setName(name);

14
application/src/test/java/org/thingsboard/server/controller/BaseEdgeControllerTest.java

@ -825,26 +825,30 @@ public abstract class BaseEdgeControllerTest extends AbstractControllerTest {
assertThat(edgeImitator.waitForMessages()).as("await for messages on first connect").isTrue();
assertThat(edgeImitator.findAllMessagesByType(QueueUpdateMsg.class)).as("one msg during sync process").hasSize(1);
assertThat(edgeImitator.findAllMessagesByType(RuleChainUpdateMsg.class)).as("one msg during sync process, another from edge creation").hasSize(2);
List<RuleChainUpdateMsg> ruleChainUpdateMsgs = edgeImitator.findAllMessagesByType(RuleChainUpdateMsg.class);
assertThat(ruleChainUpdateMsgs).as("one msg during sync process, another from edge creation").hasSize(2);
assertThat(edgeImitator.findAllMessagesByType(DeviceProfileUpdateMsg.class)).as("one msg during sync process for 'default' device profile").hasSize(3);
assertThat(edgeImitator.findAllMessagesByType(DeviceUpdateMsg.class)).as("one msg once device assigned to edge").hasSize(2);
assertThat(edgeImitator.findAllMessagesByType(AssetProfileUpdateMsg.class)).as("two msgs during sync process for 'default' and 'test' asset profiles").hasSize(4);
assertThat(edgeImitator.findAllMessagesByType(AssetUpdateMsg.class)).as("two msgs - one during sync process, and one more once asset assigned to edge").hasSize(2);
assertThat(edgeImitator.findAllMessagesByType(UserUpdateMsg.class)).as("one msg during sync process for tenant admin user").hasSize(1);
assertThat(edgeImitator.findAllMessagesByType(AdminSettingsUpdateMsg.class)).as("admin setting update").hasSize(4);
verifyRuleChainMsgsAreRoot(ruleChainUpdateMsgs);
edgeImitator.expectMessageAmount(14);
doPost("/api/edge/sync/" + edge.getId());
assertThat(edgeImitator.waitForMessages()).as("await for messages after edge sync rest api call").isTrue();
assertThat(edgeImitator.findAllMessagesByType(QueueUpdateMsg.class)).as("queue msg").hasSize(1);
assertThat(edgeImitator.findAllMessagesByType(RuleChainUpdateMsg.class)).as("rule chain msg").hasSize(1);
ruleChainUpdateMsgs = edgeImitator.findAllMessagesByType(RuleChainUpdateMsg.class);
assertThat(ruleChainUpdateMsgs).as("rule chain msg").hasSize(1);
assertThat(edgeImitator.findAllMessagesByType(DeviceProfileUpdateMsg.class)).as("device profile msg").hasSize(2);
assertThat(edgeImitator.findAllMessagesByType(AssetProfileUpdateMsg.class)).as("asset profile msg").hasSize(3);
assertThat(edgeImitator.findAllMessagesByType(AssetUpdateMsg.class)).as("asset update msg").hasSize(1);
assertThat(edgeImitator.findAllMessagesByType(UserUpdateMsg.class)).as("user update msg").hasSize(1);
assertThat(edgeImitator.findAllMessagesByType(AdminSettingsUpdateMsg.class)).as("admin setting update msg").hasSize(4);
assertThat(edgeImitator.findAllMessagesByType(DeviceUpdateMsg.class)).as("asset update msg").hasSize(1);
verifyRuleChainMsgsAreRoot(ruleChainUpdateMsgs);
edgeImitator.allowIgnoredTypes();
try {
@ -860,6 +864,12 @@ public abstract class BaseEdgeControllerTest extends AbstractControllerTest {
.andExpect(status().isOk());
}
private void verifyRuleChainMsgsAreRoot(List<RuleChainUpdateMsg> ruleChainUpdateMsgs) {
for (RuleChainUpdateMsg ruleChainUpdateMsg : ruleChainUpdateMsgs) {
Assert.assertTrue(ruleChainUpdateMsg.getRoot());
}
}
@Test
public void testDeleteEdgeWithDeleteRelationsOk() throws Exception {
EdgeId edgeId = savedEdge("Edge for Test WithRelationsOk").getId();

22
application/src/test/java/org/thingsboard/server/controller/BaseEntityQueryControllerTest.java

@ -22,6 +22,7 @@ import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.springframework.test.web.servlet.ResultActions;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.Device;
@ -55,6 +56,7 @@ import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
public abstract class BaseEntityQueryControllerTest extends AbstractControllerTest {
@ -381,4 +383,24 @@ public abstract class BaseEntityQueryControllerTest extends AbstractControllerTe
Assert.assertEquals("1" + i, alarmActiveTime);
}
}
@Test
public void givenInvalidEntityDataPageLink_thenReturnError() throws Exception {
DeviceTypeFilter filter = new DeviceTypeFilter();
filter.setDeviceType("default");
filter.setDeviceNameFilter("");
String invalidSortProperty = "created(Time)";
EntityDataSortOrder sortOrder = new EntityDataSortOrder(
new EntityKey(EntityKeyType.ENTITY_FIELD, invalidSortProperty), EntityDataSortOrder.Direction.ASC
);
EntityDataPageLink pageLink = new EntityDataPageLink(10, 0, null, sortOrder);
List<EntityKey> entityFields = Collections.singletonList(new EntityKey(EntityKeyType.ENTITY_FIELD, "name"));
List<EntityKey> latestValues = Collections.singletonList(new EntityKey(EntityKeyType.ATTRIBUTE, "temperature"));
EntityDataQuery query = new EntityDataQuery(filter, pageLink, entityFields, latestValues, null);
ResultActions result = doPost("/api/entitiesQuery/find", query).andExpect(status().isBadRequest());
assertThat(getErrorMessage(result)).contains("Invalid").contains("sort property");
}
}

31
application/src/test/java/org/thingsboard/server/controller/BaseRuleChainControllerTest.java

@ -26,6 +26,8 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
import org.springframework.test.context.ContextConfiguration;
import org.thingsboard.rule.engine.action.TbCreateAlarmNode;
import org.thingsboard.rule.engine.action.TbCreateAlarmNodeConfiguration;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.User;
@ -35,7 +37,9 @@ import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.data.rule.RuleChainMetaData;
import org.thingsboard.server.common.data.rule.RuleChainType;
import org.thingsboard.server.common.data.rule.RuleNode;
import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.rule.RuleChainDao;
@ -44,6 +48,7 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@ -254,9 +259,35 @@ public abstract class BaseRuleChainControllerTest extends AbstractControllerTest
testEntityDaoWithRelationsTransactionalException(ruleChainDao, savedTenant.getId(), ruleChainId, "/api/ruleChain/" + ruleChainId);
}
@Test
public void givenRuleNodeWithInvalidConfiguration_thenReturnError() throws Exception {
RuleChain ruleChain = createRuleChain("Rule chain with invalid nodes");
RuleChainMetaData ruleChainMetaData = new RuleChainMetaData();
ruleChainMetaData.setRuleChainId(ruleChain.getId());
RuleNode createAlarmNode = new RuleNode();
createAlarmNode.setName("Create alarm");
createAlarmNode.setType(TbCreateAlarmNode.class.getName());
TbCreateAlarmNodeConfiguration invalidCreateAlarmNodeConfiguration = new TbCreateAlarmNodeConfiguration();
invalidCreateAlarmNodeConfiguration.setSeverity("<script/>");
invalidCreateAlarmNodeConfiguration.setAlarmType("<script/>");
createAlarmNode.setConfiguration(mapper.valueToTree(invalidCreateAlarmNodeConfiguration));
List<RuleNode> ruleNodes = new ArrayList<>();
ruleNodes.add(createAlarmNode);
ruleChainMetaData.setFirstNodeIndex(0);
ruleChainMetaData.setNodes(ruleNodes);
String error = getErrorMessage(doPost("/api/ruleChain/metadata", ruleChainMetaData)
.andExpect(status().isBadRequest()));
assertThat(error).contains("severity is malformed");
assertThat(error).contains("alarmType is malformed");
}
private RuleChain createRuleChain(String name) {
RuleChain ruleChain = new RuleChain();
ruleChain.setName(name);
return doPost("/api/ruleChain", ruleChain, RuleChain.class);
}
}

12
application/src/test/java/org/thingsboard/server/controller/BaseUserControllerTest.java

@ -28,6 +28,7 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
import org.springframework.http.HttpHeaders;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.web.servlet.ResultActions;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.Tenant;
@ -729,6 +730,17 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest {
testEntityDaoWithRelationsTransactionalException(userDao, tenantId, userId, "/api/user/" + userId);
}
@Test
public void givenInvalidPageLink_thenReturnError() throws Exception {
loginTenantAdmin();
String invalidSortProperty = "abc(abc)";
ResultActions result = doGet("/api/users?page={page}&pageSize={pageSize}&sortProperty={sortProperty}", 0, 100, invalidSortProperty)
.andExpect(status().isBadRequest());
assertThat(getErrorMessage(result)).containsIgnoringCase("invalid sort property");
}
private User createUser() throws Exception {
loginSysAdmin();
String email = "tenant2@thingsboard.org";

38
application/src/test/java/org/thingsboard/server/controller/BaseWebsocketApiTest.java

@ -15,6 +15,7 @@
*/
package org.thingsboard.server.controller;
import com.fasterxml.jackson.databind.JsonNode;
import com.google.common.util.concurrent.FutureCallback;
import lombok.extern.slf4j.Slf4j;
import org.checkerframework.checker.nullness.qual.Nullable;
@ -23,11 +24,15 @@ import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.kv.AttributeKvEntry;
import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry;
import org.thingsboard.server.common.data.kv.BasicTsKvEntry;
import org.thingsboard.server.common.data.kv.LongDataEntry;
import org.thingsboard.server.common.data.kv.StringDataEntry;
import org.thingsboard.server.common.data.kv.TsKvEntry;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.query.DeviceTypeFilter;
@ -41,12 +46,14 @@ import org.thingsboard.server.common.data.query.EntityKeyValueType;
import org.thingsboard.server.common.data.query.FilterPredicateValue;
import org.thingsboard.server.common.data.query.KeyFilter;
import org.thingsboard.server.common.data.query.NumericFilterPredicate;
import org.thingsboard.server.common.data.query.SingleEntityFilter;
import org.thingsboard.server.common.data.query.TsValue;
import org.thingsboard.server.service.subscription.TbAttributeSubscriptionScope;
import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService;
import org.thingsboard.server.service.telemetry.cmd.v2.EntityCountCmd;
import org.thingsboard.server.service.telemetry.cmd.v2.EntityCountUpdate;
import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataUpdate;
import org.thingsboard.server.service.telemetry.sub.SubscriptionErrorCode;
import java.util.Arrays;
import java.util.Collections;
@ -54,6 +61,8 @@ import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatNoException;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@Slf4j
@ -78,6 +87,7 @@ public abstract class BaseWebsocketApiTest extends AbstractControllerTest {
@After
public void tearDown() throws Exception {
loginTenantAdmin();
doDelete("/api/device/" + device.getId().getId())
.andExpect(status().isOk());
}
@ -532,6 +542,28 @@ public abstract class BaseWebsocketApiTest extends AbstractControllerTest {
Assert.assertEquals(new TsValue(dataPoint5.getLastUpdateTs(), dataPoint5.getValueAsString()), attrValue);
}
@Test
public void testAttributesSubscription_sysAdmin() throws Exception {
loginSysAdmin();
SingleEntityFilter entityFilter = new SingleEntityFilter();
entityFilter.setSingleEntity(tenantId);
assertThatNoException().isThrownBy(() -> {
JsonNode update = getWsClient().subscribeForAttributes(tenantId, TbAttributeSubscriptionScope.SERVER_SCOPE.name(), List.of("attr"));
assertThat(update.get("errorMsg").isNull()).isTrue();
assertThat(update.get("errorCode").asInt()).isEqualTo(SubscriptionErrorCode.NO_ERROR.getCode());
});
getWsClient().registerWaitForUpdate();
String expectedAttrValue = "42";
sendAttributes(TenantId.SYS_TENANT_ID, tenantId, TbAttributeSubscriptionScope.SERVER_SCOPE, List.of(
new BaseAttributeKvEntry(System.currentTimeMillis(), new StringDataEntry("attr", expectedAttrValue))
));
JsonNode update = JacksonUtil.toJsonNode(getWsClient().waitForUpdate());
assertThat(update).isNotNull();
assertThat(update.get("data").get("attr").get(0).get(1).asText()).isEqualTo(expectedAttrValue);
}
private void sendTelemetry(Device device, List<TsKvEntry> tsData) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(1);
tsService.saveAndNotify(device.getTenantId(), null, device.getId(), tsData, 0, new FutureCallback<Void>() {
@ -549,8 +581,12 @@ public abstract class BaseWebsocketApiTest extends AbstractControllerTest {
}
private void sendAttributes(Device device, TbAttributeSubscriptionScope scope, List<AttributeKvEntry> attrData) throws InterruptedException {
sendAttributes(device.getTenantId(), device.getId(), scope, attrData);
}
private void sendAttributes(TenantId tenantId, EntityId entityId, TbAttributeSubscriptionScope scope, List<AttributeKvEntry> attrData) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(1);
tsService.saveAndNotify(device.getTenantId(), device.getId(), scope.name(), attrData, new FutureCallback<Void>() {
tsService.saveAndNotify(tenantId, entityId, scope.name(), attrData, new FutureCallback<Void>() {
@Override
public void onSuccess(@Nullable Void result) {
latch.countDown();

19
application/src/test/java/org/thingsboard/server/controller/TbTestWebSocketClient.java

@ -15,16 +15,20 @@
*/
package org.thingsboard.server.controller;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import lombok.extern.slf4j.Slf4j;
import org.java_websocket.client.WebSocketClient;
import org.java_websocket.handshake.ServerHandshake;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.kv.Aggregation;
import org.thingsboard.server.common.data.query.EntityDataPageLink;
import org.thingsboard.server.common.data.query.EntityDataQuery;
import org.thingsboard.server.common.data.query.EntityFilter;
import org.thingsboard.server.common.data.query.EntityKey;
import org.thingsboard.server.service.telemetry.cmd.TelemetryPluginCmdsWrapper;
import org.thingsboard.server.service.telemetry.cmd.v1.AttributesSubscriptionCmd;
import org.thingsboard.server.service.telemetry.cmd.v2.EntityCountCmd;
import org.thingsboard.server.service.telemetry.cmd.v2.EntityCountUpdate;
import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataCmd;
@ -177,6 +181,21 @@ public class TbTestWebSocketClient extends WebSocketClient {
return parseDataReply(waitForReply());
}
public JsonNode subscribeForAttributes(EntityId entityId, String scope, List<String> keys) {
AttributesSubscriptionCmd cmd = new AttributesSubscriptionCmd();
cmd.setCmdId(1);
cmd.setEntityType(entityId.getEntityType().toString());
cmd.setEntityId(entityId.getId().toString());
cmd.setScope(scope);
cmd.setKeys(String.join(",", keys));
TelemetryPluginCmdsWrapper cmdsWrapper = new TelemetryPluginCmdsWrapper();
cmdsWrapper.setAttrSubCmds(List.of(cmd));
JsonNode msg = JacksonUtil.valueToTree(cmdsWrapper);
((ObjectNode) msg.get("attrSubCmds").get(0)).remove("type");
send(msg.toString());
return JacksonUtil.toJsonNode(waitForReply());
}
public EntityDataUpdate sendHistoryCmd(List<String> keys, long startTs, long timeWindow) {
return sendHistoryCmd(keys, startTs, timeWindow, (EntityDataQuery) null);
}

4
application/src/test/java/org/thingsboard/server/controller/TwoFactorAuthTest.java

@ -51,7 +51,7 @@ import org.thingsboard.server.dao.user.UserService;
import org.thingsboard.server.service.security.auth.mfa.TwoFactorAuthService;
import org.thingsboard.server.service.security.auth.mfa.config.TwoFaConfigManager;
import org.thingsboard.server.service.security.auth.rest.LoginRequest;
import org.thingsboard.server.service.security.model.JwtTokenPair;
import org.thingsboard.server.common.data.security.model.JwtPair;
import java.time.Duration;
import java.util.Arrays;
@ -396,7 +396,7 @@ public abstract class TwoFactorAuthTest extends AbstractControllerTest {
private void logInWithPreVerificationToken(String username, String password) throws Exception {
LoginRequest loginRequest = new LoginRequest(username, password);
JwtTokenPair response = readResponse(doPost("/api/auth/login", loginRequest).andExpect(status().isOk()), JwtTokenPair.class);
JwtPair response = readResponse(doPost("/api/auth/login", loginRequest).andExpect(status().isOk()), JwtPair.class);
assertThat(response.getToken()).isNotNull();
assertThat(response.getRefreshToken()).isNull();
assertThat(response.getScope()).isEqualTo(Authority.PRE_VERIFICATION_TOKEN);

25
application/src/test/java/org/thingsboard/server/edge/BaseDeviceEdgeTest.java

@ -319,6 +319,8 @@ abstract public class BaseDeviceEdgeTest extends AbstractEdgeTest {
Device device = findDeviceByName("Edge Device 1");
sendAttributesRequestAndVerify(device, DataConstants.SERVER_SCOPE, "{\"key1\":\"value1\"}",
"key1", "value1");
sendAttributesRequestAndVerify(device, DataConstants.SERVER_SCOPE, "{\"inactivityTimeout\":3600000}",
"inactivityTimeout", "3600000");
sendAttributesRequestAndVerify(device, DataConstants.SHARED_SCOPE, "{\"key2\":\"value2\"}",
"key2", "value2");
}
@ -439,9 +441,9 @@ abstract public class BaseDeviceEdgeTest extends AbstractEdgeTest {
Assert.assertTrue(edgeImitator.waitForResponses());
Assert.assertTrue(edgeImitator.waitForMessages());
AbstractMessage latestMessage = edgeImitator.getMessageFromTail(2);
Assert.assertTrue(latestMessage instanceof DeviceUpdateMsg);
DeviceUpdateMsg latestDeviceUpdateMsg = (DeviceUpdateMsg) latestMessage;
Optional<DeviceUpdateMsg> deviceUpdateMsgOpt = edgeImitator.findMessageByType(DeviceUpdateMsg.class);
Assert.assertTrue(deviceUpdateMsgOpt.isPresent());
DeviceUpdateMsg latestDeviceUpdateMsg = deviceUpdateMsgOpt.get();
Assert.assertNotEquals(deviceOnCloudName, latestDeviceUpdateMsg.getName());
Assert.assertEquals(deviceOnCloudName, latestDeviceUpdateMsg.getConflictName());
@ -453,9 +455,9 @@ abstract public class BaseDeviceEdgeTest extends AbstractEdgeTest {
Assert.assertNotNull(device);
Assert.assertNotEquals(deviceOnCloudName, device.getName());
latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof DeviceCredentialsRequestMsg);
DeviceCredentialsRequestMsg latestDeviceCredentialsRequestMsg = (DeviceCredentialsRequestMsg) latestMessage;
Optional<DeviceCredentialsRequestMsg> deviceCredentialsUpdateMsgOpt = edgeImitator.findMessageByType(DeviceCredentialsRequestMsg.class);
Assert.assertTrue(deviceCredentialsUpdateMsgOpt.isPresent());
DeviceCredentialsRequestMsg latestDeviceCredentialsRequestMsg = deviceCredentialsUpdateMsgOpt.get();
Assert.assertEquals(uuid.getMostSignificantBits(), latestDeviceCredentialsRequestMsg.getDeviceIdMSB());
Assert.assertEquals(uuid.getLeastSignificantBits(), latestDeviceCredentialsRequestMsg.getDeviceIdLSB());
@ -576,7 +578,16 @@ abstract public class BaseDeviceEdgeTest extends AbstractEdgeTest {
for (TransportProtos.KeyValueProto keyValueProto : attributesUpdatedMsg.getKvList()) {
if (keyValueProto.getKey().equals(expectedKey)) {
Assert.assertEquals(expectedKey, keyValueProto.getKey());
Assert.assertEquals(expectedValue, keyValueProto.getStringV());
switch (keyValueProto.getType()) {
case STRING_V:
Assert.assertEquals(expectedValue, keyValueProto.getStringV());
break;
case LONG_V:
Assert.assertEquals(Long.parseLong(expectedValue), keyValueProto.getLongV());
break;
default:
Assert.fail("Unexpected data type: " + keyValueProto.getType());
}
found = true;
}
}

45
application/src/test/java/org/thingsboard/server/edge/BaseRuleChainEdgeTest.java

@ -18,6 +18,7 @@ package org.thingsboard.server.edge;
import com.google.protobuf.AbstractMessage;
import org.junit.Assert;
import org.junit.Test;
import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.data.rule.RuleChainMetaData;
@ -165,4 +166,48 @@ abstract public class BaseRuleChainEdgeTest extends AbstractEdgeTest {
doPost("/api/ruleChain/metadata", ruleChainMetaData, RuleChainMetaData.class);
}
@Test
public void testSetRootRuleChain() throws Exception {
// create rule chain
edgeImitator.expectMessageAmount(1);
RuleChain ruleChain = new RuleChain();
ruleChain.setName("Edge New Root Rule Chain");
ruleChain.setType(RuleChainType.EDGE);
RuleChain savedRuleChain = doPost("/api/ruleChain", ruleChain, RuleChain.class);
doPost("/api/edge/" + edge.getUuidId()
+ "/ruleChain/" + savedRuleChain.getUuidId(), RuleChain.class);
Assert.assertTrue(edgeImitator.waitForMessages());
// set new rule chain as root
RuleChainId currentRootRuleChainId = edge.getRootRuleChainId();
edgeImitator.expectMessageAmount(1);
doPost("/api/edge/" + edge.getUuidId()
+ "/" + savedRuleChain.getUuidId() + "/root", Edge.class);
Assert.assertTrue(edgeImitator.waitForMessages());
Optional<RuleChainUpdateMsg> ruleChainUpdateMsgOpt = edgeImitator.findMessageByType(RuleChainUpdateMsg.class);
Assert.assertTrue(ruleChainUpdateMsgOpt.isPresent());
RuleChainUpdateMsg ruleChainUpdateMsg = ruleChainUpdateMsgOpt.get();
Assert.assertEquals(UpdateMsgType.ENTITY_UPDATED_RPC_MESSAGE, ruleChainUpdateMsg.getMsgType());
Assert.assertEquals(ruleChainUpdateMsg.getIdMSB(), savedRuleChain.getUuidId().getMostSignificantBits());
Assert.assertEquals(ruleChainUpdateMsg.getIdLSB(), savedRuleChain.getUuidId().getLeastSignificantBits());
Assert.assertTrue(ruleChainUpdateMsg.getRoot());
// revert root rule chain
edgeImitator.expectMessageAmount(1);
doPost("/api/edge/" + edge.getUuidId()
+ "/" + currentRootRuleChainId.getId() + "/root", Edge.class);
Assert.assertTrue(edgeImitator.waitForMessages());
// unassign rule chain from edge
edgeImitator.expectMessageAmount(1);
doDelete("/api/edge/" + edge.getUuidId()
+ "/ruleChain/" + savedRuleChain.getUuidId(), RuleChain.class);
Assert.assertTrue(edgeImitator.waitForMessages());
// delete rule chain
edgeImitator.expectMessageAmount(1);
doDelete("/api/ruleChain/" + savedRuleChain.getUuidId())
.andExpect(status().isOk());
Assert.assertFalse(edgeImitator.waitForMessages(1));
}
}

61
application/src/test/java/org/thingsboard/server/edge/BaseTelemetryEdgeTest.java

@ -23,6 +23,7 @@ import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.edge.EdgeEvent;
import org.thingsboard.server.common.data.edge.EdgeEventActionType;
import org.thingsboard.server.common.data.edge.EdgeEventType;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.gen.edge.v1.AttributeDeleteMsg;
import org.thingsboard.server.gen.edge.v1.DeviceUpdateMsg;
import org.thingsboard.server.gen.edge.v1.EntityDataProto;
@ -34,7 +35,7 @@ abstract public class BaseTelemetryEdgeTest extends AbstractEdgeTest {
@Test
public void testTimeseriesWithFailures() throws Exception {
int numberOfTimeseriesToSend = 1000;
int numberOfTimeseriesToSend = 333;
Device device = findDeviceByName("Edge Device 1");
@ -67,36 +68,11 @@ abstract public class BaseTelemetryEdgeTest extends AbstractEdgeTest {
public void testAttributes() throws Exception {
Device device = findDeviceByName("Edge Device 1");
testAttributesUpdatedMsg(device);
testAttributesUpdatedMsg(device.getId());
testPostAttributesMsg(device);
testAttributesDeleteMsg(device);
}
private void testAttributesUpdatedMsg(Device device) throws Exception {
String attributesData = "{\"scope\":\"SERVER_SCOPE\",\"kv\":{\"key1\":\"value1\"}}";
JsonNode attributesEntityData = mapper.readTree(attributesData);
EdgeEvent edgeEvent1 = constructEdgeEvent(tenantId, edge.getId(), EdgeEventActionType.ATTRIBUTES_UPDATED, device.getId().getId(), EdgeEventType.DEVICE, attributesEntityData);
edgeImitator.expectMessageAmount(1);
edgeEventService.saveAsync(edgeEvent1).get();
clusterService.onEdgeEventUpdate(tenantId, edge.getId());
Assert.assertTrue(edgeImitator.waitForMessages());
AbstractMessage latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof EntityDataProto);
EntityDataProto latestEntityDataMsg = (EntityDataProto) latestMessage;
Assert.assertEquals(device.getUuidId().getMostSignificantBits(), latestEntityDataMsg.getEntityIdMSB());
Assert.assertEquals(device.getUuidId().getLeastSignificantBits(), latestEntityDataMsg.getEntityIdLSB());
Assert.assertEquals(device.getId().getEntityType().name(), latestEntityDataMsg.getEntityType());
Assert.assertEquals("SERVER_SCOPE", latestEntityDataMsg.getPostAttributeScope());
Assert.assertTrue(latestEntityDataMsg.hasAttributesUpdatedMsg());
TransportProtos.PostAttributeMsg attributesUpdatedMsg = latestEntityDataMsg.getAttributesUpdatedMsg();
Assert.assertEquals(1, attributesUpdatedMsg.getKvCount());
TransportProtos.KeyValueProto keyValueProto = attributesUpdatedMsg.getKv(0);
Assert.assertEquals("key1", keyValueProto.getKey());
Assert.assertEquals("value1", keyValueProto.getStringV());
}
private void testPostAttributesMsg(Device device) throws Exception {
String postAttributesData = "{\"scope\":\"SERVER_SCOPE\",\"kv\":{\"key2\":\"value2\"}}";
JsonNode postAttributesEntityData = mapper.readTree(postAttributesData);
@ -226,4 +202,35 @@ abstract public class BaseTelemetryEdgeTest extends AbstractEdgeTest {
edgeImitator.setRandomFailuresOnTimeseriesDownlink(false);
}
@Test
public void testAttributesUpdatedMsg_userEntity() throws Exception {
testAttributesUpdatedMsg(tenantAdmin.getId());
}
private void testAttributesUpdatedMsg(EntityId entityId) throws Exception {
String attributesData = "{\"scope\":\"SERVER_SCOPE\",\"kv\":{\"key1\":\"value1\"}}";
JsonNode attributesEntityData = mapper.readTree(attributesData);
EdgeEvent edgeEvent1 = constructEdgeEvent(tenantId, edge.getId(), EdgeEventActionType.ATTRIBUTES_UPDATED, entityId.getId(), EdgeEventType.valueOf(entityId.getEntityType().name()), attributesEntityData);
edgeImitator.expectMessageAmount(1);
edgeEventService.saveAsync(edgeEvent1).get();
clusterService.onEdgeEventUpdate(tenantId, edge.getId());
Assert.assertTrue(edgeImitator.waitForMessages());
AbstractMessage latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof EntityDataProto);
EntityDataProto latestEntityDataMsg = (EntityDataProto) latestMessage;
Assert.assertEquals(entityId.getId().getMostSignificantBits(), latestEntityDataMsg.getEntityIdMSB());
Assert.assertEquals(entityId.getId().getLeastSignificantBits(), latestEntityDataMsg.getEntityIdLSB());
Assert.assertEquals(entityId.getEntityType().name(), latestEntityDataMsg.getEntityType());
Assert.assertEquals("SERVER_SCOPE", latestEntityDataMsg.getPostAttributeScope());
Assert.assertTrue(latestEntityDataMsg.hasAttributesUpdatedMsg());
TransportProtos.PostAttributeMsg attributesUpdatedMsg = latestEntityDataMsg.getAttributesUpdatedMsg();
Assert.assertEquals(1, attributesUpdatedMsg.getKvCount());
TransportProtos.KeyValueProto keyValueProto = attributesUpdatedMsg.getKv(0);
Assert.assertEquals("key1", keyValueProto.getKey());
Assert.assertEquals("value1", keyValueProto.getStringV());
}
}

6
application/src/test/java/org/thingsboard/server/edge/imitator/EdgeImitator.java

@ -365,11 +365,7 @@ public class EdgeImitator {
}
public AbstractMessage getLatestMessage() {
return getMessageFromTail(1);
}
public AbstractMessage getMessageFromTail(int offset) {
return downlinkMsgs.get(downlinkMsgs.size() - offset);
return downlinkMsgs.get(downlinkMsgs.size() - 1);
}
public void ignoreType(Class<? extends AbstractMessage> type) {

247
application/src/test/java/org/thingsboard/server/service/queue/DefaultTbClusterServiceTest.java

@ -0,0 +1,247 @@
/**
* Copyright © 2016-2022 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.queue;
import com.google.common.collect.Sets;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.boot.test.mock.mockito.SpyBean;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.common.data.id.QueueId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.queue.Queue;
import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.queue.TbQueueProducer;
import org.thingsboard.server.queue.common.TbProtoQueueMsg;
import org.thingsboard.server.queue.discovery.NotificationsTopicService;
import org.thingsboard.server.queue.discovery.PartitionService;
import org.thingsboard.server.queue.provider.TbQueueProducerProvider;
import org.thingsboard.server.queue.util.DataDecodingEncodingService;
import org.thingsboard.server.service.gateway_device.GatewayNotificationsService;
import org.thingsboard.server.service.profile.TbAssetProfileCache;
import org.thingsboard.server.service.profile.TbDeviceProfileCache;
import java.util.UUID;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@Slf4j
@RunWith(SpringRunner.class)
@ContextConfiguration(classes = DefaultTbClusterService.class)
public class DefaultTbClusterServiceTest {
public static final String MONOLITH = "monolith";
public static final String CORE = "core";
public static final String RULE_ENGINE = "rule_engine";
public static final String TRANSPORT = "transport";
@MockBean
protected DataDecodingEncodingService encodingService;
@MockBean
protected TbDeviceProfileCache deviceProfileCache;
@MockBean
protected TbAssetProfileCache assetProfileCache;
@MockBean
protected GatewayNotificationsService gatewayNotificationsService;
@MockBean
protected PartitionService partitionService;
@MockBean
protected TbQueueProducerProvider producerProvider;
@SpyBean
protected NotificationsTopicService notificationsTopicService;
@SpyBean
protected TbClusterService clusterService;
@Test
public void testOnQueueChangeSingleMonolith() {
when(partitionService.getAllServiceIds(ServiceType.TB_RULE_ENGINE)).thenReturn(Sets.newHashSet(MONOLITH));
when(partitionService.getAllServiceIds(ServiceType.TB_CORE)).thenReturn(Sets.newHashSet(MONOLITH));
when(partitionService.getAllServiceIds(ServiceType.TB_TRANSPORT)).thenReturn(Sets.newHashSet(MONOLITH));
TbQueueProducer<TbProtoQueueMsg<TransportProtos.ToRuleEngineNotificationMsg>> tbQueueProducer = mock(TbQueueProducer.class);
when(producerProvider.getRuleEngineNotificationsMsgProducer()).thenReturn(tbQueueProducer);
clusterService.onQueueChange(createTestQueue());
verify(notificationsTopicService, times(1)).getNotificationsTopic(ServiceType.TB_RULE_ENGINE, MONOLITH);
verify(notificationsTopicService, never()).getNotificationsTopic(eq(ServiceType.TB_CORE), any());
verify(notificationsTopicService, never()).getNotificationsTopic(eq(ServiceType.TB_TRANSPORT), any());
verify(tbQueueProducer, times(1))
.send(eq(notificationsTopicService.getNotificationsTopic(ServiceType.TB_RULE_ENGINE, MONOLITH)), any(TbProtoQueueMsg.class), isNull());
verify(producerProvider, never()).getTbCoreNotificationsMsgProducer();
verify(producerProvider, never()).getTransportNotificationsMsgProducer();
}
@Test
public void testOnQueueChangeMultipleMonoliths() {
String monolith1 = MONOLITH + 1;
String monolith2 = MONOLITH + 2;
when(partitionService.getAllServiceIds(ServiceType.TB_RULE_ENGINE)).thenReturn(Sets.newHashSet(monolith1, monolith2));
when(partitionService.getAllServiceIds(ServiceType.TB_CORE)).thenReturn(Sets.newHashSet(monolith1, monolith2));
when(partitionService.getAllServiceIds(ServiceType.TB_TRANSPORT)).thenReturn(Sets.newHashSet(monolith1, monolith2));
TbQueueProducer<TbProtoQueueMsg<TransportProtos.ToRuleEngineNotificationMsg>> tbQueueProducer = mock(TbQueueProducer.class);
when(producerProvider.getRuleEngineNotificationsMsgProducer()).thenReturn(tbQueueProducer);
clusterService.onQueueChange(createTestQueue());
verify(notificationsTopicService, times(1)).getNotificationsTopic(ServiceType.TB_RULE_ENGINE, monolith1);
verify(notificationsTopicService, times(1)).getNotificationsTopic(ServiceType.TB_RULE_ENGINE, monolith2);
verify(notificationsTopicService, never()).getNotificationsTopic(eq(ServiceType.TB_CORE), any());
verify(notificationsTopicService, never()).getNotificationsTopic(eq(ServiceType.TB_TRANSPORT), any());
verify(tbQueueProducer, times(1))
.send(eq(notificationsTopicService.getNotificationsTopic(ServiceType.TB_RULE_ENGINE, monolith1)), any(TbProtoQueueMsg.class), isNull());
verify(tbQueueProducer, times(1))
.send(eq(notificationsTopicService.getNotificationsTopic(ServiceType.TB_RULE_ENGINE, monolith2)), any(TbProtoQueueMsg.class), isNull());
verify(producerProvider, never()).getTbCoreNotificationsMsgProducer();
verify(producerProvider, never()).getTransportNotificationsMsgProducer();
}
@Test
public void testOnQueueChangeSingleMonolithAndSingleRemoteTransport() {
when(partitionService.getAllServiceIds(ServiceType.TB_RULE_ENGINE)).thenReturn(Sets.newHashSet(MONOLITH));
when(partitionService.getAllServiceIds(ServiceType.TB_CORE)).thenReturn(Sets.newHashSet(MONOLITH));
when(partitionService.getAllServiceIds(ServiceType.TB_TRANSPORT)).thenReturn(Sets.newHashSet(MONOLITH, TRANSPORT));
TbQueueProducer<TbProtoQueueMsg<TransportProtos.ToRuleEngineNotificationMsg>> tbREQueueProducer = mock(TbQueueProducer.class);
TbQueueProducer<TbProtoQueueMsg<TransportProtos.ToTransportMsg>> tbTransportQueueProducer = mock(TbQueueProducer.class);
when(producerProvider.getRuleEngineNotificationsMsgProducer()).thenReturn(tbREQueueProducer);
when(producerProvider.getTransportNotificationsMsgProducer()).thenReturn(tbTransportQueueProducer);
clusterService.onQueueChange(createTestQueue());
verify(notificationsTopicService, times(1)).getNotificationsTopic(ServiceType.TB_RULE_ENGINE, MONOLITH);
verify(notificationsTopicService, times(1)).getNotificationsTopic(ServiceType.TB_TRANSPORT, TRANSPORT);
verify(notificationsTopicService, never()).getNotificationsTopic(eq(ServiceType.TB_CORE), any());
verify(tbREQueueProducer, times(1))
.send(eq(notificationsTopicService.getNotificationsTopic(ServiceType.TB_RULE_ENGINE, MONOLITH)), any(TbProtoQueueMsg.class), isNull());
verify(tbTransportQueueProducer, times(1))
.send(eq(notificationsTopicService.getNotificationsTopic(ServiceType.TB_TRANSPORT, TRANSPORT)), any(TbProtoQueueMsg.class), isNull());
verify(tbTransportQueueProducer, never())
.send(eq(notificationsTopicService.getNotificationsTopic(ServiceType.TB_TRANSPORT, MONOLITH)), any(TbProtoQueueMsg.class), isNull());
verify(tbTransportQueueProducer, never())
.send(eq(notificationsTopicService.getNotificationsTopic(ServiceType.TB_CORE, MONOLITH)), any(TbProtoQueueMsg.class), isNull());
verify(producerProvider, never()).getTbCoreNotificationsMsgProducer();
}
@Test
public void testOnQueueChangeMultipleMicroservices() {
String monolith1 = MONOLITH + 1;
String monolith2 = MONOLITH + 2;
String core1 = CORE + 1;
String core2 = CORE + 2;
String ruleEngine1 = RULE_ENGINE + 1;
String ruleEngine2 = RULE_ENGINE + 2;
String transport1 = TRANSPORT + 1;
String transport2 = TRANSPORT + 2;
when(partitionService.getAllServiceIds(ServiceType.TB_RULE_ENGINE)).thenReturn(Sets.newHashSet(monolith1, monolith2, ruleEngine1, ruleEngine2));
when(partitionService.getAllServiceIds(ServiceType.TB_CORE)).thenReturn(Sets.newHashSet(monolith1, monolith2, core1, core2));
when(partitionService.getAllServiceIds(ServiceType.TB_TRANSPORT)).thenReturn(Sets.newHashSet(monolith1, monolith2, transport1, transport2));
TbQueueProducer<TbProtoQueueMsg<TransportProtos.ToRuleEngineNotificationMsg>> tbREQueueProducer = mock(TbQueueProducer.class);
TbQueueProducer<TbProtoQueueMsg<TransportProtos.ToCoreNotificationMsg>> tbCoreQueueProducer = mock(TbQueueProducer.class);
TbQueueProducer<TbProtoQueueMsg<TransportProtos.ToTransportMsg>> tbTransportQueueProducer = mock(TbQueueProducer.class);
when(producerProvider.getRuleEngineNotificationsMsgProducer()).thenReturn(tbREQueueProducer);
when(producerProvider.getTbCoreNotificationsMsgProducer()).thenReturn(tbCoreQueueProducer);
when(producerProvider.getTransportNotificationsMsgProducer()).thenReturn(tbTransportQueueProducer);
clusterService.onQueueChange(createTestQueue());
verify(notificationsTopicService, times(1)).getNotificationsTopic(ServiceType.TB_RULE_ENGINE, monolith1);
verify(notificationsTopicService, times(1)).getNotificationsTopic(ServiceType.TB_RULE_ENGINE, monolith2);
verify(notificationsTopicService, times(1)).getNotificationsTopic(ServiceType.TB_RULE_ENGINE, ruleEngine1);
verify(notificationsTopicService, times(1)).getNotificationsTopic(ServiceType.TB_RULE_ENGINE, ruleEngine2);
verify(notificationsTopicService, times(1)).getNotificationsTopic(ServiceType.TB_CORE, core1);
verify(notificationsTopicService, times(1)).getNotificationsTopic(ServiceType.TB_CORE, core2);
verify(notificationsTopicService, never()).getNotificationsTopic(ServiceType.TB_CORE, monolith1);
verify(notificationsTopicService, never()).getNotificationsTopic(ServiceType.TB_CORE, monolith2);
verify(notificationsTopicService, times(1)).getNotificationsTopic(ServiceType.TB_TRANSPORT, transport1);
verify(notificationsTopicService, times(1)).getNotificationsTopic(ServiceType.TB_TRANSPORT, transport2);
verify(notificationsTopicService, never()).getNotificationsTopic(ServiceType.TB_TRANSPORT, monolith1);
verify(notificationsTopicService, never()).getNotificationsTopic(ServiceType.TB_TRANSPORT, monolith2);
verify(tbREQueueProducer, times(1))
.send(eq(notificationsTopicService.getNotificationsTopic(ServiceType.TB_RULE_ENGINE, monolith1)), any(TbProtoQueueMsg.class), isNull());
verify(tbREQueueProducer, times(1))
.send(eq(notificationsTopicService.getNotificationsTopic(ServiceType.TB_RULE_ENGINE, monolith2)), any(TbProtoQueueMsg.class), isNull());
verify(tbREQueueProducer, times(1))
.send(eq(notificationsTopicService.getNotificationsTopic(ServiceType.TB_RULE_ENGINE, ruleEngine1)), any(TbProtoQueueMsg.class), isNull());
verify(tbREQueueProducer, times(1))
.send(eq(notificationsTopicService.getNotificationsTopic(ServiceType.TB_RULE_ENGINE, ruleEngine2)), any(TbProtoQueueMsg.class), isNull());
verify(tbCoreQueueProducer, times(1))
.send(eq(notificationsTopicService.getNotificationsTopic(ServiceType.TB_CORE, core1)), any(TbProtoQueueMsg.class), isNull());
verify(tbCoreQueueProducer, times(1))
.send(eq(notificationsTopicService.getNotificationsTopic(ServiceType.TB_CORE, core2)), any(TbProtoQueueMsg.class), isNull());
verify(tbCoreQueueProducer, never())
.send(eq(notificationsTopicService.getNotificationsTopic(ServiceType.TB_CORE, monolith1)), any(TbProtoQueueMsg.class), isNull());
verify(tbCoreQueueProducer, never())
.send(eq(notificationsTopicService.getNotificationsTopic(ServiceType.TB_CORE, monolith2)), any(TbProtoQueueMsg.class), isNull());
verify(tbTransportQueueProducer, times(1))
.send(eq(notificationsTopicService.getNotificationsTopic(ServiceType.TB_TRANSPORT, transport1)), any(TbProtoQueueMsg.class), isNull());
verify(tbTransportQueueProducer, times(1))
.send(eq(notificationsTopicService.getNotificationsTopic(ServiceType.TB_TRANSPORT, transport2)), any(TbProtoQueueMsg.class), isNull());
verify(tbTransportQueueProducer, never())
.send(eq(notificationsTopicService.getNotificationsTopic(ServiceType.TB_TRANSPORT, monolith1)), any(TbProtoQueueMsg.class), isNull());
verify(tbTransportQueueProducer, never())
.send(eq(notificationsTopicService.getNotificationsTopic(ServiceType.TB_TRANSPORT, monolith2)), any(TbProtoQueueMsg.class), isNull());
}
protected Queue createTestQueue() {
TenantId tenantId = TenantId.SYS_TENANT_ID;
Queue queue = new Queue(new QueueId(UUID.randomUUID()));
queue.setTenantId(tenantId);
queue.setName("Main");
queue.setTopic("main");
queue.setPartitions(10);
return queue;
}
}

2
application/src/test/java/org/thingsboard/server/service/script/NashornJsInvokeServiceTest.java

@ -70,7 +70,7 @@ class NashornJsInvokeServiceTest extends AbstractControllerTest {
}
long duration = System.currentTimeMillis() - startTs;
System.out.println(iterations + " invocations took: " + duration + "ms");
Assert.assertTrue(duration < TimeUnit.MINUTES.toMillis(3));
Assert.assertTrue(duration < TimeUnit.MINUTES.toMillis(4));
}
@Test

107
application/src/test/java/org/thingsboard/server/service/script/MvelInvokeServiceTest.java → application/src/test/java/org/thingsboard/server/service/script/TbelInvokeServiceTest.java

@ -16,6 +16,7 @@
package org.thingsboard.server.service.script;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.github.benmanes.caffeine.cache.Cache;
import org.junit.Assert;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
@ -23,31 +24,38 @@ import org.springframework.beans.factory.annotation.Value;
import org.springframework.test.context.TestPropertySource;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.script.api.ScriptType;
import org.thingsboard.script.api.mvel.MvelInvokeService;
import org.thingsboard.script.api.tbel.TbelInvokeService;
import org.thingsboard.script.api.tbel.TbelScript;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.controller.AbstractControllerTest;
import org.thingsboard.server.dao.service.DaoSqlTest;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
@DaoSqlTest
@TestPropertySource(properties = {
"mvel.max_script_body_size=100",
"mvel.max_total_args_size=50",
"mvel.max_result_size=50",
"mvel.max_errors=2",
"tbel.max_script_body_size=100",
"tbel.max_total_args_size=50",
"tbel.max_result_size=50",
"tbel.max_errors=2",
"tbel.compiled_scripts_cache_size=100"
})
class MvelInvokeServiceTest extends AbstractControllerTest {
class TbelInvokeServiceTest extends AbstractControllerTest {
@Autowired
private MvelInvokeService invokeService;
private TbelInvokeService invokeService;
@Value("${mvel.max_errors}")
@Value("${tbel.max_errors}")
private int maxJsErrors;
@Test
@ -110,6 +118,89 @@ class MvelInvokeServiceTest extends AbstractControllerTest {
assertThatScriptIsBlocked(scriptId);
}
@Test
void givenScriptsWithSameBody_thenCompileAndCacheOnlyOnce() throws Exception {
String script = "return msg.temperature > 20;";
List<UUID> scriptsIds = new ArrayList<>();
for (int i = 0; i < 100; i++) {
UUID scriptId = evalScript(script);
scriptsIds.add(scriptId);
}
Map<UUID, String> scriptIdToHash = getFieldValue(invokeService, "scriptIdToHash");
Map<String, TbelScript> scriptMap = getFieldValue(invokeService, "scriptMap");
Cache<String, Serializable> compiledScriptsCache = getFieldValue(invokeService, "compiledScriptsCache");
String scriptHash = scriptIdToHash.get(scriptsIds.get(0));
assertThat(scriptsIds.stream().map(scriptIdToHash::get)).containsOnly(scriptHash);
assertThat(scriptMap).containsKey(scriptHash);
assertThat(compiledScriptsCache.getIfPresent(scriptHash)).isNotNull();
}
@Test
public void whenReleasingScript_thenCheckForScriptHashUsages() throws Exception {
String script = "return msg.temperature > 20;";
List<UUID> scriptsIds = new ArrayList<>();
for (int i = 0; i < 10; i++) {
UUID scriptId = evalScript(script);
scriptsIds.add(scriptId);
}
Map<UUID, String> scriptIdToHash = getFieldValue(invokeService, "scriptIdToHash");
Map<String, TbelScript> scriptMap = getFieldValue(invokeService, "scriptMap");
Cache<String, Serializable> compiledScriptsCache = getFieldValue(invokeService, "compiledScriptsCache");
String scriptHash = scriptIdToHash.get(scriptsIds.get(0));
for (int i = 0; i < 9; i++) {
UUID scriptId = scriptsIds.get(i);
assertThat(scriptIdToHash).containsKey(scriptId);
invokeService.release(scriptId);
assertThat(scriptIdToHash).doesNotContainKey(scriptId);
}
assertThat(scriptMap).containsKey(scriptHash);
assertThat(compiledScriptsCache.getIfPresent(scriptHash)).isNotNull();
invokeService.release(scriptsIds.get(9));
assertThat(scriptMap).doesNotContainKey(scriptHash);
assertThat(compiledScriptsCache.getIfPresent(scriptHash)).isNull();
}
@Test
public void whenCompiledScriptsCacheIsTooBig_thenRemoveRarelyUsedScripts() throws Exception {
Map<UUID, String> scriptIdToHash = getFieldValue(invokeService, "scriptIdToHash");
Cache<String, Serializable> compiledScriptsCache = getFieldValue(invokeService, "compiledScriptsCache");
List<UUID> scriptsIds = new ArrayList<>();
for (int i = 0; i < 110; i++) { // tbel.compiled_scripts_cache_size = 100
String script = "return msg.temperature > " + i;
UUID scriptId = evalScript(script);
scriptsIds.add(scriptId);
for (int j = 0; j < i; j++) {
invokeScript(scriptId, "{ \"temperature\": 12 }"); // so that scriptsIds is ordered by number of invocations
}
}
ConcurrentMap<String, Serializable> cache = compiledScriptsCache.asMap();
for (int i = 0; i < 10; i++) { // iterating rarely used scripts
UUID scriptId = scriptsIds.get(i);
String scriptHash = scriptIdToHash.get(scriptId);
assertThat(cache).doesNotContainKey(scriptHash);
}
for (int i = 10; i < 110; i++) {
UUID scriptId = scriptsIds.get(i);
String scriptHash = scriptIdToHash.get(scriptId);
assertThat(cache).containsKey(scriptHash);
}
UUID scriptRemovedFromCache = scriptsIds.get(0);
assertThat(compiledScriptsCache.getIfPresent(scriptIdToHash.get(scriptRemovedFromCache))).isNull();
invokeScript(scriptRemovedFromCache, "{ \"temperature\": 12 }");
assertThat(compiledScriptsCache.getIfPresent(scriptIdToHash.get(scriptRemovedFromCache))).isNotNull();
}
private void assertThatScriptIsBlocked(UUID scriptId) {
assertThatThrownBy(() -> {
invokeScript(scriptId, "{}");

10
application/src/test/java/org/thingsboard/server/service/security/auth/JwtTokenFactoryTest.java

@ -23,7 +23,8 @@ import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.common.data.security.model.JwtToken;
import org.thingsboard.server.config.JwtSettings;
import org.thingsboard.server.common.data.security.model.JwtSettings;
import org.thingsboard.server.service.security.auth.jwt.settings.JwtSettingsService;
import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.service.security.model.UserPrincipal;
import org.thingsboard.server.service.security.model.token.AccessJwtToken;
@ -36,6 +37,8 @@ import java.util.UUID;
import java.util.concurrent.TimeUnit;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.willReturn;
import static org.mockito.Mockito.mock;
public class JwtTokenFactoryTest {
@ -50,7 +53,10 @@ public class JwtTokenFactoryTest {
jwtSettings.setTokenExpirationTime((int) TimeUnit.HOURS.toSeconds(2));
jwtSettings.setRefreshTokenExpTime((int) TimeUnit.DAYS.toSeconds(7));
tokenFactory = new JwtTokenFactory(jwtSettings);
JwtSettingsService jwtSettingsService = mock(JwtSettingsService.class);
willReturn(jwtSettings).given(jwtSettingsService).getJwtSettings();
tokenFactory = new JwtTokenFactory(jwtSettingsService);
}
@Test

68
application/src/test/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandlerTest.java

@ -0,0 +1,68 @@
/**
* Copyright © 2016-2022 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.Mock;
import org.springframework.beans.factory.annotation.Autowired;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.security.model.JwtPair;
import org.thingsboard.server.controller.AbstractControllerTest;
import org.thingsboard.server.dao.service.DaoSqlTest;
import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.service.security.model.token.JwtTokenFactory;
import java.util.UUID;
import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.when;
@DaoSqlTest
public class Oauth2AuthenticationSuccessHandlerTest extends AbstractControllerTest {
@Autowired
private Oauth2AuthenticationSuccessHandler oauth2AuthenticationSuccessHandler;
@Mock
private JwtTokenFactory jwtTokenFactory;
private SecurityUser securityUser;
@Before
public void before() {
UserId userId = new UserId(UUID.randomUUID());
securityUser = new SecurityUser(userId);
when(jwtTokenFactory.createTokenPair(eq(securityUser))).thenReturn(new JwtPair("testAccessToken", "testRefreshToken"));
}
@Test
public void testGetRedirectUrl() {
JwtPair jwtPair = jwtTokenFactory.createTokenPair(securityUser);
String urlWithoutParams = "http://localhost:8080/dashboardGroups/3fa13530-6597-11ed-bd76-8bd591f0ec3e";
String urlWithParams = "http://localhost:8080/dashboardGroups/3fa13530-6597-11ed-bd76-8bd591f0ec3e?state=someState&page=1";
String redirectUrl = oauth2AuthenticationSuccessHandler.getRedirectUrl(urlWithoutParams, jwtPair);
String expectedUrl = urlWithoutParams + "/?accessToken=" + jwtPair.getToken() + "&refreshToken=" + jwtPair.getRefreshToken();
assertEquals(expectedUrl, redirectUrl);
redirectUrl = oauth2AuthenticationSuccessHandler.getRedirectUrl(urlWithParams, jwtPair);
expectedUrl = urlWithParams + "&accessToken=" + jwtPair.getToken() + "&refreshToken=" + jwtPair.getRefreshToken();
assertEquals(expectedUrl, redirectUrl);
}
}

2
application/src/test/resources/logback-test.xml

@ -16,6 +16,8 @@
<logger name="org.cassandraunit" level="INFO"/>
<logger name="org.eclipse.leshan" level="INFO"/>
<!-- mute TelemetryEdgeSqlTest that causes a lot of randomly generated errors -->
<logger name="org.thingsboard.server.service.edge.rpc.EdgeGrpcSession" level="OFF"/>
<root level="WARN">
<appender-ref ref="console"/>

2
common/actor/pom.xml

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

2
common/cache/pom.xml

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

2
common/cluster-api/pom.xml

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

1
common/cluster-api/src/main/proto/queue.proto

@ -600,6 +600,7 @@ message TbAttributeDeleteProto {
int64 tenantIdLSB = 5;
string scope = 6;
repeated string keys = 7;
bool notifyDevice = 8;
}
message TbTimeSeriesDeleteProto {

2
common/coap-server/pom.xml

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

2
common/dao-api/pom.xml

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

2
common/data/pom.xml

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

1
common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java

@ -28,6 +28,7 @@ public class DataConstants {
public static final String CLIENT_SCOPE = "CLIENT_SCOPE";
public static final String SERVER_SCOPE = "SERVER_SCOPE";
public static final String SHARED_SCOPE = "SHARED_SCOPE";
public static final String NOTIFY_DEVICE_METADATA_KEY = "notifyDevice";
public static final String LATEST_TS = "LATEST_TS";
public static final String IS_NEW_ALARM = "isNewAlarm";
public static final String IS_EXISTING_ALARM = "isExistingAlarm";

19
common/data/src/main/java/org/thingsboard/server/common/data/EdgeUtils.java

@ -16,6 +16,7 @@
package org.thingsboard.server.common.data;
import com.fasterxml.jackson.databind.JsonNode;
import com.google.common.base.Throwables;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.common.data.edge.EdgeEvent;
import org.thingsboard.server.common.data.edge.EdgeEventActionType;
@ -29,6 +30,8 @@ import java.util.concurrent.ThreadLocalRandom;
@Slf4j
public final class EdgeUtils {
private static final int STACK_TRACE_LIMIT = 10;
private EdgeUtils() {
}
@ -93,4 +96,20 @@ public final class EdgeUtils {
edgeEvent.setBody(body);
return edgeEvent;
}
public static String createErrorMsgFromRootCauseAndStackTrace(Throwable t) {
Throwable rootCause = Throwables.getRootCause(t);
StringBuilder errorMsg = new StringBuilder(rootCause.getMessage() != null ? rootCause.getMessage() : "");
if (rootCause.getStackTrace().length > 0) {
int idx = 0;
for (StackTraceElement stackTraceElement : rootCause.getStackTrace()) {
errorMsg.append("\n").append(stackTraceElement.toString());
idx++;
if (idx > STACK_TRACE_LIMIT) {
break;
}
}
}
return errorMsg.toString();
}
}

2
common/data/src/main/java/org/thingsboard/server/common/data/SearchTextBasedWithAdditionalInfo.java

@ -21,6 +21,7 @@ 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;
@ -36,6 +37,7 @@ import java.util.function.Consumer;
public abstract class SearchTextBasedWithAdditionalInfo<I extends UUIDBased> extends SearchTextBased<I> implements HasAdditionalInfo {
public static final ObjectMapper mapper = new ObjectMapper();
@NoXss
private transient JsonNode additionalInfo;
@JsonIgnore
private byte[] additionalInfoBytes;

2
common/data/src/main/java/org/thingsboard/server/common/data/alarm/Alarm.java

@ -31,6 +31,7 @@ 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.data.validation.Length;
import org.thingsboard.server.common.data.validation.NoXss;
import java.util.List;
@ -49,6 +50,7 @@ public class Alarm extends BaseData<AlarmId> implements HasName, HasTenantId, Ha
@ApiModelProperty(position = 4, value = "JSON object with Customer Id", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
private CustomerId customerId;
@NoXss
@ApiModelProperty(position = 6, required = true, value = "representing type of the Alarm", example = "High Temperature Alarm")
@Length(fieldName = "type")
private String type;

8
common/data/src/main/java/org/thingsboard/server/common/data/kv/BaseReadTsKvQuery.java

@ -47,4 +47,12 @@ public class BaseReadTsKvQuery extends BaseTsKvQuery implements ReadTsKvQuery {
this(key, startTs, endTs, endTs - startTs, limit, Aggregation.NONE, order);
}
public BaseReadTsKvQuery(ReadTsKvQuery query, long startTs, long endTs) {
super(query.getId(), query.getKey(), startTs, endTs);
this.interval = query.getInterval();
this.limit = query.getLimit();
this.aggregation = query.getAggregation();
this.order = query.getOrder();
}
}

6
common/data/src/main/java/org/thingsboard/server/common/data/kv/BaseTsKvQuery.java

@ -28,8 +28,12 @@ public class BaseTsKvQuery implements TsKvQuery {
private final long endTs;
public BaseTsKvQuery(String key, long startTs, long endTs) {
this.id = idSeq.get();
this(idSeq.get(), key, startTs, endTs);
idSeq.set(id + 1);
}
protected BaseTsKvQuery(int id, String key, long startTs, long endTs) {
this.id = id;
this.key = key;
this.startTs = startTs;
this.endTs = endTs;

1
common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleChainMetaData.java

@ -39,7 +39,6 @@ public class RuleChainMetaData {
@ApiModelProperty(position = 2, required = true, value = "Index of the first rule node in the 'nodes' list")
private Integer firstNodeIndex;
@Valid
@ApiModelProperty(position = 3, required = true, value = "List of rule node JSON objects")
private List<RuleNode> nodes;

2
common/data/src/main/java/org/thingsboard/server/common/data/script/ScriptLanguage.java

@ -16,5 +16,5 @@
package org.thingsboard.server.common.data.script;
public enum ScriptLanguage {
JS, MVEL
JS, TBEL
}

9
application/src/main/java/org/thingsboard/server/service/security/model/JwtTokenPair.java → common/data/src/main/java/org/thingsboard/server/common/data/security/model/JwtPair.java

@ -13,19 +13,18 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.security.model;
package org.thingsboard.server.common.data.security.model;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.thingsboard.server.common.data.security.Authority;
@ApiModel(value = "JWT Token Pair")
@ApiModel(value = "JWT Pair")
@Data
@NoArgsConstructor
public class JwtTokenPair {
public class JwtPair {
@ApiModelProperty(position = 1, value = "The JWT Access Token. Used to perform API calls.", example = "AAB254FF67D..")
private String token;
@ -34,7 +33,7 @@ public class JwtTokenPair {
private Authority scope;
public JwtTokenPair(String token, String refreshToken) {
public JwtPair(String token, String refreshToken) {
this.token = token;
this.refreshToken = refreshToken;
}

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

Loading…
Cancel
Save