Commit 969a686c74fd33269500227685dfb8834f3bcf59
Merge branch 'develop/3.2' of github.com:thingsboard/thingsboard into develop/3.2
Showing
12 changed files
with
326 additions
and
42 deletions
@@ -23,12 +23,14 @@ import com.google.common.util.concurrent.ListenableFuture; | @@ -23,12 +23,14 @@ import com.google.common.util.concurrent.ListenableFuture; | ||
23 | import com.google.common.util.concurrent.MoreExecutors; | 23 | import com.google.common.util.concurrent.MoreExecutors; |
24 | import com.google.protobuf.ByteString; | 24 | import com.google.protobuf.ByteString; |
25 | import lombok.extern.slf4j.Slf4j; | 25 | import lombok.extern.slf4j.Slf4j; |
26 | +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; | ||
26 | import org.springframework.stereotype.Service; | 27 | import org.springframework.stereotype.Service; |
27 | import org.springframework.util.StringUtils; | 28 | import org.springframework.util.StringUtils; |
28 | import org.thingsboard.server.common.data.DataConstants; | 29 | import org.thingsboard.server.common.data.DataConstants; |
29 | import org.thingsboard.server.common.data.Device; | 30 | import org.thingsboard.server.common.data.Device; |
30 | import org.thingsboard.server.common.data.DeviceProfile; | 31 | import org.thingsboard.server.common.data.DeviceProfile; |
31 | import org.thingsboard.server.common.data.TenantProfile; | 32 | import org.thingsboard.server.common.data.TenantProfile; |
33 | +import org.thingsboard.server.common.data.device.credentials.BasicMqttCredentials; | ||
32 | import org.thingsboard.server.common.data.id.CustomerId; | 34 | import org.thingsboard.server.common.data.id.CustomerId; |
33 | import org.thingsboard.server.common.data.id.DeviceId; | 35 | import org.thingsboard.server.common.data.id.DeviceId; |
34 | import org.thingsboard.server.common.data.id.DeviceProfileId; | 36 | import org.thingsboard.server.common.data.id.DeviceProfileId; |
@@ -36,6 +38,7 @@ import org.thingsboard.server.common.data.id.TenantId; | @@ -36,6 +38,7 @@ import org.thingsboard.server.common.data.id.TenantId; | ||
36 | import org.thingsboard.server.common.data.relation.EntityRelation; | 38 | import org.thingsboard.server.common.data.relation.EntityRelation; |
37 | import org.thingsboard.server.common.data.security.DeviceCredentials; | 39 | import org.thingsboard.server.common.data.security.DeviceCredentials; |
38 | import org.thingsboard.server.common.data.security.DeviceCredentialsType; | 40 | import org.thingsboard.server.common.data.security.DeviceCredentialsType; |
41 | +import org.thingsboard.server.common.msg.EncryptionUtil; | ||
39 | import org.thingsboard.server.common.msg.TbMsg; | 42 | import org.thingsboard.server.common.msg.TbMsg; |
40 | import org.thingsboard.server.common.msg.TbMsgDataType; | 43 | import org.thingsboard.server.common.msg.TbMsgDataType; |
41 | import org.thingsboard.server.common.msg.TbMsgMetaData; | 44 | import org.thingsboard.server.common.msg.TbMsgMetaData; |
@@ -46,6 +49,7 @@ import org.thingsboard.server.dao.device.DeviceService; | @@ -46,6 +49,7 @@ import org.thingsboard.server.dao.device.DeviceService; | ||
46 | import org.thingsboard.server.dao.relation.RelationService; | 49 | import org.thingsboard.server.dao.relation.RelationService; |
47 | import org.thingsboard.server.dao.tenant.TenantProfileService; | 50 | import org.thingsboard.server.dao.tenant.TenantProfileService; |
48 | import org.thingsboard.server.dao.tenant.TenantService; | 51 | import org.thingsboard.server.dao.tenant.TenantService; |
52 | +import org.thingsboard.server.dao.util.mapping.JacksonUtil; | ||
49 | import org.thingsboard.server.gen.transport.TransportProtos; | 53 | import org.thingsboard.server.gen.transport.TransportProtos; |
50 | import org.thingsboard.server.gen.transport.TransportProtos.DeviceInfoProto; | 54 | import org.thingsboard.server.gen.transport.TransportProtos.DeviceInfoProto; |
51 | import org.thingsboard.server.gen.transport.TransportProtos.GetOrCreateDeviceFromGatewayRequestMsg; | 55 | import org.thingsboard.server.gen.transport.TransportProtos.GetOrCreateDeviceFromGatewayRequestMsg; |
@@ -91,6 +95,7 @@ public class DefaultTransportApiService implements TransportApiService { | @@ -91,6 +95,7 @@ public class DefaultTransportApiService implements TransportApiService { | ||
91 | private final TbClusterService tbClusterService; | 95 | private final TbClusterService tbClusterService; |
92 | private final DataDecodingEncodingService dataDecodingEncodingService; | 96 | private final DataDecodingEncodingService dataDecodingEncodingService; |
93 | 97 | ||
98 | + | ||
94 | private final ConcurrentMap<String, ReentrantLock> deviceCreationLocks = new ConcurrentHashMap<>(); | 99 | private final ConcurrentMap<String, ReentrantLock> deviceCreationLocks = new ConcurrentHashMap<>(); |
95 | 100 | ||
96 | public DefaultTransportApiService(DeviceProfileService deviceProfileService, TenantService tenantService, | 101 | public DefaultTransportApiService(DeviceProfileService deviceProfileService, TenantService tenantService, |
@@ -117,6 +122,10 @@ public class DefaultTransportApiService implements TransportApiService { | @@ -117,6 +122,10 @@ public class DefaultTransportApiService implements TransportApiService { | ||
117 | ValidateDeviceTokenRequestMsg msg = transportApiRequestMsg.getValidateTokenRequestMsg(); | 122 | ValidateDeviceTokenRequestMsg msg = transportApiRequestMsg.getValidateTokenRequestMsg(); |
118 | return Futures.transform(validateCredentials(msg.getToken(), DeviceCredentialsType.ACCESS_TOKEN), | 123 | return Futures.transform(validateCredentials(msg.getToken(), DeviceCredentialsType.ACCESS_TOKEN), |
119 | value -> new TbProtoQueueMsg<>(tbProtoQueueMsg.getKey(), value, tbProtoQueueMsg.getHeaders()), MoreExecutors.directExecutor()); | 124 | value -> new TbProtoQueueMsg<>(tbProtoQueueMsg.getKey(), value, tbProtoQueueMsg.getHeaders()), MoreExecutors.directExecutor()); |
125 | + } else if (transportApiRequestMsg.hasValidateBasicMqttCredRequestMsg()) { | ||
126 | + TransportProtos.ValidateBasicMqttCredRequestMsg msg = transportApiRequestMsg.getValidateBasicMqttCredRequestMsg(); | ||
127 | + return Futures.transform(validateCredentials(msg), | ||
128 | + value -> new TbProtoQueueMsg<>(tbProtoQueueMsg.getKey(), value, tbProtoQueueMsg.getHeaders()), MoreExecutors.directExecutor()); | ||
120 | } else if (transportApiRequestMsg.hasValidateX509CertRequestMsg()) { | 129 | } else if (transportApiRequestMsg.hasValidateX509CertRequestMsg()) { |
121 | ValidateDeviceX509CertRequestMsg msg = transportApiRequestMsg.getValidateX509CertRequestMsg(); | 130 | ValidateDeviceX509CertRequestMsg msg = transportApiRequestMsg.getValidateX509CertRequestMsg(); |
122 | return Futures.transform(validateCredentials(msg.getHash(), DeviceCredentialsType.X509_CERTIFICATE), | 131 | return Futures.transform(validateCredentials(msg.getHash(), DeviceCredentialsType.X509_CERTIFICATE), |
@@ -130,7 +139,6 @@ public class DefaultTransportApiService implements TransportApiService { | @@ -130,7 +139,6 @@ public class DefaultTransportApiService implements TransportApiService { | ||
130 | } else if (transportApiRequestMsg.hasGetDeviceProfileRequestMsg()) { | 139 | } else if (transportApiRequestMsg.hasGetDeviceProfileRequestMsg()) { |
131 | return Futures.transform(handle(transportApiRequestMsg.getGetDeviceProfileRequestMsg()), | 140 | return Futures.transform(handle(transportApiRequestMsg.getGetDeviceProfileRequestMsg()), |
132 | value -> new TbProtoQueueMsg<>(tbProtoQueueMsg.getKey(), value, tbProtoQueueMsg.getHeaders()), MoreExecutors.directExecutor()); | 141 | value -> new TbProtoQueueMsg<>(tbProtoQueueMsg.getKey(), value, tbProtoQueueMsg.getHeaders()), MoreExecutors.directExecutor()); |
133 | - | ||
134 | } | 142 | } |
135 | return Futures.transform(getEmptyTransportApiResponseFuture(), | 143 | return Futures.transform(getEmptyTransportApiResponseFuture(), |
136 | value -> new TbProtoQueueMsg<>(tbProtoQueueMsg.getKey(), value, tbProtoQueueMsg.getHeaders()), MoreExecutors.directExecutor()); | 144 | value -> new TbProtoQueueMsg<>(tbProtoQueueMsg.getKey(), value, tbProtoQueueMsg.getHeaders()), MoreExecutors.directExecutor()); |
@@ -146,6 +154,62 @@ public class DefaultTransportApiService implements TransportApiService { | @@ -146,6 +154,62 @@ public class DefaultTransportApiService implements TransportApiService { | ||
146 | } | 154 | } |
147 | } | 155 | } |
148 | 156 | ||
157 | + private ListenableFuture<TransportApiResponseMsg> validateCredentials(TransportProtos.ValidateBasicMqttCredRequestMsg mqtt) { | ||
158 | + DeviceCredentials credentials = deviceCredentialsService.findDeviceCredentialsByCredentialsId(mqtt.getUserName()); | ||
159 | + if (credentials != null) { | ||
160 | + if (credentials.getCredentialsType() == DeviceCredentialsType.ACCESS_TOKEN) { | ||
161 | + return getDeviceInfo(credentials.getDeviceId(), credentials); | ||
162 | + } else if (credentials.getCredentialsType() == DeviceCredentialsType.MQTT_BASIC) { | ||
163 | + if (!checkMqttCredentials(mqtt, credentials)) { | ||
164 | + credentials = null; | ||
165 | + } | ||
166 | + } | ||
167 | + } | ||
168 | + if (credentials == null) { | ||
169 | + credentials = checkMqttCredentials(mqtt, EncryptionUtil.getSha3Hash("|", mqtt.getClientId(), mqtt.getUserName())); | ||
170 | + if (credentials == null) { | ||
171 | + credentials = checkMqttCredentials(mqtt, EncryptionUtil.getSha3Hash(mqtt.getClientId())); | ||
172 | + } | ||
173 | + } | ||
174 | + if (credentials != null) { | ||
175 | + return getDeviceInfo(credentials.getDeviceId(), credentials); | ||
176 | + } else { | ||
177 | + return getEmptyTransportApiResponseFuture(); | ||
178 | + } | ||
179 | + } | ||
180 | + | ||
181 | + private DeviceCredentials checkMqttCredentials(TransportProtos.ValidateBasicMqttCredRequestMsg clientCred, String credId) { | ||
182 | + DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByCredentialsId(credId); | ||
183 | + if (deviceCredentials != null && deviceCredentials.getCredentialsType() == DeviceCredentialsType.MQTT_BASIC) { | ||
184 | + if (!checkMqttCredentials(clientCred, deviceCredentials)) { | ||
185 | + return null; | ||
186 | + } else { | ||
187 | + return deviceCredentials; | ||
188 | + } | ||
189 | + } | ||
190 | + return null; | ||
191 | + } | ||
192 | + | ||
193 | + private boolean checkMqttCredentials(TransportProtos.ValidateBasicMqttCredRequestMsg clientCred, DeviceCredentials deviceCredentials) { | ||
194 | + BasicMqttCredentials dbCred = JacksonUtil.fromString(deviceCredentials.getCredentialsValue(), BasicMqttCredentials.class); | ||
195 | + if (!StringUtils.isEmpty(dbCred.getClientId()) && !dbCred.getClientId().equals(clientCred.getClientId())) { | ||
196 | + return false; | ||
197 | + } | ||
198 | + if (!StringUtils.isEmpty(dbCred.getUserName()) && !dbCred.getUserName().equals(clientCred.getUserName())) { | ||
199 | + return false; | ||
200 | + } | ||
201 | + if (!StringUtils.isEmpty(dbCred.getPassword())) { | ||
202 | + if (StringUtils.isEmpty(clientCred.getPassword())) { | ||
203 | + return false; | ||
204 | + } else { | ||
205 | + if (!dbCred.getPassword().equals(clientCred.getPassword())) { | ||
206 | + return false; | ||
207 | + } | ||
208 | + } | ||
209 | + } | ||
210 | + return true; | ||
211 | + } | ||
212 | + | ||
149 | private ListenableFuture<TransportApiResponseMsg> handle(GetOrCreateDeviceFromGatewayRequestMsg requestMsg) { | 213 | private ListenableFuture<TransportApiResponseMsg> handle(GetOrCreateDeviceFromGatewayRequestMsg requestMsg) { |
150 | DeviceId gatewayId = new DeviceId(new UUID(requestMsg.getGatewayIdMSB(), requestMsg.getGatewayIdLSB())); | 214 | DeviceId gatewayId = new DeviceId(new UUID(requestMsg.getGatewayIdMSB(), requestMsg.getGatewayIdLSB())); |
151 | ListenableFuture<Device> gatewayFuture = deviceService.findDeviceByIdAsync(TenantId.SYS_TENANT_ID, gatewayId); | 215 | ListenableFuture<Device> gatewayFuture = deviceService.findDeviceByIdAsync(TenantId.SYS_TENANT_ID, gatewayId); |
@@ -237,7 +301,7 @@ public class DefaultTransportApiService implements TransportApiService { | @@ -237,7 +301,7 @@ public class DefaultTransportApiService implements TransportApiService { | ||
237 | builder.setCredentialsBody(credentials.getCredentialsValue()); | 301 | builder.setCredentialsBody(credentials.getCredentialsValue()); |
238 | } | 302 | } |
239 | return TransportApiResponseMsg.newBuilder() | 303 | return TransportApiResponseMsg.newBuilder() |
240 | - .setValidateTokenResponseMsg(builder.build()).build(); | 304 | + .setValidateCredResponseMsg(builder.build()).build(); |
241 | } catch (JsonProcessingException e) { | 305 | } catch (JsonProcessingException e) { |
242 | log.warn("[{}] Failed to lookup device by id", deviceId, e); | 306 | log.warn("[{}] Failed to lookup device by id", deviceId, e); |
243 | return getEmptyTransportApiResponse(); | 307 | return getEmptyTransportApiResponse(); |
@@ -265,6 +329,6 @@ public class DefaultTransportApiService implements TransportApiService { | @@ -265,6 +329,6 @@ public class DefaultTransportApiService implements TransportApiService { | ||
265 | 329 | ||
266 | private TransportApiResponseMsg getEmptyTransportApiResponse() { | 330 | private TransportApiResponseMsg getEmptyTransportApiResponse() { |
267 | return TransportApiResponseMsg.newBuilder() | 331 | return TransportApiResponseMsg.newBuilder() |
268 | - .setValidateTokenResponseMsg(ValidateDeviceCredentialsResponseMsg.getDefaultInstance()).build(); | 332 | + .setValidateCredResponseMsg(ValidateDeviceCredentialsResponseMsg.getDefaultInstance()).build(); |
269 | } | 333 | } |
270 | } | 334 | } |
1 | +/** | ||
2 | + * Copyright © 2016-2020 The Thingsboard Authors | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | +package org.thingsboard.server.common.data.device.credentials; | ||
17 | + | ||
18 | +import lombok.Data; | ||
19 | + | ||
20 | +@Data | ||
21 | +public class BasicMqttCredentials { | ||
22 | + | ||
23 | + private String clientId; | ||
24 | + private String userName; | ||
25 | + private String password; | ||
26 | + | ||
27 | +} |
@@ -18,6 +18,7 @@ package org.thingsboard.server.common.msg; | @@ -18,6 +18,7 @@ package org.thingsboard.server.common.msg; | ||
18 | import lombok.extern.slf4j.Slf4j; | 18 | import lombok.extern.slf4j.Slf4j; |
19 | import org.bouncycastle.crypto.digests.SHA3Digest; | 19 | import org.bouncycastle.crypto.digests.SHA3Digest; |
20 | import org.bouncycastle.pqc.math.linearalgebra.ByteUtils; | 20 | import org.bouncycastle.pqc.math.linearalgebra.ByteUtils; |
21 | + | ||
21 | /** | 22 | /** |
22 | * @author Valerii Sosliuk | 23 | * @author Valerii Sosliuk |
23 | */ | 24 | */ |
@@ -30,8 +31,8 @@ public class EncryptionUtil { | @@ -30,8 +31,8 @@ public class EncryptionUtil { | ||
30 | public static String trimNewLines(String input) { | 31 | public static String trimNewLines(String input) { |
31 | return input.replaceAll("-----BEGIN CERTIFICATE-----", "") | 32 | return input.replaceAll("-----BEGIN CERTIFICATE-----", "") |
32 | .replaceAll("-----END CERTIFICATE-----", "") | 33 | .replaceAll("-----END CERTIFICATE-----", "") |
33 | - .replaceAll("\n","") | ||
34 | - .replaceAll("\r",""); | 34 | + .replaceAll("\n", "") |
35 | + .replaceAll("\r", ""); | ||
35 | } | 36 | } |
36 | 37 | ||
37 | public static String getSha3Hash(String data) { | 38 | public static String getSha3Hash(String data) { |
@@ -45,4 +46,20 @@ public class EncryptionUtil { | @@ -45,4 +46,20 @@ public class EncryptionUtil { | ||
45 | String sha3Hash = ByteUtils.toHexString(hashedBytes); | 46 | String sha3Hash = ByteUtils.toHexString(hashedBytes); |
46 | return sha3Hash; | 47 | return sha3Hash; |
47 | } | 48 | } |
49 | + | ||
50 | + public static String getSha3Hash(String delim, String... tokens) { | ||
51 | + StringBuilder sb = new StringBuilder(); | ||
52 | + boolean first = true; | ||
53 | + for (String token : tokens) { | ||
54 | + if (token != null && !token.isEmpty()) { | ||
55 | + if (first) { | ||
56 | + first = false; | ||
57 | + } else { | ||
58 | + sb.append(delim); | ||
59 | + } | ||
60 | + sb.append(token); | ||
61 | + } | ||
62 | + } | ||
63 | + return getSha3Hash(sb.toString()); | ||
64 | + } | ||
48 | } | 65 | } |
@@ -147,6 +147,12 @@ message ValidateDeviceX509CertRequestMsg { | @@ -147,6 +147,12 @@ message ValidateDeviceX509CertRequestMsg { | ||
147 | string hash = 1; | 147 | string hash = 1; |
148 | } | 148 | } |
149 | 149 | ||
150 | +message ValidateBasicMqttCredRequestMsg { | ||
151 | + string clientId = 1; | ||
152 | + string userName = 2; | ||
153 | + string password = 3; | ||
154 | +} | ||
155 | + | ||
150 | message ValidateDeviceCredentialsResponseMsg { | 156 | message ValidateDeviceCredentialsResponseMsg { |
151 | DeviceInfoProto deviceInfo = 1; | 157 | DeviceInfoProto deviceInfo = 1; |
152 | string credentialsBody = 2; | 158 | string credentialsBody = 2; |
@@ -429,11 +435,12 @@ message TransportApiRequestMsg { | @@ -429,11 +435,12 @@ message TransportApiRequestMsg { | ||
429 | GetOrCreateDeviceFromGatewayRequestMsg getOrCreateDeviceRequestMsg = 3; | 435 | GetOrCreateDeviceFromGatewayRequestMsg getOrCreateDeviceRequestMsg = 3; |
430 | GetTenantRoutingInfoRequestMsg getTenantRoutingInfoRequestMsg = 4; | 436 | GetTenantRoutingInfoRequestMsg getTenantRoutingInfoRequestMsg = 4; |
431 | GetDeviceProfileRequestMsg getDeviceProfileRequestMsg = 5; | 437 | GetDeviceProfileRequestMsg getDeviceProfileRequestMsg = 5; |
438 | + ValidateBasicMqttCredRequestMsg validateBasicMqttCredRequestMsg = 6; | ||
432 | } | 439 | } |
433 | 440 | ||
434 | /* Response from ThingsBoard Core Service to Transport Service */ | 441 | /* Response from ThingsBoard Core Service to Transport Service */ |
435 | message TransportApiResponseMsg { | 442 | message TransportApiResponseMsg { |
436 | - ValidateDeviceCredentialsResponseMsg validateTokenResponseMsg = 1; | 443 | + ValidateDeviceCredentialsResponseMsg validateCredResponseMsg = 1; |
437 | GetOrCreateDeviceFromGatewayResponseMsg getOrCreateDeviceResponseMsg = 2; | 444 | GetOrCreateDeviceFromGatewayResponseMsg getOrCreateDeviceResponseMsg = 2; |
438 | GetTenantRoutingInfoResponseMsg getTenantRoutingInfoResponseMsg = 4; | 445 | GetTenantRoutingInfoResponseMsg getTenantRoutingInfoResponseMsg = 4; |
439 | GetDeviceProfileResponseMsg getDeviceProfileResponseMsg = 5; | 446 | GetDeviceProfileResponseMsg getDeviceProfileResponseMsg = 5; |
@@ -66,6 +66,7 @@ import javax.net.ssl.SSLPeerUnverifiedException; | @@ -66,6 +66,7 @@ import javax.net.ssl.SSLPeerUnverifiedException; | ||
66 | import javax.security.cert.X509Certificate; | 66 | import javax.security.cert.X509Certificate; |
67 | import java.io.IOException; | 67 | import java.io.IOException; |
68 | import java.net.InetSocketAddress; | 68 | import java.net.InetSocketAddress; |
69 | +import java.nio.charset.StandardCharsets; | ||
69 | import java.util.ArrayList; | 70 | import java.util.ArrayList; |
70 | import java.util.List; | 71 | import java.util.List; |
71 | import java.util.UUID; | 72 | import java.util.UUID; |
@@ -365,25 +366,27 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement | @@ -365,25 +366,27 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement | ||
365 | private void processAuthTokenConnect(ChannelHandlerContext ctx, MqttConnectMessage msg) { | 366 | private void processAuthTokenConnect(ChannelHandlerContext ctx, MqttConnectMessage msg) { |
366 | String userName = msg.payload().userName(); | 367 | String userName = msg.payload().userName(); |
367 | log.info("[{}] Processing connect msg for client with user name: {}!", sessionId, userName); | 368 | log.info("[{}] Processing connect msg for client with user name: {}!", sessionId, userName); |
368 | - if (StringUtils.isEmpty(userName)) { | ||
369 | - ctx.writeAndFlush(createMqttConnAckMsg(CONNECTION_REFUSED_BAD_USER_NAME_OR_PASSWORD)); | ||
370 | - ctx.close(); | ||
371 | - } else { | ||
372 | - transportService.process(DeviceTransportType.MQTT, ValidateDeviceTokenRequestMsg.newBuilder().setToken(userName).build(), | ||
373 | - new TransportServiceCallback<ValidateDeviceCredentialsResponse>() { | ||
374 | - @Override | ||
375 | - public void onSuccess(ValidateDeviceCredentialsResponse msg) { | ||
376 | - onValidateDeviceResponse(msg, ctx); | ||
377 | - } | ||
378 | - | ||
379 | - @Override | ||
380 | - public void onError(Throwable e) { | ||
381 | - log.trace("[{}] Failed to process credentials: {}", address, userName, e); | ||
382 | - ctx.writeAndFlush(createMqttConnAckMsg(MqttConnectReturnCode.CONNECTION_REFUSED_SERVER_UNAVAILABLE)); | ||
383 | - ctx.close(); | ||
384 | - } | ||
385 | - }); | 369 | + TransportProtos.ValidateBasicMqttCredRequestMsg.Builder request = TransportProtos.ValidateBasicMqttCredRequestMsg.newBuilder() |
370 | + .setClientId(msg.payload().clientIdentifier()) | ||
371 | + .setUserName(userName); | ||
372 | + String password = msg.payload().password(); | ||
373 | + if (password != null) { | ||
374 | + request.setPassword(password); | ||
386 | } | 375 | } |
376 | + transportService.process(DeviceTransportType.MQTT, request.build(), | ||
377 | + new TransportServiceCallback<ValidateDeviceCredentialsResponse>() { | ||
378 | + @Override | ||
379 | + public void onSuccess(ValidateDeviceCredentialsResponse msg) { | ||
380 | + onValidateDeviceResponse(msg, ctx); | ||
381 | + } | ||
382 | + | ||
383 | + @Override | ||
384 | + public void onError(Throwable e) { | ||
385 | + log.trace("[{}] Failed to process credentials: {}", address, userName, e); | ||
386 | + ctx.writeAndFlush(createMqttConnAckMsg(MqttConnectReturnCode.CONNECTION_REFUSED_SERVER_UNAVAILABLE)); | ||
387 | + ctx.close(); | ||
388 | + } | ||
389 | + }); | ||
387 | } | 390 | } |
388 | 391 | ||
389 | private void processX509CertConnect(ChannelHandlerContext ctx, X509Certificate cert) { | 392 | private void processX509CertConnect(ChannelHandlerContext ctx, X509Certificate cert) { |
@@ -23,7 +23,6 @@ import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsRes | @@ -23,7 +23,6 @@ import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsRes | ||
23 | import org.thingsboard.server.gen.transport.TransportProtos.ClaimDeviceMsg; | 23 | import org.thingsboard.server.gen.transport.TransportProtos.ClaimDeviceMsg; |
24 | import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeRequestMsg; | 24 | import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeRequestMsg; |
25 | import org.thingsboard.server.gen.transport.TransportProtos.GetOrCreateDeviceFromGatewayRequestMsg; | 25 | import org.thingsboard.server.gen.transport.TransportProtos.GetOrCreateDeviceFromGatewayRequestMsg; |
26 | -import org.thingsboard.server.gen.transport.TransportProtos.GetOrCreateDeviceFromGatewayResponseMsg; | ||
27 | import org.thingsboard.server.gen.transport.TransportProtos.GetTenantRoutingInfoRequestMsg; | 26 | import org.thingsboard.server.gen.transport.TransportProtos.GetTenantRoutingInfoRequestMsg; |
28 | import org.thingsboard.server.gen.transport.TransportProtos.GetTenantRoutingInfoResponseMsg; | 27 | import org.thingsboard.server.gen.transport.TransportProtos.GetTenantRoutingInfoResponseMsg; |
29 | import org.thingsboard.server.gen.transport.TransportProtos.PostAttributeMsg; | 28 | import org.thingsboard.server.gen.transport.TransportProtos.PostAttributeMsg; |
@@ -35,7 +34,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.SubscribeToRPCMsg; | @@ -35,7 +34,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.SubscribeToRPCMsg; | ||
35 | import org.thingsboard.server.gen.transport.TransportProtos.SubscriptionInfoProto; | 34 | import org.thingsboard.server.gen.transport.TransportProtos.SubscriptionInfoProto; |
36 | import org.thingsboard.server.gen.transport.TransportProtos.ToDeviceRpcResponseMsg; | 35 | import org.thingsboard.server.gen.transport.TransportProtos.ToDeviceRpcResponseMsg; |
37 | import org.thingsboard.server.gen.transport.TransportProtos.ToServerRpcRequestMsg; | 36 | import org.thingsboard.server.gen.transport.TransportProtos.ToServerRpcRequestMsg; |
38 | -import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceCredentialsResponseMsg; | 37 | +import org.thingsboard.server.gen.transport.TransportProtos.ValidateBasicMqttCredRequestMsg; |
39 | import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceTokenRequestMsg; | 38 | import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceTokenRequestMsg; |
40 | import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceX509CertRequestMsg; | 39 | import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceX509CertRequestMsg; |
41 | 40 | ||
@@ -49,6 +48,9 @@ public interface TransportService { | @@ -49,6 +48,9 @@ public interface TransportService { | ||
49 | void process(DeviceTransportType transportType, ValidateDeviceTokenRequestMsg msg, | 48 | void process(DeviceTransportType transportType, ValidateDeviceTokenRequestMsg msg, |
50 | TransportServiceCallback<ValidateDeviceCredentialsResponse> callback); | 49 | TransportServiceCallback<ValidateDeviceCredentialsResponse> callback); |
51 | 50 | ||
51 | + void process(DeviceTransportType transportType, ValidateBasicMqttCredRequestMsg msg, | ||
52 | + TransportServiceCallback<ValidateDeviceCredentialsResponse> callback); | ||
53 | + | ||
52 | void process(DeviceTransportType transportType, ValidateDeviceX509CertRequestMsg msg, | 54 | void process(DeviceTransportType transportType, ValidateDeviceX509CertRequestMsg msg, |
53 | TransportServiceCallback<ValidateDeviceCredentialsResponse> callback); | 55 | TransportServiceCallback<ValidateDeviceCredentialsResponse> callback); |
54 | 56 |
@@ -252,9 +252,20 @@ public class DefaultTransportService implements TransportService { | @@ -252,9 +252,20 @@ public class DefaultTransportService implements TransportService { | ||
252 | } | 252 | } |
253 | 253 | ||
254 | @Override | 254 | @Override |
255 | - public void process(DeviceTransportType transportType, TransportProtos.ValidateDeviceTokenRequestMsg msg, TransportServiceCallback<ValidateDeviceCredentialsResponse> callback) { | 255 | + public void process(DeviceTransportType transportType, TransportProtos.ValidateDeviceTokenRequestMsg msg, |
256 | + TransportServiceCallback<ValidateDeviceCredentialsResponse> callback) { | ||
256 | log.trace("Processing msg: {}", msg); | 257 | log.trace("Processing msg: {}", msg); |
257 | - TbProtoQueueMsg<TransportApiRequestMsg> protoMsg = new TbProtoQueueMsg<>(UUID.randomUUID(), TransportApiRequestMsg.newBuilder().setValidateTokenRequestMsg(msg).build()); | 258 | + TbProtoQueueMsg<TransportApiRequestMsg> protoMsg = new TbProtoQueueMsg<>(UUID.randomUUID(), |
259 | + TransportApiRequestMsg.newBuilder().setValidateTokenRequestMsg(msg).build()); | ||
260 | + doProcess(transportType, protoMsg, callback); | ||
261 | + } | ||
262 | + | ||
263 | + @Override | ||
264 | + public void process(DeviceTransportType transportType, TransportProtos.ValidateBasicMqttCredRequestMsg msg, | ||
265 | + TransportServiceCallback<ValidateDeviceCredentialsResponse> callback) { | ||
266 | + log.trace("Processing msg: {}", msg); | ||
267 | + TbProtoQueueMsg<TransportApiRequestMsg> protoMsg = new TbProtoQueueMsg<>(UUID.randomUUID(), | ||
268 | + TransportApiRequestMsg.newBuilder().setValidateBasicMqttCredRequestMsg(msg).build()); | ||
258 | doProcess(transportType, protoMsg, callback); | 269 | doProcess(transportType, protoMsg, callback); |
259 | } | 270 | } |
260 | 271 | ||
@@ -265,9 +276,10 @@ public class DefaultTransportService implements TransportService { | @@ -265,9 +276,10 @@ public class DefaultTransportService implements TransportService { | ||
265 | doProcess(transportType, protoMsg, callback); | 276 | doProcess(transportType, protoMsg, callback); |
266 | } | 277 | } |
267 | 278 | ||
268 | - private void doProcess(DeviceTransportType transportType, TbProtoQueueMsg<TransportApiRequestMsg> protoMsg, TransportServiceCallback<ValidateDeviceCredentialsResponse> callback) { | 279 | + private void doProcess(DeviceTransportType transportType, TbProtoQueueMsg<TransportApiRequestMsg> protoMsg, |
280 | + TransportServiceCallback<ValidateDeviceCredentialsResponse> callback) { | ||
269 | ListenableFuture<ValidateDeviceCredentialsResponse> response = Futures.transform(transportApiRequestTemplate.send(protoMsg), tmp -> { | 281 | ListenableFuture<ValidateDeviceCredentialsResponse> response = Futures.transform(transportApiRequestTemplate.send(protoMsg), tmp -> { |
270 | - TransportProtos.ValidateDeviceCredentialsResponseMsg msg = tmp.getValue().getValidateTokenResponseMsg(); | 282 | + TransportProtos.ValidateDeviceCredentialsResponseMsg msg = tmp.getValue().getValidateCredResponseMsg(); |
271 | ValidateDeviceCredentialsResponse.ValidateDeviceCredentialsResponseBuilder result = ValidateDeviceCredentialsResponse.builder(); | 283 | ValidateDeviceCredentialsResponse.ValidateDeviceCredentialsResponseBuilder result = ValidateDeviceCredentialsResponse.builder(); |
272 | if (msg.hasDeviceInfo()) { | 284 | if (msg.hasDeviceInfo()) { |
273 | result.credentials(msg.getCredentialsBody()); | 285 | result.credentials(msg.getCredentialsBody()); |
@@ -21,18 +21,20 @@ import org.hibernate.exception.ConstraintViolationException; | @@ -21,18 +21,20 @@ import org.hibernate.exception.ConstraintViolationException; | ||
21 | import org.springframework.beans.factory.annotation.Autowired; | 21 | import org.springframework.beans.factory.annotation.Autowired; |
22 | import org.springframework.cache.annotation.CacheEvict; | 22 | import org.springframework.cache.annotation.CacheEvict; |
23 | import org.springframework.cache.annotation.Cacheable; | 23 | import org.springframework.cache.annotation.Cacheable; |
24 | +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; | ||
24 | import org.springframework.stereotype.Service; | 25 | import org.springframework.stereotype.Service; |
25 | import org.springframework.util.StringUtils; | 26 | import org.springframework.util.StringUtils; |
26 | import org.thingsboard.server.common.data.Device; | 27 | import org.thingsboard.server.common.data.Device; |
28 | +import org.thingsboard.server.common.data.device.credentials.BasicMqttCredentials; | ||
27 | import org.thingsboard.server.common.data.id.DeviceId; | 29 | import org.thingsboard.server.common.data.id.DeviceId; |
28 | import org.thingsboard.server.common.data.id.EntityId; | 30 | import org.thingsboard.server.common.data.id.EntityId; |
29 | import org.thingsboard.server.common.data.id.TenantId; | 31 | import org.thingsboard.server.common.data.id.TenantId; |
30 | import org.thingsboard.server.common.data.security.DeviceCredentials; | 32 | import org.thingsboard.server.common.data.security.DeviceCredentials; |
31 | -import org.thingsboard.server.common.data.security.DeviceCredentialsType; | ||
32 | import org.thingsboard.server.common.msg.EncryptionUtil; | 33 | import org.thingsboard.server.common.msg.EncryptionUtil; |
33 | import org.thingsboard.server.dao.entity.AbstractEntityService; | 34 | import org.thingsboard.server.dao.entity.AbstractEntityService; |
34 | import org.thingsboard.server.dao.exception.DataValidationException; | 35 | import org.thingsboard.server.dao.exception.DataValidationException; |
35 | import org.thingsboard.server.dao.service.DataValidator; | 36 | import org.thingsboard.server.dao.service.DataValidator; |
37 | +import org.thingsboard.server.dao.util.mapping.JacksonUtil; | ||
36 | 38 | ||
37 | import static org.thingsboard.server.common.data.CacheConstants.DEVICE_CREDENTIALS_CACHE; | 39 | import static org.thingsboard.server.common.data.CacheConstants.DEVICE_CREDENTIALS_CACHE; |
38 | import static org.thingsboard.server.dao.service.Validator.validateId; | 40 | import static org.thingsboard.server.dao.service.Validator.validateId; |
@@ -75,8 +77,16 @@ public class DeviceCredentialsServiceImpl extends AbstractEntityService implemen | @@ -75,8 +77,16 @@ public class DeviceCredentialsServiceImpl extends AbstractEntityService implemen | ||
75 | } | 77 | } |
76 | 78 | ||
77 | private DeviceCredentials saveOrUpdate(TenantId tenantId, DeviceCredentials deviceCredentials) { | 79 | private DeviceCredentials saveOrUpdate(TenantId tenantId, DeviceCredentials deviceCredentials) { |
78 | - if (deviceCredentials.getCredentialsType() == DeviceCredentialsType.X509_CERTIFICATE) { | ||
79 | - formatCertData(deviceCredentials); | 80 | + if(deviceCredentials.getCredentialsType() == null){ |
81 | + throw new DataValidationException("Device credentials type should be specified"); | ||
82 | + } | ||
83 | + switch (deviceCredentials.getCredentialsType()) { | ||
84 | + case X509_CERTIFICATE: | ||
85 | + formatCertData(deviceCredentials); | ||
86 | + break; | ||
87 | + case MQTT_BASIC: | ||
88 | + formatSimpleMqttCredentials(deviceCredentials); | ||
89 | + break; | ||
80 | } | 90 | } |
81 | log.trace("Executing updateDeviceCredentials [{}]", deviceCredentials); | 91 | log.trace("Executing updateDeviceCredentials [{}]", deviceCredentials); |
82 | credentialsValidator.validate(deviceCredentials, id -> tenantId); | 92 | credentialsValidator.validate(deviceCredentials, id -> tenantId); |
@@ -93,6 +103,32 @@ public class DeviceCredentialsServiceImpl extends AbstractEntityService implemen | @@ -93,6 +103,32 @@ public class DeviceCredentialsServiceImpl extends AbstractEntityService implemen | ||
93 | } | 103 | } |
94 | } | 104 | } |
95 | 105 | ||
106 | + private void formatSimpleMqttCredentials(DeviceCredentials deviceCredentials) { | ||
107 | + BasicMqttCredentials mqttCredentials; | ||
108 | + try { | ||
109 | + mqttCredentials = JacksonUtil.fromString(deviceCredentials.getCredentialsValue(), BasicMqttCredentials.class); | ||
110 | + if (mqttCredentials == null) { | ||
111 | + throw new IllegalArgumentException(); | ||
112 | + } | ||
113 | + } catch (IllegalArgumentException e) { | ||
114 | + throw new DataValidationException("Invalid credentials body for simple mqtt credentials!"); | ||
115 | + } | ||
116 | + if (StringUtils.isEmpty(mqttCredentials.getClientId()) && StringUtils.isEmpty(mqttCredentials.getUserName())) { | ||
117 | + throw new DataValidationException("Both mqtt client id and user name are empty!"); | ||
118 | + } | ||
119 | + if (StringUtils.isEmpty(mqttCredentials.getClientId())) { | ||
120 | + deviceCredentials.setCredentialsId(mqttCredentials.getUserName()); | ||
121 | + } else if (StringUtils.isEmpty(mqttCredentials.getUserName())) { | ||
122 | + deviceCredentials.setCredentialsId(EncryptionUtil.getSha3Hash(mqttCredentials.getClientId())); | ||
123 | + } else { | ||
124 | + deviceCredentials.setCredentialsId(EncryptionUtil.getSha3Hash("|", mqttCredentials.getClientId(), mqttCredentials.getUserName())); | ||
125 | + } | ||
126 | + if (!StringUtils.isEmpty(mqttCredentials.getPassword())) { | ||
127 | + mqttCredentials.setPassword(mqttCredentials.getPassword()); | ||
128 | + } | ||
129 | + deviceCredentials.setCredentialsValue(JacksonUtil.toString(mqttCredentials)); | ||
130 | + } | ||
131 | + | ||
96 | private void formatCertData(DeviceCredentials deviceCredentials) { | 132 | private void formatCertData(DeviceCredentials deviceCredentials) { |
97 | String cert = EncryptionUtil.trimNewLines(deviceCredentials.getCredentialsValue()); | 133 | String cert = EncryptionUtil.trimNewLines(deviceCredentials.getCredentialsValue()); |
98 | String sha3Hash = EncryptionUtil.getSha3Hash(cert); | 134 | String sha3Hash = EncryptionUtil.getSha3Hash(cert); |
@@ -58,6 +58,37 @@ | @@ -58,6 +58,37 @@ | ||
58 | {{ 'device.rsa-key-required' | translate }} | 58 | {{ 'device.rsa-key-required' | translate }} |
59 | </mat-error> | 59 | </mat-error> |
60 | </mat-form-field> | 60 | </mat-form-field> |
61 | + <section *ngIf="deviceCredentialsFormGroup.get('credentialsType').value === deviceCredentialsType.MQTT_BASIC" formGroupName="credentialsBasic"> | ||
62 | + <mat-form-field class="mat-block"> | ||
63 | + <mat-label translate>device.client-id</mat-label> | ||
64 | + <input matInput formControlName="clientId"> | ||
65 | + <mat-error *ngIf="deviceCredentialsFormGroup.get('credentialsBasic.clientId').hasError('pattern')"> | ||
66 | + {{ 'device.client-id-pattern' | translate }} | ||
67 | + </mat-error> | ||
68 | + </mat-form-field> | ||
69 | + <mat-form-field class="mat-block"> | ||
70 | + <mat-label translate>device.user-name</mat-label> | ||
71 | + <input matInput formControlName="userName" [required]="!!deviceCredentialsFormGroup.get('credentialsBasic.password').value"> | ||
72 | + <mat-error *ngIf="deviceCredentialsFormGroup.get('credentialsBasic.userName').hasError('required')"> | ||
73 | + {{ 'device.user-name-required' | translate }} | ||
74 | + </mat-error> | ||
75 | + </mat-form-field> | ||
76 | + <mat-form-field class="mat-block"> | ||
77 | + <mat-label translate>device.password</mat-label> | ||
78 | + <input matInput formControlName="password" | ||
79 | + autocomplete="new-password" | ||
80 | + (ngModelChange)="passwordChanged()" | ||
81 | + [type]="hidePassword ? 'password' : 'text'"> | ||
82 | + <button mat-icon-button matSuffix type="button" | ||
83 | + (click)="hidePassword = !hidePassword" | ||
84 | + [attr.aria-pressed]="hidePassword"> | ||
85 | + <mat-icon>{{hidePassword ? 'visibility_off' : 'visibility'}}</mat-icon> | ||
86 | + </button> | ||
87 | + </mat-form-field> | ||
88 | + <mat-error *ngIf="deviceCredentialsFormGroup.get('credentialsBasic').hasError('atLeastOne')"> | ||
89 | + {{ 'device.client-id-or-user-name-necessary' | translate }} | ||
90 | + </mat-error> | ||
91 | + </section> | ||
61 | </fieldset> | 92 | </fieldset> |
62 | </div> | 93 | </div> |
63 | <div mat-dialog-actions fxLayoutAlign="end center"> | 94 | <div mat-dialog-actions fxLayoutAlign="end center"> |
@@ -19,9 +19,23 @@ import { ErrorStateMatcher } from '@angular/material/core'; | @@ -19,9 +19,23 @@ import { ErrorStateMatcher } from '@angular/material/core'; | ||
19 | import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; | 19 | import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; |
20 | import { Store } from '@ngrx/store'; | 20 | import { Store } from '@ngrx/store'; |
21 | import { AppState } from '@core/core.state'; | 21 | import { AppState } from '@core/core.state'; |
22 | -import { FormBuilder, FormControl, FormGroup, FormGroupDirective, NgForm, Validators } from '@angular/forms'; | 22 | +import { |
23 | + FormBuilder, | ||
24 | + FormControl, | ||
25 | + FormGroup, | ||
26 | + FormGroupDirective, | ||
27 | + NgForm, | ||
28 | + ValidationErrors, | ||
29 | + ValidatorFn, | ||
30 | + Validators | ||
31 | +} from '@angular/forms'; | ||
23 | import { DeviceService } from '@core/http/device.service'; | 32 | import { DeviceService } from '@core/http/device.service'; |
24 | -import { credentialTypeNames, DeviceCredentials, DeviceCredentialsType } from '@shared/models/device.models'; | 33 | +import { |
34 | + credentialTypeNames, | ||
35 | + DeviceCredentialMQTTBasic, | ||
36 | + DeviceCredentials, | ||
37 | + DeviceCredentialsType | ||
38 | +} from '@shared/models/device.models'; | ||
25 | import { DialogComponent } from '@shared/components/dialog.component'; | 39 | import { DialogComponent } from '@shared/components/dialog.component'; |
26 | import { Router } from '@angular/router'; | 40 | import { Router } from '@angular/router'; |
27 | 41 | ||
@@ -53,6 +67,8 @@ export class DeviceCredentialsDialogComponent extends | @@ -53,6 +67,8 @@ export class DeviceCredentialsDialogComponent extends | ||
53 | 67 | ||
54 | credentialTypeNamesMap = credentialTypeNames; | 68 | credentialTypeNamesMap = credentialTypeNames; |
55 | 69 | ||
70 | + hidePassword = true; | ||
71 | + | ||
56 | constructor(protected store: Store<AppState>, | 72 | constructor(protected store: Store<AppState>, |
57 | protected router: Router, | 73 | protected router: Router, |
58 | @Inject(MAT_DIALOG_DATA) public data: DeviceCredentialsDialogData, | 74 | @Inject(MAT_DIALOG_DATA) public data: DeviceCredentialsDialogData, |
@@ -69,7 +85,12 @@ export class DeviceCredentialsDialogComponent extends | @@ -69,7 +85,12 @@ export class DeviceCredentialsDialogComponent extends | ||
69 | this.deviceCredentialsFormGroup = this.fb.group({ | 85 | this.deviceCredentialsFormGroup = this.fb.group({ |
70 | credentialsType: [DeviceCredentialsType.ACCESS_TOKEN], | 86 | credentialsType: [DeviceCredentialsType.ACCESS_TOKEN], |
71 | credentialsId: [''], | 87 | credentialsId: [''], |
72 | - credentialsValue: [''] | 88 | + credentialsValue: [''], |
89 | + credentialsBasic: this.fb.group({ | ||
90 | + clientId: ['', [Validators.pattern(/^[A-Za-z0-9]+$/)]], | ||
91 | + userName: [''], | ||
92 | + password: [''] | ||
93 | + }, {validators: this.atLeastOne(Validators.required, ['clientId', 'userName'])}) | ||
73 | }); | 94 | }); |
74 | if (this.isReadOnly) { | 95 | if (this.isReadOnly) { |
75 | this.deviceCredentialsFormGroup.disable({emitEvent: false}); | 96 | this.deviceCredentialsFormGroup.disable({emitEvent: false}); |
@@ -89,10 +110,17 @@ export class DeviceCredentialsDialogComponent extends | @@ -89,10 +110,17 @@ export class DeviceCredentialsDialogComponent extends | ||
89 | this.deviceService.getDeviceCredentials(this.data.deviceId).subscribe( | 110 | this.deviceService.getDeviceCredentials(this.data.deviceId).subscribe( |
90 | (deviceCredentials) => { | 111 | (deviceCredentials) => { |
91 | this.deviceCredentials = deviceCredentials; | 112 | this.deviceCredentials = deviceCredentials; |
113 | + let credentialsValue = deviceCredentials.credentialsValue; | ||
114 | + let credentialsBasic = {clientId: null, userName: null, password: null}; | ||
115 | + if (deviceCredentials.credentialsType === DeviceCredentialsType.MQTT_BASIC) { | ||
116 | + credentialsValue = null; | ||
117 | + credentialsBasic = JSON.parse(deviceCredentials.credentialsValue) as DeviceCredentialMQTTBasic; | ||
118 | + } | ||
92 | this.deviceCredentialsFormGroup.patchValue({ | 119 | this.deviceCredentialsFormGroup.patchValue({ |
93 | credentialsType: deviceCredentials.credentialsType, | 120 | credentialsType: deviceCredentials.credentialsType, |
94 | credentialsId: deviceCredentials.credentialsId, | 121 | credentialsId: deviceCredentials.credentialsId, |
95 | - credentialsValue: deviceCredentials.credentialsValue | 122 | + credentialsValue, |
123 | + credentialsBasic | ||
96 | }); | 124 | }); |
97 | this.updateValidators(); | 125 | this.updateValidators(); |
98 | } | 126 | } |
@@ -100,12 +128,16 @@ export class DeviceCredentialsDialogComponent extends | @@ -100,12 +128,16 @@ export class DeviceCredentialsDialogComponent extends | ||
100 | } | 128 | } |
101 | 129 | ||
102 | credentialsTypeChanged(): void { | 130 | credentialsTypeChanged(): void { |
103 | - this.deviceCredentialsFormGroup.patchValue( | ||
104 | - {credentialsId: null, credentialsValue: null}, {emitEvent: true}); | 131 | + this.deviceCredentialsFormGroup.patchValue({ |
132 | + credentialsId: null, | ||
133 | + credentialsValue: null, | ||
134 | + credentialsBasic: {clientId: '', userName: '', password: ''} | ||
135 | + }, {emitEvent: true}); | ||
105 | this.updateValidators(); | 136 | this.updateValidators(); |
106 | } | 137 | } |
107 | 138 | ||
108 | updateValidators(): void { | 139 | updateValidators(): void { |
140 | + this.hidePassword = true; | ||
109 | const crendetialsType = this.deviceCredentialsFormGroup.get('credentialsType').value as DeviceCredentialsType; | 141 | const crendetialsType = this.deviceCredentialsFormGroup.get('credentialsType').value as DeviceCredentialsType; |
110 | switch (crendetialsType) { | 142 | switch (crendetialsType) { |
111 | case DeviceCredentialsType.ACCESS_TOKEN: | 143 | case DeviceCredentialsType.ACCESS_TOKEN: |
@@ -113,27 +145,66 @@ export class DeviceCredentialsDialogComponent extends | @@ -113,27 +145,66 @@ export class DeviceCredentialsDialogComponent extends | ||
113 | this.deviceCredentialsFormGroup.get('credentialsId').updateValueAndValidity(); | 145 | this.deviceCredentialsFormGroup.get('credentialsId').updateValueAndValidity(); |
114 | this.deviceCredentialsFormGroup.get('credentialsValue').setValidators([]); | 146 | this.deviceCredentialsFormGroup.get('credentialsValue').setValidators([]); |
115 | this.deviceCredentialsFormGroup.get('credentialsValue').updateValueAndValidity(); | 147 | this.deviceCredentialsFormGroup.get('credentialsValue').updateValueAndValidity(); |
148 | + this.deviceCredentialsFormGroup.get('credentialsBasic').disable(); | ||
116 | break; | 149 | break; |
117 | case DeviceCredentialsType.X509_CERTIFICATE: | 150 | case DeviceCredentialsType.X509_CERTIFICATE: |
118 | this.deviceCredentialsFormGroup.get('credentialsValue').setValidators([Validators.required]); | 151 | this.deviceCredentialsFormGroup.get('credentialsValue').setValidators([Validators.required]); |
119 | this.deviceCredentialsFormGroup.get('credentialsValue').updateValueAndValidity(); | 152 | this.deviceCredentialsFormGroup.get('credentialsValue').updateValueAndValidity(); |
120 | this.deviceCredentialsFormGroup.get('credentialsId').setValidators([]); | 153 | this.deviceCredentialsFormGroup.get('credentialsId').setValidators([]); |
121 | this.deviceCredentialsFormGroup.get('credentialsId').updateValueAndValidity(); | 154 | this.deviceCredentialsFormGroup.get('credentialsId').updateValueAndValidity(); |
155 | + this.deviceCredentialsFormGroup.get('credentialsBasic').disable(); | ||
122 | break; | 156 | break; |
157 | + case DeviceCredentialsType.MQTT_BASIC: | ||
158 | + this.deviceCredentialsFormGroup.get('credentialsBasic').enable(); | ||
159 | + this.deviceCredentialsFormGroup.get('credentialsBasic').updateValueAndValidity(); | ||
160 | + this.deviceCredentialsFormGroup.get('credentialsId').setValidators([]); | ||
161 | + this.deviceCredentialsFormGroup.get('credentialsId').updateValueAndValidity(); | ||
162 | + this.deviceCredentialsFormGroup.get('credentialsValue').setValidators([]); | ||
163 | + this.deviceCredentialsFormGroup.get('credentialsValue').updateValueAndValidity(); | ||
123 | } | 164 | } |
124 | } | 165 | } |
125 | 166 | ||
167 | + private atLeastOne(validator: ValidatorFn, controls: string[] = null) { | ||
168 | + return (group: FormGroup): ValidationErrors | null => { | ||
169 | + if (!controls) { | ||
170 | + controls = Object.keys(group.controls); | ||
171 | + } | ||
172 | + const hasAtLeastOne = group?.controls && controls.some(k => !validator(group.controls[k])); | ||
173 | + | ||
174 | + return hasAtLeastOne ? null : {atLeastOne: true}; | ||
175 | + }; | ||
176 | + } | ||
177 | + | ||
126 | cancel(): void { | 178 | cancel(): void { |
127 | this.dialogRef.close(null); | 179 | this.dialogRef.close(null); |
128 | } | 180 | } |
129 | 181 | ||
130 | save(): void { | 182 | save(): void { |
131 | this.submitted = true; | 183 | this.submitted = true; |
132 | - this.deviceCredentials = {...this.deviceCredentials, ...this.deviceCredentialsFormGroup.value}; | 184 | + const deviceCredentialsValue = this.deviceCredentialsFormGroup.value; |
185 | + if (deviceCredentialsValue.credentialsType === DeviceCredentialsType.MQTT_BASIC) { | ||
186 | + deviceCredentialsValue.credentialsValue = JSON.stringify(deviceCredentialsValue.credentialsBasic); | ||
187 | + } | ||
188 | + delete deviceCredentialsValue.credentialsBasic; | ||
189 | + this.deviceCredentials = {...this.deviceCredentials, ...deviceCredentialsValue}; | ||
133 | this.deviceService.saveDeviceCredentials(this.deviceCredentials).subscribe( | 190 | this.deviceService.saveDeviceCredentials(this.deviceCredentials).subscribe( |
134 | (deviceCredentials) => { | 191 | (deviceCredentials) => { |
135 | this.dialogRef.close(deviceCredentials); | 192 | this.dialogRef.close(deviceCredentials); |
136 | } | 193 | } |
137 | ); | 194 | ); |
138 | } | 195 | } |
196 | + | ||
197 | + passwordChanged() { | ||
198 | + const value = this.deviceCredentialsFormGroup.get('credentialsBasic.password').value; | ||
199 | + if (value !== '') { | ||
200 | + this.deviceCredentialsFormGroup.get('credentialsBasic.userName').setValidators([Validators.required]); | ||
201 | + if (this.deviceCredentialsFormGroup.get('credentialsBasic.userName').untouched) { | ||
202 | + this.deviceCredentialsFormGroup.get('credentialsBasic.userName').markAsTouched({onlySelf: true}); | ||
203 | + } | ||
204 | + this.deviceCredentialsFormGroup.get('credentialsBasic.userName').updateValueAndValidity(); | ||
205 | + } else { | ||
206 | + this.deviceCredentialsFormGroup.get('credentialsBasic.userName').setValidators([]); | ||
207 | + this.deviceCredentialsFormGroup.get('credentialsBasic.userName').updateValueAndValidity(); | ||
208 | + } | ||
209 | + } | ||
139 | } | 210 | } |
@@ -292,13 +292,15 @@ export interface DeviceInfo extends Device { | @@ -292,13 +292,15 @@ export interface DeviceInfo extends Device { | ||
292 | 292 | ||
293 | export enum DeviceCredentialsType { | 293 | export enum DeviceCredentialsType { |
294 | ACCESS_TOKEN = 'ACCESS_TOKEN', | 294 | ACCESS_TOKEN = 'ACCESS_TOKEN', |
295 | - X509_CERTIFICATE = 'X509_CERTIFICATE' | 295 | + X509_CERTIFICATE = 'X509_CERTIFICATE', |
296 | + MQTT_BASIC = 'MQTT_BASIC' | ||
296 | } | 297 | } |
297 | 298 | ||
298 | export const credentialTypeNames = new Map<DeviceCredentialsType, string>( | 299 | export const credentialTypeNames = new Map<DeviceCredentialsType, string>( |
299 | [ | 300 | [ |
300 | [DeviceCredentialsType.ACCESS_TOKEN, 'Access token'], | 301 | [DeviceCredentialsType.ACCESS_TOKEN, 'Access token'], |
301 | - [DeviceCredentialsType.X509_CERTIFICATE, 'X.509 Certificate'], | 302 | + [DeviceCredentialsType.X509_CERTIFICATE, 'MQTT X.509'], |
303 | + [DeviceCredentialsType.MQTT_BASIC, 'MQTT Basic'] | ||
302 | ] | 304 | ] |
303 | ); | 305 | ); |
304 | 306 | ||
@@ -309,6 +311,12 @@ export interface DeviceCredentials extends BaseData<DeviceCredentialsId> { | @@ -309,6 +311,12 @@ export interface DeviceCredentials extends BaseData<DeviceCredentialsId> { | ||
309 | credentialsValue: string; | 311 | credentialsValue: string; |
310 | } | 312 | } |
311 | 313 | ||
314 | +export interface DeviceCredentialMQTTBasic { | ||
315 | + clientId: string; | ||
316 | + userName: string; | ||
317 | + password: string; | ||
318 | +} | ||
319 | + | ||
312 | export interface DeviceSearchQuery extends EntitySearchQuery { | 320 | export interface DeviceSearchQuery extends EntitySearchQuery { |
313 | deviceTypes: Array<string>; | 321 | deviceTypes: Array<string>; |
314 | } | 322 | } |
@@ -718,6 +718,12 @@ | @@ -718,6 +718,12 @@ | ||
718 | "access-token-invalid": "Access token length must be from 1 to 20 characters.", | 718 | "access-token-invalid": "Access token length must be from 1 to 20 characters.", |
719 | "rsa-key": "RSA public key", | 719 | "rsa-key": "RSA public key", |
720 | "rsa-key-required": "RSA public key is required.", | 720 | "rsa-key-required": "RSA public key is required.", |
721 | + "client-id": "Client ID", | ||
722 | + "client-id-pattern": "Contains invalid character.", | ||
723 | + "user-name": "User Name", | ||
724 | + "user-name-required": "User Name is required.", | ||
725 | + "client-id-or-user-name-necessary": "Client ID and/or User Name are necessary", | ||
726 | + "password": "Password", | ||
721 | "secret": "Secret", | 727 | "secret": "Secret", |
722 | "secret-required": "Secret is required.", | 728 | "secret-required": "Secret is required.", |
723 | "device-type": "Device type", | 729 | "device-type": "Device type", |