Commit 4c087448172bb00960fc8ac7cb161a7dfc3dff75
Committed by
GitHub
Merge pull request #5614 from thingsboard/bug/mqtt-credentials
[3.3.3] Fix corner case when access token matches user name in credentials
Showing
5 changed files
with
306 additions
and
37 deletions
1 | +/** | ||
2 | + * Copyright © 2016-2021 The Thingsboard Authors | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | +package org.thingsboard.server.service.transport; | ||
17 | + | ||
18 | +enum BasicCredentialsValidationResult {HASH_MISMATCH, PASSWORD_MISMATCH, VALID} |
@@ -25,7 +25,6 @@ import com.google.protobuf.ByteString; | @@ -25,7 +25,6 @@ import com.google.protobuf.ByteString; | ||
25 | import lombok.RequiredArgsConstructor; | 25 | import lombok.RequiredArgsConstructor; |
26 | import lombok.extern.slf4j.Slf4j; | 26 | import lombok.extern.slf4j.Slf4j; |
27 | import org.springframework.stereotype.Service; | 27 | import org.springframework.stereotype.Service; |
28 | -import org.springframework.util.StringUtils; | ||
29 | import org.thingsboard.common.util.JacksonUtil; | 28 | import org.thingsboard.common.util.JacksonUtil; |
30 | import org.thingsboard.server.cache.ota.OtaPackageDataCache; | 29 | import org.thingsboard.server.cache.ota.OtaPackageDataCache; |
31 | import org.thingsboard.server.common.data.ApiUsageState; | 30 | import org.thingsboard.server.common.data.ApiUsageState; |
@@ -37,6 +36,7 @@ import org.thingsboard.server.common.data.EntityType; | @@ -37,6 +36,7 @@ import org.thingsboard.server.common.data.EntityType; | ||
37 | import org.thingsboard.server.common.data.OtaPackage; | 36 | import org.thingsboard.server.common.data.OtaPackage; |
38 | import org.thingsboard.server.common.data.OtaPackageInfo; | 37 | import org.thingsboard.server.common.data.OtaPackageInfo; |
39 | import org.thingsboard.server.common.data.ResourceType; | 38 | import org.thingsboard.server.common.data.ResourceType; |
39 | +import org.thingsboard.server.common.data.StringUtils; | ||
40 | import org.thingsboard.server.common.data.TbResource; | 40 | import org.thingsboard.server.common.data.TbResource; |
41 | import org.thingsboard.server.common.data.TenantProfile; | 41 | import org.thingsboard.server.common.data.TenantProfile; |
42 | import org.thingsboard.server.common.data.device.credentials.BasicMqttCredentials; | 42 | import org.thingsboard.server.common.data.device.credentials.BasicMqttCredentials; |
@@ -107,6 +107,9 @@ import java.util.concurrent.locks.Lock; | @@ -107,6 +107,9 @@ import java.util.concurrent.locks.Lock; | ||
107 | import java.util.concurrent.locks.ReentrantLock; | 107 | import java.util.concurrent.locks.ReentrantLock; |
108 | import java.util.stream.Collectors; | 108 | import java.util.stream.Collectors; |
109 | 109 | ||
110 | +import static org.thingsboard.server.service.transport.BasicCredentialsValidationResult.PASSWORD_MISMATCH; | ||
111 | +import static org.thingsboard.server.service.transport.BasicCredentialsValidationResult.VALID; | ||
112 | + | ||
110 | /** | 113 | /** |
111 | * Created by ashvayka on 05.10.18. | 114 | * Created by ashvayka on 05.10.18. |
112 | */ | 115 | */ |
@@ -181,71 +184,89 @@ public class DefaultTransportApiService implements TransportApiService { | @@ -181,71 +184,89 @@ public class DefaultTransportApiService implements TransportApiService { | ||
181 | //TODO: Make async and enable caching | 184 | //TODO: Make async and enable caching |
182 | DeviceCredentials credentials = deviceCredentialsService.findDeviceCredentialsByCredentialsId(credentialsId); | 185 | DeviceCredentials credentials = deviceCredentialsService.findDeviceCredentialsByCredentialsId(credentialsId); |
183 | if (credentials != null && credentials.getCredentialsType() == credentialsType) { | 186 | if (credentials != null && credentials.getCredentialsType() == credentialsType) { |
184 | - return getDeviceInfo(credentials.getDeviceId(), credentials); | 187 | + return getDeviceInfo(credentials); |
185 | } else { | 188 | } else { |
186 | return getEmptyTransportApiResponseFuture(); | 189 | return getEmptyTransportApiResponseFuture(); |
187 | } | 190 | } |
188 | } | 191 | } |
189 | 192 | ||
190 | private ListenableFuture<TransportApiResponseMsg> validateCredentials(TransportProtos.ValidateBasicMqttCredRequestMsg mqtt) { | 193 | private ListenableFuture<TransportApiResponseMsg> validateCredentials(TransportProtos.ValidateBasicMqttCredRequestMsg mqtt) { |
191 | - DeviceCredentials credentials = null; | ||
192 | - if (!StringUtils.isEmpty(mqtt.getUserName())) { | ||
193 | - credentials = deviceCredentialsService.findDeviceCredentialsByCredentialsId(mqtt.getUserName()); | 194 | + DeviceCredentials credentials; |
195 | + if (StringUtils.isEmpty(mqtt.getUserName())) { | ||
196 | + credentials = checkMqttCredentials(mqtt, EncryptionUtil.getSha3Hash(mqtt.getClientId())); | ||
194 | if (credentials != null) { | 197 | if (credentials != null) { |
195 | - if (credentials.getCredentialsType() == DeviceCredentialsType.ACCESS_TOKEN) { | ||
196 | - return getDeviceInfo(credentials.getDeviceId(), credentials); | ||
197 | - } else if (credentials.getCredentialsType() == DeviceCredentialsType.MQTT_BASIC) { | ||
198 | - if (!checkMqttCredentials(mqtt, credentials)) { | ||
199 | - credentials = null; | ||
200 | - } | ||
201 | - } else { | 198 | + return getDeviceInfo(credentials); |
199 | + } else { | ||
200 | + return getEmptyTransportApiResponseFuture(); | ||
201 | + } | ||
202 | + } else { | ||
203 | + credentials = deviceCredentialsService.findDeviceCredentialsByCredentialsId( | ||
204 | + EncryptionUtil.getSha3Hash("|", mqtt.getClientId(), mqtt.getUserName())); | ||
205 | + if (checkIsMqttCredentials(credentials)) { | ||
206 | + var validationResult = validateMqttCredentials(mqtt, credentials); | ||
207 | + if (VALID.equals(validationResult)) { | ||
208 | + return getDeviceInfo(credentials); | ||
209 | + } else if (PASSWORD_MISMATCH.equals(validationResult)) { | ||
202 | return getEmptyTransportApiResponseFuture(); | 210 | return getEmptyTransportApiResponseFuture(); |
211 | + } else { | ||
212 | + return validateUserNameCredentials(mqtt); | ||
203 | } | 213 | } |
214 | + } else { | ||
215 | + return validateUserNameCredentials(mqtt); | ||
204 | } | 216 | } |
205 | - if (credentials == null) { | ||
206 | - credentials = checkMqttCredentials(mqtt, EncryptionUtil.getSha3Hash("|", mqtt.getClientId(), mqtt.getUserName())); | ||
207 | - } | ||
208 | - } | ||
209 | - if (credentials == null) { | ||
210 | - credentials = checkMqttCredentials(mqtt, EncryptionUtil.getSha3Hash(mqtt.getClientId())); | ||
211 | } | 217 | } |
218 | + } | ||
219 | + | ||
220 | + private ListenableFuture<TransportApiResponseMsg> validateUserNameCredentials(TransportProtos.ValidateBasicMqttCredRequestMsg mqtt) { | ||
221 | + DeviceCredentials credentials = deviceCredentialsService.findDeviceCredentialsByCredentialsId(mqtt.getUserName()); | ||
212 | if (credentials != null) { | 222 | if (credentials != null) { |
213 | - return getDeviceInfo(credentials.getDeviceId(), credentials); | ||
214 | - } else { | ||
215 | - return getEmptyTransportApiResponseFuture(); | 223 | + switch (credentials.getCredentialsType()) { |
224 | + case ACCESS_TOKEN: | ||
225 | + return getDeviceInfo(credentials); | ||
226 | + case MQTT_BASIC: | ||
227 | + if (VALID.equals(validateMqttCredentials(mqtt, credentials))) { | ||
228 | + return getDeviceInfo(credentials); | ||
229 | + } else { | ||
230 | + return getEmptyTransportApiResponseFuture(); | ||
231 | + } | ||
232 | + } | ||
216 | } | 233 | } |
234 | + return getEmptyTransportApiResponseFuture(); | ||
235 | + } | ||
236 | + | ||
237 | + private static boolean checkIsMqttCredentials(DeviceCredentials credentials) { | ||
238 | + return credentials != null && DeviceCredentialsType.MQTT_BASIC.equals(credentials.getCredentialsType()); | ||
217 | } | 239 | } |
218 | 240 | ||
219 | private DeviceCredentials checkMqttCredentials(TransportProtos.ValidateBasicMqttCredRequestMsg clientCred, String credId) { | 241 | private DeviceCredentials checkMqttCredentials(TransportProtos.ValidateBasicMqttCredRequestMsg clientCred, String credId) { |
220 | - DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByCredentialsId(credId); | 242 | + return checkMqttCredentials(clientCred, deviceCredentialsService.findDeviceCredentialsByCredentialsId(credId)); |
243 | + } | ||
244 | + | ||
245 | + private DeviceCredentials checkMqttCredentials(TransportProtos.ValidateBasicMqttCredRequestMsg clientCred, DeviceCredentials deviceCredentials) { | ||
221 | if (deviceCredentials != null && deviceCredentials.getCredentialsType() == DeviceCredentialsType.MQTT_BASIC) { | 246 | if (deviceCredentials != null && deviceCredentials.getCredentialsType() == DeviceCredentialsType.MQTT_BASIC) { |
222 | - if (!checkMqttCredentials(clientCred, deviceCredentials)) { | ||
223 | - return null; | ||
224 | - } else { | 247 | + if (VALID.equals(validateMqttCredentials(clientCred, deviceCredentials))) { |
225 | return deviceCredentials; | 248 | return deviceCredentials; |
226 | } | 249 | } |
227 | } | 250 | } |
228 | return null; | 251 | return null; |
229 | } | 252 | } |
230 | 253 | ||
231 | - private boolean checkMqttCredentials(TransportProtos.ValidateBasicMqttCredRequestMsg clientCred, DeviceCredentials deviceCredentials) { | 254 | + private BasicCredentialsValidationResult validateMqttCredentials(TransportProtos.ValidateBasicMqttCredRequestMsg clientCred, DeviceCredentials deviceCredentials) { |
232 | BasicMqttCredentials dbCred = JacksonUtil.fromString(deviceCredentials.getCredentialsValue(), BasicMqttCredentials.class); | 255 | BasicMqttCredentials dbCred = JacksonUtil.fromString(deviceCredentials.getCredentialsValue(), BasicMqttCredentials.class); |
233 | if (!StringUtils.isEmpty(dbCred.getClientId()) && !dbCred.getClientId().equals(clientCred.getClientId())) { | 256 | if (!StringUtils.isEmpty(dbCred.getClientId()) && !dbCred.getClientId().equals(clientCred.getClientId())) { |
234 | - return false; | 257 | + return BasicCredentialsValidationResult.HASH_MISMATCH; |
235 | } | 258 | } |
236 | if (!StringUtils.isEmpty(dbCred.getUserName()) && !dbCred.getUserName().equals(clientCred.getUserName())) { | 259 | if (!StringUtils.isEmpty(dbCred.getUserName()) && !dbCred.getUserName().equals(clientCred.getUserName())) { |
237 | - return false; | 260 | + return BasicCredentialsValidationResult.HASH_MISMATCH; |
238 | } | 261 | } |
239 | if (!StringUtils.isEmpty(dbCred.getPassword())) { | 262 | if (!StringUtils.isEmpty(dbCred.getPassword())) { |
240 | if (StringUtils.isEmpty(clientCred.getPassword())) { | 263 | if (StringUtils.isEmpty(clientCred.getPassword())) { |
241 | - return false; | 264 | + return BasicCredentialsValidationResult.PASSWORD_MISMATCH; |
242 | } else { | 265 | } else { |
243 | - if (!dbCred.getPassword().equals(clientCred.getPassword())) { | ||
244 | - return false; | ||
245 | - } | 266 | + return dbCred.getPassword().equals(clientCred.getPassword()) ? VALID : BasicCredentialsValidationResult.PASSWORD_MISMATCH; |
246 | } | 267 | } |
247 | } | 268 | } |
248 | - return true; | 269 | + return VALID; |
249 | } | 270 | } |
250 | 271 | ||
251 | private ListenableFuture<TransportApiResponseMsg> handle(GetOrCreateDeviceFromGatewayRequestMsg requestMsg) { | 272 | private ListenableFuture<TransportApiResponseMsg> handle(GetOrCreateDeviceFromGatewayRequestMsg requestMsg) { |
@@ -437,10 +458,10 @@ public class DefaultTransportApiService implements TransportApiService { | @@ -437,10 +458,10 @@ public class DefaultTransportApiService implements TransportApiService { | ||
437 | .build()); | 458 | .build()); |
438 | } | 459 | } |
439 | 460 | ||
440 | - private ListenableFuture<TransportApiResponseMsg> getDeviceInfo(DeviceId deviceId, DeviceCredentials credentials) { | ||
441 | - return Futures.transform(deviceService.findDeviceByIdAsync(TenantId.SYS_TENANT_ID, deviceId), device -> { | 461 | + private ListenableFuture<TransportApiResponseMsg> getDeviceInfo(DeviceCredentials credentials) { |
462 | + return Futures.transform(deviceService.findDeviceByIdAsync(TenantId.SYS_TENANT_ID, credentials.getDeviceId()), device -> { | ||
442 | if (device == null) { | 463 | if (device == null) { |
443 | - log.trace("[{}] Failed to lookup device by id", deviceId); | 464 | + log.trace("[{}] Failed to lookup device by id", credentials.getDeviceId()); |
444 | return getEmptyTransportApiResponse(); | 465 | return getEmptyTransportApiResponse(); |
445 | } | 466 | } |
446 | try { | 467 | try { |
@@ -458,7 +479,7 @@ public class DefaultTransportApiService implements TransportApiService { | @@ -458,7 +479,7 @@ public class DefaultTransportApiService implements TransportApiService { | ||
458 | return TransportApiResponseMsg.newBuilder() | 479 | return TransportApiResponseMsg.newBuilder() |
459 | .setValidateCredResponseMsg(builder.build()).build(); | 480 | .setValidateCredResponseMsg(builder.build()).build(); |
460 | } catch (JsonProcessingException e) { | 481 | } catch (JsonProcessingException e) { |
461 | - log.warn("[{}] Failed to lookup device by id", deviceId, e); | 482 | + log.warn("[{}] Failed to lookup device by id", credentials.getDeviceId(), e); |
462 | return getEmptyTransportApiResponse(); | 483 | return getEmptyTransportApiResponse(); |
463 | } | 484 | } |
464 | }, MoreExecutors.directExecutor()); | 485 | }, MoreExecutors.directExecutor()); |
@@ -33,6 +33,7 @@ import java.util.Arrays; | @@ -33,6 +33,7 @@ import java.util.Arrays; | ||
33 | "org.thingsboard.server.transport.*.attributes.request.sql.*Test", | 33 | "org.thingsboard.server.transport.*.attributes.request.sql.*Test", |
34 | "org.thingsboard.server.transport.*.claim.sql.*Test", | 34 | "org.thingsboard.server.transport.*.claim.sql.*Test", |
35 | "org.thingsboard.server.transport.*.provision.sql.*Test", | 35 | "org.thingsboard.server.transport.*.provision.sql.*Test", |
36 | + "org.thingsboard.server.transport.*.credentials.sql.*Test", | ||
36 | "org.thingsboard.server.transport.lwm2m.sql.*Test" | 37 | "org.thingsboard.server.transport.lwm2m.sql.*Test" |
37 | }) | 38 | }) |
38 | public class TransportSqlTestSuite { | 39 | public class TransportSqlTestSuite { |
1 | +/** | ||
2 | + * Copyright © 2016-2021 The Thingsboard Authors | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | +package org.thingsboard.server.transport.mqtt.credentials.sql; | ||
17 | + | ||
18 | +import com.fasterxml.jackson.core.type.TypeReference; | ||
19 | +import org.apache.commons.lang3.RandomStringUtils; | ||
20 | +import org.eclipse.paho.client.mqttv3.MqttAsyncClient; | ||
21 | +import org.eclipse.paho.client.mqttv3.MqttConnectOptions; | ||
22 | +import org.eclipse.paho.client.mqttv3.MqttException; | ||
23 | +import org.eclipse.paho.client.mqttv3.MqttSecurityException; | ||
24 | +import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence; | ||
25 | +import org.junit.After; | ||
26 | +import org.junit.Assert; | ||
27 | +import org.junit.Before; | ||
28 | +import org.junit.Test; | ||
29 | +import org.thingsboard.common.util.JacksonUtil; | ||
30 | +import org.thingsboard.server.common.data.Device; | ||
31 | +import org.thingsboard.server.common.data.StringUtils; | ||
32 | +import org.thingsboard.server.common.data.Tenant; | ||
33 | +import org.thingsboard.server.common.data.User; | ||
34 | +import org.thingsboard.server.common.data.device.credentials.BasicMqttCredentials; | ||
35 | +import org.thingsboard.server.common.data.device.profile.MqttTopics; | ||
36 | +import org.thingsboard.server.common.data.security.Authority; | ||
37 | +import org.thingsboard.server.common.data.security.DeviceCredentials; | ||
38 | +import org.thingsboard.server.common.data.security.DeviceCredentialsType; | ||
39 | +import org.thingsboard.server.dao.service.DaoSqlTest; | ||
40 | +import org.thingsboard.server.transport.mqtt.AbstractMqttIntegrationTest; | ||
41 | + | ||
42 | +import java.util.Arrays; | ||
43 | +import java.util.HashSet; | ||
44 | +import java.util.List; | ||
45 | +import java.util.Set; | ||
46 | + | ||
47 | +import static org.junit.Assert.assertEquals; | ||
48 | +import static org.junit.Assert.assertNotNull; | ||
49 | +import static org.junit.Assert.assertNull; | ||
50 | +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; | ||
51 | + | ||
52 | +@DaoSqlTest | ||
53 | +public class BasicMqttCredentialsTest extends AbstractMqttIntegrationTest { | ||
54 | + | ||
55 | + public static final String CLIENT_ID = "ClientId"; | ||
56 | + public static final String USER_NAME1 = "UserName1"; | ||
57 | + public static final String USER_NAME2 = "UserName2"; | ||
58 | + public static final String USER_NAME3 = "UserName3"; | ||
59 | + public static final String PASSWORD = "secret"; | ||
60 | + | ||
61 | + private Device clientIdDevice; | ||
62 | + private Device clientIdAndUserNameDevice1; | ||
63 | + private Device clientIdAndUserNameAndPasswordDevice2; | ||
64 | + private Device clientIdAndUserNameAndPasswordDevice3; | ||
65 | + private Device accessTokenDevice; | ||
66 | + private Device accessToken2Device; | ||
67 | + | ||
68 | + | ||
69 | + @Before | ||
70 | + public void before() throws Exception { | ||
71 | + loginSysAdmin(); | ||
72 | + | ||
73 | + Tenant tenant = new Tenant(); | ||
74 | + tenant.setTitle("My tenant"); | ||
75 | + savedTenant = doPost("/api/tenant", tenant, Tenant.class); | ||
76 | + Assert.assertNotNull(savedTenant); | ||
77 | + | ||
78 | + tenantAdmin = new User(); | ||
79 | + tenantAdmin.setAuthority(Authority.TENANT_ADMIN); | ||
80 | + tenantAdmin.setTenantId(savedTenant.getId()); | ||
81 | + tenantAdmin.setEmail("tenant" + atomicInteger.getAndIncrement() + "@thingsboard.org"); | ||
82 | + tenantAdmin.setFirstName("Joe"); | ||
83 | + tenantAdmin.setLastName("Downs"); | ||
84 | + | ||
85 | + tenantAdmin = createUserAndLogin(tenantAdmin, "testPassword1"); | ||
86 | + | ||
87 | + BasicMqttCredentials credValue = new BasicMqttCredentials(); | ||
88 | + credValue.setClientId(CLIENT_ID); | ||
89 | + clientIdDevice = createDevice("clientIdDevice", credValue); | ||
90 | + | ||
91 | + credValue = new BasicMqttCredentials(); | ||
92 | + credValue.setClientId(CLIENT_ID); | ||
93 | + credValue.setUserName(USER_NAME1); | ||
94 | + clientIdAndUserNameDevice1 = createDevice("clientIdAndUserNameDevice", credValue); | ||
95 | + | ||
96 | + credValue = new BasicMqttCredentials(); | ||
97 | + credValue.setClientId(CLIENT_ID); | ||
98 | + credValue.setUserName(USER_NAME2); | ||
99 | + credValue.setPassword(PASSWORD); | ||
100 | + clientIdAndUserNameAndPasswordDevice2 = createDevice("clientIdAndUserNameAndPasswordDevice", credValue); | ||
101 | + | ||
102 | + credValue = new BasicMqttCredentials(); | ||
103 | + credValue.setClientId(CLIENT_ID); | ||
104 | + credValue.setUserName(USER_NAME3); | ||
105 | + credValue.setPassword(PASSWORD); | ||
106 | + clientIdAndUserNameAndPasswordDevice3 = createDevice("clientIdAndUserNameAndPasswordDevice2", credValue); | ||
107 | + | ||
108 | + accessTokenDevice = createDevice("accessTokenDevice", USER_NAME1); | ||
109 | + accessToken2Device = createDevice("accessToken2Device", USER_NAME2); | ||
110 | + } | ||
111 | + | ||
112 | + @Test | ||
113 | + public void testCorrectCredentials() throws Exception { | ||
114 | + // Check that correct devices receive telemetry | ||
115 | + testTelemetryIsDelivered(accessTokenDevice, getMqttAsyncClient(null, USER_NAME1, null)); | ||
116 | + testTelemetryIsDelivered(clientIdDevice, getMqttAsyncClient(CLIENT_ID, null, null)); | ||
117 | + testTelemetryIsDelivered(clientIdAndUserNameDevice1, getMqttAsyncClient(CLIENT_ID, USER_NAME1, null)); | ||
118 | + testTelemetryIsDelivered(clientIdAndUserNameAndPasswordDevice2, getMqttAsyncClient(CLIENT_ID, USER_NAME2, PASSWORD)); | ||
119 | + | ||
120 | + // Also correct. Random clientId and password, but matches access token | ||
121 | + testTelemetryIsDelivered(accessToken2Device, getMqttAsyncClient(RandomStringUtils.randomAlphanumeric(10), USER_NAME2, RandomStringUtils.randomAlphanumeric(10))); | ||
122 | + } | ||
123 | + | ||
124 | + @Test(expected = MqttSecurityException.class) | ||
125 | + public void testCorrectClientIdAndUserNameButWrongPassword() throws Exception { | ||
126 | + // Not correct. Correct clientId and username, but wrong password | ||
127 | + testTelemetryIsNotDelivered(clientIdAndUserNameAndPasswordDevice3, getMqttAsyncClient(CLIENT_ID, USER_NAME3, "WRONG PASSWORD")); | ||
128 | + } | ||
129 | + | ||
130 | + private void testTelemetryIsDelivered(Device device, MqttAsyncClient client) throws Exception { | ||
131 | + testTelemetryIsDelivered(device, client, true); | ||
132 | + } | ||
133 | + | ||
134 | + private void testTelemetryIsNotDelivered(Device device, MqttAsyncClient client) throws Exception { | ||
135 | + testTelemetryIsDelivered(device, client, false); | ||
136 | + } | ||
137 | + | ||
138 | + private void testTelemetryIsDelivered(Device device, MqttAsyncClient client, boolean ok) throws Exception { | ||
139 | + String randomKey = RandomStringUtils.randomAlphanumeric(10); | ||
140 | + List<String> expectedKeys = Arrays.asList(randomKey); | ||
141 | + publishMqttMsg(client, JacksonUtil.toString(JacksonUtil.newObjectNode().put(randomKey, true)).getBytes(), MqttTopics.DEVICE_TELEMETRY_TOPIC); | ||
142 | + | ||
143 | + String deviceId = device.getId().getId().toString(); | ||
144 | + | ||
145 | + long start = System.currentTimeMillis(); | ||
146 | + long end = System.currentTimeMillis() + 5000; | ||
147 | + | ||
148 | + List<String> actualKeys = null; | ||
149 | + while (start <= end) { | ||
150 | + actualKeys = doGetAsyncTyped("/api/plugins/telemetry/DEVICE/" + deviceId + "/keys/timeseries", new TypeReference<>() { | ||
151 | + }); | ||
152 | + if (actualKeys.size() == expectedKeys.size()) { | ||
153 | + break; | ||
154 | + } | ||
155 | + Thread.sleep(100); | ||
156 | + start += 100; | ||
157 | + } | ||
158 | + if (ok) { | ||
159 | + assertNotNull(actualKeys); | ||
160 | + | ||
161 | + Set<String> actualKeySet = new HashSet<>(actualKeys); | ||
162 | + Set<String> expectedKeySet = new HashSet<>(expectedKeys); | ||
163 | + | ||
164 | + assertEquals(expectedKeySet, actualKeySet); | ||
165 | + } else { | ||
166 | + assertNull(actualKeys); | ||
167 | + } | ||
168 | + client.disconnect().waitForCompletion(); | ||
169 | + } | ||
170 | + | ||
171 | + @After | ||
172 | + public void after() throws Exception { | ||
173 | + processAfterTest(); | ||
174 | + } | ||
175 | + | ||
176 | + protected MqttAsyncClient getMqttAsyncClient(String clientId, String username, String password) throws MqttException { | ||
177 | + if (StringUtils.isEmpty(clientId)) { | ||
178 | + clientId = MqttAsyncClient.generateClientId(); | ||
179 | + } | ||
180 | + MqttAsyncClient client = new MqttAsyncClient(MQTT_URL, clientId, new MemoryPersistence()); | ||
181 | + | ||
182 | + MqttConnectOptions options = new MqttConnectOptions(); | ||
183 | + if (StringUtils.isNotEmpty(username)) { | ||
184 | + options.setUserName(username); | ||
185 | + } | ||
186 | + if (StringUtils.isNotEmpty(password)) { | ||
187 | + options.setPassword(password.toCharArray()); | ||
188 | + } | ||
189 | + client.connect(options).waitForCompletion(); | ||
190 | + return client; | ||
191 | + } | ||
192 | + | ||
193 | + private Device createDevice(String deviceName, BasicMqttCredentials clientIdCredValue) throws Exception { | ||
194 | + Device device = new Device(); | ||
195 | + device.setName(deviceName); | ||
196 | + device.setType("default"); | ||
197 | + | ||
198 | + device = doPost("/api/device", device, Device.class); | ||
199 | + | ||
200 | + DeviceCredentials clientIdCred = | ||
201 | + doGet("/api/device/" + device.getId().getId().toString() + "/credentials", DeviceCredentials.class); | ||
202 | + | ||
203 | + clientIdCred.setCredentialsType(DeviceCredentialsType.MQTT_BASIC); | ||
204 | + | ||
205 | + | ||
206 | + clientIdCred.setCredentialsValue(JacksonUtil.toString(clientIdCredValue)); | ||
207 | + doPost("/api/device/credentials", clientIdCred).andExpect(status().isOk()); | ||
208 | + return device; | ||
209 | + } | ||
210 | + | ||
211 | + private Device createDevice(String deviceName, String accessToken) throws Exception { | ||
212 | + Device device = new Device(); | ||
213 | + device.setName(deviceName); | ||
214 | + device.setType("default"); | ||
215 | + | ||
216 | + device = doPost("/api/device", device, Device.class); | ||
217 | + | ||
218 | + DeviceCredentials clientIdCred = | ||
219 | + doGet("/api/device/" + device.getId().getId().toString() + "/credentials", DeviceCredentials.class); | ||
220 | + | ||
221 | + clientIdCred.setCredentialsType(DeviceCredentialsType.ACCESS_TOKEN); | ||
222 | + clientIdCred.setCredentialsId(accessToken); | ||
223 | + doPost("/api/device/credentials", clientIdCred).andExpect(status().isOk()); | ||
224 | + return device; | ||
225 | + } | ||
226 | +} |
@@ -19,6 +19,8 @@ import org.thingsboard.server.common.data.id.DeviceId; | @@ -19,6 +19,8 @@ import org.thingsboard.server.common.data.id.DeviceId; | ||
19 | import org.thingsboard.server.common.data.id.TenantId; | 19 | import org.thingsboard.server.common.data.id.TenantId; |
20 | import org.thingsboard.server.common.data.security.DeviceCredentials; | 20 | import org.thingsboard.server.common.data.security.DeviceCredentials; |
21 | 21 | ||
22 | +import java.util.List; | ||
23 | + | ||
22 | public interface DeviceCredentialsService { | 24 | public interface DeviceCredentialsService { |
23 | 25 | ||
24 | DeviceCredentials findDeviceCredentialsByDeviceId(TenantId tenantId, DeviceId deviceId); | 26 | DeviceCredentials findDeviceCredentialsByDeviceId(TenantId tenantId, DeviceId deviceId); |
@@ -32,4 +34,5 @@ public interface DeviceCredentialsService { | @@ -32,4 +34,5 @@ public interface DeviceCredentialsService { | ||
32 | void formatCredentials(DeviceCredentials deviceCredentials); | 34 | void formatCredentials(DeviceCredentials deviceCredentials); |
33 | 35 | ||
34 | void deleteDeviceCredentials(TenantId tenantId, DeviceCredentials deviceCredentials); | 36 | void deleteDeviceCredentials(TenantId tenantId, DeviceCredentials deviceCredentials); |
37 | + | ||
35 | } | 38 | } |