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 final WebSocketHandler wsHandler;
private final ApiKeyHandshakeInterceptor apiKeyHandshakeInterceptor;
@Value("${server.ws.max_text_message_buffer_size:32768}")
private int maxTextMessageBufferSize;
@ -60,7 +61,9 @@ public class WebSocketConfiguration implements WebSocketConfigurer {
log.error("TbWebSocketHandler expected but [{}] provided", wsHandler);
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.limit.LimitedApi;
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.dao.tenant.TbTenantProfileCache;
import org.thingsboard.server.queue.util.TbCoreComponent;
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.model.SecurityUser;
import org.thingsboard.server.service.security.model.UserPrincipal;
@ -100,6 +102,8 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements WebSocke
private RateLimitService rateLimitService;
@Autowired
private JwtAuthenticationProvider authenticationProvider;
@Autowired
private ApiKeyAuthenticationProvider apiKeyAuthenticationProvider;
@Value("${server.ws.send_timeout:5000}")
private long sendTimeout;
@ -194,7 +198,11 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements WebSocke
log.trace("{} Authenticating session", sessionRef);
SecurityUser securityCtx;
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) {
close(sessionRef, CloseStatus.BAD_DATA.withReason(e.getMessage()));
return;
@ -328,9 +336,17 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements WebSocke
}
SecurityUser securityCtx = null;
String token = StringUtils.substringAfter(session.getUri().getQuery(), "token=");
if (StringUtils.isNotEmpty(token)) {
securityCtx = authenticationProvider.authenticate(token);
Object apiKeyCtx = session.getAttributes().get(ApiKeyHandshakeInterceptor.API_KEY_SECURITY_CTX_ATTR);
if (apiKeyCtx instanceof SecurityUser) {
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()
.sessionId(UUID.randomUUID().toString())
@ -341,6 +357,15 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements WebSocke
.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) {
SessionMetaData sessionMd = internalSessionMap.get(internalSessionId);
if (sessionMd == null) {
@ -482,6 +507,7 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements WebSocke
}
}
}
}
@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);
}
private SecurityUser authenticate(String key) {
public SecurityUser authenticate(String key) {
if (StringUtils.isEmpty(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
@AllArgsConstructor
public class AuthCmd implements WsCmd {
private int cmdId;
private String token;
private String apiKey;
@Override
public WsCmdType getType() {
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.URISyntaxException;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import static org.assertj.core.api.Assertions.assertThat;
@ -86,12 +87,12 @@ public abstract class AbstractControllerTest extends AbstractNotifyEntityTest {
}
@Before
public void beforeWsTest() throws Exception {
public void beforeWsTest() {
// placeholder
}
@After
public void afterWsTest() throws Exception {
public void afterWsTest() {
if (wsClient != null) {
wsClient.close();
}
@ -113,4 +114,17 @@ public abstract class AbstractControllerTest extends AbstractNotifyEntityTest {
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.LatestValueCmd;
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.nio.channels.NotYetConnectedException;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@ -62,6 +62,10 @@ public class TbTestWebSocketClient extends WebSocketClient {
super(serverUri);
}
public TbTestWebSocketClient(URI serverUri, Map<String, String> httpHeaders) {
super(serverUri, httpHeaders);
}
@Override
public void onOpen(ServerHandshake serverHandshake) {
@ -69,7 +73,13 @@ public class TbTestWebSocketClient extends WebSocketClient {
public void authenticate(String token) {
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));
}
@ -275,7 +285,7 @@ public class TbTestWebSocketClient extends WebSocketClient {
public JsonNode sendTimeseriesCmd(EntityId entityId, String 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.setEntityType(entityId.getEntityType().toString());
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.EntityCountUpdate;
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.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
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.dynamic_page_link.refresh_interval=15"
})
public class WebsocketApiTest extends AbstractControllerTest {
public class WebSocketApiTest extends AbstractControllerTest {
@Autowired
private TelemetrySubscriptionService tsService;
@ -128,8 +127,8 @@ public class WebsocketApiTest extends AbstractControllerTest {
PageData<EntityData> pageData = update.getData();
Assert.assertNotNull(pageData);
Assert.assertEquals(1, pageData.getData().size());
Assert.assertEquals(device.getId(), pageData.getData().get(0).getEntityId());
Assert.assertEquals(0, pageData.getData().get(0).getTimeseries().get("temperature").length);
Assert.assertEquals(device.getId(), pageData.getData().getFirst().getEntityId());
Assert.assertEquals(0, pageData.getData().getFirst().getTimeseries().get("temperature").length);
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));
@ -144,8 +143,8 @@ public class WebsocketApiTest extends AbstractControllerTest {
List<EntityData> dataList = update.getUpdate();
Assert.assertNotNull(dataList);
Assert.assertEquals(1, dataList.size());
Assert.assertEquals(device.getId(), dataList.get(0).getEntityId());
TsValue[] tsArray = dataList.get(0).getTimeseries().get("temperature");
Assert.assertEquals(device.getId(), dataList.getFirst().getEntityId());
TsValue[] tsArray = dataList.getFirst().getTimeseries().get("temperature");
Assert.assertEquals(3, tsArray.length);
Assert.assertEquals(new TsValue(dataPoint1.getTs(), dataPoint1.getValueAsString()), tsArray[0]);
Assert.assertEquals(new TsValue(dataPoint2.getTs(), dataPoint2.getValueAsString()), tsArray[1]);
@ -162,7 +161,7 @@ public class WebsocketApiTest extends AbstractControllerTest {
PageData<EntityData> pageData = update.getData();
Assert.assertNotNull(pageData);
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 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();
Assert.assertNotNull(listData);
Assert.assertEquals(1, listData.size());
Assert.assertEquals(device.getId(), listData.get(0).getEntityId());
TsValue[] tsArray = listData.get(0).getTimeseries().get("temperature");
Assert.assertEquals(device.getId(), listData.getFirst().getEntityId());
TsValue[] tsArray = listData.getFirst().getTimeseries().get("temperature");
Assert.assertEquals(3, tsArray.length);
Assert.assertEquals(new TsValue(dataPoint1.getTs(), dataPoint1.getValueAsString()), tsArray[0]);
Assert.assertEquals(new TsValue(dataPoint2.getTs(), dataPoint2.getValueAsString()), tsArray[1]);
@ -186,7 +185,7 @@ public class WebsocketApiTest extends AbstractControllerTest {
now = System.currentTimeMillis();
TsKvEntry dataPoint4 = new BasicTsKvEntry(now, new LongDataEntry("temperature", 45L));
getWsClient().registerWaitForUpdate();
sendTelemetry(device, Arrays.asList(dataPoint4));
sendTelemetry(device, List.of(dataPoint4));
String msg = getWsClient().waitForUpdate();
update = JacksonUtil.fromString(msg, EntityDataUpdate.class);
@ -194,9 +193,9 @@ public class WebsocketApiTest extends AbstractControllerTest {
List<EntityData> eData = update.getUpdate();
Assert.assertNotNull(eData);
Assert.assertEquals(1, eData.size());
Assert.assertEquals(device.getId(), eData.get(0).getEntityId());
Assert.assertNotNull(eData.get(0).getTimeseries());
TsValue[] tsValues = eData.get(0).getTimeseries().get("temperature");
Assert.assertEquals(device.getId(), eData.getFirst().getEntityId());
Assert.assertNotNull(eData.getFirst().getTimeseries());
TsValue[] tsValues = eData.getFirst().getTimeseries().get("temperature");
Assert.assertNotNull(tsValues);
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));
Assert.assertNull(msg);
// check device
// check a device
AlarmStatusCmd deviceCmd = new AlarmStatusCmd(2, device.getId(), null, List.of(AlarmSeverity.CRITICAL));
getWsClient().send(deviceCmd);
@ -589,13 +588,13 @@ public class WebsocketApiTest extends AbstractControllerTest {
PageData<EntityData> pageData = update.getData();
Assert.assertNotNull(pageData);
Assert.assertEquals(1, pageData.getData().size());
Assert.assertEquals(device.getId(), pageData.getData().get(0).getEntityId());
Assert.assertNotNull(pageData.getData().get(0).getLatest().get(EntityKeyType.TIME_SERIES).get("temperature"));
Assert.assertEquals(0, pageData.getData().get(0).getLatest().get(EntityKeyType.TIME_SERIES).get("temperature").getTs());
Assert.assertEquals("", pageData.getData().get(0).getLatest().get(EntityKeyType.TIME_SERIES).get("temperature").getValue());
Assert.assertEquals(device.getId(), pageData.getData().getFirst().getEntityId());
Assert.assertNotNull(pageData.getData().getFirst().getLatest().get(EntityKeyType.TIME_SERIES).get("temperature"));
Assert.assertEquals(0, pageData.getData().getFirst().getLatest().get(EntityKeyType.TIME_SERIES).get("temperature").getTs());
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));
List<TsKvEntry> tsData = Arrays.asList(dataPoint1);
List<TsKvEntry> tsData = List.of(dataPoint1);
sendTelemetry(device, tsData);
update = getWsClient().subscribeLatestUpdate(keys);
@ -605,36 +604,36 @@ public class WebsocketApiTest extends AbstractControllerTest {
List<EntityData> listData = update.getUpdate();
Assert.assertNotNull(listData);
Assert.assertEquals(1, listData.size());
Assert.assertEquals(device.getId(), listData.get(0).getEntityId());
Assert.assertNotNull(listData.get(0).getLatest().get(EntityKeyType.TIME_SERIES));
TsValue tsValue = listData.get(0).getLatest().get(EntityKeyType.TIME_SERIES).get("temperature");
Assert.assertEquals(device.getId(), listData.getFirst().getEntityId());
Assert.assertNotNull(listData.getFirst().getLatest().get(EntityKeyType.TIME_SERIES));
TsValue tsValue = listData.getFirst().getLatest().get(EntityKeyType.TIME_SERIES).get("temperature");
Assert.assertEquals(new TsValue(dataPoint1.getTs(), dataPoint1.getValueAsString()), tsValue);
now = System.currentTimeMillis();
TsKvEntry dataPoint2 = new BasicTsKvEntry(now, new LongDataEntry("temperature", 52L));
getWsClient().registerWaitForUpdate();
sendTelemetry(device, Arrays.asList(dataPoint2));
sendTelemetry(device, List.of(dataPoint2));
update = getWsClient().parseDataReply(getWsClient().waitForUpdate());
Assert.assertEquals(1, update.getCmdId());
List<EntityData> eData = update.getUpdate();
Assert.assertNotNull(eData);
Assert.assertEquals(1, eData.size());
Assert.assertEquals(device.getId(), eData.get(0).getEntityId());
Assert.assertNotNull(eData.get(0).getLatest().get(EntityKeyType.TIME_SERIES));
tsValue = eData.get(0).getLatest().get(EntityKeyType.TIME_SERIES).get("temperature");
Assert.assertEquals(device.getId(), eData.getFirst().getEntityId());
Assert.assertNotNull(eData.getFirst().getLatest().get(EntityKeyType.TIME_SERIES));
tsValue = eData.getFirst().getLatest().get(EntityKeyType.TIME_SERIES).get("temperature");
Assert.assertEquals(new TsValue(dataPoint2.getTs(), dataPoint2.getValueAsString()), tsValue);
//Sending update from the past, while latest value has new timestamp;
getWsClient().registerWaitForUpdate();
sendTelemetry(device, Arrays.asList(dataPoint1));
sendTelemetry(device, List.of(dataPoint1));
String msg = getWsClient().waitForUpdate(TimeUnit.SECONDS.toMillis(1));
Assert.assertNull(msg);
//Sending duplicate update again
getWsClient().registerWaitForUpdate();
sendTelemetry(device, Arrays.asList(dataPoint2));
sendTelemetry(device, List.of(dataPoint2));
msg = getWsClient().waitForUpdate(TimeUnit.SECONDS.toMillis(1));
Assert.assertNull(msg);
}
@ -677,14 +676,14 @@ public class WebsocketApiTest extends AbstractControllerTest {
PageData<EntityData> pageData = update.getData();
Assert.assertNotNull(pageData);
Assert.assertEquals(1, pageData.getData().size());
Assert.assertEquals(device.getId(), pageData.getData().get(0).getEntityId());
Assert.assertNotNull(pageData.getData().get(0).getLatest().get(EntityKeyType.TIME_SERIES).get("temperature"));
Assert.assertEquals(0, pageData.getData().get(0).getLatest().get(EntityKeyType.TIME_SERIES).get("temperature").getTs());
Assert.assertEquals("", pageData.getData().get(0).getLatest().get(EntityKeyType.TIME_SERIES).get("temperature").getValue());
Assert.assertEquals(device.getId(), pageData.getData().getFirst().getEntityId());
Assert.assertNotNull(pageData.getData().getFirst().getLatest().get(EntityKeyType.TIME_SERIES).get("temperature"));
Assert.assertEquals(0, pageData.getData().getFirst().getLatest().get(EntityKeyType.TIME_SERIES).get("temperature").getTs());
Assert.assertEquals("", pageData.getData().getFirst().getLatest().get(EntityKeyType.TIME_SERIES).get("temperature").getValue());
getWsClient().registerWaitForUpdate();
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);
update = getWsClient().parseDataReply(getWsClient().waitForUpdate());
@ -694,34 +693,34 @@ public class WebsocketApiTest extends AbstractControllerTest {
List<EntityData> listData = update.getUpdate();
Assert.assertNotNull(listData);
Assert.assertEquals(1, listData.size());
Assert.assertEquals(device.getId(), listData.get(0).getEntityId());
Assert.assertNotNull(listData.get(0).getLatest().get(EntityKeyType.TIME_SERIES));
TsValue tsValue = listData.get(0).getLatest().get(EntityKeyType.TIME_SERIES).get("temperature");
Assert.assertEquals(device.getId(), listData.getFirst().getEntityId());
Assert.assertNotNull(listData.getFirst().getLatest().get(EntityKeyType.TIME_SERIES));
TsValue tsValue = listData.getFirst().getLatest().get(EntityKeyType.TIME_SERIES).get("temperature");
Assert.assertEquals(new TsValue(dataPoint1.getTs(), dataPoint1.getValueAsString()), tsValue);
now = System.currentTimeMillis();
TsKvEntry dataPoint2 = new BasicTsKvEntry(now, new LongDataEntry("temperature", 52L));
getWsClient().registerWaitForUpdate();
sendTelemetry(device, Arrays.asList(dataPoint2));
sendTelemetry(device, List.of(dataPoint2));
update = getWsClient().parseDataReply(getWsClient().waitForUpdate());
Assert.assertEquals(1, update.getCmdId());
List<EntityData> eData = update.getUpdate();
Assert.assertNotNull(eData);
Assert.assertEquals(1, eData.size());
Assert.assertEquals(device.getId(), eData.get(0).getEntityId());
Assert.assertNotNull(eData.get(0).getLatest().get(EntityKeyType.TIME_SERIES));
tsValue = eData.get(0).getLatest().get(EntityKeyType.TIME_SERIES).get("temperature");
Assert.assertEquals(device.getId(), eData.getFirst().getEntityId());
Assert.assertNotNull(eData.getFirst().getLatest().get(EntityKeyType.TIME_SERIES));
tsValue = eData.getFirst().getLatest().get(EntityKeyType.TIME_SERIES).get("temperature");
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();
sendTelemetry(device, Arrays.asList(dataPoint1));
sendTelemetry(device, List.of(dataPoint1));
String msg = getWsClient().waitForUpdate(TimeUnit.SECONDS.toMillis(1));
Assert.assertNull(msg);
//Sending duplicate update again
getWsClient().registerWaitForUpdate();
sendTelemetry(device, Arrays.asList(dataPoint2));
sendTelemetry(device, List.of(dataPoint2));
msg = getWsClient().waitForUpdate(TimeUnit.SECONDS.toMillis(1));
Assert.assertNull(msg);
}
@ -736,21 +735,21 @@ public class WebsocketApiTest extends AbstractControllerTest {
PageData<EntityData> pageData = update.getData();
Assert.assertNotNull(pageData);
Assert.assertEquals(1, pageData.getData().size());
Assert.assertEquals(device.getId(), pageData.getData().get(0).getEntityId());
Assert.assertNotNull(pageData.getData().get(0).getLatest().get(EntityKeyType.SERVER_ATTRIBUTE).get("serverAttributeKey"));
Assert.assertEquals(0, pageData.getData().get(0).getLatest().get(EntityKeyType.SERVER_ATTRIBUTE).get("serverAttributeKey").getTs());
Assert.assertEquals("", pageData.getData().get(0).getLatest().get(EntityKeyType.SERVER_ATTRIBUTE).get("serverAttributeKey").getValue());
Assert.assertEquals(device.getId(), pageData.getData().getFirst().getEntityId());
Assert.assertNotNull(pageData.getData().getFirst().getLatest().get(EntityKeyType.SERVER_ATTRIBUTE).get("serverAttributeKey"));
Assert.assertEquals(0, pageData.getData().getFirst().getLatest().get(EntityKeyType.SERVER_ATTRIBUTE).get("serverAttributeKey").getTs());
Assert.assertEquals("", pageData.getData().getFirst().getLatest().get(EntityKeyType.SERVER_ATTRIBUTE).get("serverAttributeKey").getValue());
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));
sendAttributes(device, TbAttributeSubscriptionScope.CLIENT_SCOPE, Arrays.asList(invalidDataPoint));
sendAttributes(device, TbAttributeSubscriptionScope.CLIENT_SCOPE, List.of(invalidDataPoint));
Assert.assertNull(getWsClient().waitForUpdate(3000));
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);
String msg = getWsClient().waitForUpdate();
@ -761,16 +760,16 @@ public class WebsocketApiTest extends AbstractControllerTest {
List<EntityData> listData = update.getUpdate();
Assert.assertNotNull(listData);
Assert.assertEquals(1, listData.size());
Assert.assertEquals(device.getId(), listData.get(0).getEntityId());
Assert.assertNotNull(listData.get(0).getLatest().get(EntityKeyType.SERVER_ATTRIBUTE));
TsValue tsValue = listData.get(0).getLatest().get(EntityKeyType.SERVER_ATTRIBUTE).get("serverAttributeKey");
Assert.assertEquals(device.getId(), listData.getFirst().getEntityId());
Assert.assertNotNull(listData.getFirst().getLatest().get(EntityKeyType.SERVER_ATTRIBUTE));
TsValue tsValue = listData.getFirst().getLatest().get(EntityKeyType.SERVER_ATTRIBUTE).get("serverAttributeKey");
Assert.assertEquals(new TsValue(dataPoint1.getLastUpdateTs(), dataPoint1.getValueAsString()), tsValue);
now = System.currentTimeMillis();
AttributeKvEntry dataPoint2 = new BaseAttributeKvEntry(now, new LongDataEntry("serverAttributeKey", 52L));
getWsClient().registerWaitForUpdate();
sendAttributes(device, TbAttributeSubscriptionScope.SERVER_SCOPE, Arrays.asList(dataPoint2));
sendAttributes(device, TbAttributeSubscriptionScope.SERVER_SCOPE, List.of(dataPoint2));
msg = getWsClient().waitForUpdate();
Assert.assertNotNull(msg);
@ -779,20 +778,20 @@ public class WebsocketApiTest extends AbstractControllerTest {
List<EntityData> eData = update.getUpdate();
Assert.assertNotNull(eData);
Assert.assertEquals(1, eData.size());
Assert.assertEquals(device.getId(), eData.get(0).getEntityId());
Assert.assertNotNull(eData.get(0).getLatest().get(EntityKeyType.SERVER_ATTRIBUTE));
tsValue = eData.get(0).getLatest().get(EntityKeyType.SERVER_ATTRIBUTE).get("serverAttributeKey");
Assert.assertEquals(device.getId(), eData.getFirst().getEntityId());
Assert.assertNotNull(eData.getFirst().getLatest().get(EntityKeyType.SERVER_ATTRIBUTE));
tsValue = eData.getFirst().getLatest().get(EntityKeyType.SERVER_ATTRIBUTE).get("serverAttributeKey");
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();
sendAttributes(device, TbAttributeSubscriptionScope.SERVER_SCOPE, Arrays.asList(dataPoint1));
sendAttributes(device, TbAttributeSubscriptionScope.SERVER_SCOPE, List.of(dataPoint1));
msg = getWsClient().waitForUpdate(TimeUnit.SECONDS.toMillis(1));
Assert.assertNull(msg);
//Sending duplicate update again
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));
Assert.assertNull(msg);
}
@ -812,23 +811,23 @@ public class WebsocketApiTest extends AbstractControllerTest {
PageData<EntityData> pageData = update.getData();
Assert.assertNotNull(pageData);
Assert.assertEquals(1, pageData.getData().size());
Assert.assertEquals(device.getId(), pageData.getData().get(0).getEntityId());
Assert.assertNotNull(pageData.getData().get(0).getLatest().get(EntityKeyType.SERVER_ATTRIBUTE).get("serverAttributeKey"));
Assert.assertEquals(0, pageData.getData().get(0).getLatest().get(EntityKeyType.SERVER_ATTRIBUTE).get("serverAttributeKey").getTs());
Assert.assertEquals("", pageData.getData().get(0).getLatest().get(EntityKeyType.SERVER_ATTRIBUTE).get("serverAttributeKey").getValue());
Assert.assertNotNull(pageData.getData().get(0).getLatest().get(EntityKeyType.CLIENT_ATTRIBUTE).get("clientAttributeKey"));
Assert.assertEquals(0, pageData.getData().get(0).getLatest().get(EntityKeyType.CLIENT_ATTRIBUTE).get("clientAttributeKey").getTs());
Assert.assertEquals("", pageData.getData().get(0).getLatest().get(EntityKeyType.CLIENT_ATTRIBUTE).get("clientAttributeKey").getValue());
Assert.assertNotNull(pageData.getData().get(0).getLatest().get(EntityKeyType.SHARED_ATTRIBUTE).get("sharedAttributeKey"));
Assert.assertEquals(0, pageData.getData().get(0).getLatest().get(EntityKeyType.SHARED_ATTRIBUTE).get("sharedAttributeKey").getTs());
Assert.assertEquals("", pageData.getData().get(0).getLatest().get(EntityKeyType.SHARED_ATTRIBUTE).get("sharedAttributeKey").getValue());
Assert.assertNotNull(pageData.getData().get(0).getLatest().get(EntityKeyType.ATTRIBUTE).get("anyAttributeKey"));
Assert.assertEquals(0, pageData.getData().get(0).getLatest().get(EntityKeyType.ATTRIBUTE).get("anyAttributeKey").getTs());
Assert.assertEquals("", pageData.getData().get(0).getLatest().get(EntityKeyType.ATTRIBUTE).get("anyAttributeKey").getValue());
Assert.assertEquals(device.getId(), pageData.getData().getFirst().getEntityId());
Assert.assertNotNull(pageData.getData().getFirst().getLatest().get(EntityKeyType.SERVER_ATTRIBUTE).get("serverAttributeKey"));
Assert.assertEquals(0, pageData.getData().getFirst().getLatest().get(EntityKeyType.SERVER_ATTRIBUTE).get("serverAttributeKey").getTs());
Assert.assertEquals("", pageData.getData().getFirst().getLatest().get(EntityKeyType.SERVER_ATTRIBUTE).get("serverAttributeKey").getValue());
Assert.assertNotNull(pageData.getData().getFirst().getLatest().get(EntityKeyType.CLIENT_ATTRIBUTE).get("clientAttributeKey"));
Assert.assertEquals(0, pageData.getData().getFirst().getLatest().get(EntityKeyType.CLIENT_ATTRIBUTE).get("clientAttributeKey").getTs());
Assert.assertEquals("", pageData.getData().getFirst().getLatest().get(EntityKeyType.CLIENT_ATTRIBUTE).get("clientAttributeKey").getValue());
Assert.assertNotNull(pageData.getData().getFirst().getLatest().get(EntityKeyType.SHARED_ATTRIBUTE).get("sharedAttributeKey"));
Assert.assertEquals(0, pageData.getData().getFirst().getLatest().get(EntityKeyType.SHARED_ATTRIBUTE).get("sharedAttributeKey").getTs());
Assert.assertEquals("", pageData.getData().getFirst().getLatest().get(EntityKeyType.SHARED_ATTRIBUTE).get("sharedAttributeKey").getValue());
Assert.assertNotNull(pageData.getData().getFirst().getLatest().get(EntityKeyType.ATTRIBUTE).get("anyAttributeKey"));
Assert.assertEquals(0, pageData.getData().getFirst().getLatest().get(EntityKeyType.ATTRIBUTE).get("anyAttributeKey").getTs());
Assert.assertEquals("", pageData.getData().getFirst().getLatest().get(EntityKeyType.ATTRIBUTE).get("anyAttributeKey").getValue());
getWsClient().registerWaitForUpdate();
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);
@ -839,78 +838,78 @@ public class WebsocketApiTest extends AbstractControllerTest {
List<EntityData> eData = update.getUpdate();
Assert.assertNotNull(eData);
Assert.assertEquals(1, eData.size());
Assert.assertEquals(device.getId(), eData.get(0).getEntityId());
Assert.assertNotNull(eData.get(0).getLatest().get(EntityKeyType.SERVER_ATTRIBUTE));
TsValue attrValue = eData.get(0).getLatest().get(EntityKeyType.SERVER_ATTRIBUTE).get("serverAttributeKey");
Assert.assertEquals(device.getId(), eData.getFirst().getEntityId());
Assert.assertNotNull(eData.getFirst().getLatest().get(EntityKeyType.SERVER_ATTRIBUTE));
TsValue attrValue = eData.getFirst().getLatest().get(EntityKeyType.SERVER_ATTRIBUTE).get("serverAttributeKey");
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();
sendAttributes(device, TbAttributeSubscriptionScope.SHARED_SCOPE, Arrays.asList(dataPoint1));
sendAttributes(device, TbAttributeSubscriptionScope.SHARED_SCOPE, List.of(dataPoint1));
msg = getWsClient().waitForUpdate(TimeUnit.SECONDS.toMillis(1));
Assert.assertNull(msg);
//Sending duplicate update again
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));
Assert.assertNull(msg);
//Sending update from the past, while latest value has new timestamp;
getWsClient().registerWaitForUpdate();
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));
update = JacksonUtil.fromString(msg, EntityDataUpdate.class);
Assert.assertEquals(1, update.getCmdId());
eData = update.getUpdate();
Assert.assertNotNull(eData);
Assert.assertEquals(1, eData.size());
Assert.assertEquals(device.getId(), eData.get(0).getEntityId());
Assert.assertNotNull(eData.get(0).getLatest().get(EntityKeyType.SHARED_ATTRIBUTE));
attrValue = eData.get(0).getLatest().get(EntityKeyType.SHARED_ATTRIBUTE).get("sharedAttributeKey");
Assert.assertEquals(device.getId(), eData.getFirst().getEntityId());
Assert.assertNotNull(eData.getFirst().getLatest().get(EntityKeyType.SHARED_ATTRIBUTE));
attrValue = eData.getFirst().getLatest().get(EntityKeyType.SHARED_ATTRIBUTE).get("sharedAttributeKey");
Assert.assertEquals(new TsValue(dataPoint2.getLastUpdateTs(), dataPoint2.getValueAsString()), attrValue);
getWsClient().registerWaitForUpdate();
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));
update = JacksonUtil.fromString(msg, EntityDataUpdate.class);
Assert.assertEquals(1, update.getCmdId());
eData = update.getUpdate();
Assert.assertNotNull(eData);
Assert.assertEquals(1, eData.size());
Assert.assertEquals(device.getId(), eData.get(0).getEntityId());
Assert.assertNotNull(eData.get(0).getLatest().get(EntityKeyType.CLIENT_ATTRIBUTE));
attrValue = eData.get(0).getLatest().get(EntityKeyType.CLIENT_ATTRIBUTE).get("clientAttributeKey");
Assert.assertEquals(device.getId(), eData.getFirst().getEntityId());
Assert.assertNotNull(eData.getFirst().getLatest().get(EntityKeyType.CLIENT_ATTRIBUTE));
attrValue = eData.getFirst().getLatest().get(EntityKeyType.CLIENT_ATTRIBUTE).get("clientAttributeKey");
Assert.assertEquals(new TsValue(dataPoint3.getLastUpdateTs(), dataPoint3.getValueAsString()), attrValue);
getWsClient().registerWaitForUpdate();
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));
update = JacksonUtil.fromString(msg, EntityDataUpdate.class);
Assert.assertEquals(1, update.getCmdId());
eData = update.getUpdate();
Assert.assertNotNull(eData);
Assert.assertEquals(1, eData.size());
Assert.assertEquals(device.getId(), eData.get(0).getEntityId());
Assert.assertNotNull(eData.get(0).getLatest().get(EntityKeyType.ATTRIBUTE));
attrValue = eData.get(0).getLatest().get(EntityKeyType.ATTRIBUTE).get("anyAttributeKey");
Assert.assertEquals(device.getId(), eData.getFirst().getEntityId());
Assert.assertNotNull(eData.getFirst().getLatest().get(EntityKeyType.ATTRIBUTE));
attrValue = eData.getFirst().getLatest().get(EntityKeyType.ATTRIBUTE).get("anyAttributeKey");
Assert.assertEquals(new TsValue(dataPoint4.getLastUpdateTs(), dataPoint4.getValueAsString()), attrValue);
getWsClient().registerWaitForUpdate();
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));
update = JacksonUtil.fromString(msg, EntityDataUpdate.class);
Assert.assertEquals(1, update.getCmdId());
eData = update.getUpdate();
Assert.assertNotNull(eData);
Assert.assertEquals(1, eData.size());
Assert.assertEquals(device.getId(), eData.get(0).getEntityId());
Assert.assertNotNull(eData.get(0).getLatest().get(EntityKeyType.ATTRIBUTE));
attrValue = eData.get(0).getLatest().get(EntityKeyType.ATTRIBUTE).get("anyAttributeKey");
Assert.assertEquals(device.getId(), eData.getFirst().getEntityId());
Assert.assertNotNull(eData.getFirst().getLatest().get(EntityKeyType.ATTRIBUTE));
attrValue = eData.getFirst().getLatest().get(EntityKeyType.ATTRIBUTE).get("anyAttributeKey");
Assert.assertEquals(new TsValue(dataPoint5.getLastUpdateTs(), dataPoint5.getValueAsString()), attrValue);
}
@ -971,7 +970,7 @@ public class WebsocketApiTest extends AbstractControllerTest {
.tenantId(device.getTenantId())
.entityId(device.getId())
.entries(tsData)
.callback(new FutureCallback<Void>() {
.callback(new FutureCallback<>() {
@Override
public void onSuccess(@Nullable Void result) {
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.Test;
import org.mockito.Mockito;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.adapter.NativeWebSocketSession;
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.WebSocketSessionType;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Deque;
import java.util.List;
import java.util.Random;
import java.util.UUID;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.CountDownLatch;
@ -184,4 +193,101 @@ class TbWebSocketHandlerTest {
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