Commit 969a686c74fd33269500227685dfb8834f3bcf59

Authored by Igor Kulikov
2 parents d9321b48 a3326b44

Merge branch 'develop/3.2' of github.com:thingsboard/thingsboard into develop/3.2

@@ -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",