Browse Source

WebSocket API key authentication

pull/15170/head
Andrii Landiak 6 months ago
parent
commit
cda0a96f8d
  1. 69
      application/src/main/java/org/thingsboard/server/config/ApiKeyHandshakeInterceptor.java
  2. 5
      application/src/main/java/org/thingsboard/server/config/WebSocketConfiguration.java
  3. 34
      application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketHandler.java
  4. 2
      application/src/main/java/org/thingsboard/server/service/security/auth/pat/ApiKeyAuthenticationProvider.java
  5. 3
      application/src/main/java/org/thingsboard/server/service/ws/AuthCmd.java
  6. 18
      application/src/test/java/org/thingsboard/server/controller/AbstractControllerTest.java
  7. 67
      application/src/test/java/org/thingsboard/server/controller/ApiKeyWebSocketApiTest.java
  8. 16
      application/src/test/java/org/thingsboard/server/controller/TbTestWebSocketClient.java
  9. 195
      application/src/test/java/org/thingsboard/server/controller/WebSocketApiTest.java
  10. 106
      application/src/test/java/org/thingsboard/server/controller/plugin/TbWebSocketHandlerTest.java

69
application/src/main/java/org/thingsboard/server/config/ApiKeyHandshakeInterceptor.java

@ -0,0 +1,69 @@
/**
* Copyright © 2016-2026 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.config;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.server.HandshakeInterceptor;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.security.auth.pat.ApiKeyAuthenticationProvider;
import org.thingsboard.server.service.security.model.SecurityUser;
import java.util.Map;
@Slf4j
@Component
@TbCoreComponent
@RequiredArgsConstructor
public class ApiKeyHandshakeInterceptor implements HandshakeInterceptor {
public static final String API_KEY_HEADER = "X-API-Key";
public static final String API_KEY_SECURITY_CTX_ATTR = "apiKeySecurityCtx";
private final ApiKeyAuthenticationProvider apiKeyAuthenticationProvider;
@Override
public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) {
String apiKey = request.getHeaders().getFirst(API_KEY_HEADER);
if (apiKey != null) {
if (apiKey.isEmpty()) {
log.debug("Empty API key provided during WS handshake");
response.setStatusCode(HttpStatus.UNAUTHORIZED);
return false;
}
try {
SecurityUser securityUser = apiKeyAuthenticationProvider.authenticate(apiKey);
attributes.put(API_KEY_SECURITY_CTX_ATTR, securityUser);
} catch (Exception e) {
log.debug("API key authentication failed during WS handshake: {}", e.getMessage());
response.setStatusCode(HttpStatus.UNAUTHORIZED);
return false;
}
}
return true;
}
@Override
public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception exception) {
// no-op
}
}

5
application/src/main/java/org/thingsboard/server/config/WebSocketConfiguration.java

@ -40,6 +40,7 @@ public class WebSocketConfiguration implements WebSocketConfigurer {
private static final String WS_API_MAPPING = "/api/ws/**"; private static final String WS_API_MAPPING = "/api/ws/**";
private final WebSocketHandler wsHandler; private final WebSocketHandler wsHandler;
private final ApiKeyHandshakeInterceptor apiKeyHandshakeInterceptor;
@Value("${server.ws.max_text_message_buffer_size:32768}") @Value("${server.ws.max_text_message_buffer_size:32768}")
private int maxTextMessageBufferSize; private int maxTextMessageBufferSize;
@ -60,7 +61,9 @@ public class WebSocketConfiguration implements WebSocketConfigurer {
log.error("TbWebSocketHandler expected but [{}] provided", wsHandler); log.error("TbWebSocketHandler expected but [{}] provided", wsHandler);
throw new RuntimeException("TbWebSocketHandler expected but " + wsHandler + " provided"); throw new RuntimeException("TbWebSocketHandler expected but " + wsHandler + " provided");
} }
registry.addHandler(wsHandler, WS_API_MAPPING).setAllowedOriginPatterns("*"); registry.addHandler(wsHandler, WS_API_MAPPING)
.addInterceptors(apiKeyHandshakeInterceptor)
.setAllowedOriginPatterns("*");
} }
} }

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

@ -48,10 +48,12 @@ import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.limit.LimitedApi; import org.thingsboard.server.common.data.limit.LimitedApi;
import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
import org.thingsboard.server.config.ApiKeyHandshakeInterceptor;
import org.thingsboard.server.config.WebSocketConfiguration; import org.thingsboard.server.config.WebSocketConfiguration;
import org.thingsboard.server.dao.tenant.TbTenantProfileCache; import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.security.auth.jwt.JwtAuthenticationProvider; import org.thingsboard.server.service.security.auth.jwt.JwtAuthenticationProvider;
import org.thingsboard.server.service.security.auth.pat.ApiKeyAuthenticationProvider;
import org.thingsboard.server.service.security.exception.JwtExpiredTokenException; import org.thingsboard.server.service.security.exception.JwtExpiredTokenException;
import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.service.security.model.UserPrincipal; import org.thingsboard.server.service.security.model.UserPrincipal;
@ -100,6 +102,8 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements WebSocke
private RateLimitService rateLimitService; private RateLimitService rateLimitService;
@Autowired @Autowired
private JwtAuthenticationProvider authenticationProvider; private JwtAuthenticationProvider authenticationProvider;
@Autowired
private ApiKeyAuthenticationProvider apiKeyAuthenticationProvider;
@Value("${server.ws.send_timeout:5000}") @Value("${server.ws.send_timeout:5000}")
private long sendTimeout; private long sendTimeout;
@ -194,7 +198,11 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements WebSocke
log.trace("{} Authenticating session", sessionRef); log.trace("{} Authenticating session", sessionRef);
SecurityUser securityCtx; SecurityUser securityCtx;
try { try {
securityCtx = authenticationProvider.authenticate(authCmd.getToken()); if (StringUtils.isNotEmpty(authCmd.getApiKey())) {
securityCtx = apiKeyAuthenticationProvider.authenticate(authCmd.getApiKey());
} else {
securityCtx = authenticationProvider.authenticate(authCmd.getToken());
}
} catch (Exception e) { } catch (Exception e) {
close(sessionRef, CloseStatus.BAD_DATA.withReason(e.getMessage())); close(sessionRef, CloseStatus.BAD_DATA.withReason(e.getMessage()));
return; return;
@ -328,9 +336,17 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements WebSocke
} }
SecurityUser securityCtx = null; SecurityUser securityCtx = null;
String token = StringUtils.substringAfter(session.getUri().getQuery(), "token="); Object apiKeyCtx = session.getAttributes().get(ApiKeyHandshakeInterceptor.API_KEY_SECURITY_CTX_ATTR);
if (StringUtils.isNotEmpty(token)) { if (apiKeyCtx instanceof SecurityUser) {
securityCtx = authenticationProvider.authenticate(token); securityCtx = (SecurityUser) apiKeyCtx;
} else {
String query = session.getUri().getQuery();
if (query != null) {
String token = extractQueryParam(query, "token");
if (StringUtils.isNotEmpty(token)) {
securityCtx = authenticationProvider.authenticate(token);
}
}
} }
return WebSocketSessionRef.builder() return WebSocketSessionRef.builder()
.sessionId(UUID.randomUUID().toString()) .sessionId(UUID.randomUUID().toString())
@ -341,6 +357,15 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements WebSocke
.build(); .build();
} }
private String extractQueryParam(String query, String paramName) {
for (String param : query.split("&")) {
if (param.startsWith(paramName + "=")) {
return param.substring(paramName.length() + 1);
}
}
return null;
}
private SessionMetaData getSessionMd(String internalSessionId) { private SessionMetaData getSessionMd(String internalSessionId) {
SessionMetaData sessionMd = internalSessionMap.get(internalSessionId); SessionMetaData sessionMd = internalSessionMap.get(internalSessionId);
if (sessionMd == null) { if (sessionMd == null) {
@ -482,6 +507,7 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements WebSocke
} }
} }
} }
} }
@Override @Override

2
application/src/main/java/org/thingsboard/server/service/security/auth/pat/ApiKeyAuthenticationProvider.java

@ -46,7 +46,7 @@ public class ApiKeyAuthenticationProvider extends AbstractAuthenticationProvider
return new ApiKeyAuthenticationToken(securityUser); return new ApiKeyAuthenticationToken(securityUser);
} }
private SecurityUser authenticate(String key) { public SecurityUser authenticate(String key) {
if (StringUtils.isEmpty(key)) { if (StringUtils.isEmpty(key)) {
throw new BadCredentialsException("Empty API key"); throw new BadCredentialsException("Empty API key");
} }

3
application/src/main/java/org/thingsboard/server/service/ws/AuthCmd.java

@ -23,11 +23,14 @@ import lombok.NoArgsConstructor;
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
public class AuthCmd implements WsCmd { public class AuthCmd implements WsCmd {
private int cmdId; private int cmdId;
private String token; private String token;
private String apiKey;
@Override @Override
public WsCmdType getType() { public WsCmdType getType() {
return WsCmdType.AUTH; return WsCmdType.AUTH;
} }
} }

18
application/src/test/java/org/thingsboard/server/controller/AbstractControllerTest.java

@ -32,6 +32,7 @@ import org.springframework.web.socket.config.annotation.EnableWebSocket;
import java.net.URI; import java.net.URI;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import java.util.Map;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
@ -86,12 +87,12 @@ public abstract class AbstractControllerTest extends AbstractNotifyEntityTest {
} }
@Before @Before
public void beforeWsTest() throws Exception { public void beforeWsTest() {
// placeholder // placeholder
} }
@After @After
public void afterWsTest() throws Exception { public void afterWsTest() {
if (wsClient != null) { if (wsClient != null) {
wsClient.close(); wsClient.close();
} }
@ -113,4 +114,17 @@ public abstract class AbstractControllerTest extends AbstractNotifyEntityTest {
return wsClient; return wsClient;
} }
protected TbTestWebSocketClient buildAndConnectWebSocketClientWithApiKey(String apiKey) throws URISyntaxException, InterruptedException {
TbTestWebSocketClient wsClient = new TbTestWebSocketClient(new URI(WS_URL + wsPort + "/api/ws"));
assertThat(wsClient.connectBlocking(TIMEOUT, TimeUnit.SECONDS)).isTrue();
wsClient.authenticateWithApiKey(apiKey);
return wsClient;
}
protected TbTestWebSocketClient buildAndConnectWebSocketClientWithApiKeyHeader(String apiKey) throws URISyntaxException, InterruptedException {
TbTestWebSocketClient wsClient = new TbTestWebSocketClient(new URI(WS_URL + wsPort + "/api/ws"), Map.of("X-API-Key", apiKey));
assertThat(wsClient.connectBlocking(TIMEOUT, TimeUnit.SECONDS)).isTrue();
return wsClient;
}
} }

67
application/src/test/java/org/thingsboard/server/controller/ApiKeyWebSocketApiTest.java

@ -0,0 +1,67 @@
/**
* Copyright © 2016-2026 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.controller;
import lombok.extern.slf4j.Slf4j;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.thingsboard.server.common.data.pat.ApiKey;
import org.thingsboard.server.common.data.pat.ApiKeyInfo;
import org.thingsboard.server.dao.service.DaoSqlTest;
import java.net.URISyntaxException;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@Slf4j
@DaoSqlTest
public class ApiKeyWebSocketApiTest extends WebSocketApiTest {
private ApiKey apiKey;
@Before
public void setUpApiKey() {
ApiKeyInfo apiKeyInfo = new ApiKeyInfo();
apiKeyInfo.setDescription("WS test API key");
apiKeyInfo.setEnabled(true);
apiKeyInfo.setUserId(tenantAdminUserId);
apiKey = doPost("/api/apiKey", apiKeyInfo, ApiKey.class);
}
@After
public void tearDownApiKey() throws Exception {
loginTenantAdmin();
doDelete("/api/apiKey/" + apiKey.getId()).andExpect(status().isOk());
}
@Override
protected TbTestWebSocketClient buildAndConnectWebSocketClient() throws URISyntaxException, InterruptedException {
return buildAndConnectWebSocketClientWithApiKey(apiKey.getValue());
}
@Test
public void testApiKeyHeaderAuth() throws Exception {
TbTestWebSocketClient client = buildAndConnectWebSocketClientWithApiKeyHeader(apiKey.getValue());
try {
assertThat(client.isOpen()).isTrue();
} finally {
client.close();
}
}
}

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

@ -39,12 +39,12 @@ import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityDataUpdate;
import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityHistoryCmd; import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityHistoryCmd;
import org.thingsboard.server.service.ws.telemetry.cmd.v2.LatestValueCmd; import org.thingsboard.server.service.ws.telemetry.cmd.v2.LatestValueCmd;
import org.thingsboard.server.service.ws.telemetry.cmd.v2.TimeSeriesCmd; import org.thingsboard.server.service.ws.telemetry.cmd.v2.TimeSeriesCmd;
import org.thingsboard.server.service.ws.telemetry.sub.TelemetrySubscriptionUpdate;
import java.net.URI; import java.net.URI;
import java.nio.channels.NotYetConnectedException; import java.nio.channels.NotYetConnectedException;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@ -62,6 +62,10 @@ public class TbTestWebSocketClient extends WebSocketClient {
super(serverUri); super(serverUri);
} }
public TbTestWebSocketClient(URI serverUri, Map<String, String> httpHeaders) {
super(serverUri, httpHeaders);
}
@Override @Override
public void onOpen(ServerHandshake serverHandshake) { public void onOpen(ServerHandshake serverHandshake) {
@ -69,7 +73,13 @@ public class TbTestWebSocketClient extends WebSocketClient {
public void authenticate(String token) { public void authenticate(String token) {
WsCommandsWrapper cmdsWrapper = new WsCommandsWrapper(); WsCommandsWrapper cmdsWrapper = new WsCommandsWrapper();
cmdsWrapper.setAuthCmd(new AuthCmd(1, token)); cmdsWrapper.setAuthCmd(new AuthCmd(1, token, null));
send(JacksonUtil.toString(cmdsWrapper));
}
public void authenticateWithApiKey(String apiKey) {
WsCommandsWrapper cmdsWrapper = new WsCommandsWrapper();
cmdsWrapper.setAuthCmd(new AuthCmd(1, null, apiKey));
send(JacksonUtil.toString(cmdsWrapper)); send(JacksonUtil.toString(cmdsWrapper));
} }
@ -275,7 +285,7 @@ public class TbTestWebSocketClient extends WebSocketClient {
public JsonNode sendTimeseriesCmd(EntityId entityId, String scope) { public JsonNode sendTimeseriesCmd(EntityId entityId, String scope) {
log.warn("sendTimeseriesCmd entityId: {}, scope: {}", entityId, scope); log.warn("sendTimeseriesCmd entityId: {}, scope: {}", entityId, scope);
TimeseriesSubscriptionCmd cmd = new TimeseriesSubscriptionCmd(0, 0, 0, 10, null); TimeseriesSubscriptionCmd cmd = new TimeseriesSubscriptionCmd(0, 0, 0, 10, null);
cmd.setEntityId(entityId.getId().toString()); cmd.setEntityId(entityId.getId().toString());
cmd.setEntityType(entityId.getEntityType().toString()); cmd.setEntityType(entityId.getEntityType().toString());
cmd.setCmdId(1); cmd.setCmdId(1);

195
application/src/test/java/org/thingsboard/server/controller/WebsocketApiTest.java → application/src/test/java/org/thingsboard/server/controller/WebSocketApiTest.java

@ -71,13 +71,11 @@ import org.thingsboard.server.service.ws.telemetry.cmd.v2.AlarmStatusUpdate;
import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityCountCmd; import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityCountCmd;
import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityCountUpdate; import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityCountUpdate;
import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityDataUpdate; import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityDataUpdate;
import org.thingsboard.server.service.ws.telemetry.sub.TelemetrySubscriptionUpdate;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@ -91,7 +89,8 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
"server.ws.alarms_per_alarm_status_subscription_cache_size=5", "server.ws.alarms_per_alarm_status_subscription_cache_size=5",
"server.ws.dynamic_page_link.refresh_interval=15" "server.ws.dynamic_page_link.refresh_interval=15"
}) })
public class WebsocketApiTest extends AbstractControllerTest { public class WebSocketApiTest extends AbstractControllerTest {
@Autowired @Autowired
private TelemetrySubscriptionService tsService; private TelemetrySubscriptionService tsService;
@ -128,8 +127,8 @@ public class WebsocketApiTest extends AbstractControllerTest {
PageData<EntityData> pageData = update.getData(); PageData<EntityData> pageData = update.getData();
Assert.assertNotNull(pageData); Assert.assertNotNull(pageData);
Assert.assertEquals(1, pageData.getData().size()); Assert.assertEquals(1, pageData.getData().size());
Assert.assertEquals(device.getId(), pageData.getData().get(0).getEntityId()); Assert.assertEquals(device.getId(), pageData.getData().getFirst().getEntityId());
Assert.assertEquals(0, pageData.getData().get(0).getTimeseries().get("temperature").length); Assert.assertEquals(0, pageData.getData().getFirst().getTimeseries().get("temperature").length);
TsKvEntry dataPoint1 = new BasicTsKvEntry(now - TimeUnit.MINUTES.toMillis(1), new LongDataEntry("temperature", 42L)); TsKvEntry dataPoint1 = new BasicTsKvEntry(now - TimeUnit.MINUTES.toMillis(1), new LongDataEntry("temperature", 42L));
TsKvEntry dataPoint2 = new BasicTsKvEntry(now - TimeUnit.MINUTES.toMillis(2), new LongDataEntry("temperature", 42L)); TsKvEntry dataPoint2 = new BasicTsKvEntry(now - TimeUnit.MINUTES.toMillis(2), new LongDataEntry("temperature", 42L));
@ -144,8 +143,8 @@ public class WebsocketApiTest extends AbstractControllerTest {
List<EntityData> dataList = update.getUpdate(); List<EntityData> dataList = update.getUpdate();
Assert.assertNotNull(dataList); Assert.assertNotNull(dataList);
Assert.assertEquals(1, dataList.size()); Assert.assertEquals(1, dataList.size());
Assert.assertEquals(device.getId(), dataList.get(0).getEntityId()); Assert.assertEquals(device.getId(), dataList.getFirst().getEntityId());
TsValue[] tsArray = dataList.get(0).getTimeseries().get("temperature"); TsValue[] tsArray = dataList.getFirst().getTimeseries().get("temperature");
Assert.assertEquals(3, tsArray.length); Assert.assertEquals(3, tsArray.length);
Assert.assertEquals(new TsValue(dataPoint1.getTs(), dataPoint1.getValueAsString()), tsArray[0]); Assert.assertEquals(new TsValue(dataPoint1.getTs(), dataPoint1.getValueAsString()), tsArray[0]);
Assert.assertEquals(new TsValue(dataPoint2.getTs(), dataPoint2.getValueAsString()), tsArray[1]); Assert.assertEquals(new TsValue(dataPoint2.getTs(), dataPoint2.getValueAsString()), tsArray[1]);
@ -162,7 +161,7 @@ public class WebsocketApiTest extends AbstractControllerTest {
PageData<EntityData> pageData = update.getData(); PageData<EntityData> pageData = update.getData();
Assert.assertNotNull(pageData); Assert.assertNotNull(pageData);
Assert.assertEquals(1, pageData.getData().size()); Assert.assertEquals(1, pageData.getData().size());
Assert.assertEquals(device.getId(), pageData.getData().get(0).getEntityId()); Assert.assertEquals(device.getId(), pageData.getData().getFirst().getEntityId());
TsKvEntry dataPoint1 = new BasicTsKvEntry(now - TimeUnit.MINUTES.toMillis(1), new LongDataEntry("temperature", 42L)); TsKvEntry dataPoint1 = new BasicTsKvEntry(now - TimeUnit.MINUTES.toMillis(1), new LongDataEntry("temperature", 42L));
TsKvEntry dataPoint2 = new BasicTsKvEntry(now - TimeUnit.MINUTES.toMillis(2), new LongDataEntry("temperature", 43L)); TsKvEntry dataPoint2 = new BasicTsKvEntry(now - TimeUnit.MINUTES.toMillis(2), new LongDataEntry("temperature", 43L));
@ -176,8 +175,8 @@ public class WebsocketApiTest extends AbstractControllerTest {
List<EntityData> listData = update.getUpdate(); List<EntityData> listData = update.getUpdate();
Assert.assertNotNull(listData); Assert.assertNotNull(listData);
Assert.assertEquals(1, listData.size()); Assert.assertEquals(1, listData.size());
Assert.assertEquals(device.getId(), listData.get(0).getEntityId()); Assert.assertEquals(device.getId(), listData.getFirst().getEntityId());
TsValue[] tsArray = listData.get(0).getTimeseries().get("temperature"); TsValue[] tsArray = listData.getFirst().getTimeseries().get("temperature");
Assert.assertEquals(3, tsArray.length); Assert.assertEquals(3, tsArray.length);
Assert.assertEquals(new TsValue(dataPoint1.getTs(), dataPoint1.getValueAsString()), tsArray[0]); Assert.assertEquals(new TsValue(dataPoint1.getTs(), dataPoint1.getValueAsString()), tsArray[0]);
Assert.assertEquals(new TsValue(dataPoint2.getTs(), dataPoint2.getValueAsString()), tsArray[1]); Assert.assertEquals(new TsValue(dataPoint2.getTs(), dataPoint2.getValueAsString()), tsArray[1]);
@ -186,7 +185,7 @@ public class WebsocketApiTest extends AbstractControllerTest {
now = System.currentTimeMillis(); now = System.currentTimeMillis();
TsKvEntry dataPoint4 = new BasicTsKvEntry(now, new LongDataEntry("temperature", 45L)); TsKvEntry dataPoint4 = new BasicTsKvEntry(now, new LongDataEntry("temperature", 45L));
getWsClient().registerWaitForUpdate(); getWsClient().registerWaitForUpdate();
sendTelemetry(device, Arrays.asList(dataPoint4)); sendTelemetry(device, List.of(dataPoint4));
String msg = getWsClient().waitForUpdate(); String msg = getWsClient().waitForUpdate();
update = JacksonUtil.fromString(msg, EntityDataUpdate.class); update = JacksonUtil.fromString(msg, EntityDataUpdate.class);
@ -194,9 +193,9 @@ public class WebsocketApiTest extends AbstractControllerTest {
List<EntityData> eData = update.getUpdate(); List<EntityData> eData = update.getUpdate();
Assert.assertNotNull(eData); Assert.assertNotNull(eData);
Assert.assertEquals(1, eData.size()); Assert.assertEquals(1, eData.size());
Assert.assertEquals(device.getId(), eData.get(0).getEntityId()); Assert.assertEquals(device.getId(), eData.getFirst().getEntityId());
Assert.assertNotNull(eData.get(0).getTimeseries()); Assert.assertNotNull(eData.getFirst().getTimeseries());
TsValue[] tsValues = eData.get(0).getTimeseries().get("temperature"); TsValue[] tsValues = eData.getFirst().getTimeseries().get("temperature");
Assert.assertNotNull(tsValues); Assert.assertNotNull(tsValues);
Assert.assertEquals(new TsValue(dataPoint4.getTs(), dataPoint4.getValueAsString()), tsValues[0]); Assert.assertEquals(new TsValue(dataPoint4.getTs(), dataPoint4.getValueAsString()), tsValues[0]);
} }
@ -521,7 +520,7 @@ public class WebsocketApiTest extends AbstractControllerTest {
String msg = getWsClient().waitForUpdate(TimeUnit.SECONDS.toMillis(1)); String msg = getWsClient().waitForUpdate(TimeUnit.SECONDS.toMillis(1));
Assert.assertNull(msg); Assert.assertNull(msg);
// check device // check a device
AlarmStatusCmd deviceCmd = new AlarmStatusCmd(2, device.getId(), null, List.of(AlarmSeverity.CRITICAL)); AlarmStatusCmd deviceCmd = new AlarmStatusCmd(2, device.getId(), null, List.of(AlarmSeverity.CRITICAL));
getWsClient().send(deviceCmd); getWsClient().send(deviceCmd);
@ -589,13 +588,13 @@ public class WebsocketApiTest extends AbstractControllerTest {
PageData<EntityData> pageData = update.getData(); PageData<EntityData> pageData = update.getData();
Assert.assertNotNull(pageData); Assert.assertNotNull(pageData);
Assert.assertEquals(1, pageData.getData().size()); Assert.assertEquals(1, pageData.getData().size());
Assert.assertEquals(device.getId(), pageData.getData().get(0).getEntityId()); Assert.assertEquals(device.getId(), pageData.getData().getFirst().getEntityId());
Assert.assertNotNull(pageData.getData().get(0).getLatest().get(EntityKeyType.TIME_SERIES).get("temperature")); Assert.assertNotNull(pageData.getData().getFirst().getLatest().get(EntityKeyType.TIME_SERIES).get("temperature"));
Assert.assertEquals(0, pageData.getData().get(0).getLatest().get(EntityKeyType.TIME_SERIES).get("temperature").getTs()); Assert.assertEquals(0, pageData.getData().getFirst().getLatest().get(EntityKeyType.TIME_SERIES).get("temperature").getTs());
Assert.assertEquals("", pageData.getData().get(0).getLatest().get(EntityKeyType.TIME_SERIES).get("temperature").getValue()); Assert.assertEquals("", pageData.getData().getFirst().getLatest().get(EntityKeyType.TIME_SERIES).get("temperature").getValue());
TsKvEntry dataPoint1 = new BasicTsKvEntry(now - TimeUnit.MINUTES.toMillis(1), new LongDataEntry("temperature", 42L)); TsKvEntry dataPoint1 = new BasicTsKvEntry(now - TimeUnit.MINUTES.toMillis(1), new LongDataEntry("temperature", 42L));
List<TsKvEntry> tsData = Arrays.asList(dataPoint1); List<TsKvEntry> tsData = List.of(dataPoint1);
sendTelemetry(device, tsData); sendTelemetry(device, tsData);
update = getWsClient().subscribeLatestUpdate(keys); update = getWsClient().subscribeLatestUpdate(keys);
@ -605,36 +604,36 @@ public class WebsocketApiTest extends AbstractControllerTest {
List<EntityData> listData = update.getUpdate(); List<EntityData> listData = update.getUpdate();
Assert.assertNotNull(listData); Assert.assertNotNull(listData);
Assert.assertEquals(1, listData.size()); Assert.assertEquals(1, listData.size());
Assert.assertEquals(device.getId(), listData.get(0).getEntityId()); Assert.assertEquals(device.getId(), listData.getFirst().getEntityId());
Assert.assertNotNull(listData.get(0).getLatest().get(EntityKeyType.TIME_SERIES)); Assert.assertNotNull(listData.getFirst().getLatest().get(EntityKeyType.TIME_SERIES));
TsValue tsValue = listData.get(0).getLatest().get(EntityKeyType.TIME_SERIES).get("temperature"); TsValue tsValue = listData.getFirst().getLatest().get(EntityKeyType.TIME_SERIES).get("temperature");
Assert.assertEquals(new TsValue(dataPoint1.getTs(), dataPoint1.getValueAsString()), tsValue); Assert.assertEquals(new TsValue(dataPoint1.getTs(), dataPoint1.getValueAsString()), tsValue);
now = System.currentTimeMillis(); now = System.currentTimeMillis();
TsKvEntry dataPoint2 = new BasicTsKvEntry(now, new LongDataEntry("temperature", 52L)); TsKvEntry dataPoint2 = new BasicTsKvEntry(now, new LongDataEntry("temperature", 52L));
getWsClient().registerWaitForUpdate(); getWsClient().registerWaitForUpdate();
sendTelemetry(device, Arrays.asList(dataPoint2)); sendTelemetry(device, List.of(dataPoint2));
update = getWsClient().parseDataReply(getWsClient().waitForUpdate()); update = getWsClient().parseDataReply(getWsClient().waitForUpdate());
Assert.assertEquals(1, update.getCmdId()); Assert.assertEquals(1, update.getCmdId());
List<EntityData> eData = update.getUpdate(); List<EntityData> eData = update.getUpdate();
Assert.assertNotNull(eData); Assert.assertNotNull(eData);
Assert.assertEquals(1, eData.size()); Assert.assertEquals(1, eData.size());
Assert.assertEquals(device.getId(), eData.get(0).getEntityId()); Assert.assertEquals(device.getId(), eData.getFirst().getEntityId());
Assert.assertNotNull(eData.get(0).getLatest().get(EntityKeyType.TIME_SERIES)); Assert.assertNotNull(eData.getFirst().getLatest().get(EntityKeyType.TIME_SERIES));
tsValue = eData.get(0).getLatest().get(EntityKeyType.TIME_SERIES).get("temperature"); tsValue = eData.getFirst().getLatest().get(EntityKeyType.TIME_SERIES).get("temperature");
Assert.assertEquals(new TsValue(dataPoint2.getTs(), dataPoint2.getValueAsString()), tsValue); Assert.assertEquals(new TsValue(dataPoint2.getTs(), dataPoint2.getValueAsString()), tsValue);
//Sending update from the past, while latest value has new timestamp; //Sending update from the past, while latest value has new timestamp;
getWsClient().registerWaitForUpdate(); getWsClient().registerWaitForUpdate();
sendTelemetry(device, Arrays.asList(dataPoint1)); sendTelemetry(device, List.of(dataPoint1));
String msg = getWsClient().waitForUpdate(TimeUnit.SECONDS.toMillis(1)); String msg = getWsClient().waitForUpdate(TimeUnit.SECONDS.toMillis(1));
Assert.assertNull(msg); Assert.assertNull(msg);
//Sending duplicate update again //Sending duplicate update again
getWsClient().registerWaitForUpdate(); getWsClient().registerWaitForUpdate();
sendTelemetry(device, Arrays.asList(dataPoint2)); sendTelemetry(device, List.of(dataPoint2));
msg = getWsClient().waitForUpdate(TimeUnit.SECONDS.toMillis(1)); msg = getWsClient().waitForUpdate(TimeUnit.SECONDS.toMillis(1));
Assert.assertNull(msg); Assert.assertNull(msg);
} }
@ -677,14 +676,14 @@ public class WebsocketApiTest extends AbstractControllerTest {
PageData<EntityData> pageData = update.getData(); PageData<EntityData> pageData = update.getData();
Assert.assertNotNull(pageData); Assert.assertNotNull(pageData);
Assert.assertEquals(1, pageData.getData().size()); Assert.assertEquals(1, pageData.getData().size());
Assert.assertEquals(device.getId(), pageData.getData().get(0).getEntityId()); Assert.assertEquals(device.getId(), pageData.getData().getFirst().getEntityId());
Assert.assertNotNull(pageData.getData().get(0).getLatest().get(EntityKeyType.TIME_SERIES).get("temperature")); Assert.assertNotNull(pageData.getData().getFirst().getLatest().get(EntityKeyType.TIME_SERIES).get("temperature"));
Assert.assertEquals(0, pageData.getData().get(0).getLatest().get(EntityKeyType.TIME_SERIES).get("temperature").getTs()); Assert.assertEquals(0, pageData.getData().getFirst().getLatest().get(EntityKeyType.TIME_SERIES).get("temperature").getTs());
Assert.assertEquals("", pageData.getData().get(0).getLatest().get(EntityKeyType.TIME_SERIES).get("temperature").getValue()); Assert.assertEquals("", pageData.getData().getFirst().getLatest().get(EntityKeyType.TIME_SERIES).get("temperature").getValue());
getWsClient().registerWaitForUpdate(); getWsClient().registerWaitForUpdate();
TsKvEntry dataPoint1 = new BasicTsKvEntry(now - TimeUnit.MINUTES.toMillis(1), new LongDataEntry("temperature", 42L)); TsKvEntry dataPoint1 = new BasicTsKvEntry(now - TimeUnit.MINUTES.toMillis(1), new LongDataEntry("temperature", 42L));
List<TsKvEntry> tsData = Arrays.asList(dataPoint1); List<TsKvEntry> tsData = List.of(dataPoint1);
sendTelemetry(device, tsData); sendTelemetry(device, tsData);
update = getWsClient().parseDataReply(getWsClient().waitForUpdate()); update = getWsClient().parseDataReply(getWsClient().waitForUpdate());
@ -694,34 +693,34 @@ public class WebsocketApiTest extends AbstractControllerTest {
List<EntityData> listData = update.getUpdate(); List<EntityData> listData = update.getUpdate();
Assert.assertNotNull(listData); Assert.assertNotNull(listData);
Assert.assertEquals(1, listData.size()); Assert.assertEquals(1, listData.size());
Assert.assertEquals(device.getId(), listData.get(0).getEntityId()); Assert.assertEquals(device.getId(), listData.getFirst().getEntityId());
Assert.assertNotNull(listData.get(0).getLatest().get(EntityKeyType.TIME_SERIES)); Assert.assertNotNull(listData.getFirst().getLatest().get(EntityKeyType.TIME_SERIES));
TsValue tsValue = listData.get(0).getLatest().get(EntityKeyType.TIME_SERIES).get("temperature"); TsValue tsValue = listData.getFirst().getLatest().get(EntityKeyType.TIME_SERIES).get("temperature");
Assert.assertEquals(new TsValue(dataPoint1.getTs(), dataPoint1.getValueAsString()), tsValue); Assert.assertEquals(new TsValue(dataPoint1.getTs(), dataPoint1.getValueAsString()), tsValue);
now = System.currentTimeMillis(); now = System.currentTimeMillis();
TsKvEntry dataPoint2 = new BasicTsKvEntry(now, new LongDataEntry("temperature", 52L)); TsKvEntry dataPoint2 = new BasicTsKvEntry(now, new LongDataEntry("temperature", 52L));
getWsClient().registerWaitForUpdate(); getWsClient().registerWaitForUpdate();
sendTelemetry(device, Arrays.asList(dataPoint2)); sendTelemetry(device, List.of(dataPoint2));
update = getWsClient().parseDataReply(getWsClient().waitForUpdate()); update = getWsClient().parseDataReply(getWsClient().waitForUpdate());
Assert.assertEquals(1, update.getCmdId()); Assert.assertEquals(1, update.getCmdId());
List<EntityData> eData = update.getUpdate(); List<EntityData> eData = update.getUpdate();
Assert.assertNotNull(eData); Assert.assertNotNull(eData);
Assert.assertEquals(1, eData.size()); Assert.assertEquals(1, eData.size());
Assert.assertEquals(device.getId(), eData.get(0).getEntityId()); Assert.assertEquals(device.getId(), eData.getFirst().getEntityId());
Assert.assertNotNull(eData.get(0).getLatest().get(EntityKeyType.TIME_SERIES)); Assert.assertNotNull(eData.getFirst().getLatest().get(EntityKeyType.TIME_SERIES));
tsValue = eData.get(0).getLatest().get(EntityKeyType.TIME_SERIES).get("temperature"); tsValue = eData.getFirst().getLatest().get(EntityKeyType.TIME_SERIES).get("temperature");
Assert.assertEquals(new TsValue(dataPoint2.getTs(), dataPoint2.getValueAsString()), tsValue); Assert.assertEquals(new TsValue(dataPoint2.getTs(), dataPoint2.getValueAsString()), tsValue);
//Sending update from the past, while latest value has new timestamp; //Sending update from the past, while the latest value has new timestamp;
getWsClient().registerWaitForUpdate(); getWsClient().registerWaitForUpdate();
sendTelemetry(device, Arrays.asList(dataPoint1)); sendTelemetry(device, List.of(dataPoint1));
String msg = getWsClient().waitForUpdate(TimeUnit.SECONDS.toMillis(1)); String msg = getWsClient().waitForUpdate(TimeUnit.SECONDS.toMillis(1));
Assert.assertNull(msg); Assert.assertNull(msg);
//Sending duplicate update again //Sending duplicate update again
getWsClient().registerWaitForUpdate(); getWsClient().registerWaitForUpdate();
sendTelemetry(device, Arrays.asList(dataPoint2)); sendTelemetry(device, List.of(dataPoint2));
msg = getWsClient().waitForUpdate(TimeUnit.SECONDS.toMillis(1)); msg = getWsClient().waitForUpdate(TimeUnit.SECONDS.toMillis(1));
Assert.assertNull(msg); Assert.assertNull(msg);
} }
@ -736,21 +735,21 @@ public class WebsocketApiTest extends AbstractControllerTest {
PageData<EntityData> pageData = update.getData(); PageData<EntityData> pageData = update.getData();
Assert.assertNotNull(pageData); Assert.assertNotNull(pageData);
Assert.assertEquals(1, pageData.getData().size()); Assert.assertEquals(1, pageData.getData().size());
Assert.assertEquals(device.getId(), pageData.getData().get(0).getEntityId()); Assert.assertEquals(device.getId(), pageData.getData().getFirst().getEntityId());
Assert.assertNotNull(pageData.getData().get(0).getLatest().get(EntityKeyType.SERVER_ATTRIBUTE).get("serverAttributeKey")); Assert.assertNotNull(pageData.getData().getFirst().getLatest().get(EntityKeyType.SERVER_ATTRIBUTE).get("serverAttributeKey"));
Assert.assertEquals(0, pageData.getData().get(0).getLatest().get(EntityKeyType.SERVER_ATTRIBUTE).get("serverAttributeKey").getTs()); Assert.assertEquals(0, pageData.getData().getFirst().getLatest().get(EntityKeyType.SERVER_ATTRIBUTE).get("serverAttributeKey").getTs());
Assert.assertEquals("", pageData.getData().get(0).getLatest().get(EntityKeyType.SERVER_ATTRIBUTE).get("serverAttributeKey").getValue()); Assert.assertEquals("", pageData.getData().getFirst().getLatest().get(EntityKeyType.SERVER_ATTRIBUTE).get("serverAttributeKey").getValue());
getWsClient().registerWaitForUpdate(); getWsClient().registerWaitForUpdate();
// Pushing update with wrong scope and make sure it will not arrive. // Pushing update with the wrong scope and make sure it will not arrive.
AttributeKvEntry invalidDataPoint = new BaseAttributeKvEntry(now - TimeUnit.MINUTES.toMillis(1), new LongDataEntry("serverAttributeKey", 55L)); AttributeKvEntry invalidDataPoint = new BaseAttributeKvEntry(now - TimeUnit.MINUTES.toMillis(1), new LongDataEntry("serverAttributeKey", 55L));
sendAttributes(device, TbAttributeSubscriptionScope.CLIENT_SCOPE, Arrays.asList(invalidDataPoint)); sendAttributes(device, TbAttributeSubscriptionScope.CLIENT_SCOPE, List.of(invalidDataPoint));
Assert.assertNull(getWsClient().waitForUpdate(3000)); Assert.assertNull(getWsClient().waitForUpdate(3000));
AttributeKvEntry dataPoint1 = new BaseAttributeKvEntry(now - TimeUnit.MINUTES.toMillis(1), new LongDataEntry("serverAttributeKey", 42L)); AttributeKvEntry dataPoint1 = new BaseAttributeKvEntry(now - TimeUnit.MINUTES.toMillis(1), new LongDataEntry("serverAttributeKey", 42L));
List<AttributeKvEntry> tsData = Arrays.asList(dataPoint1); List<AttributeKvEntry> tsData = List.of(dataPoint1);
sendAttributes(device, TbAttributeSubscriptionScope.SERVER_SCOPE, tsData); sendAttributes(device, TbAttributeSubscriptionScope.SERVER_SCOPE, tsData);
String msg = getWsClient().waitForUpdate(); String msg = getWsClient().waitForUpdate();
@ -761,16 +760,16 @@ public class WebsocketApiTest extends AbstractControllerTest {
List<EntityData> listData = update.getUpdate(); List<EntityData> listData = update.getUpdate();
Assert.assertNotNull(listData); Assert.assertNotNull(listData);
Assert.assertEquals(1, listData.size()); Assert.assertEquals(1, listData.size());
Assert.assertEquals(device.getId(), listData.get(0).getEntityId()); Assert.assertEquals(device.getId(), listData.getFirst().getEntityId());
Assert.assertNotNull(listData.get(0).getLatest().get(EntityKeyType.SERVER_ATTRIBUTE)); Assert.assertNotNull(listData.getFirst().getLatest().get(EntityKeyType.SERVER_ATTRIBUTE));
TsValue tsValue = listData.get(0).getLatest().get(EntityKeyType.SERVER_ATTRIBUTE).get("serverAttributeKey"); TsValue tsValue = listData.getFirst().getLatest().get(EntityKeyType.SERVER_ATTRIBUTE).get("serverAttributeKey");
Assert.assertEquals(new TsValue(dataPoint1.getLastUpdateTs(), dataPoint1.getValueAsString()), tsValue); Assert.assertEquals(new TsValue(dataPoint1.getLastUpdateTs(), dataPoint1.getValueAsString()), tsValue);
now = System.currentTimeMillis(); now = System.currentTimeMillis();
AttributeKvEntry dataPoint2 = new BaseAttributeKvEntry(now, new LongDataEntry("serverAttributeKey", 52L)); AttributeKvEntry dataPoint2 = new BaseAttributeKvEntry(now, new LongDataEntry("serverAttributeKey", 52L));
getWsClient().registerWaitForUpdate(); getWsClient().registerWaitForUpdate();
sendAttributes(device, TbAttributeSubscriptionScope.SERVER_SCOPE, Arrays.asList(dataPoint2)); sendAttributes(device, TbAttributeSubscriptionScope.SERVER_SCOPE, List.of(dataPoint2));
msg = getWsClient().waitForUpdate(); msg = getWsClient().waitForUpdate();
Assert.assertNotNull(msg); Assert.assertNotNull(msg);
@ -779,20 +778,20 @@ public class WebsocketApiTest extends AbstractControllerTest {
List<EntityData> eData = update.getUpdate(); List<EntityData> eData = update.getUpdate();
Assert.assertNotNull(eData); Assert.assertNotNull(eData);
Assert.assertEquals(1, eData.size()); Assert.assertEquals(1, eData.size());
Assert.assertEquals(device.getId(), eData.get(0).getEntityId()); Assert.assertEquals(device.getId(), eData.getFirst().getEntityId());
Assert.assertNotNull(eData.get(0).getLatest().get(EntityKeyType.SERVER_ATTRIBUTE)); Assert.assertNotNull(eData.getFirst().getLatest().get(EntityKeyType.SERVER_ATTRIBUTE));
tsValue = eData.get(0).getLatest().get(EntityKeyType.SERVER_ATTRIBUTE).get("serverAttributeKey"); tsValue = eData.getFirst().getLatest().get(EntityKeyType.SERVER_ATTRIBUTE).get("serverAttributeKey");
Assert.assertEquals(new TsValue(dataPoint2.getLastUpdateTs(), dataPoint2.getValueAsString()), tsValue); Assert.assertEquals(new TsValue(dataPoint2.getLastUpdateTs(), dataPoint2.getValueAsString()), tsValue);
//Sending update from the past, while latest value has new timestamp; //Sending update from the past, while the latest value has new timestamp;
getWsClient().registerWaitForUpdate(); getWsClient().registerWaitForUpdate();
sendAttributes(device, TbAttributeSubscriptionScope.SERVER_SCOPE, Arrays.asList(dataPoint1)); sendAttributes(device, TbAttributeSubscriptionScope.SERVER_SCOPE, List.of(dataPoint1));
msg = getWsClient().waitForUpdate(TimeUnit.SECONDS.toMillis(1)); msg = getWsClient().waitForUpdate(TimeUnit.SECONDS.toMillis(1));
Assert.assertNull(msg); Assert.assertNull(msg);
//Sending duplicate update again //Sending duplicate update again
getWsClient().registerWaitForUpdate(); getWsClient().registerWaitForUpdate();
sendAttributes(device, TbAttributeSubscriptionScope.SERVER_SCOPE, Arrays.asList(dataPoint2)); sendAttributes(device, TbAttributeSubscriptionScope.SERVER_SCOPE, List.of(dataPoint2));
msg = getWsClient().waitForUpdate(TimeUnit.SECONDS.toMillis(1)); msg = getWsClient().waitForUpdate(TimeUnit.SECONDS.toMillis(1));
Assert.assertNull(msg); Assert.assertNull(msg);
} }
@ -812,23 +811,23 @@ public class WebsocketApiTest extends AbstractControllerTest {
PageData<EntityData> pageData = update.getData(); PageData<EntityData> pageData = update.getData();
Assert.assertNotNull(pageData); Assert.assertNotNull(pageData);
Assert.assertEquals(1, pageData.getData().size()); Assert.assertEquals(1, pageData.getData().size());
Assert.assertEquals(device.getId(), pageData.getData().get(0).getEntityId()); Assert.assertEquals(device.getId(), pageData.getData().getFirst().getEntityId());
Assert.assertNotNull(pageData.getData().get(0).getLatest().get(EntityKeyType.SERVER_ATTRIBUTE).get("serverAttributeKey")); Assert.assertNotNull(pageData.getData().getFirst().getLatest().get(EntityKeyType.SERVER_ATTRIBUTE).get("serverAttributeKey"));
Assert.assertEquals(0, pageData.getData().get(0).getLatest().get(EntityKeyType.SERVER_ATTRIBUTE).get("serverAttributeKey").getTs()); Assert.assertEquals(0, pageData.getData().getFirst().getLatest().get(EntityKeyType.SERVER_ATTRIBUTE).get("serverAttributeKey").getTs());
Assert.assertEquals("", pageData.getData().get(0).getLatest().get(EntityKeyType.SERVER_ATTRIBUTE).get("serverAttributeKey").getValue()); Assert.assertEquals("", pageData.getData().getFirst().getLatest().get(EntityKeyType.SERVER_ATTRIBUTE).get("serverAttributeKey").getValue());
Assert.assertNotNull(pageData.getData().get(0).getLatest().get(EntityKeyType.CLIENT_ATTRIBUTE).get("clientAttributeKey")); Assert.assertNotNull(pageData.getData().getFirst().getLatest().get(EntityKeyType.CLIENT_ATTRIBUTE).get("clientAttributeKey"));
Assert.assertEquals(0, pageData.getData().get(0).getLatest().get(EntityKeyType.CLIENT_ATTRIBUTE).get("clientAttributeKey").getTs()); Assert.assertEquals(0, pageData.getData().getFirst().getLatest().get(EntityKeyType.CLIENT_ATTRIBUTE).get("clientAttributeKey").getTs());
Assert.assertEquals("", pageData.getData().get(0).getLatest().get(EntityKeyType.CLIENT_ATTRIBUTE).get("clientAttributeKey").getValue()); Assert.assertEquals("", pageData.getData().getFirst().getLatest().get(EntityKeyType.CLIENT_ATTRIBUTE).get("clientAttributeKey").getValue());
Assert.assertNotNull(pageData.getData().get(0).getLatest().get(EntityKeyType.SHARED_ATTRIBUTE).get("sharedAttributeKey")); Assert.assertNotNull(pageData.getData().getFirst().getLatest().get(EntityKeyType.SHARED_ATTRIBUTE).get("sharedAttributeKey"));
Assert.assertEquals(0, pageData.getData().get(0).getLatest().get(EntityKeyType.SHARED_ATTRIBUTE).get("sharedAttributeKey").getTs()); Assert.assertEquals(0, pageData.getData().getFirst().getLatest().get(EntityKeyType.SHARED_ATTRIBUTE).get("sharedAttributeKey").getTs());
Assert.assertEquals("", pageData.getData().get(0).getLatest().get(EntityKeyType.SHARED_ATTRIBUTE).get("sharedAttributeKey").getValue()); Assert.assertEquals("", pageData.getData().getFirst().getLatest().get(EntityKeyType.SHARED_ATTRIBUTE).get("sharedAttributeKey").getValue());
Assert.assertNotNull(pageData.getData().get(0).getLatest().get(EntityKeyType.ATTRIBUTE).get("anyAttributeKey")); Assert.assertNotNull(pageData.getData().getFirst().getLatest().get(EntityKeyType.ATTRIBUTE).get("anyAttributeKey"));
Assert.assertEquals(0, pageData.getData().get(0).getLatest().get(EntityKeyType.ATTRIBUTE).get("anyAttributeKey").getTs()); Assert.assertEquals(0, pageData.getData().getFirst().getLatest().get(EntityKeyType.ATTRIBUTE).get("anyAttributeKey").getTs());
Assert.assertEquals("", pageData.getData().get(0).getLatest().get(EntityKeyType.ATTRIBUTE).get("anyAttributeKey").getValue()); Assert.assertEquals("", pageData.getData().getFirst().getLatest().get(EntityKeyType.ATTRIBUTE).get("anyAttributeKey").getValue());
getWsClient().registerWaitForUpdate(); getWsClient().registerWaitForUpdate();
AttributeKvEntry dataPoint1 = new BaseAttributeKvEntry(now - TimeUnit.MINUTES.toMillis(1), new LongDataEntry("serverAttributeKey", 42L)); AttributeKvEntry dataPoint1 = new BaseAttributeKvEntry(now - TimeUnit.MINUTES.toMillis(1), new LongDataEntry("serverAttributeKey", 42L));
List<AttributeKvEntry> tsData = Arrays.asList(dataPoint1); List<AttributeKvEntry> tsData = List.of(dataPoint1);
sendAttributes(device, TbAttributeSubscriptionScope.SERVER_SCOPE, tsData); sendAttributes(device, TbAttributeSubscriptionScope.SERVER_SCOPE, tsData);
@ -839,78 +838,78 @@ public class WebsocketApiTest extends AbstractControllerTest {
List<EntityData> eData = update.getUpdate(); List<EntityData> eData = update.getUpdate();
Assert.assertNotNull(eData); Assert.assertNotNull(eData);
Assert.assertEquals(1, eData.size()); Assert.assertEquals(1, eData.size());
Assert.assertEquals(device.getId(), eData.get(0).getEntityId()); Assert.assertEquals(device.getId(), eData.getFirst().getEntityId());
Assert.assertNotNull(eData.get(0).getLatest().get(EntityKeyType.SERVER_ATTRIBUTE)); Assert.assertNotNull(eData.getFirst().getLatest().get(EntityKeyType.SERVER_ATTRIBUTE));
TsValue attrValue = eData.get(0).getLatest().get(EntityKeyType.SERVER_ATTRIBUTE).get("serverAttributeKey"); TsValue attrValue = eData.getFirst().getLatest().get(EntityKeyType.SERVER_ATTRIBUTE).get("serverAttributeKey");
Assert.assertEquals(new TsValue(dataPoint1.getLastUpdateTs(), dataPoint1.getValueAsString()), attrValue); Assert.assertEquals(new TsValue(dataPoint1.getLastUpdateTs(), dataPoint1.getValueAsString()), attrValue);
//Sending update from the past, while latest value has new timestamp; //Sending update from the past, while the latest value has new timestamp;
getWsClient().registerWaitForUpdate(); getWsClient().registerWaitForUpdate();
sendAttributes(device, TbAttributeSubscriptionScope.SHARED_SCOPE, Arrays.asList(dataPoint1)); sendAttributes(device, TbAttributeSubscriptionScope.SHARED_SCOPE, List.of(dataPoint1));
msg = getWsClient().waitForUpdate(TimeUnit.SECONDS.toMillis(1)); msg = getWsClient().waitForUpdate(TimeUnit.SECONDS.toMillis(1));
Assert.assertNull(msg); Assert.assertNull(msg);
//Sending duplicate update again //Sending duplicate update again
getWsClient().registerWaitForUpdate(); getWsClient().registerWaitForUpdate();
sendAttributes(device, TbAttributeSubscriptionScope.CLIENT_SCOPE, Arrays.asList(dataPoint1)); sendAttributes(device, TbAttributeSubscriptionScope.CLIENT_SCOPE, List.of(dataPoint1));
msg = getWsClient().waitForUpdate(TimeUnit.SECONDS.toMillis(1)); msg = getWsClient().waitForUpdate(TimeUnit.SECONDS.toMillis(1));
Assert.assertNull(msg); Assert.assertNull(msg);
//Sending update from the past, while latest value has new timestamp; //Sending update from the past, while latest value has new timestamp;
getWsClient().registerWaitForUpdate(); getWsClient().registerWaitForUpdate();
AttributeKvEntry dataPoint2 = new BaseAttributeKvEntry(now, new LongDataEntry("sharedAttributeKey", 42L)); AttributeKvEntry dataPoint2 = new BaseAttributeKvEntry(now, new LongDataEntry("sharedAttributeKey", 42L));
sendAttributes(device, TbAttributeSubscriptionScope.SHARED_SCOPE, Arrays.asList(dataPoint2)); sendAttributes(device, TbAttributeSubscriptionScope.SHARED_SCOPE, List.of(dataPoint2));
msg = getWsClient().waitForUpdate(TimeUnit.SECONDS.toMillis(1)); msg = getWsClient().waitForUpdate(TimeUnit.SECONDS.toMillis(1));
update = JacksonUtil.fromString(msg, EntityDataUpdate.class); update = JacksonUtil.fromString(msg, EntityDataUpdate.class);
Assert.assertEquals(1, update.getCmdId()); Assert.assertEquals(1, update.getCmdId());
eData = update.getUpdate(); eData = update.getUpdate();
Assert.assertNotNull(eData); Assert.assertNotNull(eData);
Assert.assertEquals(1, eData.size()); Assert.assertEquals(1, eData.size());
Assert.assertEquals(device.getId(), eData.get(0).getEntityId()); Assert.assertEquals(device.getId(), eData.getFirst().getEntityId());
Assert.assertNotNull(eData.get(0).getLatest().get(EntityKeyType.SHARED_ATTRIBUTE)); Assert.assertNotNull(eData.getFirst().getLatest().get(EntityKeyType.SHARED_ATTRIBUTE));
attrValue = eData.get(0).getLatest().get(EntityKeyType.SHARED_ATTRIBUTE).get("sharedAttributeKey"); attrValue = eData.getFirst().getLatest().get(EntityKeyType.SHARED_ATTRIBUTE).get("sharedAttributeKey");
Assert.assertEquals(new TsValue(dataPoint2.getLastUpdateTs(), dataPoint2.getValueAsString()), attrValue); Assert.assertEquals(new TsValue(dataPoint2.getLastUpdateTs(), dataPoint2.getValueAsString()), attrValue);
getWsClient().registerWaitForUpdate(); getWsClient().registerWaitForUpdate();
AttributeKvEntry dataPoint3 = new BaseAttributeKvEntry(now, new LongDataEntry("clientAttributeKey", 42L)); AttributeKvEntry dataPoint3 = new BaseAttributeKvEntry(now, new LongDataEntry("clientAttributeKey", 42L));
sendAttributes(device, TbAttributeSubscriptionScope.CLIENT_SCOPE, Arrays.asList(dataPoint3)); sendAttributes(device, TbAttributeSubscriptionScope.CLIENT_SCOPE, List.of(dataPoint3));
msg = getWsClient().waitForUpdate(TimeUnit.SECONDS.toMillis(1)); msg = getWsClient().waitForUpdate(TimeUnit.SECONDS.toMillis(1));
update = JacksonUtil.fromString(msg, EntityDataUpdate.class); update = JacksonUtil.fromString(msg, EntityDataUpdate.class);
Assert.assertEquals(1, update.getCmdId()); Assert.assertEquals(1, update.getCmdId());
eData = update.getUpdate(); eData = update.getUpdate();
Assert.assertNotNull(eData); Assert.assertNotNull(eData);
Assert.assertEquals(1, eData.size()); Assert.assertEquals(1, eData.size());
Assert.assertEquals(device.getId(), eData.get(0).getEntityId()); Assert.assertEquals(device.getId(), eData.getFirst().getEntityId());
Assert.assertNotNull(eData.get(0).getLatest().get(EntityKeyType.CLIENT_ATTRIBUTE)); Assert.assertNotNull(eData.getFirst().getLatest().get(EntityKeyType.CLIENT_ATTRIBUTE));
attrValue = eData.get(0).getLatest().get(EntityKeyType.CLIENT_ATTRIBUTE).get("clientAttributeKey"); attrValue = eData.getFirst().getLatest().get(EntityKeyType.CLIENT_ATTRIBUTE).get("clientAttributeKey");
Assert.assertEquals(new TsValue(dataPoint3.getLastUpdateTs(), dataPoint3.getValueAsString()), attrValue); Assert.assertEquals(new TsValue(dataPoint3.getLastUpdateTs(), dataPoint3.getValueAsString()), attrValue);
getWsClient().registerWaitForUpdate(); getWsClient().registerWaitForUpdate();
AttributeKvEntry dataPoint4 = new BaseAttributeKvEntry(now, new LongDataEntry("anyAttributeKey", 42L)); AttributeKvEntry dataPoint4 = new BaseAttributeKvEntry(now, new LongDataEntry("anyAttributeKey", 42L));
sendAttributes(device, TbAttributeSubscriptionScope.CLIENT_SCOPE, Arrays.asList(dataPoint4)); sendAttributes(device, TbAttributeSubscriptionScope.CLIENT_SCOPE, List.of(dataPoint4));
msg = getWsClient().waitForUpdate(TimeUnit.SECONDS.toMillis(1)); msg = getWsClient().waitForUpdate(TimeUnit.SECONDS.toMillis(1));
update = JacksonUtil.fromString(msg, EntityDataUpdate.class); update = JacksonUtil.fromString(msg, EntityDataUpdate.class);
Assert.assertEquals(1, update.getCmdId()); Assert.assertEquals(1, update.getCmdId());
eData = update.getUpdate(); eData = update.getUpdate();
Assert.assertNotNull(eData); Assert.assertNotNull(eData);
Assert.assertEquals(1, eData.size()); Assert.assertEquals(1, eData.size());
Assert.assertEquals(device.getId(), eData.get(0).getEntityId()); Assert.assertEquals(device.getId(), eData.getFirst().getEntityId());
Assert.assertNotNull(eData.get(0).getLatest().get(EntityKeyType.ATTRIBUTE)); Assert.assertNotNull(eData.getFirst().getLatest().get(EntityKeyType.ATTRIBUTE));
attrValue = eData.get(0).getLatest().get(EntityKeyType.ATTRIBUTE).get("anyAttributeKey"); attrValue = eData.getFirst().getLatest().get(EntityKeyType.ATTRIBUTE).get("anyAttributeKey");
Assert.assertEquals(new TsValue(dataPoint4.getLastUpdateTs(), dataPoint4.getValueAsString()), attrValue); Assert.assertEquals(new TsValue(dataPoint4.getLastUpdateTs(), dataPoint4.getValueAsString()), attrValue);
getWsClient().registerWaitForUpdate(); getWsClient().registerWaitForUpdate();
AttributeKvEntry dataPoint5 = new BaseAttributeKvEntry(now, new LongDataEntry("anyAttributeKey", 43L)); AttributeKvEntry dataPoint5 = new BaseAttributeKvEntry(now, new LongDataEntry("anyAttributeKey", 43L));
sendAttributes(device, TbAttributeSubscriptionScope.SERVER_SCOPE, Arrays.asList(dataPoint5)); sendAttributes(device, TbAttributeSubscriptionScope.SERVER_SCOPE, List.of(dataPoint5));
msg = getWsClient().waitForUpdate(TimeUnit.SECONDS.toMillis(1)); msg = getWsClient().waitForUpdate(TimeUnit.SECONDS.toMillis(1));
update = JacksonUtil.fromString(msg, EntityDataUpdate.class); update = JacksonUtil.fromString(msg, EntityDataUpdate.class);
Assert.assertEquals(1, update.getCmdId()); Assert.assertEquals(1, update.getCmdId());
eData = update.getUpdate(); eData = update.getUpdate();
Assert.assertNotNull(eData); Assert.assertNotNull(eData);
Assert.assertEquals(1, eData.size()); Assert.assertEquals(1, eData.size());
Assert.assertEquals(device.getId(), eData.get(0).getEntityId()); Assert.assertEquals(device.getId(), eData.getFirst().getEntityId());
Assert.assertNotNull(eData.get(0).getLatest().get(EntityKeyType.ATTRIBUTE)); Assert.assertNotNull(eData.getFirst().getLatest().get(EntityKeyType.ATTRIBUTE));
attrValue = eData.get(0).getLatest().get(EntityKeyType.ATTRIBUTE).get("anyAttributeKey"); attrValue = eData.getFirst().getLatest().get(EntityKeyType.ATTRIBUTE).get("anyAttributeKey");
Assert.assertEquals(new TsValue(dataPoint5.getLastUpdateTs(), dataPoint5.getValueAsString()), attrValue); Assert.assertEquals(new TsValue(dataPoint5.getLastUpdateTs(), dataPoint5.getValueAsString()), attrValue);
} }
@ -971,7 +970,7 @@ public class WebsocketApiTest extends AbstractControllerTest {
.tenantId(device.getTenantId()) .tenantId(device.getTenantId())
.entityId(device.getId()) .entityId(device.getId())
.entries(tsData) .entries(tsData)
.callback(new FutureCallback<Void>() { .callback(new FutureCallback<>() {
@Override @Override
public void onSuccess(@Nullable Void result) { public void onSuccess(@Nullable Void result) {
log.debug("sendTelemetry callback onSuccess"); log.debug("sendTelemetry callback onSuccess");

106
application/src/test/java/org/thingsboard/server/controller/plugin/TbWebSocketHandlerTest.java

@ -25,16 +25,25 @@ import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.mockito.Mockito; import org.mockito.Mockito;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.web.socket.CloseStatus; import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.adapter.NativeWebSocketSession; import org.springframework.web.socket.adapter.NativeWebSocketSession;
import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import org.thingsboard.server.service.security.auth.jwt.JwtAuthenticationProvider;
import org.thingsboard.server.service.security.auth.pat.ApiKeyAuthenticationProvider;
import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.service.ws.WebSocketService;
import org.thingsboard.server.service.ws.WebSocketSessionRef; import org.thingsboard.server.service.ws.WebSocketSessionRef;
import org.thingsboard.server.service.ws.WebSocketSessionType;
import java.io.IOException; import java.io.IOException;
import java.lang.reflect.Method;
import java.util.Collection; import java.util.Collection;
import java.util.Deque; import java.util.Deque;
import java.util.List; import java.util.List;
import java.util.Random; import java.util.Random;
import java.util.UUID;
import java.util.concurrent.ConcurrentLinkedDeque; import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
@ -184,4 +193,101 @@ class TbWebSocketHandlerTest {
assertThat(msgs).map(Integer::parseInt).doesNotHaveDuplicates().hasSize(100); assertThat(msgs).map(Integer::parseInt).doesNotHaveDuplicates().hasSize(100);
} }
private AuthTestFixture createAuthTestFixture() throws IOException {
TbWebSocketHandler handler = spy(new TbWebSocketHandler());
willDoNothing().given(handler).close(any(), any());
ApiKeyAuthenticationProvider apiKeyProvider = mock(ApiKeyAuthenticationProvider.class);
JwtAuthenticationProvider jwtProvider = mock(JwtAuthenticationProvider.class);
WebSocketService wsService = mock(WebSocketService.class);
ReflectionTestUtils.setField(handler, "apiKeyAuthenticationProvider", apiKeyProvider);
ReflectionTestUtils.setField(handler, "authenticationProvider", jwtProvider);
ReflectionTestUtils.setField(handler, "webSocketService", wsService);
ReflectionTestUtils.setField(handler, "tenantProfileCache", mock(TbTenantProfileCache.class));
ReflectionTestUtils.setField(handler, "authTimeoutMs", 10000);
ReflectionTestUtils.invokeMethod(handler, "init");
WebSocketSessionRef ref = WebSocketSessionRef.builder()
.sessionId(UUID.randomUUID().toString())
.sessionType(WebSocketSessionType.GENERAL)
.build();
NativeWebSocketSession wsSession = mock(NativeWebSocketSession.class);
Session nativeSess = mock(Session.class);
willReturn(nativeSess).given(wsSession).getNativeSession(Session.class);
RemoteEndpoint.Async async = mock(RemoteEndpoint.Async.class);
willReturn(async).given(nativeSess).getAsyncRemote();
willReturn("test-session-id").given(wsSession).getId();
TbWebSocketHandler.SessionMetaData sessionMd = handler.new SessionMetaData(wsSession, ref);
return new AuthTestFixture(handler, apiKeyProvider, jwtProvider, ref, sessionMd);
}
@Test
void processMsg_authenticatesWithApiKey() throws Exception {
AuthTestFixture f = createAuthTestFixture();
SecurityUser securityUser = mock(SecurityUser.class, Mockito.RETURNS_DEEP_STUBS);
willReturn(securityUser).given(f.apiKeyProvider).authenticate("my-api-key");
String msg = "{\"authCmd\":{\"cmdId\":1,\"apiKey\":\"my-api-key\"},\"cmds\":[]}";
f.handler.processMsg(f.sessionMd, msg);
verify(f.apiKeyProvider).authenticate("my-api-key");
verify(f.jwtProvider, never()).authenticate(anyString());
assertThat(f.ref.getSecurityCtx()).isSameAs(securityUser);
}
@Test
void processMsg_authenticatesWithJwtToken() throws Exception {
AuthTestFixture f = createAuthTestFixture();
SecurityUser securityUser = mock(SecurityUser.class, Mockito.RETURNS_DEEP_STUBS);
willReturn(securityUser).given(f.jwtProvider).authenticate("my-jwt-token");
String msg = "{\"authCmd\":{\"cmdId\":1,\"token\":\"my-jwt-token\"},\"cmds\":[]}";
f.handler.processMsg(f.sessionMd, msg);
verify(f.jwtProvider).authenticate("my-jwt-token");
verify(f.apiKeyProvider, never()).authenticate(anyString());
assertThat(f.ref.getSecurityCtx()).isSameAs(securityUser);
}
@Test
void processMsg_apiKeyTakesPrecedenceOverToken() throws Exception {
AuthTestFixture f = createAuthTestFixture();
SecurityUser securityUser = mock(SecurityUser.class, Mockito.RETURNS_DEEP_STUBS);
willReturn(securityUser).given(f.apiKeyProvider).authenticate("my-api-key");
String msg = "{\"authCmd\":{\"cmdId\":1,\"apiKey\":\"my-api-key\",\"token\":\"my-jwt-token\"},\"cmds\":[]}";
f.handler.processMsg(f.sessionMd, msg);
verify(f.apiKeyProvider).authenticate("my-api-key");
verify(f.jwtProvider, never()).authenticate(anyString());
assertThat(f.ref.getSecurityCtx()).isSameAs(securityUser);
}
@Test
void extractQueryParam_parsesCorrectly() throws Exception {
TbWebSocketHandler handler = new TbWebSocketHandler();
Method method = TbWebSocketHandler.class.getDeclaredMethod("extractQueryParam", String.class, String.class);
method.setAccessible(true);
assertThat(method.invoke(handler, "token=jwt123", "token")).isEqualTo("jwt123");
assertThat(method.invoke(handler, "token=jwt123&other=abc123", "token")).isEqualTo("jwt123");
assertThat(method.invoke(handler, "other=value", "token")).isNull();
assertThat(method.invoke(handler, "tokenExtra=value", "token")).isNull();
}
private record AuthTestFixture(
TbWebSocketHandler handler,
ApiKeyAuthenticationProvider apiKeyProvider,
JwtAuthenticationProvider jwtProvider,
WebSocketSessionRef ref,
TbWebSocketHandler.SessionMetaData sessionMd
) {}
} }

Loading…
Cancel
Save