Commit 19b8464937e242ebf3777aa648ed623e1b7195b9

Authored by Igor Kulikov
2 parents d0fafb95 1a718593

Merge device provisioning feature

Showing 68 changed files with 2211 additions and 41 deletions
... ... @@ -89,13 +89,16 @@ CREATE TABLE IF NOT EXISTS device_profile (
89 89 name varchar(255),
90 90 type varchar(255),
91 91 transport_type varchar(255),
  92 + provision_type varchar(255),
92 93 profile_data jsonb,
93 94 description varchar,
94 95 search_text varchar(255),
95 96 is_default boolean,
96 97 tenant_id uuid,
97 98 default_rule_chain_id uuid,
  99 + provision_device_key varchar,
98 100 CONSTRAINT device_profile_name_unq_key UNIQUE (tenant_id, name),
  101 + CONSTRAINT device_provision_key_unq_key UNIQUE (provision_device_key),
99 102 CONSTRAINT fk_default_rule_chain_device_profile FOREIGN KEY (default_rule_chain_id) REFERENCES rule_chain(id)
100 103 );
101 104
... ...
... ... @@ -700,6 +700,12 @@ public abstract class BaseController {
700 700 case ASSIGNED_TO_TENANT:
701 701 msgType = DataConstants.ENTITY_ASSIGNED_TO_TENANT;
702 702 break;
  703 + case PROVISION_SUCCESS:
  704 + msgType = DataConstants.PROVISION_SUCCESS;
  705 + break;
  706 + case PROVISION_FAILURE:
  707 + msgType = DataConstants.PROVISION_FAILURE;
  708 + break;
703 709 }
704 710 if (!StringUtils.isEmpty(msgType)) {
705 711 try {
... ...
  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.service.device;
  17 +
  18 +import com.fasterxml.jackson.core.JsonProcessingException;
  19 +import com.fasterxml.jackson.databind.JsonNode;
  20 +import com.fasterxml.jackson.databind.node.ObjectNode;
  21 +import com.google.common.util.concurrent.Futures;
  22 +import com.google.common.util.concurrent.ListenableFuture;
  23 +import com.google.common.util.concurrent.MoreExecutors;
  24 +import lombok.extern.slf4j.Slf4j;
  25 +import org.springframework.beans.factory.annotation.Autowired;
  26 +import org.springframework.stereotype.Service;
  27 +import org.springframework.util.StringUtils;
  28 +import org.thingsboard.server.common.data.DataConstants;
  29 +import org.thingsboard.server.common.data.Device;
  30 +import org.thingsboard.server.common.data.DeviceProfile;
  31 +import org.thingsboard.server.common.data.audit.ActionType;
  32 +import org.thingsboard.server.common.data.id.CustomerId;
  33 +import org.thingsboard.server.common.data.id.TenantId;
  34 +import org.thingsboard.server.common.data.id.UserId;
  35 +import org.thingsboard.server.common.data.kv.AttributeKvEntry;
  36 +import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry;
  37 +import org.thingsboard.server.common.data.kv.StringDataEntry;
  38 +import org.thingsboard.server.common.data.security.DeviceCredentials;
  39 +import org.thingsboard.server.common.msg.TbMsg;
  40 +import org.thingsboard.server.common.msg.TbMsgMetaData;
  41 +import org.thingsboard.server.common.msg.queue.ServiceType;
  42 +import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
  43 +import org.thingsboard.server.dao.attributes.AttributesService;
  44 +import org.thingsboard.server.dao.audit.AuditLogService;
  45 +import org.thingsboard.server.dao.device.DeviceCredentialsService;
  46 +import org.thingsboard.server.dao.device.DeviceDao;
  47 +import org.thingsboard.server.dao.device.DeviceProfileDao;
  48 +import org.thingsboard.server.dao.device.DeviceProvisionService;
  49 +import org.thingsboard.server.dao.device.DeviceService;
  50 +import org.thingsboard.server.dao.device.provision.ProvisionFailedException;
  51 +import org.thingsboard.server.dao.device.provision.ProvisionRequest;
  52 +import org.thingsboard.server.dao.device.provision.ProvisionResponse;
  53 +import org.thingsboard.server.dao.device.provision.ProvisionResponseStatus;
  54 +import org.thingsboard.server.dao.util.mapping.JacksonUtil;
  55 +import org.thingsboard.server.gen.transport.TransportProtos;
  56 +import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg;
  57 +import org.thingsboard.server.queue.TbQueueCallback;
  58 +import org.thingsboard.server.queue.TbQueueProducer;
  59 +import org.thingsboard.server.queue.common.TbProtoQueueMsg;
  60 +import org.thingsboard.server.queue.discovery.PartitionService;
  61 +import org.thingsboard.server.queue.provider.TbQueueProducerProvider;
  62 +import org.thingsboard.server.queue.util.TbCoreComponent;
  63 +import org.thingsboard.server.service.state.DeviceStateService;
  64 +
  65 +import java.util.Collections;
  66 +import java.util.List;
  67 +import java.util.Optional;
  68 +import java.util.concurrent.ExecutionException;
  69 +import java.util.concurrent.locks.ReentrantLock;
  70 +
  71 +
  72 +@Service
  73 +@Slf4j
  74 +@TbCoreComponent
  75 +public class DeviceProvisionServiceImpl implements DeviceProvisionService {
  76 +
  77 + protected TbQueueProducer<TbProtoQueueMsg<ToRuleEngineMsg>> ruleEngineMsgProducer;
  78 +
  79 + private static final String DEVICE_PROVISION_STATE = "provisionState";
  80 + private static final String PROVISIONED_STATE = "provisioned";
  81 +
  82 + private final ReentrantLock deviceCreationLock = new ReentrantLock();
  83 +
  84 + @Autowired
  85 + DeviceDao deviceDao;
  86 +
  87 + @Autowired
  88 + DeviceProfileDao deviceProfileDao;
  89 +
  90 + @Autowired
  91 + DeviceService deviceService;
  92 +
  93 + @Autowired
  94 + DeviceCredentialsService deviceCredentialsService;
  95 +
  96 + @Autowired
  97 + AttributesService attributesService;
  98 +
  99 + @Autowired
  100 + DeviceStateService deviceStateService;
  101 +
  102 + @Autowired
  103 + AuditLogService auditLogService;
  104 +
  105 + @Autowired
  106 + PartitionService partitionService;
  107 +
  108 + public DeviceProvisionServiceImpl(TbQueueProducerProvider producerProvider) {
  109 + ruleEngineMsgProducer = producerProvider.getRuleEngineMsgProducer();
  110 + }
  111 +
  112 + @Override
  113 + public ProvisionResponse provisionDevice(ProvisionRequest provisionRequest) {
  114 + String provisionRequestKey = provisionRequest.getCredentials().getProvisionDeviceKey();
  115 + String provisionRequestSecret = provisionRequest.getCredentials().getProvisionDeviceSecret();
  116 +
  117 + if (StringUtils.isEmpty(provisionRequestKey) || StringUtils.isEmpty(provisionRequestSecret)) {
  118 + throw new ProvisionFailedException(ProvisionResponseStatus.NOT_FOUND.name());
  119 + }
  120 +
  121 + DeviceProfile targetProfile = deviceProfileDao.findByProvisionDeviceKey(provisionRequestKey);
  122 +
  123 + if (targetProfile == null || targetProfile.getProfileData().getProvisionConfiguration() == null ||
  124 + targetProfile.getProfileData().getProvisionConfiguration().getProvisionDeviceSecret() == null) {
  125 + throw new ProvisionFailedException(ProvisionResponseStatus.NOT_FOUND.name());
  126 + }
  127 +
  128 + Device targetDevice = deviceDao.findDeviceByTenantIdAndName(targetProfile.getTenantId().getId(), provisionRequest.getDeviceName()).orElse(null);
  129 +
  130 + switch (targetProfile.getProvisionType()) {
  131 + case ALLOW_CREATE_NEW_DEVICES:
  132 + if (targetProfile.getProfileData().getProvisionConfiguration().getProvisionDeviceSecret().equals(provisionRequestSecret)) {
  133 + if (targetDevice != null) {
  134 + log.warn("[{}] The device is present and could not be provisioned once more!", targetDevice.getName());
  135 + notify(targetDevice, provisionRequest, DataConstants.PROVISION_FAILURE, false);
  136 + throw new ProvisionFailedException(ProvisionResponseStatus.FAILURE.name());
  137 + } else {
  138 + return createDevice(provisionRequest, targetProfile);
  139 + }
  140 + }
  141 + break;
  142 + case CHECK_PRE_PROVISIONED_DEVICES:
  143 + if (targetProfile.getProfileData().getProvisionConfiguration().getProvisionDeviceSecret().equals(provisionRequestSecret)) {
  144 + if (targetDevice != null && targetDevice.getDeviceProfileId().equals(targetProfile.getId())) {
  145 + return processProvision(targetDevice, provisionRequest);
  146 + } else {
  147 + log.warn("[{}] Failed to find pre provisioned device!", provisionRequest.getDeviceName());
  148 + throw new ProvisionFailedException(ProvisionResponseStatus.FAILURE.name());
  149 + }
  150 + }
  151 + break;
  152 + }
  153 + throw new ProvisionFailedException(ProvisionResponseStatus.NOT_FOUND.name());
  154 + }
  155 +
  156 + private ProvisionResponse processProvision(Device device, ProvisionRequest provisionRequest) {
  157 + try {
  158 + Optional<AttributeKvEntry> provisionState = attributesService.find(device.getTenantId(), device.getId(),
  159 + DataConstants.SERVER_SCOPE, DEVICE_PROVISION_STATE).get();
  160 + if (provisionState != null && provisionState.isPresent() && !provisionState.get().getValueAsString().equals(PROVISIONED_STATE)) {
  161 + notify(device, provisionRequest, DataConstants.PROVISION_FAILURE, false);
  162 + throw new ProvisionFailedException(ProvisionResponseStatus.FAILURE.name());
  163 + } else {
  164 + saveProvisionStateAttribute(device).get();
  165 + notify(device, provisionRequest, DataConstants.PROVISION_SUCCESS, true);
  166 + }
  167 + } catch (InterruptedException | ExecutionException e) {
  168 + throw new ProvisionFailedException(ProvisionResponseStatus.FAILURE.name());
  169 + }
  170 + return new ProvisionResponse(deviceCredentialsService.findDeviceCredentialsByDeviceId(device.getTenantId(), device.getId()), ProvisionResponseStatus.SUCCESS);
  171 + }
  172 +
  173 + private ProvisionResponse createDevice(ProvisionRequest provisionRequest, DeviceProfile profile) {
  174 + deviceCreationLock.lock();
  175 + try {
  176 + return processCreateDevice(provisionRequest, profile);
  177 + } finally {
  178 + deviceCreationLock.unlock();
  179 + }
  180 + }
  181 +
  182 + private void notify(Device device, ProvisionRequest provisionRequest, String type, boolean success) {
  183 + pushProvisionEventToRuleEngine(provisionRequest, device, type);
  184 + logAction(device.getTenantId(), device.getCustomerId(), device, success, provisionRequest);
  185 + }
  186 +
  187 + private ProvisionResponse processCreateDevice(ProvisionRequest provisionRequest, DeviceProfile profile) {
  188 + Device device = deviceService.findDeviceByTenantIdAndName(profile.getTenantId(), provisionRequest.getDeviceName());
  189 + try {
  190 + if (device == null) {
  191 + Device savedDevice = deviceService.saveDevice(provisionRequest, profile);
  192 +
  193 + deviceStateService.onDeviceAdded(savedDevice);
  194 + saveProvisionStateAttribute(savedDevice).get();
  195 + pushDeviceCreatedEventToRuleEngine(savedDevice);
  196 + notify(savedDevice, provisionRequest, DataConstants.PROVISION_SUCCESS, true);
  197 +
  198 + return new ProvisionResponse(getDeviceCredentials(savedDevice), ProvisionResponseStatus.SUCCESS);
  199 + } else {
  200 + log.warn("[{}] The device is already provisioned!", device.getName());
  201 + notify(device, provisionRequest, DataConstants.PROVISION_FAILURE, false);
  202 + throw new ProvisionFailedException(ProvisionResponseStatus.FAILURE.name());
  203 + }
  204 + } catch (InterruptedException | ExecutionException e) {
  205 + throw new ProvisionFailedException(ProvisionResponseStatus.FAILURE.name());
  206 + }
  207 + }
  208 +
  209 + private ListenableFuture<List<Void>> saveProvisionStateAttribute(Device device) {
  210 + return attributesService.save(device.getTenantId(), device.getId(), DataConstants.SERVER_SCOPE,
  211 + Collections.singletonList(new BaseAttributeKvEntry(new StringDataEntry(DEVICE_PROVISION_STATE, PROVISIONED_STATE),
  212 + System.currentTimeMillis())));
  213 + }
  214 +
  215 + private DeviceCredentials getDeviceCredentials(Device device) {
  216 + return deviceCredentialsService.findDeviceCredentialsByDeviceId(device.getTenantId(), device.getId());
  217 + }
  218 +
  219 + private void pushProvisionEventToRuleEngine(ProvisionRequest request, Device device, String type) {
  220 + try {
  221 + JsonNode entityNode = JacksonUtil.valueToTree(request);
  222 + TbMsg msg = TbMsg.newMsg(type, device.getId(), createTbMsgMetaData(device), JacksonUtil.toString(entityNode));
  223 + sendToRuleEngine(device.getTenantId(), msg, null);
  224 + } catch (IllegalArgumentException e) {
  225 + log.warn("[{}] Failed to push device action to rule engine: {}", device.getId(), type, e);
  226 + }
  227 + }
  228 +
  229 + private void pushDeviceCreatedEventToRuleEngine(Device device) {
  230 + try {
  231 + ObjectNode entityNode = JacksonUtil.OBJECT_MAPPER.valueToTree(device);
  232 + TbMsg msg = TbMsg.newMsg(DataConstants.ENTITY_CREATED, device.getId(), createTbMsgMetaData(device), JacksonUtil.OBJECT_MAPPER.writeValueAsString(entityNode));
  233 + sendToRuleEngine(device.getTenantId(), msg, null);
  234 + } catch (JsonProcessingException | IllegalArgumentException e) {
  235 + log.warn("[{}] Failed to push device action to rule engine: {}", device.getId(), DataConstants.ENTITY_CREATED, e);
  236 + }
  237 + }
  238 +
  239 + protected void sendToRuleEngine(TenantId tenantId, TbMsg tbMsg, TbQueueCallback callback) {
  240 + TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_RULE_ENGINE, tenantId, tbMsg.getOriginator());
  241 + TransportProtos.ToRuleEngineMsg msg = TransportProtos.ToRuleEngineMsg.newBuilder().setTbMsg(TbMsg.toByteString(tbMsg))
  242 + .setTenantIdMSB(tenantId.getId().getMostSignificantBits())
  243 + .setTenantIdLSB(tenantId.getId().getLeastSignificantBits()).build();
  244 + ruleEngineMsgProducer.send(tpi, new TbProtoQueueMsg<>(tbMsg.getId(), msg), callback);
  245 + }
  246 +
  247 + private TbMsgMetaData createTbMsgMetaData(Device device) {
  248 + TbMsgMetaData metaData = new TbMsgMetaData();
  249 + metaData.putValue("tenantId", device.getTenantId().toString());
  250 + return metaData;
  251 + }
  252 +
  253 + private void logAction(TenantId tenantId, CustomerId customerId, Device device, boolean success, ProvisionRequest provisionRequest) {
  254 + ActionType actionType = success ? ActionType.PROVISION_SUCCESS : ActionType.PROVISION_FAILURE;
  255 + auditLogService.logEntityAction(tenantId, customerId, new UserId(UserId.NULL_UUID), device.getName(), device.getId(), device, actionType, null, provisionRequest);
  256 + }
  257 +}
... ...
... ... @@ -23,7 +23,6 @@ import com.google.common.util.concurrent.ListenableFuture;
23 23 import com.google.common.util.concurrent.MoreExecutors;
24 24 import com.google.protobuf.ByteString;
25 25 import lombok.extern.slf4j.Slf4j;
26   -import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
27 26 import org.springframework.stereotype.Service;
28 27 import org.springframework.util.StringUtils;
29 28 import org.thingsboard.server.common.data.DataConstants;
... ... @@ -31,6 +30,8 @@ import org.thingsboard.server.common.data.Device;
31 30 import org.thingsboard.server.common.data.DeviceProfile;
32 31 import org.thingsboard.server.common.data.TenantProfile;
33 32 import org.thingsboard.server.common.data.device.credentials.BasicMqttCredentials;
  33 +import org.thingsboard.server.common.data.device.credentials.ProvisionDeviceCredentialsData;
  34 +import org.thingsboard.server.common.data.device.profile.ProvisionDeviceProfileCredentials;
34 35 import org.thingsboard.server.common.data.id.CustomerId;
35 36 import org.thingsboard.server.common.data.id.DeviceId;
36 37 import org.thingsboard.server.common.data.id.DeviceProfileId;
... ... @@ -45,7 +46,10 @@ import org.thingsboard.server.common.msg.TbMsgMetaData;
45 46 import org.thingsboard.server.common.transport.util.DataDecodingEncodingService;
46 47 import org.thingsboard.server.dao.device.DeviceCredentialsService;
47 48 import org.thingsboard.server.dao.device.DeviceProfileService;
  49 +import org.thingsboard.server.dao.device.DeviceProvisionService;
48 50 import org.thingsboard.server.dao.device.DeviceService;
  51 +import org.thingsboard.server.dao.device.provision.ProvisionRequest;
  52 +import org.thingsboard.server.dao.device.provision.ProvisionResponse;
49 53 import org.thingsboard.server.dao.relation.RelationService;
50 54 import org.thingsboard.server.dao.tenant.TenantProfileService;
51 55 import org.thingsboard.server.dao.tenant.TenantService;
... ... @@ -56,6 +60,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.GetOrCreateDeviceFro
56 60 import org.thingsboard.server.gen.transport.TransportProtos.GetOrCreateDeviceFromGatewayResponseMsg;
57 61 import org.thingsboard.server.gen.transport.TransportProtos.GetTenantRoutingInfoRequestMsg;
58 62 import org.thingsboard.server.gen.transport.TransportProtos.GetTenantRoutingInfoResponseMsg;
  63 +import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceRequestMsg;
59 64 import org.thingsboard.server.gen.transport.TransportProtos.TransportApiRequestMsg;
60 65 import org.thingsboard.server.gen.transport.TransportProtos.TransportApiResponseMsg;
61 66 import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceCredentialsResponseMsg;
... ... @@ -63,6 +68,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceTokenR
63 68 import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceX509CertRequestMsg;
64 69 import org.thingsboard.server.queue.common.TbProtoQueueMsg;
65 70 import org.thingsboard.server.queue.util.TbCoreComponent;
  71 +import org.thingsboard.server.dao.device.provision.ProvisionFailedException;
66 72 import org.thingsboard.server.service.executors.DbCallbackExecutorService;
67 73 import org.thingsboard.server.service.queue.TbClusterService;
68 74 import org.thingsboard.server.service.state.DeviceStateService;
... ... @@ -94,6 +100,7 @@ public class DefaultTransportApiService implements TransportApiService {
94 100 private final DbCallbackExecutorService dbCallbackExecutorService;
95 101 private final TbClusterService tbClusterService;
96 102 private final DataDecodingEncodingService dataDecodingEncodingService;
  103 + private final DeviceProvisionService deviceProvisionService;
97 104
98 105
99 106 private final ConcurrentMap<String, ReentrantLock> deviceCreationLocks = new ConcurrentHashMap<>();
... ... @@ -102,7 +109,8 @@ public class DefaultTransportApiService implements TransportApiService {
102 109 TenantProfileService tenantProfileService, DeviceService deviceService,
103 110 RelationService relationService, DeviceCredentialsService deviceCredentialsService,
104 111 DeviceStateService deviceStateService, DbCallbackExecutorService dbCallbackExecutorService,
105   - TbClusterService tbClusterService, DataDecodingEncodingService dataDecodingEncodingService) {
  112 + TbClusterService tbClusterService, DataDecodingEncodingService dataDecodingEncodingService,
  113 + DeviceProvisionService deviceProvisionService) {
106 114 this.deviceProfileService = deviceProfileService;
107 115 this.tenantService = tenantService;
108 116 this.tenantProfileService = tenantProfileService;
... ... @@ -113,6 +121,7 @@ public class DefaultTransportApiService implements TransportApiService {
113 121 this.dbCallbackExecutorService = dbCallbackExecutorService;
114 122 this.tbClusterService = tbClusterService;
115 123 this.dataDecodingEncodingService = dataDecodingEncodingService;
  124 + this.deviceProvisionService = deviceProvisionService;
116 125 }
117 126
118 127 @Override
... ... @@ -139,6 +148,9 @@ public class DefaultTransportApiService implements TransportApiService {
139 148 } else if (transportApiRequestMsg.hasGetDeviceProfileRequestMsg()) {
140 149 return Futures.transform(handle(transportApiRequestMsg.getGetDeviceProfileRequestMsg()),
141 150 value -> new TbProtoQueueMsg<>(tbProtoQueueMsg.getKey(), value, tbProtoQueueMsg.getHeaders()), MoreExecutors.directExecutor());
  151 + } else if (transportApiRequestMsg.hasProvisionDeviceRequestMsg()) {
  152 + return Futures.transform(handle(transportApiRequestMsg.getProvisionDeviceRequestMsg()),
  153 + value -> new TbProtoQueueMsg<>(tbProtoQueueMsg.getKey(), value, tbProtoQueueMsg.getHeaders()), MoreExecutors.directExecutor());
142 154 }
143 155 return Futures.transform(getEmptyTransportApiResponseFuture(),
144 156 value -> new TbProtoQueueMsg<>(tbProtoQueueMsg.getKey(), value, tbProtoQueueMsg.getHeaders()), MoreExecutors.directExecutor());
... ... @@ -263,6 +275,50 @@ public class DefaultTransportApiService implements TransportApiService {
263 275 }, dbCallbackExecutorService);
264 276 }
265 277
  278 + private ListenableFuture<TransportApiResponseMsg> handle(ProvisionDeviceRequestMsg requestMsg) {
  279 + ListenableFuture<ProvisionResponse> provisionResponseFuture = null;
  280 + try {
  281 + provisionResponseFuture = Futures.immediateFuture(deviceProvisionService.provisionDevice(
  282 + new ProvisionRequest(
  283 + requestMsg.getDeviceName(),
  284 + requestMsg.getCredentialsType() != null ? DeviceCredentialsType.valueOf(requestMsg.getCredentialsType().name()) : null,
  285 + new ProvisionDeviceCredentialsData(requestMsg.getCredentialsDataProto().getValidateDeviceTokenRequestMsg().getToken(),
  286 + requestMsg.getCredentialsDataProto().getValidateBasicMqttCredRequestMsg().getClientId(),
  287 + requestMsg.getCredentialsDataProto().getValidateBasicMqttCredRequestMsg().getUserName(),
  288 + requestMsg.getCredentialsDataProto().getValidateBasicMqttCredRequestMsg().getPassword(),
  289 + requestMsg.getCredentialsDataProto().getValidateDeviceX509CertRequestMsg().getHash()),
  290 + new ProvisionDeviceProfileCredentials(
  291 + requestMsg.getProvisionDeviceCredentialsMsg().getProvisionDeviceKey(),
  292 + requestMsg.getProvisionDeviceCredentialsMsg().getProvisionDeviceSecret()))));
  293 + } catch (ProvisionFailedException e) {
  294 + return Futures.immediateFuture(getTransportApiResponseMsg(
  295 + TransportProtos.DeviceCredentialsProto.getDefaultInstance(),
  296 + TransportProtos.ProvisionResponseStatus.valueOf(e.getMessage())));
  297 + }
  298 + return Futures.transform(provisionResponseFuture, provisionResponse -> getTransportApiResponseMsg(
  299 + getDeviceCredentials(provisionResponse.getDeviceCredentials()), TransportProtos.ProvisionResponseStatus.SUCCESS),
  300 + dbCallbackExecutorService);
  301 + }
  302 +
  303 + private TransportApiResponseMsg getTransportApiResponseMsg(TransportProtos.DeviceCredentialsProto deviceCredentials, TransportProtos.ProvisionResponseStatus status) {
  304 + return TransportApiResponseMsg.newBuilder()
  305 + .setProvisionDeviceResponseMsg(TransportProtos.ProvisionDeviceResponseMsg.newBuilder()
  306 + .setDeviceCredentials(deviceCredentials)
  307 + .setProvisionResponseStatus(status)
  308 + .build())
  309 + .build();
  310 + }
  311 +
  312 + private TransportProtos.DeviceCredentialsProto getDeviceCredentials(DeviceCredentials deviceCredentials) {
  313 + return TransportProtos.DeviceCredentialsProto.newBuilder()
  314 + .setDeviceIdMSB(deviceCredentials.getDeviceId().getId().getMostSignificantBits())
  315 + .setDeviceIdLSB(deviceCredentials.getDeviceId().getId().getLeastSignificantBits())
  316 + .setCredentialsType(TransportProtos.CredentialsType.valueOf(deviceCredentials.getCredentialsType().name()))
  317 + .setCredentialsId(deviceCredentials.getCredentialsId())
  318 + .setCredentialsValue(deviceCredentials.getCredentialsValue() != null ? deviceCredentials.getCredentialsValue() : "")
  319 + .build();
  320 + }
  321 +
266 322 private ListenableFuture<TransportApiResponseMsg> handle(GetTenantRoutingInfoRequestMsg requestMsg) {
267 323 TenantId tenantId = new TenantId(new UUID(requestMsg.getTenantIdMSB(), requestMsg.getTenantIdLSB()));
268 324 // TODO: Tenant Profile from cache
... ...
... ... @@ -68,6 +68,7 @@ import org.thingsboard.server.common.data.User;
68 68 import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileConfiguration;
69 69 import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileTransportConfiguration;
70 70 import org.thingsboard.server.common.data.device.profile.DeviceProfileData;
  71 +import org.thingsboard.server.common.data.device.profile.ProvisionDeviceProfileCredentials;
71 72 import org.thingsboard.server.common.data.id.HasId;
72 73 import org.thingsboard.server.common.data.id.RuleChainId;
73 74 import org.thingsboard.server.common.data.id.TenantId;
... ...
... ... @@ -28,6 +28,7 @@ import org.thingsboard.server.common.data.DeviceProfileType;
28 28 import org.thingsboard.server.common.data.DeviceTransportType;
29 29 import org.thingsboard.server.common.data.Tenant;
30 30 import org.thingsboard.server.common.data.User;
  31 +import org.thingsboard.server.common.data.device.profile.ProvisionDeviceProfileCredentials;
31 32 import org.thingsboard.server.common.data.page.PageData;
32 33 import org.thingsboard.server.common.data.page.PageLink;
33 34 import org.thingsboard.server.common.data.security.Authority;
... ... @@ -153,6 +154,17 @@ public abstract class BaseDeviceProfileControllerTest extends AbstractController
153 154 .andExpect(statusReason(containsString("Device profile with such name already exists")));
154 155 }
155 156
  157 + @Test
  158 + public void testSaveDeviceProfileWithSameProvisionDeviceKey() throws Exception {
  159 + DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile");
  160 + deviceProfile.setProvisionDeviceKey("testProvisionDeviceKey");
  161 + doPost("/api/deviceProfile", deviceProfile).andExpect(status().isOk());
  162 + DeviceProfile deviceProfile2 = this.createDeviceProfile("Device Profile 2");
  163 + deviceProfile2.setProvisionDeviceKey("testProvisionDeviceKey");
  164 + doPost("/api/deviceProfile", deviceProfile2).andExpect(status().isBadRequest())
  165 + .andExpect(statusReason(containsString("Device profile with such provision device key already exists")));
  166 + }
  167 +
156 168 @Ignore
157 169 @Test
158 170 public void testChangeDeviceProfileTypeWithExistingDevices() throws Exception {
... ...
... ... @@ -26,13 +26,18 @@ import org.junit.Assert;
26 26 import org.springframework.util.StringUtils;
27 27 import org.thingsboard.server.common.data.Device;
28 28 import org.thingsboard.server.common.data.DeviceProfile;
  29 +import org.thingsboard.server.common.data.DeviceProfileProvisionType;
29 30 import org.thingsboard.server.common.data.DeviceProfileType;
30 31 import org.thingsboard.server.common.data.DeviceTransportType;
31 32 import org.thingsboard.server.common.data.Tenant;
32 33 import org.thingsboard.server.common.data.TransportPayloadType;
33 34 import org.thingsboard.server.common.data.User;
  35 +import org.thingsboard.server.common.data.device.profile.AllowCreateNewDevicesDeviceProfileProvisionConfiguration;
  36 +import org.thingsboard.server.common.data.device.profile.CheckPreProvisionedDevicesDeviceProfileProvisionConfiguration;
34 37 import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileConfiguration;
35 38 import org.thingsboard.server.common.data.device.profile.DeviceProfileData;
  39 +import org.thingsboard.server.common.data.device.profile.DeviceProfileProvisionConfiguration;
  40 +import org.thingsboard.server.common.data.device.profile.DisabledDeviceProfileProvisionConfiguration;
36 41 import org.thingsboard.server.common.data.device.profile.MqttDeviceProfileTransportConfiguration;
37 42 import org.thingsboard.server.common.data.security.Authority;
38 43 import org.thingsboard.server.common.data.security.DeviceCredentials;
... ... @@ -64,7 +69,18 @@ public abstract class AbstractMqttIntegrationTest extends AbstractControllerTest
64 69 protected Device savedGateway;
65 70 protected String gatewayAccessToken;
66 71
67   - protected void processBeforeTest(String deviceName, String gatewayName, TransportPayloadType payloadType, String telemetryTopic, String attributesTopic) throws Exception {
  72 + protected void processBeforeTest (String deviceName, String gatewayName, TransportPayloadType payloadType, String telemetryTopic, String attributesTopic) throws Exception {
  73 + this.processBeforeTest(deviceName, gatewayName, payloadType, telemetryTopic, attributesTopic, DeviceProfileProvisionType.DISABLED, null, null);
  74 + }
  75 +
  76 + protected void processBeforeTest(String deviceName,
  77 + String gatewayName,
  78 + TransportPayloadType payloadType,
  79 + String telemetryTopic,
  80 + String attributesTopic,
  81 + DeviceProfileProvisionType provisionType,
  82 + String provisionKey, String provisionSecret
  83 + ) throws Exception {
68 84 loginSysAdmin();
69 85
70 86 Tenant tenant = new Tenant();
... ... @@ -93,7 +109,7 @@ public abstract class AbstractMqttIntegrationTest extends AbstractControllerTest
93 109 gateway.setAdditionalInfo(additionalInfo);
94 110
95 111 if (payloadType != null) {
96   - DeviceProfile mqttDeviceProfile = createMqttDeviceProfile(payloadType, telemetryTopic, attributesTopic);
  112 + DeviceProfile mqttDeviceProfile = createMqttDeviceProfile(payloadType, telemetryTopic, attributesTopic, provisionType, provisionKey, provisionSecret);
97 113 DeviceProfile savedDeviceProfile = doPost("/api/deviceProfile", mqttDeviceProfile, DeviceProfile.class);
98 114 device.setType(savedDeviceProfile.getName());
99 115 device.setDeviceProfileId(savedDeviceProfile.getId());
... ... @@ -183,11 +199,17 @@ public abstract class AbstractMqttIntegrationTest extends AbstractControllerTest
183 199 return keyValueProtoBuilder.build();
184 200 }
185 201
186   - protected DeviceProfile createMqttDeviceProfile(TransportPayloadType transportPayloadType, String telemetryTopic, String attributesTopic) {
  202 + protected DeviceProfile createMqttDeviceProfile(TransportPayloadType transportPayloadType,
  203 + String telemetryTopic, String attributesTopic,
  204 + DeviceProfileProvisionType provisionType,
  205 + String provisionKey, String provisionSecret
  206 + ) {
187 207 DeviceProfile deviceProfile = new DeviceProfile();
188 208 deviceProfile.setName(transportPayloadType.name());
189 209 deviceProfile.setType(DeviceProfileType.DEFAULT);
190 210 deviceProfile.setTransportType(DeviceTransportType.MQTT);
  211 + deviceProfile.setProvisionType(provisionType);
  212 + deviceProfile.setProvisionDeviceKey(provisionKey);
191 213 deviceProfile.setDescription(transportPayloadType.name() + " Test");
192 214 DeviceProfileData deviceProfileData = new DeviceProfileData();
193 215 DefaultDeviceProfileConfiguration configuration = new DefaultDeviceProfileConfiguration();
... ... @@ -200,6 +222,19 @@ public abstract class AbstractMqttIntegrationTest extends AbstractControllerTest
200 222 transportConfiguration.setDeviceAttributesTopic(attributesTopic);
201 223 }
202 224 deviceProfileData.setTransportConfiguration(transportConfiguration);
  225 + DeviceProfileProvisionConfiguration provisionConfiguration;
  226 + switch (provisionType) {
  227 + case ALLOW_CREATE_NEW_DEVICES:
  228 + provisionConfiguration = new AllowCreateNewDevicesDeviceProfileProvisionConfiguration(provisionSecret);
  229 + break;
  230 + case CHECK_PRE_PROVISIONED_DEVICES:
  231 + provisionConfiguration = new CheckPreProvisionedDevicesDeviceProfileProvisionConfiguration(provisionSecret);
  232 + break;
  233 + case DISABLED:
  234 + default:
  235 + provisionConfiguration = new DisabledDeviceProfileProvisionConfiguration(provisionSecret);
  236 + }
  237 + deviceProfileData.setProvisionConfiguration(provisionConfiguration);
203 238 deviceProfileData.setConfiguration(configuration);
204 239 deviceProfile.setProfileData(deviceProfileData);
205 240 deviceProfile.setDefault(false);
... ...
... ... @@ -31,7 +31,8 @@ import java.util.Arrays;
31 31 "org.thingsboard.server.mqtt.telemetry.attributes.sql.*Test",
32 32 "org.thingsboard.server.mqtt.attributes.updates.sql.*Test",
33 33 "org.thingsboard.server.mqtt.attributes.request.sql.*Test",
34   - "org.thingsboard.server.mqtt.claim.sql.*Test"
  34 + "org.thingsboard.server.mqtt.claim.sql.*Test",
  35 + "org.thingsboard.server.mqtt.provision.sql.*Test"
35 36 })
36 37 public class MqttSqlTestSuite {
37 38
... ...
  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.mqtt.provision;
  17 +
  18 +import com.google.gson.JsonObject;
  19 +import io.netty.handler.codec.mqtt.MqttQoS;
  20 +import lombok.extern.slf4j.Slf4j;
  21 +import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
  22 +import org.eclipse.paho.client.mqttv3.MqttAsyncClient;
  23 +import org.eclipse.paho.client.mqttv3.MqttCallback;
  24 +import org.eclipse.paho.client.mqttv3.MqttMessage;
  25 +import org.junit.After;
  26 +import org.junit.Assert;
  27 +import org.junit.Test;
  28 +import org.springframework.beans.factory.annotation.Autowired;
  29 +import org.thingsboard.server.common.data.Device;
  30 +import org.thingsboard.server.common.data.DeviceProfileProvisionType;
  31 +import org.thingsboard.server.common.data.TransportPayloadType;
  32 +import org.thingsboard.server.common.data.device.credentials.BasicMqttCredentials;
  33 +import org.thingsboard.server.common.data.device.profile.MqttTopics;
  34 +import org.thingsboard.server.common.data.security.DeviceCredentials;
  35 +import org.thingsboard.server.common.msg.EncryptionUtil;
  36 +import org.thingsboard.server.common.transport.util.JsonUtils;
  37 +import org.thingsboard.server.dao.device.DeviceCredentialsService;
  38 +import org.thingsboard.server.dao.device.DeviceService;
  39 +import org.thingsboard.server.dao.device.provision.ProvisionResponseStatus;
  40 +import org.thingsboard.server.dao.util.mapping.JacksonUtil;
  41 +import org.thingsboard.server.mqtt.AbstractMqttIntegrationTest;
  42 +
  43 +import java.util.concurrent.CountDownLatch;
  44 +import java.util.concurrent.TimeUnit;
  45 +
  46 +@Slf4j
  47 +public abstract class AbstractMqttProvisionJsonDeviceTest extends AbstractMqttIntegrationTest {
  48 +
  49 + @Autowired
  50 + DeviceCredentialsService deviceCredentialsService;
  51 +
  52 + @Autowired
  53 + DeviceService deviceService;
  54 +
  55 + @After
  56 + public void afterTest() throws Exception {
  57 + super.processAfterTest();
  58 + }
  59 +
  60 + @Test
  61 + public void testProvisioningDisabledDevice() throws Exception {
  62 + processTestProvisioningDisabledDevice();
  63 + }
  64 +
  65 + @Test
  66 + public void testProvisioningCheckPreProvisionedDevice() throws Exception {
  67 + processTestProvisioningCheckPreProvisionedDevice();
  68 + }
  69 +
  70 + @Test
  71 + public void testProvisioningCreateNewDeviceWithoutCredentials() throws Exception {
  72 + processTestProvisioningCreateNewDeviceWithoutCredentials();
  73 + }
  74 +
  75 + @Test
  76 + public void testProvisioningCreateNewDeviceWithAccessToken() throws Exception {
  77 + processTestProvisioningCreateNewDeviceWithAccessToken();
  78 + }
  79 +
  80 + @Test
  81 + public void testProvisioningCreateNewDeviceWithCert() throws Exception {
  82 + processTestProvisioningCreateNewDeviceWithCert();
  83 + }
  84 +
  85 + @Test
  86 + public void testProvisioningCreateNewDeviceWithMqttBasic() throws Exception {
  87 + processTestProvisioningCreateNewDeviceWithMqttBasic();
  88 + }
  89 +
  90 + @Test
  91 + public void testProvisioningWithBadKeyDevice() throws Exception {
  92 + processTestProvisioningWithBadKeyDevice();
  93 + }
  94 +
  95 +
  96 + protected void processTestProvisioningDisabledDevice() throws Exception {
  97 + super.processBeforeTest("Test Provision device", "Test Provision gateway", TransportPayloadType.JSON, null, null, DeviceProfileProvisionType.DISABLED, null, null);
  98 + byte[] result = createMqttClientAndPublish().getPayloadBytes();
  99 + JsonObject response = JsonUtils.parse(new String(result)).getAsJsonObject();
  100 + Assert.assertEquals("Provision data was not found!", response.get("errorMsg").getAsString());
  101 + Assert.assertEquals(ProvisionResponseStatus.NOT_FOUND.name(), response.get("provisionDeviceStatus").getAsString());
  102 + }
  103 +
  104 +
  105 + protected void processTestProvisioningCreateNewDeviceWithoutCredentials() throws Exception {
  106 + super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.JSON, null, null, DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES, "testProvisionKey", "testProvisionSecret");
  107 + byte[] result = createMqttClientAndPublish().getPayloadBytes();
  108 + JsonObject response = JsonUtils.parse(new String(result)).getAsJsonObject();
  109 +
  110 + Device createdDevice = deviceService.findDeviceByTenantIdAndName(savedTenant.getTenantId(), "Test Provision device");
  111 +
  112 + Assert.assertNotNull(createdDevice);
  113 + Assert.assertEquals(createdDevice.getId().toString(), response.get("deviceId").getAsString());
  114 +
  115 + DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(savedTenant.getTenantId(), createdDevice.getId());
  116 +
  117 + Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.get("credentialsType").getAsString());
  118 + Assert.assertEquals(deviceCredentials.getCredentialsId(), response.get("credentialsId").getAsString());
  119 + Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.get("provisionDeviceStatus").getAsString());
  120 + }
  121 +
  122 +
  123 + protected void processTestProvisioningCreateNewDeviceWithAccessToken() throws Exception {
  124 + super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.JSON, null, null, DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES, "testProvisionKey", "testProvisionSecret");
  125 + String requestCredentials = ",\"credentialsType\": \"ACCESS_TOKEN\",\"token\": \"test_token\"";
  126 + byte[] result = createMqttClientAndPublish(requestCredentials).getPayloadBytes();
  127 + JsonObject response = JsonUtils.parse(new String(result)).getAsJsonObject();
  128 +
  129 + Device createdDevice = deviceService.findDeviceByTenantIdAndName(savedTenant.getTenantId(), "Test Provision device");
  130 +
  131 + Assert.assertNotNull(createdDevice);
  132 + Assert.assertEquals(createdDevice.getId().toString(), response.get("deviceId").getAsString());
  133 +
  134 + DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(savedTenant.getTenantId(), createdDevice.getId());
  135 +
  136 + Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.get("credentialsType").getAsString());
  137 + Assert.assertEquals(deviceCredentials.getCredentialsId(), response.get("credentialsId").getAsString());
  138 + Assert.assertEquals(deviceCredentials.getCredentialsType().name(), "ACCESS_TOKEN");
  139 + Assert.assertEquals(deviceCredentials.getCredentialsId(), "test_token");
  140 + Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.get("provisionDeviceStatus").getAsString());
  141 + }
  142 +
  143 +
  144 + protected void processTestProvisioningCreateNewDeviceWithCert() throws Exception {
  145 + super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.JSON, null, null, DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES, "testProvisionKey", "testProvisionSecret");
  146 + String requestCredentials = ",\"credentialsType\": \"X509_CERTIFICATE\",\"hash\": \"testHash\"";
  147 + byte[] result = createMqttClientAndPublish(requestCredentials).getPayloadBytes();
  148 + JsonObject response = JsonUtils.parse(new String(result)).getAsJsonObject();
  149 +
  150 + Device createdDevice = deviceService.findDeviceByTenantIdAndName(savedTenant.getTenantId(), "Test Provision device");
  151 +
  152 + Assert.assertNotNull(createdDevice);
  153 + Assert.assertEquals(createdDevice.getId().toString(), response.get("deviceId").getAsString());
  154 +
  155 + DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(savedTenant.getTenantId(), createdDevice.getId());
  156 +
  157 + Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.get("credentialsType").getAsString());
  158 + Assert.assertEquals(deviceCredentials.getCredentialsId(), response.get("credentialsId").getAsString());
  159 + Assert.assertEquals(deviceCredentials.getCredentialsType().name(), "X509_CERTIFICATE");
  160 +
  161 + String cert = EncryptionUtil.trimNewLines(deviceCredentials.getCredentialsValue());
  162 + String sha3Hash = EncryptionUtil.getSha3Hash(cert);
  163 +
  164 + Assert.assertEquals(deviceCredentials.getCredentialsId(), sha3Hash);
  165 +
  166 + Assert.assertEquals(deviceCredentials.getCredentialsValue(), "testHash");
  167 + Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.get("provisionDeviceStatus").getAsString());
  168 + }
  169 +
  170 +
  171 + protected void processTestProvisioningCreateNewDeviceWithMqttBasic() throws Exception {
  172 + super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.JSON, null, null, DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES, "testProvisionKey", "testProvisionSecret");
  173 + String requestCredentials = ",\"credentialsType\": \"MQTT_BASIC\",\"clientId\": \"test_clientId\",\"username\": \"test_username\",\"password\": \"test_password\"";
  174 + byte[] result = createMqttClientAndPublish(requestCredentials).getPayloadBytes();
  175 + JsonObject response = JsonUtils.parse(new String(result)).getAsJsonObject();
  176 +
  177 + Device createdDevice = deviceService.findDeviceByTenantIdAndName(savedTenant.getTenantId(), "Test Provision device");
  178 +
  179 + Assert.assertNotNull(createdDevice);
  180 + Assert.assertEquals(createdDevice.getId().toString(), response.get("deviceId").getAsString());
  181 +
  182 + DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(savedTenant.getTenantId(), createdDevice.getId());
  183 +
  184 + Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.get("credentialsType").getAsString());
  185 + Assert.assertEquals(deviceCredentials.getCredentialsId(), response.get("credentialsId").getAsString());
  186 + Assert.assertEquals(deviceCredentials.getCredentialsType().name(), "MQTT_BASIC");
  187 + Assert.assertEquals(deviceCredentials.getCredentialsId(), EncryptionUtil.getSha3Hash("|", "test_clientId", "test_username"));
  188 +
  189 + BasicMqttCredentials mqttCredentials = new BasicMqttCredentials();
  190 + mqttCredentials.setClientId("test_clientId");
  191 + mqttCredentials.setUserName("test_username");
  192 + mqttCredentials.setPassword("test_password");
  193 +
  194 + Assert.assertEquals(deviceCredentials.getCredentialsValue(), JacksonUtil.toString(mqttCredentials));
  195 + Assert.assertEquals(deviceCredentials.getCredentialsId(), response.get("credentialsId").getAsString());
  196 + Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.get("provisionDeviceStatus").getAsString());
  197 + }
  198 +
  199 + protected void processTestProvisioningCheckPreProvisionedDevice() throws Exception {
  200 + super.processBeforeTest("Test Provision device", "Test Provision gateway", TransportPayloadType.JSON, null, null, DeviceProfileProvisionType.CHECK_PRE_PROVISIONED_DEVICES, "testProvisionKey", "testProvisionSecret");
  201 + byte[] result = createMqttClientAndPublish().getPayloadBytes();
  202 + JsonObject response = JsonUtils.parse(new String(result)).getAsJsonObject();
  203 + Assert.assertEquals(savedDevice.getId().toString(), response.get("deviceId").getAsString());
  204 +
  205 + DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(savedTenant.getTenantId(), savedDevice.getId());
  206 +
  207 + Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.get("credentialsType").getAsString());
  208 + Assert.assertEquals(deviceCredentials.getCredentialsId(), response.get("credentialsId").getAsString());
  209 + Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.get("provisionDeviceStatus").getAsString());
  210 + }
  211 +
  212 + protected void processTestProvisioningWithBadKeyDevice() throws Exception {
  213 + super.processBeforeTest("Test Provision device", "Test Provision gateway", TransportPayloadType.JSON, null, null, DeviceProfileProvisionType.CHECK_PRE_PROVISIONED_DEVICES, "testProvisionKeyOrig", "testProvisionSecret");
  214 + byte[] result = createMqttClientAndPublish().getPayloadBytes();
  215 + JsonObject response = JsonUtils.parse(new String(result)).getAsJsonObject();
  216 + Assert.assertEquals("Provision data was not found!", response.get("errorMsg").getAsString());
  217 + Assert.assertEquals(ProvisionResponseStatus.NOT_FOUND.name(), response.get("provisionDeviceStatus").getAsString());
  218 + }
  219 +
  220 + protected TestMqttCallback createMqttClientAndPublish() throws Exception {
  221 + return createMqttClientAndPublish("");
  222 + }
  223 +
  224 + protected TestMqttCallback createMqttClientAndPublish(String deviceCredentials) throws Exception {
  225 + String provisionRequestMsg = createTestProvisionMessage(deviceCredentials);
  226 + MqttAsyncClient client = getMqttAsyncClient("provision");
  227 + TestMqttCallback onProvisionCallback = getTestMqttCallback();
  228 + client.setCallback(onProvisionCallback);
  229 + client.subscribe(MqttTopics.DEVICE_PROVISION_RESPONSE_TOPIC, MqttQoS.AT_MOST_ONCE.value());
  230 + Thread.sleep(2000);
  231 + client.publish(MqttTopics.DEVICE_PROVISION_REQUEST_TOPIC, new MqttMessage(provisionRequestMsg.getBytes()));
  232 + onProvisionCallback.getLatch().await(3, TimeUnit.SECONDS);
  233 + return onProvisionCallback;
  234 + }
  235 +
  236 +
  237 + protected TestMqttCallback getTestMqttCallback() {
  238 + CountDownLatch latch = new CountDownLatch(1);
  239 + return new TestMqttCallback(latch);
  240 + }
  241 +
  242 +
  243 + protected static class TestMqttCallback implements MqttCallback {
  244 +
  245 + private final CountDownLatch latch;
  246 + private Integer qoS;
  247 + private byte[] payloadBytes;
  248 +
  249 + TestMqttCallback(CountDownLatch latch) {
  250 + this.latch = latch;
  251 + }
  252 +
  253 + public int getQoS() {
  254 + return qoS;
  255 + }
  256 +
  257 + public byte[] getPayloadBytes() {
  258 + return payloadBytes;
  259 + }
  260 +
  261 + public CountDownLatch getLatch() {
  262 + return latch;
  263 + }
  264 +
  265 + @Override
  266 + public void connectionLost(Throwable throwable) {
  267 + }
  268 +
  269 + @Override
  270 + public void messageArrived(String requestTopic, MqttMessage mqttMessage) throws Exception {
  271 + qoS = mqttMessage.getQos();
  272 + payloadBytes = mqttMessage.getPayload();
  273 + latch.countDown();
  274 + }
  275 +
  276 + @Override
  277 + public void deliveryComplete(IMqttDeliveryToken iMqttDeliveryToken) {
  278 +
  279 + }
  280 + }
  281 +
  282 + protected String createTestProvisionMessage(String deviceCredentials) {
  283 + return "{\"deviceName\":\"Test Provision device\",\"provisionDeviceKey\":\"testProvisionKey\", \"provisionDeviceSecret\":\"testProvisionSecret\"" + deviceCredentials + "}";
  284 + }
  285 +}
... ...
  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.mqtt.provision;
  17 +
  18 +import io.netty.handler.codec.mqtt.MqttQoS;
  19 +import lombok.extern.slf4j.Slf4j;
  20 +import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
  21 +import org.eclipse.paho.client.mqttv3.MqttAsyncClient;
  22 +import org.eclipse.paho.client.mqttv3.MqttCallback;
  23 +import org.eclipse.paho.client.mqttv3.MqttMessage;
  24 +import org.junit.After;
  25 +import org.junit.Assert;
  26 +import org.junit.Test;
  27 +import org.springframework.beans.factory.annotation.Autowired;
  28 +import org.thingsboard.server.common.data.Device;
  29 +import org.thingsboard.server.common.data.DeviceProfileProvisionType;
  30 +import org.thingsboard.server.common.data.TransportPayloadType;
  31 +import org.thingsboard.server.common.data.device.credentials.BasicMqttCredentials;
  32 +import org.thingsboard.server.common.data.device.profile.MqttTopics;
  33 +import org.thingsboard.server.common.data.security.DeviceCredentials;
  34 +import org.thingsboard.server.common.data.security.DeviceCredentialsType;
  35 +import org.thingsboard.server.common.msg.EncryptionUtil;
  36 +import org.thingsboard.server.dao.device.DeviceCredentialsService;
  37 +import org.thingsboard.server.dao.device.DeviceService;
  38 +import org.thingsboard.server.dao.device.provision.ProvisionResponseStatus;
  39 +import org.thingsboard.server.dao.util.mapping.JacksonUtil;
  40 +import org.thingsboard.server.gen.transport.TransportProtos.CredentialsDataProto;
  41 +import org.thingsboard.server.gen.transport.TransportProtos.CredentialsType;
  42 +import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceCredentialsMsg;
  43 +import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceRequestMsg;
  44 +import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceResponseMsg;
  45 +import org.thingsboard.server.gen.transport.TransportProtos.ValidateBasicMqttCredRequestMsg;
  46 +import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceTokenRequestMsg;
  47 +import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceX509CertRequestMsg;
  48 +import org.thingsboard.server.mqtt.AbstractMqttIntegrationTest;
  49 +
  50 +import java.util.UUID;
  51 +import java.util.concurrent.CountDownLatch;
  52 +import java.util.concurrent.TimeUnit;
  53 +
  54 +@Slf4j
  55 +public abstract class AbstractMqttProvisionProtoDeviceTest extends AbstractMqttIntegrationTest {
  56 +
  57 + @Autowired
  58 + DeviceCredentialsService deviceCredentialsService;
  59 +
  60 + @Autowired
  61 + DeviceService deviceService;
  62 +
  63 + @After
  64 + public void afterTest() throws Exception {
  65 + super.processAfterTest();
  66 + }
  67 +
  68 + @Test
  69 + public void testProvisioningDisabledDevice() throws Exception {
  70 + processTestProvisioningDisabledDevice();
  71 + }
  72 +
  73 + @Test
  74 + public void testProvisioningCheckPreProvisionedDevice() throws Exception {
  75 + processTestProvisioningCheckPreProvisionedDevice();
  76 + }
  77 +
  78 + @Test
  79 + public void testProvisioningCreateNewDeviceWithoutCredentials() throws Exception {
  80 + processTestProvisioningCreateNewDeviceWithoutCredentials();
  81 + }
  82 +
  83 + @Test
  84 + public void testProvisioningCreateNewDeviceWithAccessToken() throws Exception {
  85 + processTestProvisioningCreateNewDeviceWithAccessToken();
  86 + }
  87 +
  88 + @Test
  89 + public void testProvisioningCreateNewDeviceWithCert() throws Exception {
  90 + processTestProvisioningCreateNewDeviceWithCert();
  91 + }
  92 +
  93 + @Test
  94 + public void testProvisioningCreateNewDeviceWithMqttBasic() throws Exception {
  95 + processTestProvisioningCreateNewDeviceWithMqttBasic();
  96 + }
  97 +
  98 + @Test
  99 + public void testProvisioningWithBadKeyDevice() throws Exception {
  100 + processTestProvisioningWithBadKeyDevice();
  101 + }
  102 +
  103 +
  104 + protected void processTestProvisioningDisabledDevice() throws Exception {
  105 + super.processBeforeTest("Test Provision device", "Test Provision gateway", TransportPayloadType.PROTOBUF, null, null, DeviceProfileProvisionType.DISABLED, null, null);
  106 + ProvisionDeviceResponseMsg result = ProvisionDeviceResponseMsg.parseFrom(createMqttClientAndPublish().getPayloadBytes());
  107 + Assert.assertNotNull(result);
  108 + Assert.assertEquals(ProvisionResponseStatus.NOT_FOUND.name(), result.getProvisionResponseStatus().toString());
  109 + }
  110 +
  111 + protected void processTestProvisioningCreateNewDeviceWithoutCredentials() throws Exception {
  112 + super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.PROTOBUF, null, null, DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES, "testProvisionKey", "testProvisionSecret");
  113 + ProvisionDeviceResponseMsg response = ProvisionDeviceResponseMsg.parseFrom(createMqttClientAndPublish().getPayloadBytes());
  114 +
  115 + Device createdDevice = deviceService.findDeviceByTenantIdAndName(savedTenant.getTenantId(), "Test Provision device");
  116 +
  117 + Assert.assertNotNull(createdDevice);
  118 + Assert.assertEquals(createdDevice.getId().getId(), new UUID(response.getDeviceCredentials().getDeviceIdMSB(), response.getDeviceCredentials().getDeviceIdLSB()));
  119 +
  120 + DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(savedTenant.getTenantId(), createdDevice.getId());
  121 +
  122 + Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.getDeviceCredentials().getCredentialsType().toString());
  123 + Assert.assertEquals(deviceCredentials.getCredentialsId(), response.getDeviceCredentials().getCredentialsId());
  124 + Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.getProvisionResponseStatus().toString());
  125 + }
  126 +
  127 + protected void processTestProvisioningCreateNewDeviceWithAccessToken() throws Exception {
  128 + super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.PROTOBUF, null, null, DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES, "testProvisionKey", "testProvisionSecret");
  129 + CredentialsDataProto requestCredentials = CredentialsDataProto.newBuilder().setValidateDeviceTokenRequestMsg(ValidateDeviceTokenRequestMsg.newBuilder().setToken("test_token").build()).build();
  130 +
  131 + ProvisionDeviceResponseMsg response = ProvisionDeviceResponseMsg.parseFrom(createMqttClientAndPublish(createTestsProvisionMessage(CredentialsType.ACCESS_TOKEN, requestCredentials)).getPayloadBytes());
  132 +
  133 + Device createdDevice = deviceService.findDeviceByTenantIdAndName(savedTenant.getTenantId(), "Test Provision device");
  134 +
  135 + Assert.assertNotNull(createdDevice);
  136 + Assert.assertEquals(createdDevice.getId().getId(), new UUID(response.getDeviceCredentials().getDeviceIdMSB(), response.getDeviceCredentials().getDeviceIdLSB()));
  137 +
  138 + DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(savedTenant.getTenantId(), createdDevice.getId());
  139 +
  140 + Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.getDeviceCredentials().getCredentialsType().toString());
  141 + Assert.assertEquals(deviceCredentials.getCredentialsId(), response.getDeviceCredentials().getCredentialsId());
  142 + Assert.assertEquals(deviceCredentials.getCredentialsType(), DeviceCredentialsType.ACCESS_TOKEN);
  143 + Assert.assertEquals(deviceCredentials.getCredentialsId(), "test_token");
  144 + Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.getProvisionResponseStatus().toString());
  145 + }
  146 +
  147 + protected void processTestProvisioningCreateNewDeviceWithCert() throws Exception {
  148 + super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.PROTOBUF, null, null, DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES, "testProvisionKey", "testProvisionSecret");
  149 + CredentialsDataProto requestCredentials = CredentialsDataProto.newBuilder().setValidateDeviceX509CertRequestMsg(ValidateDeviceX509CertRequestMsg.newBuilder().setHash("testHash").build()).build();
  150 +
  151 + ProvisionDeviceResponseMsg response = ProvisionDeviceResponseMsg.parseFrom(createMqttClientAndPublish(createTestsProvisionMessage(CredentialsType.X509_CERTIFICATE, requestCredentials)).getPayloadBytes());
  152 +
  153 + Device createdDevice = deviceService.findDeviceByTenantIdAndName(savedTenant.getTenantId(), "Test Provision device");
  154 +
  155 + Assert.assertNotNull(createdDevice);
  156 + Assert.assertEquals(createdDevice.getId().getId(), new UUID(response.getDeviceCredentials().getDeviceIdMSB(), response.getDeviceCredentials().getDeviceIdLSB()));
  157 +
  158 + DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(savedTenant.getTenantId(), createdDevice.getId());
  159 +
  160 + Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.getDeviceCredentials().getCredentialsType().toString());
  161 + Assert.assertEquals(deviceCredentials.getCredentialsId(), response.getDeviceCredentials().getCredentialsId());
  162 + Assert.assertEquals(deviceCredentials.getCredentialsType(), DeviceCredentialsType.X509_CERTIFICATE);
  163 +
  164 + String cert = EncryptionUtil.trimNewLines(deviceCredentials.getCredentialsValue());
  165 + String sha3Hash = EncryptionUtil.getSha3Hash(cert);
  166 +
  167 + Assert.assertEquals(deviceCredentials.getCredentialsId(), sha3Hash);
  168 +
  169 + Assert.assertEquals(deviceCredentials.getCredentialsValue(), "testHash");
  170 + Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.getProvisionResponseStatus().toString());
  171 + }
  172 +
  173 + protected void processTestProvisioningCreateNewDeviceWithMqttBasic() throws Exception {
  174 + super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.PROTOBUF, null, null, DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES, "testProvisionKey", "testProvisionSecret");
  175 + CredentialsDataProto requestCredentials = CredentialsDataProto.newBuilder().setValidateBasicMqttCredRequestMsg(
  176 + ValidateBasicMqttCredRequestMsg.newBuilder()
  177 + .setClientId("test_clientId")
  178 + .setUserName("test_username")
  179 + .setPassword("test_password")
  180 + .build()
  181 + ).build();
  182 +
  183 + ProvisionDeviceResponseMsg response = ProvisionDeviceResponseMsg.parseFrom(createMqttClientAndPublish(createTestsProvisionMessage(CredentialsType.MQTT_BASIC, requestCredentials)).getPayloadBytes());
  184 +
  185 + Device createdDevice = deviceService.findDeviceByTenantIdAndName(savedTenant.getTenantId(), "Test Provision device");
  186 +
  187 + Assert.assertNotNull(createdDevice);
  188 + Assert.assertEquals(createdDevice.getId().getId(), new UUID(response.getDeviceCredentials().getDeviceIdMSB(), response.getDeviceCredentials().getDeviceIdLSB()));
  189 +
  190 + DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(savedTenant.getTenantId(), createdDevice.getId());
  191 +
  192 + Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.getDeviceCredentials().getCredentialsType().toString());
  193 + Assert.assertEquals(deviceCredentials.getCredentialsId(), response.getDeviceCredentials().getCredentialsId());
  194 + Assert.assertEquals(deviceCredentials.getCredentialsType(), DeviceCredentialsType.MQTT_BASIC);
  195 + Assert.assertEquals(deviceCredentials.getCredentialsId(), EncryptionUtil.getSha3Hash("|", "test_clientId", "test_username"));
  196 +
  197 + BasicMqttCredentials mqttCredentials = new BasicMqttCredentials();
  198 + mqttCredentials.setClientId("test_clientId");
  199 + mqttCredentials.setUserName("test_username");
  200 + mqttCredentials.setPassword("test_password");
  201 +
  202 + Assert.assertEquals(deviceCredentials.getCredentialsValue(), JacksonUtil.toString(mqttCredentials));
  203 + Assert.assertEquals(deviceCredentials.getCredentialsId(), response.getDeviceCredentials().getCredentialsId());
  204 + Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.getProvisionResponseStatus().toString());
  205 + }
  206 +
  207 + protected void processTestProvisioningCheckPreProvisionedDevice() throws Exception {
  208 + super.processBeforeTest("Test Provision device", "Test Provision gateway", TransportPayloadType.PROTOBUF, null, null, DeviceProfileProvisionType.CHECK_PRE_PROVISIONED_DEVICES, "testProvisionKey", "testProvisionSecret");
  209 + ProvisionDeviceResponseMsg response = ProvisionDeviceResponseMsg.parseFrom(createMqttClientAndPublish().getPayloadBytes());
  210 + Assert.assertEquals(savedDevice.getId().getId(), new UUID(response.getDeviceCredentials().getDeviceIdMSB(), response.getDeviceCredentials().getDeviceIdLSB()));
  211 +
  212 + DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(savedTenant.getTenantId(), savedDevice.getId());
  213 +
  214 + Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.getDeviceCredentials().getCredentialsType().toString());
  215 + Assert.assertEquals(deviceCredentials.getCredentialsId(), response.getDeviceCredentials().getCredentialsId());
  216 + Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.getProvisionResponseStatus().toString());
  217 + }
  218 +
  219 + protected void processTestProvisioningWithBadKeyDevice() throws Exception {
  220 + super.processBeforeTest("Test Provision device", "Test Provision gateway", TransportPayloadType.PROTOBUF, null, null, DeviceProfileProvisionType.CHECK_PRE_PROVISIONED_DEVICES, "testProvisionKeyOrig", "testProvisionSecret");
  221 + ProvisionDeviceResponseMsg response = ProvisionDeviceResponseMsg.parseFrom(createMqttClientAndPublish().getPayloadBytes());
  222 + Assert.assertEquals(ProvisionResponseStatus.NOT_FOUND.name(), response.getProvisionResponseStatus().toString());
  223 + }
  224 +
  225 + protected TestMqttCallback createMqttClientAndPublish() throws Exception {
  226 + byte[] provisionRequestMsg = createTestProvisionMessage();
  227 + return createMqttClientAndPublish(provisionRequestMsg);
  228 + }
  229 +
  230 + protected TestMqttCallback createMqttClientAndPublish(byte[] provisionRequestMsg) throws Exception {
  231 + MqttAsyncClient client = getMqttAsyncClient("provision");
  232 + TestMqttCallback onProvisionCallback = getTestMqttCallback();
  233 + client.setCallback(onProvisionCallback);
  234 + client.subscribe(MqttTopics.DEVICE_PROVISION_RESPONSE_TOPIC, MqttQoS.AT_MOST_ONCE.value());
  235 + Thread.sleep(2000);
  236 + client.publish(MqttTopics.DEVICE_PROVISION_REQUEST_TOPIC, new MqttMessage(provisionRequestMsg));
  237 + onProvisionCallback.getLatch().await(3, TimeUnit.SECONDS);
  238 + return onProvisionCallback;
  239 + }
  240 +
  241 +
  242 + protected TestMqttCallback getTestMqttCallback() {
  243 + CountDownLatch latch = new CountDownLatch(1);
  244 + return new TestMqttCallback(latch);
  245 + }
  246 +
  247 +
  248 + protected static class TestMqttCallback implements MqttCallback {
  249 +
  250 + private final CountDownLatch latch;
  251 + private Integer qoS;
  252 + private byte[] payloadBytes;
  253 +
  254 + TestMqttCallback(CountDownLatch latch) {
  255 + this.latch = latch;
  256 + }
  257 +
  258 + public int getQoS() {
  259 + return qoS;
  260 + }
  261 +
  262 + public byte[] getPayloadBytes() {
  263 + return payloadBytes;
  264 + }
  265 +
  266 + public CountDownLatch getLatch() {
  267 + return latch;
  268 + }
  269 +
  270 + @Override
  271 + public void connectionLost(Throwable throwable) {
  272 + }
  273 +
  274 + @Override
  275 + public void messageArrived(String requestTopic, MqttMessage mqttMessage) throws Exception {
  276 + qoS = mqttMessage.getQos();
  277 + payloadBytes = mqttMessage.getPayload();
  278 + latch.countDown();
  279 + }
  280 +
  281 + @Override
  282 + public void deliveryComplete(IMqttDeliveryToken iMqttDeliveryToken) {
  283 +
  284 + }
  285 + }
  286 +
  287 + protected byte[] createTestsProvisionMessage(CredentialsType credentialsType, CredentialsDataProto credentialsData) throws Exception {
  288 + return ProvisionDeviceRequestMsg.newBuilder()
  289 + .setDeviceName("Test Provision device")
  290 + .setCredentialsType(credentialsType != null ? credentialsType : CredentialsType.ACCESS_TOKEN)
  291 + .setCredentialsDataProto(credentialsData != null ? credentialsData: CredentialsDataProto.newBuilder().build())
  292 + .setProvisionDeviceCredentialsMsg(
  293 + ProvisionDeviceCredentialsMsg.newBuilder()
  294 + .setProvisionDeviceKey("testProvisionKey")
  295 + .setProvisionDeviceSecret("testProvisionSecret")
  296 + ).build()
  297 + .toByteArray();
  298 + }
  299 +
  300 +
  301 + protected byte[] createTestProvisionMessage() throws Exception {
  302 + return createTestsProvisionMessage(null, null);
  303 + }
  304 +
  305 +}
... ...
  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.mqtt.provision.sql;
  17 +
  18 +import org.thingsboard.server.dao.service.DaoSqlTest;
  19 +import org.thingsboard.server.mqtt.provision.AbstractMqttProvisionJsonDeviceTest;
  20 +
  21 +@DaoSqlTest
  22 +public class MqttProvisionDeviceJsonSqlTest extends AbstractMqttProvisionJsonDeviceTest {
  23 +}
... ...
  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.mqtt.provision.sql;
  17 +
  18 +import org.thingsboard.server.dao.service.DaoSqlTest;
  19 +import org.thingsboard.server.mqtt.provision.AbstractMqttProvisionProtoDeviceTest;
  20 +
  21 +@DaoSqlTest
  22 +public class MqttProvisionDeviceProtoSqlTest extends AbstractMqttProvisionProtoDeviceTest {
  23 +}
... ...
  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.dao.device;
  17 +
  18 +import com.google.common.util.concurrent.ListenableFuture;
  19 +import org.thingsboard.server.common.data.Device;
  20 +import org.thingsboard.server.common.data.DeviceProfile;
  21 +import org.thingsboard.server.dao.device.provision.ProvisionFailedException;
  22 +import org.thingsboard.server.dao.device.provision.ProvisionRequest;
  23 +import org.thingsboard.server.dao.device.provision.ProvisionResponse;
  24 +
  25 +public interface DeviceProvisionService {
  26 +
  27 + ProvisionResponse provisionDevice(ProvisionRequest provisionRequest) throws ProvisionFailedException;
  28 +}
... ...
... ... @@ -18,6 +18,7 @@ package org.thingsboard.server.dao.device;
18 18 import com.google.common.util.concurrent.ListenableFuture;
19 19 import org.thingsboard.server.common.data.Device;
20 20 import org.thingsboard.server.common.data.DeviceInfo;
  21 +import org.thingsboard.server.common.data.DeviceProfile;
21 22 import org.thingsboard.server.common.data.EntitySubtype;
22 23 import org.thingsboard.server.common.data.device.DeviceSearchQuery;
23 24 import org.thingsboard.server.common.data.id.CustomerId;
... ... @@ -26,6 +27,7 @@ import org.thingsboard.server.common.data.id.DeviceProfileId;
26 27 import org.thingsboard.server.common.data.id.TenantId;
27 28 import org.thingsboard.server.common.data.page.PageData;
28 29 import org.thingsboard.server.common.data.page.PageLink;
  30 +import org.thingsboard.server.dao.device.provision.ProvisionRequest;
29 31
30 32 import java.util.List;
31 33
... ... @@ -83,4 +85,6 @@ public interface DeviceService {
83 85
84 86 Device assignDeviceToTenant(TenantId tenantId, Device device);
85 87
  88 + Device saveDevice(ProvisionRequest provisionRequest, DeviceProfile profile);
  89 +
86 90 }
... ...
  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.dao.device.provision;
  17 +
  18 +public class ProvisionFailedException extends RuntimeException {
  19 + public ProvisionFailedException(String errorMsg) {
  20 + super(errorMsg);
  21 + }
  22 +}
... ...
  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.dao.device.provision;
  17 +
  18 +import lombok.AllArgsConstructor;
  19 +import lombok.Data;
  20 +import org.thingsboard.server.common.data.device.credentials.ProvisionDeviceCredentialsData;
  21 +import org.thingsboard.server.common.data.device.profile.ProvisionDeviceProfileCredentials;
  22 +import org.thingsboard.server.common.data.security.DeviceCredentialsType;
  23 +
  24 +@Data
  25 +@AllArgsConstructor
  26 +public class ProvisionRequest {
  27 + private String deviceName;
  28 + private DeviceCredentialsType credentialsType;
  29 + private ProvisionDeviceCredentialsData credentialsData;
  30 + private ProvisionDeviceProfileCredentials credentials;
  31 +}
... ...
  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.dao.device.provision;
  17 +
  18 +import lombok.Data;
  19 +import org.thingsboard.server.common.data.security.DeviceCredentials;
  20 +
  21 +@Data
  22 +public class ProvisionResponse {
  23 + private final DeviceCredentials deviceCredentials;
  24 + private final ProvisionResponseStatus responseStatus;
  25 +}
... ...
  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.dao.device.provision;
  17 +
  18 +public enum ProvisionResponseStatus {
  19 + UNKNOWN,
  20 + SUCCESS,
  21 + NOT_FOUND,
  22 + FAILURE
  23 +}
... ...
... ... @@ -63,6 +63,8 @@ public class DataConstants {
63 63 public static final String ALARM_CLEAR = "ALARM_CLEAR";
64 64 public static final String ENTITY_ASSIGNED_FROM_TENANT = "ENTITY_ASSIGNED_FROM_TENANT";
65 65 public static final String ENTITY_ASSIGNED_TO_TENANT = "ENTITY_ASSIGNED_TO_TENANT";
  66 + public static final String PROVISION_SUCCESS = "PROVISION_SUCCESS";
  67 + public static final String PROVISION_FAILURE = "PROVISION_FAILURE";
66 68
67 69 public static final String RPC_CALL_FROM_SERVER_TO_DEVICE = "RPC_CALL_FROM_SERVER_TO_DEVICE";
68 70
... ... @@ -70,4 +72,18 @@ public class DataConstants {
70 72 public static final String SECRET_KEY_FIELD_NAME = "secretKey";
71 73 public static final String DURATION_MS_FIELD_NAME = "durationMs";
72 74
  75 + public static final String PROVISION = "provision";
  76 + public static final String PROVISION_KEY = "provisionDeviceKey";
  77 + public static final String PROVISION_SECRET = "provisionDeviceSecret";
  78 +
  79 + public static final String DEVICE_NAME = "deviceName";
  80 + public static final String DEVICE_TYPE = "deviceType";
  81 + public static final String CERT_PUB_KEY = "x509CertPubKey";
  82 + public static final String CREDENTIALS_TYPE = "credentialsType";
  83 + public static final String TOKEN = "token";
  84 + public static final String HASH = "hash";
  85 + public static final String CLIENT_ID = "clientId";
  86 + public static final String USERNAME = "username";
  87 + public static final String PASSWORD = "password";
  88 +
73 89 }
... ...
... ... @@ -41,10 +41,12 @@ public class DeviceProfile extends SearchTextBased<DeviceProfileId> implements H
41 41 private boolean isDefault;
42 42 private DeviceProfileType type;
43 43 private DeviceTransportType transportType;
  44 + private DeviceProfileProvisionType provisionType;
44 45 private RuleChainId defaultRuleChainId;
45 46 private transient DeviceProfileData profileData;
46 47 @JsonIgnore
47 48 private byte[] profileDataBytes;
  49 + private String provisionDeviceKey;
48 50
49 51 public DeviceProfile() {
50 52 super();
... ... @@ -62,6 +64,7 @@ public class DeviceProfile extends SearchTextBased<DeviceProfileId> implements H
62 64 this.isDefault = deviceProfile.isDefault();
63 65 this.defaultRuleChainId = deviceProfile.getDefaultRuleChainId();
64 66 this.setProfileData(deviceProfile.getProfileData());
  67 + this.provisionDeviceKey = deviceProfile.getProvisionDeviceKey();
65 68 }
66 69
67 70 @Override
... ...
  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;
  17 +
  18 +public enum DeviceProfileProvisionType {
  19 + DISABLED,
  20 + ALLOW_CREATE_NEW_DEVICES,
  21 + CHECK_PRE_PROVISIONED_DEVICES
  22 +}
... ...
... ... @@ -42,7 +42,9 @@ public enum ActionType {
42 42 LOGOUT(false),
43 43 LOCKOUT(false),
44 44 ASSIGNED_FROM_TENANT(false),
45   - ASSIGNED_TO_TENANT(false);
  45 + ASSIGNED_TO_TENANT(false),
  46 + PROVISION_SUCCESS(false),
  47 + PROVISION_FAILURE(false);
46 48
47 49 private final boolean isRead;
48 50
... ...
  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 ProvisionDeviceCredentialsData {
  22 + private final String token;
  23 + private final String clientId;
  24 + private final String username;
  25 + private final String password;
  26 + private final String x509CertHash;
  27 +}
... ...
  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.profile;
  17 +
  18 +import lombok.Data;
  19 +import org.thingsboard.server.common.data.DeviceProfileProvisionType;
  20 +
  21 +@Data
  22 +public class AllowCreateNewDevicesDeviceProfileProvisionConfiguration implements DeviceProfileProvisionConfiguration {
  23 +
  24 + private final String provisionDeviceSecret;
  25 +
  26 + @Override
  27 + public DeviceProfileProvisionType getType() {
  28 + return DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES;
  29 + }
  30 +
  31 +}
... ...
  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.profile;
  17 +
  18 +import lombok.Data;
  19 +import org.thingsboard.server.common.data.DeviceProfileProvisionType;
  20 +
  21 +@Data
  22 +public class CheckPreProvisionedDevicesDeviceProfileProvisionConfiguration implements DeviceProfileProvisionConfiguration {
  23 +
  24 + private final String provisionDeviceSecret;
  25 +
  26 + @Override
  27 + public DeviceProfileProvisionType getType() {
  28 + return DeviceProfileProvisionType.CHECK_PRE_PROVISIONED_DEVICES;
  29 + }
  30 +
  31 +}
... ...
... ... @@ -24,6 +24,7 @@ public class DeviceProfileData {
24 24
25 25 private DeviceProfileConfiguration configuration;
26 26 private DeviceProfileTransportConfiguration transportConfiguration;
  27 + private DeviceProfileProvisionConfiguration provisionConfiguration;
27 28 private List<DeviceProfileAlarm> alarms;
28 29
29 30 }
... ...
  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.profile;
  17 +
  18 +import com.fasterxml.jackson.annotation.JsonIgnore;
  19 +import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
  20 +import com.fasterxml.jackson.annotation.JsonSubTypes;
  21 +import com.fasterxml.jackson.annotation.JsonTypeInfo;
  22 +import org.thingsboard.server.common.data.DeviceProfileProvisionType;
  23 +
  24 +
  25 +@JsonIgnoreProperties(ignoreUnknown = true)
  26 +@JsonTypeInfo(
  27 + use = JsonTypeInfo.Id.NAME,
  28 + include = JsonTypeInfo.As.PROPERTY,
  29 + property = "type")
  30 +@JsonSubTypes({
  31 + @JsonSubTypes.Type(value = DisabledDeviceProfileProvisionConfiguration.class, name = "DISABLED"),
  32 + @JsonSubTypes.Type(value = AllowCreateNewDevicesDeviceProfileProvisionConfiguration.class, name = "ALLOW_CREATE_NEW_DEVICES"),
  33 + @JsonSubTypes.Type(value = CheckPreProvisionedDevicesDeviceProfileProvisionConfiguration.class, name = "CHECK_PRE_PROVISIONED_DEVICES")})
  34 +public interface DeviceProfileProvisionConfiguration {
  35 +
  36 + String getProvisionDeviceSecret();
  37 +
  38 + @JsonIgnore
  39 + DeviceProfileProvisionType getType();
  40 +
  41 +}
... ...
  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.profile;
  17 +
  18 +import lombok.Data;
  19 +import org.thingsboard.server.common.data.DeviceProfileProvisionType;
  20 +
  21 +@Data
  22 +public class DisabledDeviceProfileProvisionConfiguration implements DeviceProfileProvisionConfiguration {
  23 +
  24 + private final String provisionDeviceSecret;
  25 +
  26 + @Override
  27 + public DeviceProfileProvisionType getType() {
  28 + return DeviceProfileProvisionType.DISABLED;
  29 + }
  30 +
  31 +}
... ...
... ... @@ -20,6 +20,8 @@ package org.thingsboard.server.common.data.device.profile;
20 20 */
21 21 public class MqttTopics {
22 22
  23 + private static final String REQUEST = "/request";
  24 + private static final String RESPONSE = "/response";
23 25 private static final String RPC = "/rpc";
24 26 private static final String CONNECT = "/connect";
25 27 private static final String DISCONNECT = "/disconnect";
... ... @@ -27,11 +29,13 @@ public class MqttTopics {
27 29 private static final String ATTRIBUTES = "/attributes";
28 30 private static final String CLAIM = "/claim";
29 31 private static final String SUB_TOPIC = "+";
30   - private static final String ATTRIBUTES_RESPONSE = "/attributes/response";
31   - private static final String ATTRIBUTES_REQUEST = "/attributes/request";
  32 + private static final String PROVISION = "/provision";
32 33
33   - private static final String DEVICE_RPC_RESPONSE = "/rpc/response/";
34   - private static final String DEVICE_RPC_REQUEST = "/rpc/request/";
  34 + private static final String ATTRIBUTES_RESPONSE = ATTRIBUTES + RESPONSE;
  35 + private static final String ATTRIBUTES_REQUEST = ATTRIBUTES + REQUEST;
  36 +
  37 + private static final String DEVICE_RPC_RESPONSE = RPC + RESPONSE + "/";
  38 + private static final String DEVICE_RPC_REQUEST = RPC + REQUEST + "/";
35 39
36 40 private static final String DEVICE_ATTRIBUTES_RESPONSE = ATTRIBUTES_RESPONSE + "/";
37 41 private static final String DEVICE_ATTRIBUTES_REQUEST = ATTRIBUTES_REQUEST + "/";
... ... @@ -50,6 +54,8 @@ public class MqttTopics {
50 54 public static final String DEVICE_TELEMETRY_TOPIC = BASE_DEVICE_API_TOPIC + TELEMETRY;
51 55 public static final String DEVICE_CLAIM_TOPIC = BASE_DEVICE_API_TOPIC + CLAIM;
52 56 public static final String DEVICE_ATTRIBUTES_TOPIC = BASE_DEVICE_API_TOPIC + ATTRIBUTES;
  57 + public static final String DEVICE_PROVISION_REQUEST_TOPIC = PROVISION + REQUEST;
  58 + public static final String DEVICE_PROVISION_RESPONSE_TOPIC = PROVISION + RESPONSE;
53 59
54 60 // V1_JSON gateway topics
55 61
... ...
  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.profile;
  17 +
  18 +import lombok.Data;
  19 +
  20 +@Data
  21 +public class ProvisionDeviceProfileCredentials {
  22 + private final String provisionDeviceKey;
  23 + private final String provisionDeviceSecret;
  24 +}
... ...
... ... @@ -16,5 +16,5 @@
16 16 package org.thingsboard.server.common.msg.session;
17 17
18 18 public enum FeatureType {
19   - ATTRIBUTES, TELEMETRY, RPC, CLAIM
  19 + ATTRIBUTES, TELEMETRY, RPC, CLAIM, PROVISION
20 20 }
... ...
... ... @@ -73,6 +73,12 @@ enum KeyValueType {
73 73 JSON_V = 4;
74 74 }
75 75
  76 +enum CredentialsType {
  77 + ACCESS_TOKEN = 0;
  78 + X509_CERTIFICATE = 1;
  79 + MQTT_BASIC = 2;
  80 +}
  81 +
76 82 message KeyValueProto {
77 83 string key = 1;
78 84 KeyValueType type = 2;
... ... @@ -241,6 +247,43 @@ message ClaimDeviceMsg {
241 247 int64 durationMs = 4;
242 248 }
243 249
  250 +message DeviceCredentialsProto {
  251 + int64 deviceIdMSB = 1;
  252 + int64 deviceIdLSB = 2;
  253 + CredentialsType credentialsType = 3;
  254 + string credentialsId = 4;
  255 + string credentialsValue = 5;
  256 +}
  257 +
  258 +message CredentialsDataProto {
  259 + ValidateDeviceTokenRequestMsg validateDeviceTokenRequestMsg = 1;
  260 + ValidateDeviceX509CertRequestMsg validateDeviceX509CertRequestMsg = 2;
  261 + ValidateBasicMqttCredRequestMsg validateBasicMqttCredRequestMsg = 3;
  262 +}
  263 +
  264 +message ProvisionDeviceRequestMsg {
  265 + string deviceName = 1;
  266 + CredentialsType credentialsType = 2;
  267 + ProvisionDeviceCredentialsMsg provisionDeviceCredentialsMsg = 3;
  268 + CredentialsDataProto credentialsDataProto = 4;
  269 +}
  270 +
  271 +message ProvisionDeviceCredentialsMsg {
  272 + string provisionDeviceKey = 1;
  273 + string provisionDeviceSecret = 2;
  274 +}
  275 +
  276 +message ProvisionDeviceResponseMsg {
  277 + DeviceCredentialsProto deviceCredentials = 1;
  278 + ProvisionResponseStatus provisionResponseStatus = 2;
  279 +}
  280 +
  281 +enum ProvisionResponseStatus {
  282 + UNKNOWN = 0;
  283 + SUCCESS = 1;
  284 + NOT_FOUND = 2;
  285 + FAILURE = 3;
  286 +}
244 287 //Used to report session state to tb-Service and persist this state in the cache on the tb-Service level.
245 288 message SubscriptionInfoProto {
246 289 int64 lastActivityTime = 1;
... ... @@ -266,6 +309,7 @@ message TransportToDeviceActorMsg {
266 309 ToDeviceRpcResponseMsg toDeviceRPCCallResponse = 6;
267 310 SubscriptionInfoProto subscriptionInfo = 7;
268 311 ClaimDeviceMsg claimDevice = 8;
  312 + ProvisionDeviceRequestMsg provisionDevice = 9;
269 313 }
270 314
271 315 message TransportToRuleEngineMsg {
... ... @@ -441,6 +485,7 @@ message TransportApiRequestMsg {
441 485 GetTenantRoutingInfoRequestMsg getTenantRoutingInfoRequestMsg = 4;
442 486 GetDeviceProfileRequestMsg getDeviceProfileRequestMsg = 5;
443 487 ValidateBasicMqttCredRequestMsg validateBasicMqttCredRequestMsg = 6;
  488 + ProvisionDeviceRequestMsg provisionDeviceRequestMsg = 7;
444 489 }
445 490
446 491 /* Response from ThingsBoard Core Service to Transport Service */
... ... @@ -449,6 +494,7 @@ message TransportApiResponseMsg {
449 494 GetOrCreateDeviceFromGatewayResponseMsg getOrCreateDeviceResponseMsg = 2;
450 495 GetTenantRoutingInfoResponseMsg getTenantRoutingInfoResponseMsg = 4;
451 496 GetDeviceProfileResponseMsg getDeviceProfileResponseMsg = 5;
  497 + ProvisionDeviceResponseMsg provisionDeviceResponseMsg = 6;
452 498 }
453 499
454 500 /* Messages that are handled by ThingsBoard Core Service */
... ... @@ -491,4 +537,5 @@ message ToTransportMsg {
491 537 ToServerRpcResponseMsg toServerResponse = 7;
492 538 DeviceProfileUpdateMsg deviceProfileUpdateMsg = 8;
493 539 DeviceProfileDeleteMsg deviceProfileDeleteMsg = 9;
  540 + ProvisionDeviceResponseMsg provisionResponse = 10;
494 541 }
... ...
... ... @@ -24,6 +24,7 @@ import org.eclipse.californium.core.network.ExchangeObserver;
24 24 import org.eclipse.californium.core.server.resources.CoapExchange;
25 25 import org.eclipse.californium.core.server.resources.Resource;
26 26 import org.springframework.util.ReflectionUtils;
  27 +import org.thingsboard.server.common.data.DataConstants;
27 28 import org.thingsboard.server.common.data.DeviceTransportType;
28 29 import org.thingsboard.server.common.data.security.DeviceTokenCredentials;
29 30 import org.thingsboard.server.common.msg.session.FeatureType;
... ... @@ -33,9 +34,11 @@ import org.thingsboard.server.common.transport.TransportContext;
33 34 import org.thingsboard.server.common.transport.TransportService;
34 35 import org.thingsboard.server.common.transport.TransportServiceCallback;
35 36 import org.thingsboard.server.common.transport.adaptor.AdaptorException;
  37 +import org.thingsboard.server.common.transport.adaptor.JsonConverter;
36 38 import org.thingsboard.server.common.transport.auth.SessionInfoCreator;
37 39 import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse;
38 40 import org.thingsboard.server.gen.transport.TransportProtos;
  41 +import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceResponseMsg;
39 42
40 43 import java.lang.reflect.Field;
41 44 import java.util.List;
... ... @@ -130,10 +133,25 @@ public class CoapTransportResource extends CoapResource {
130 133 case CLAIM:
131 134 processRequest(exchange, SessionMsgType.CLAIM_REQUEST);
132 135 break;
  136 + case PROVISION:
  137 + processProvision(exchange);
  138 + break;
133 139 }
134 140 }
135 141 }
136 142
  143 + private void processProvision(CoapExchange exchange) {
  144 + log.trace("Processing {}", exchange.advanced().getRequest());
  145 + exchange.accept();
  146 + try {
  147 + transportService.process(transportContext.getAdaptor().convertToProvisionRequestMsg(UUID.randomUUID(), exchange.advanced().getRequest()),
  148 + new DeviceProvisionCallback(exchange));
  149 + } catch (AdaptorException e) {
  150 + log.trace("Failed to decode message: ", e);
  151 + exchange.respond(ResponseCode.BAD_REQUEST);
  152 + }
  153 + }
  154 +
137 155 private void processRequest(CoapExchange exchange, SessionMsgType type) {
138 156 log.trace("Processing {}", exchange.advanced().getRequest());
139 157 exchange.accept();
... ... @@ -274,6 +292,8 @@ public class CoapTransportResource extends CoapResource {
274 292 try {
275 293 if (uriPath.size() >= FEATURE_TYPE_POSITION) {
276 294 return Optional.of(FeatureType.valueOf(uriPath.get(FEATURE_TYPE_POSITION - 1).toUpperCase()));
  295 + } else if (uriPath.size() == 3 && uriPath.contains(DataConstants.PROVISION)) {
  296 + return Optional.of(FeatureType.valueOf(DataConstants.PROVISION.toUpperCase()));
277 297 }
278 298 } catch (RuntimeException e) {
279 299 log.warn("Failed to decode feature type: {}", uriPath);
... ... @@ -325,6 +345,25 @@ public class CoapTransportResource extends CoapResource {
325 345 }
326 346 }
327 347
  348 + private static class DeviceProvisionCallback implements TransportServiceCallback<ProvisionDeviceResponseMsg> {
  349 + private final CoapExchange exchange;
  350 +
  351 + DeviceProvisionCallback(CoapExchange exchange) {
  352 + this.exchange = exchange;
  353 + }
  354 +
  355 + @Override
  356 + public void onSuccess(TransportProtos.ProvisionDeviceResponseMsg msg) {
  357 + exchange.respond(JsonConverter.toJson(msg).toString());
  358 + }
  359 +
  360 + @Override
  361 + public void onError(Throwable e) {
  362 + log.warn("Failed to process request", e);
  363 + exchange.respond(ResponseCode.INTERNAL_SERVER_ERROR);
  364 + }
  365 + }
  366 +
328 367 private static class CoapOkCallback implements TransportServiceCallback<Void> {
329 368 private final CoapExchange exchange;
330 369
... ...
... ... @@ -19,6 +19,7 @@ import org.eclipse.californium.core.coap.Request;
19 19 import org.eclipse.californium.core.coap.Response;
20 20 import org.thingsboard.server.common.transport.adaptor.AdaptorException;
21 21 import org.thingsboard.server.gen.transport.TransportProtos;
  22 +import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceRequestMsg;
22 23 import org.thingsboard.server.transport.coap.CoapTransportResource;
23 24
24 25 import java.util.UUID;
... ... @@ -45,4 +46,6 @@ public interface CoapTransportAdaptor {
45 46
46 47 Response convertToPublish(CoapTransportResource.CoapSessionListener coapSessionListener, TransportProtos.ToServerRpcResponseMsg msg) throws AdaptorException;
47 48
  49 + ProvisionDeviceRequestMsg convertToProvisionRequestMsg(UUID sessionId, Request inbound) throws AdaptorException;
  50 +
48 51 }
... ...
... ... @@ -124,6 +124,16 @@ public class JsonCoapAdaptor implements CoapTransportAdaptor {
124 124 }
125 125
126 126 @Override
  127 + public TransportProtos.ProvisionDeviceRequestMsg convertToProvisionRequestMsg(UUID sessionId, Request inbound) throws AdaptorException {
  128 + String payload = validatePayload(sessionId, inbound, false);
  129 + try {
  130 + return JsonConverter.convertToProvisionRequestMsg(payload);
  131 + } catch (IllegalStateException | JsonSyntaxException ex) {
  132 + throw new AdaptorException(ex);
  133 + }
  134 + }
  135 +
  136 + @Override
127 137 public Response convertToPublish(CoapTransportResource.CoapSessionListener session, TransportProtos.GetAttributeResponseMsg msg) throws AdaptorException {
128 138 if (msg.getClientAttributeListCount() == 0 && msg.getSharedAttributeListCount() == 0) {
129 139 return new Response(CoAP.ResponseCode.NOT_FOUND);
... ...
... ... @@ -44,6 +44,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.AttributeUpdateNotif
44 44 import org.thingsboard.server.gen.transport.TransportProtos.DeviceInfoProto;
45 45 import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeRequestMsg;
46 46 import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeResponseMsg;
  47 +import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceResponseMsg;
47 48 import org.thingsboard.server.gen.transport.TransportProtos.SessionCloseNotificationProto;
48 49 import org.thingsboard.server.gen.transport.TransportProtos.SessionInfoProto;
49 50 import org.thingsboard.server.gen.transport.TransportProtos.SubscribeToAttributeUpdatesMsg;
... ... @@ -203,6 +204,14 @@ public class DeviceApiController {
203 204 return responseWriter;
204 205 }
205 206
  207 + @RequestMapping(value = "/provision", method = RequestMethod.POST)
  208 + public DeferredResult<ResponseEntity> provisionDevice(@RequestBody String json, HttpServletRequest httpRequest) {
  209 + DeferredResult<ResponseEntity> responseWriter = new DeferredResult<>();
  210 + transportContext.getTransportService().process(JsonConverter.convertToProvisionRequestMsg(json),
  211 + new DeviceProvisionCallback(responseWriter));
  212 + return responseWriter;
  213 + }
  214 +
206 215 private static class DeviceAuthCallback implements TransportServiceCallback<ValidateDeviceCredentialsResponse> {
207 216 private final TransportContext transportContext;
208 217 private final DeferredResult<ResponseEntity> responseWriter;
... ... @@ -230,6 +239,25 @@ public class DeviceApiController {
230 239 }
231 240 }
232 241
  242 + private static class DeviceProvisionCallback implements TransportServiceCallback<ProvisionDeviceResponseMsg> {
  243 + private final DeferredResult<ResponseEntity> responseWriter;
  244 +
  245 + DeviceProvisionCallback(DeferredResult<ResponseEntity> responseWriter) {
  246 + this.responseWriter = responseWriter;
  247 + }
  248 +
  249 + @Override
  250 + public void onSuccess(ProvisionDeviceResponseMsg msg) {
  251 + responseWriter.setResult(new ResponseEntity<>(JsonConverter.toJson(msg).toString(), HttpStatus.OK));
  252 + }
  253 +
  254 + @Override
  255 + public void onError(Throwable e) {
  256 + log.warn("Failed to process request", e);
  257 + responseWriter.setResult(new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR));
  258 + }
  259 + }
  260 +
233 261 private static class SessionCloseOnErrorCallback implements TransportServiceCallback<Void> {
234 262 private final TransportService transportService;
235 263 private final SessionInfoProto sessionInfo;
... ...
... ... @@ -16,6 +16,7 @@
16 16 package org.thingsboard.server.transport.mqtt;
17 17
18 18 import com.fasterxml.jackson.databind.JsonNode;
  19 +import com.google.gson.JsonParseException;
19 20 import io.netty.channel.ChannelHandlerContext;
20 21 import io.netty.channel.ChannelInboundHandlerAdapter;
21 22 import io.netty.handler.codec.mqtt.MqttConnAckMessage;
... ... @@ -38,8 +39,10 @@ import io.netty.util.ReferenceCountUtil;
38 39 import io.netty.util.concurrent.Future;
39 40 import io.netty.util.concurrent.GenericFutureListener;
40 41 import lombok.extern.slf4j.Slf4j;
  42 +import org.thingsboard.server.common.data.DataConstants;
41 43 import org.thingsboard.server.common.data.DeviceProfile;
42 44 import org.thingsboard.server.common.data.DeviceTransportType;
  45 +import org.thingsboard.server.common.data.TransportPayloadType;
43 46 import org.thingsboard.server.common.data.device.profile.MqttTopics;
44 47 import org.thingsboard.server.common.msg.EncryptionUtil;
45 48 import org.thingsboard.server.common.transport.SessionMsgListener;
... ... @@ -51,6 +54,7 @@ import org.thingsboard.server.common.transport.auth.TransportDeviceInfo;
51 54 import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse;
52 55 import org.thingsboard.server.common.transport.service.DefaultTransportService;
53 56 import org.thingsboard.server.gen.transport.TransportProtos;
  57 +import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceResponseMsg;
54 58 import org.thingsboard.server.gen.transport.TransportProtos.SessionEvent;
55 59 import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceX509CertRequestMsg;
56 60 import org.thingsboard.server.transport.mqtt.adaptors.MqttTransportAdaptor;
... ... @@ -68,11 +72,12 @@ import java.util.List;
68 72 import java.util.UUID;
69 73 import java.util.concurrent.ConcurrentHashMap;
70 74 import java.util.concurrent.ConcurrentMap;
71   -import java.util.Date;
  75 +import java.util.concurrent.TimeUnit;
72 76
73 77 import static io.netty.handler.codec.mqtt.MqttConnectReturnCode.CONNECTION_ACCEPTED;
74 78 import static io.netty.handler.codec.mqtt.MqttConnectReturnCode.CONNECTION_REFUSED_NOT_AUTHORIZED;
75 79 import static io.netty.handler.codec.mqtt.MqttMessageType.CONNACK;
  80 +import static io.netty.handler.codec.mqtt.MqttMessageType.CONNECT;
76 81 import static io.netty.handler.codec.mqtt.MqttMessageType.PINGRESP;
77 82 import static io.netty.handler.codec.mqtt.MqttMessageType.PUBACK;
78 83 import static io.netty.handler.codec.mqtt.MqttMessageType.SUBACK;
... ... @@ -130,10 +135,58 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
130 135 return;
131 136 }
132 137 deviceSessionCtx.setChannel(ctx);
  138 + if (CONNECT.equals(msg.fixedHeader().messageType())) {
  139 + processConnect(ctx, (MqttConnectMessage) msg);
  140 + } else if (deviceSessionCtx.isProvisionOnly()) {
  141 + processProvisionSessionMsg(ctx, msg);
  142 + } else {
  143 + processRegularSessionMsg(ctx, msg);
  144 + }
  145 + }
  146 +
  147 + private void processProvisionSessionMsg(ChannelHandlerContext ctx, MqttMessage msg) {
133 148 switch (msg.fixedHeader().messageType()) {
134   - case CONNECT:
135   - processConnect(ctx, (MqttConnectMessage) msg);
  149 + case PUBLISH:
  150 + MqttPublishMessage mqttMsg = (MqttPublishMessage) msg;
  151 + String topicName = mqttMsg.variableHeader().topicName();
  152 + int msgId = mqttMsg.variableHeader().packetId();
  153 + try {
  154 + if (topicName.equals(MqttTopics.DEVICE_PROVISION_REQUEST_TOPIC)) {
  155 + try {
  156 + TransportProtos.ProvisionDeviceRequestMsg provisionRequestMsg = deviceSessionCtx.getContext().getJsonMqttAdaptor().convertToProvisionRequestMsg(deviceSessionCtx, mqttMsg);
  157 + transportService.process(provisionRequestMsg, new DeviceProvisionCallback(ctx, msgId, provisionRequestMsg));
  158 + log.trace("[{}][{}] Processing provision publish msg [{}][{}]!", sessionId, deviceSessionCtx.getDeviceId(), topicName, msgId);
  159 + } catch (Exception e) {
  160 + if (e instanceof JsonParseException || (e.getCause() != null && e.getCause() instanceof JsonParseException)) {
  161 + TransportProtos.ProvisionDeviceRequestMsg provisionRequestMsg = deviceSessionCtx.getContext().getProtoMqttAdaptor().convertToProvisionRequestMsg(deviceSessionCtx, mqttMsg);
  162 + transportService.process(provisionRequestMsg, new DeviceProvisionCallback(ctx, msgId, provisionRequestMsg));
  163 + deviceSessionCtx.setProvisionPayloadType(TransportPayloadType.PROTOBUF);
  164 + log.trace("[{}][{}] Processing provision publish msg [{}][{}]!", sessionId, deviceSessionCtx.getDeviceId(), topicName, msgId);
  165 + } else {
  166 + throw e;
  167 + }
  168 + }
  169 + } else {
  170 + throw new RuntimeException("Unsupported topic for provisioning requests!");
  171 + }
  172 + } catch (RuntimeException | AdaptorException e) {
  173 + log.warn("[{}] Failed to process publish msg [{}][{}]", sessionId, topicName, msgId, e);
  174 + ctx.close();
  175 + }
136 176 break;
  177 + case PINGREQ:
  178 + ctx.writeAndFlush(new MqttMessage(new MqttFixedHeader(PINGRESP, false, AT_MOST_ONCE, false, 0)));
  179 + break;
  180 + case DISCONNECT:
  181 + if (checkConnected(ctx, msg)) {
  182 + processDisconnect(ctx);
  183 + }
  184 + break;
  185 + }
  186 + }
  187 +
  188 + private void processRegularSessionMsg(ChannelHandlerContext ctx, MqttMessage msg) {
  189 + switch (msg.fixedHeader().messageType()) {
137 190 case PUBLISH:
138 191 processPublish(ctx, (MqttPublishMessage) msg);
139 192 break;
... ... @@ -257,6 +310,42 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
257 310 };
258 311 }
259 312
  313 + private class DeviceProvisionCallback implements TransportServiceCallback<ProvisionDeviceResponseMsg> {
  314 + private final ChannelHandlerContext ctx;
  315 + private final int msgId;
  316 + private final TransportProtos.ProvisionDeviceRequestMsg msg;
  317 +
  318 + DeviceProvisionCallback(ChannelHandlerContext ctx, int msgId, TransportProtos.ProvisionDeviceRequestMsg msg) {
  319 + this.ctx = ctx;
  320 + this.msgId = msgId;
  321 + this.msg = msg;
  322 + }
  323 +
  324 + @Override
  325 + public void onSuccess(TransportProtos.ProvisionDeviceResponseMsg provisionResponseMsg) {
  326 + log.trace("[{}] Published msg: {}", sessionId, msg);
  327 + if (msgId > 0) {
  328 + ctx.writeAndFlush(createMqttPubAckMsg(msgId));
  329 + }
  330 + try {
  331 + if (deviceSessionCtx.getProvisionPayloadType().equals(TransportPayloadType.JSON)) {
  332 + deviceSessionCtx.getContext().getJsonMqttAdaptor().convertToPublish(deviceSessionCtx, provisionResponseMsg).ifPresent(deviceSessionCtx.getChannel()::writeAndFlush);
  333 + } else {
  334 + deviceSessionCtx.getContext().getProtoMqttAdaptor().convertToPublish(deviceSessionCtx, provisionResponseMsg).ifPresent(deviceSessionCtx.getChannel()::writeAndFlush);
  335 + }
  336 + transportService.getSchedulerExecutor().schedule(() -> processDisconnect(ctx), 60, TimeUnit.SECONDS);
  337 + } catch (Exception e) {
  338 + log.trace("[{}] Failed to convert device attributes response to MQTT msg", sessionId, e);
  339 + }
  340 + }
  341 +
  342 + @Override
  343 + public void onError(Throwable e) {
  344 + log.trace("[{}] Failed to publish msg: {}", sessionId, msg, e);
  345 + processDisconnect(ctx);
  346 + }
  347 + }
  348 +
260 349 private void processSubscribe(ChannelHandlerContext ctx, MqttSubscribeMessage mqttMsg) {
261 350 if (!checkConnected(ctx, mqttMsg)) {
262 351 return;
... ... @@ -286,6 +375,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
286 375 case MqttTopics.GATEWAY_ATTRIBUTES_TOPIC:
287 376 case MqttTopics.GATEWAY_RPC_TOPIC:
288 377 case MqttTopics.GATEWAY_ATTRIBUTES_RESPONSE_TOPIC:
  378 + case MqttTopics.DEVICE_PROVISION_RESPONSE_TOPIC:
289 379 registerSubQoS(topic, grantedQoSList, reqQoS);
290 380 break;
291 381 default:
... ... @@ -351,11 +441,18 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
351 441
352 442 private void processConnect(ChannelHandlerContext ctx, MqttConnectMessage msg) {
353 443 log.info("[{}] Processing connect msg for client: {}!", sessionId, msg.payload().clientIdentifier());
354   - X509Certificate cert;
355   - if (sslHandler != null && (cert = getX509Certificate()) != null) {
356   - processX509CertConnect(ctx, cert);
  444 + String userName = msg.payload().userName();
  445 + String clientId = msg.payload().clientIdentifier();
  446 + if (DataConstants.PROVISION.equals(userName) || DataConstants.PROVISION.equals(clientId)) {
  447 + deviceSessionCtx.setProvisionOnly(true);
  448 + ctx.writeAndFlush(createMqttConnAckMsg(CONNECTION_ACCEPTED));
357 449 } else {
358   - processAuthTokenConnect(ctx, msg);
  450 + X509Certificate cert;
  451 + if (sslHandler != null && (cert = getX509Certificate()) != null) {
  452 + processX509CertConnect(ctx, cert);
  453 + } else {
  454 + processAuthTokenConnect(ctx, msg);
  455 + }
359 456 }
360 457 }
361 458
... ... @@ -387,7 +484,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
387 484
388 485 private void processX509CertConnect(ChannelHandlerContext ctx, X509Certificate cert) {
389 486 try {
390   - if(!context.isSkipValidityCheckForClientCert()){
  487 + if (!context.isSkipValidityCheckForClientCert()) {
391 488 cert.checkValidity();
392 489 }
393 490 String strCert = SslUtil.getX509CertificateString(cert);
... ...
... ... @@ -88,6 +88,16 @@ public class JsonMqttAdaptor implements MqttTransportAdaptor {
88 88 }
89 89
90 90 @Override
  91 + public TransportProtos.ProvisionDeviceRequestMsg convertToProvisionRequestMsg(MqttDeviceAwareSessionContext ctx, MqttPublishMessage inbound) throws AdaptorException {
  92 + String payload = validatePayload(ctx.getSessionId(), inbound.payload(), false);
  93 + try {
  94 + return JsonConverter.convertToProvisionRequestMsg(payload);
  95 + } catch (IllegalStateException | JsonSyntaxException ex) {
  96 + throw new AdaptorException(ex);
  97 + }
  98 + }
  99 +
  100 + @Override
91 101 public TransportProtos.GetAttributeRequestMsg convertToGetAttributes(MqttDeviceAwareSessionContext ctx, MqttPublishMessage inbound) throws AdaptorException {
92 102 return processGetAttributeRequestMsg(inbound, MqttTopics.DEVICE_ATTRIBUTES_REQUEST_TOPIC_PREFIX);
93 103 }
... ... @@ -138,6 +148,11 @@ public class JsonMqttAdaptor implements MqttTransportAdaptor {
138 148 return Optional.of(createMqttPublishMsg(ctx, MqttTopics.DEVICE_RPC_RESPONSE_TOPIC + rpcResponse.getRequestId(), JsonConverter.toJson(rpcResponse)));
139 149 }
140 150
  151 + @Override
  152 + public Optional<MqttMessage> convertToPublish(MqttDeviceAwareSessionContext ctx, TransportProtos.ProvisionDeviceResponseMsg provisionResponse) {
  153 + return Optional.of(createMqttPublishMsg(ctx, MqttTopics.DEVICE_PROVISION_RESPONSE_TOPIC, JsonConverter.toJson(provisionResponse)));
  154 + }
  155 +
141 156 public static JsonElement validateJsonPayload(UUID sessionId, ByteBuf payloadData) throws AdaptorException {
142 157 String payload = validatePayload(sessionId, payloadData, false);
143 158 try {
... ...
... ... @@ -24,6 +24,8 @@ import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeRequestM
24 24 import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeResponseMsg;
25 25 import org.thingsboard.server.gen.transport.TransportProtos.PostAttributeMsg;
26 26 import org.thingsboard.server.gen.transport.TransportProtos.PostTelemetryMsg;
  27 +import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceRequestMsg;
  28 +import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceResponseMsg;
27 29 import org.thingsboard.server.gen.transport.TransportProtos.ToDeviceRpcRequestMsg;
28 30 import org.thingsboard.server.gen.transport.TransportProtos.ToDeviceRpcResponseMsg;
29 31 import org.thingsboard.server.gen.transport.TransportProtos.ToServerRpcRequestMsg;
... ... @@ -63,4 +65,8 @@ public interface MqttTransportAdaptor {
63 65
64 66 Optional<MqttMessage> convertToPublish(MqttDeviceAwareSessionContext ctx, ToServerRpcResponseMsg rpcResponse) throws AdaptorException;
65 67
  68 + ProvisionDeviceRequestMsg convertToProvisionRequestMsg(MqttDeviceAwareSessionContext ctx, MqttPublishMessage inbound) throws AdaptorException;
  69 +
  70 + Optional<MqttMessage> convertToPublish(MqttDeviceAwareSessionContext ctx, ProvisionDeviceResponseMsg provisionResponse) throws AdaptorException;
  71 +
66 72 }
... ...
... ... @@ -32,6 +32,7 @@ import org.thingsboard.server.common.transport.adaptor.AdaptorException;
32 32 import org.thingsboard.server.common.transport.adaptor.ProtoConverter;
33 33 import org.thingsboard.server.gen.transport.TransportApiProtos;
34 34 import org.thingsboard.server.gen.transport.TransportProtos;
  35 +import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceResponseMsg;
35 36 import org.thingsboard.server.transport.mqtt.session.MqttDeviceAwareSessionContext;
36 37
37 38 import java.util.Optional;
... ... @@ -109,6 +110,17 @@ public class ProtoMqttAdaptor implements MqttTransportAdaptor {
109 110 }
110 111
111 112 @Override
  113 + public TransportProtos.ProvisionDeviceRequestMsg convertToProvisionRequestMsg(MqttDeviceAwareSessionContext ctx, MqttPublishMessage mqttMsg) throws AdaptorException {
  114 + byte[] bytes = toBytes(mqttMsg.payload());
  115 + String topicName = mqttMsg.variableHeader().topicName();
  116 + try {
  117 + return ProtoConverter.convertToProvisionRequestMsg(bytes);
  118 + } catch (InvalidProtocolBufferException ex) {
  119 + throw new AdaptorException(ex);
  120 + }
  121 + }
  122 +
  123 + @Override
112 124 public Optional<MqttMessage> convertToPublish(MqttDeviceAwareSessionContext ctx, TransportProtos.GetAttributeResponseMsg responseMsg) throws AdaptorException {
113 125 if (!StringUtils.isEmpty(responseMsg.getError())) {
114 126 throw new AdaptorException(responseMsg.getError());
... ... @@ -140,6 +152,11 @@ public class ProtoMqttAdaptor implements MqttTransportAdaptor {
140 152 }
141 153
142 154 @Override
  155 + public Optional<MqttMessage> convertToPublish(MqttDeviceAwareSessionContext ctx, TransportProtos.ProvisionDeviceResponseMsg provisionResponse) {
  156 + return Optional.of(createMqttPublishMsg(ctx, MqttTopics.DEVICE_PROVISION_RESPONSE_TOPIC, provisionResponse.toByteArray()));
  157 + }
  158 +
  159 + @Override
143 160 public Optional<MqttMessage> convertToGatewayPublish(MqttDeviceAwareSessionContext ctx, String deviceName, TransportProtos.GetAttributeResponseMsg responseMsg) throws AdaptorException {
144 161 if (!StringUtils.isEmpty(responseMsg.getError())) {
145 162 throw new AdaptorException(responseMsg.getError());
... ...
... ... @@ -17,6 +17,7 @@ package org.thingsboard.server.transport.mqtt.session;
17 17
18 18 import io.netty.channel.ChannelHandlerContext;
19 19 import lombok.Getter;
  20 +import lombok.Setter;
20 21 import lombok.extern.slf4j.Slf4j;
21 22 import org.thingsboard.server.common.data.DeviceProfile;
22 23 import org.thingsboard.server.common.data.DeviceTransportType;
... ... @@ -46,10 +47,18 @@ public class DeviceSessionCtx extends MqttDeviceAwareSessionContext {
46 47
47 48 private final AtomicInteger msgIdSeq = new AtomicInteger(0);
48 49
  50 + @Getter
  51 + @Setter
  52 + private boolean provisionOnly = false;
  53 +
49 54 private volatile MqttTopicFilter telemetryTopicFilter = MqttTopicFilterFactory.getDefaultTelemetryFilter();
50 55 private volatile MqttTopicFilter attributesTopicFilter = MqttTopicFilterFactory.getDefaultAttributesFilter();
51 56 private volatile TransportPayloadType payloadType = TransportPayloadType.JSON;
52 57
  58 + @Getter
  59 + @Setter
  60 + private TransportPayloadType provisionPayloadType = payloadType;
  61 +
53 62 public DeviceSessionCtx(UUID sessionId, ConcurrentMap<MqttTopicMatcher, Integer> mqttQoSMap, MqttTransportContext context) {
54 63 super(sessionId, mqttQoSMap);
55 64 this.context = context;
... ...
... ... @@ -27,6 +27,8 @@ import org.thingsboard.server.gen.transport.TransportProtos.GetTenantRoutingInfo
27 27 import org.thingsboard.server.gen.transport.TransportProtos.GetTenantRoutingInfoResponseMsg;
28 28 import org.thingsboard.server.gen.transport.TransportProtos.PostAttributeMsg;
29 29 import org.thingsboard.server.gen.transport.TransportProtos.PostTelemetryMsg;
  30 +import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceRequestMsg;
  31 +import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceResponseMsg;
30 32 import org.thingsboard.server.gen.transport.TransportProtos.SessionEventMsg;
31 33 import org.thingsboard.server.gen.transport.TransportProtos.SessionInfoProto;
32 34 import org.thingsboard.server.gen.transport.TransportProtos.SubscribeToAttributeUpdatesMsg;
... ... @@ -38,6 +40,8 @@ import org.thingsboard.server.gen.transport.TransportProtos.ValidateBasicMqttCre
38 40 import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceTokenRequestMsg;
39 41 import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceX509CertRequestMsg;
40 42
  43 +import java.util.concurrent.ScheduledExecutorService;
  44 +
41 45 /**
42 46 * Created by ashvayka on 04.10.18.
43 47 */
... ... @@ -57,6 +61,9 @@ public interface TransportService {
57 61 void process(GetOrCreateDeviceFromGatewayRequestMsg msg,
58 62 TransportServiceCallback<GetOrCreateDeviceFromGatewayResponse> callback);
59 63
  64 + void process(ProvisionDeviceRequestMsg msg,
  65 + TransportServiceCallback<ProvisionDeviceResponseMsg> callback);
  66 +
60 67 void getDeviceProfile(DeviceProfileId deviceProfileId, TransportServiceCallback<DeviceProfile> callback);
61 68
62 69 void onProfileUpdate(DeviceProfile deviceProfile);
... ... @@ -83,6 +90,8 @@ public interface TransportService {
83 90
84 91 void process(SessionInfoProto sessionInfo, ClaimDeviceMsg msg, TransportServiceCallback<Void> callback);
85 92
  93 + ScheduledExecutorService getSchedulerExecutor();
  94 +
86 95 void registerAsyncSession(SessionInfoProto sessionInfo, SessionMsgListener listener);
87 96
88 97 void registerSyncSession(SessionInfoProto sessionInfo, SessionMsgListener listener, long timeout);
... ... @@ -90,5 +99,4 @@ public interface TransportService {
90 99 void reportActivity(SessionInfoProto sessionInfo);
91 100
92 101 void deregisterSession(SessionInfoProto sessionInfo);
93   -
94 102 }
... ...
... ... @@ -37,13 +37,19 @@ import org.thingsboard.server.common.data.kv.StringDataEntry;
37 37 import org.thingsboard.server.gen.transport.TransportProtos;
38 38 import org.thingsboard.server.gen.transport.TransportProtos.AttributeUpdateNotificationMsg;
39 39 import org.thingsboard.server.gen.transport.TransportProtos.ClaimDeviceMsg;
  40 +import org.thingsboard.server.gen.transport.TransportProtos.CredentialsType;
40 41 import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeResponseMsg;
41 42 import org.thingsboard.server.gen.transport.TransportProtos.KeyValueProto;
42 43 import org.thingsboard.server.gen.transport.TransportProtos.KeyValueType;
43 44 import org.thingsboard.server.gen.transport.TransportProtos.PostAttributeMsg;
44 45 import org.thingsboard.server.gen.transport.TransportProtos.PostTelemetryMsg;
  46 +import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceResponseMsg;
  47 +import org.thingsboard.server.gen.transport.TransportProtos.ProvisionResponseStatus;
45 48 import org.thingsboard.server.gen.transport.TransportProtos.TsKvListProto;
46 49 import org.thingsboard.server.gen.transport.TransportProtos.TsKvProto;
  50 +import org.thingsboard.server.gen.transport.TransportProtos.ValidateBasicMqttCredRequestMsg;
  51 +import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceTokenRequestMsg;
  52 +import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceX509CertRequestMsg;
47 53
48 54 import java.util.ArrayList;
49 55 import java.util.HashMap;
... ... @@ -53,6 +59,7 @@ import java.util.Map;
53 59 import java.util.Map.Entry;
54 60 import java.util.Set;
55 61 import java.util.TreeMap;
  62 +import java.util.UUID;
56 63 import java.util.function.Consumer;
57 64 import java.util.stream.Collectors;
58 65
... ... @@ -397,6 +404,36 @@ public class JsonConverter {
397 404 }
398 405 }
399 406
  407 + public static JsonObject toJson(ProvisionDeviceResponseMsg payload) {
  408 + return toJson(payload, false, 0);
  409 + }
  410 +
  411 + public static JsonObject toJson(ProvisionDeviceResponseMsg payload, int requestId) {
  412 + return toJson(payload, true, requestId);
  413 + }
  414 +
  415 + private static JsonObject toJson(ProvisionDeviceResponseMsg payload, boolean toGateway, int requestId) {
  416 + JsonObject result = new JsonObject();
  417 + if (payload.getProvisionResponseStatus() == TransportProtos.ProvisionResponseStatus.NOT_FOUND) {
  418 + result.addProperty("errorMsg", "Provision data was not found!");
  419 + result.addProperty("provisionDeviceStatus", ProvisionResponseStatus.NOT_FOUND.name());
  420 + } else if (payload.getProvisionResponseStatus() == TransportProtos.ProvisionResponseStatus.FAILURE) {
  421 + result.addProperty("errorMsg", "Failed to provision device!");
  422 + result.addProperty("provisionDeviceStatus", ProvisionResponseStatus.FAILURE.name());
  423 + } else {
  424 + if (toGateway) {
  425 + result.addProperty("id", requestId);
  426 + }
  427 + result.addProperty("deviceId", new UUID(payload.getDeviceCredentials().getDeviceIdMSB(), payload.getDeviceCredentials().getDeviceIdLSB()).toString());
  428 + result.addProperty("credentialsType", payload.getDeviceCredentials().getCredentialsType().name());
  429 + result.addProperty("credentialsId", payload.getDeviceCredentials().getCredentialsId());
  430 + result.addProperty("credentialsValue",
  431 + StringUtils.isEmpty(payload.getDeviceCredentials().getCredentialsValue()) ? null : payload.getDeviceCredentials().getCredentialsValue());
  432 + result.addProperty("provisionDeviceStatus", ProvisionResponseStatus.SUCCESS.name());
  433 + }
  434 + return result;
  435 + }
  436 +
400 437 public static JsonElement toErrorJson(String errorMsg) {
401 438 JsonObject error = new JsonObject();
402 439 error.addProperty("error", errorMsg);
... ... @@ -410,6 +447,13 @@ public class JsonConverter {
410 447 return result;
411 448 }
412 449
  450 + public static JsonElement toGatewayJson(String deviceName, TransportProtos.ProvisionDeviceResponseMsg responseRequest) {
  451 + JsonObject result = new JsonObject();
  452 + result.addProperty(DEVICE_PROPERTY, deviceName);
  453 + result.add("data", JsonConverter.toJson(responseRequest));
  454 + return result;
  455 + }
  456 +
413 457 public static Set<AttributeKvEntry> convertToAttributes(JsonElement element) {
414 458 Set<AttributeKvEntry> result = new HashSet<>();
415 459 long ts = System.currentTimeMillis();
... ... @@ -498,4 +542,55 @@ public class JsonConverter {
498 542 maxStringValueLength = length;
499 543 }
500 544
  545 + public static TransportProtos.ProvisionDeviceRequestMsg convertToProvisionRequestMsg(String json) {
  546 + JsonElement jsonElement = new JsonParser().parse(json);
  547 + if (jsonElement.isJsonObject()) {
  548 + return buildProvisionRequestMsg(jsonElement.getAsJsonObject());
  549 + } else {
  550 + throw new JsonSyntaxException(CAN_T_PARSE_VALUE + jsonElement);
  551 + }
  552 + }
  553 +
  554 + public static TransportProtos.ProvisionDeviceRequestMsg convertToProvisionRequestMsg(JsonObject jo) {
  555 + return buildProvisionRequestMsg(jo);
  556 + }
  557 +
  558 + private static TransportProtos.ProvisionDeviceRequestMsg buildProvisionRequestMsg(JsonObject jo) {
  559 + return TransportProtos.ProvisionDeviceRequestMsg.newBuilder()
  560 + .setDeviceName(getStrValue(jo, DataConstants.DEVICE_NAME, true))
  561 + .setCredentialsType(jo.get(DataConstants.CREDENTIALS_TYPE) != null ? TransportProtos.CredentialsType.valueOf(getStrValue(jo, DataConstants.CREDENTIALS_TYPE, false)) : CredentialsType.ACCESS_TOKEN)
  562 + .setCredentialsDataProto(TransportProtos.CredentialsDataProto.newBuilder()
  563 + .setValidateDeviceTokenRequestMsg(ValidateDeviceTokenRequestMsg.newBuilder().setToken(getStrValue(jo, DataConstants.TOKEN, false)).build())
  564 + .setValidateBasicMqttCredRequestMsg(ValidateBasicMqttCredRequestMsg.newBuilder()
  565 + .setClientId(getStrValue(jo, DataConstants.CLIENT_ID, false))
  566 + .setUserName(getStrValue(jo, DataConstants.USERNAME, false))
  567 + .setPassword(getStrValue(jo, DataConstants.PASSWORD, false))
  568 + .build())
  569 + .setValidateDeviceX509CertRequestMsg(ValidateDeviceX509CertRequestMsg.newBuilder()
  570 + .setHash(getStrValue(jo, DataConstants.HASH, false)).build())
  571 + .build())
  572 + .setProvisionDeviceCredentialsMsg(buildProvisionDeviceCredentialsMsg(
  573 + getStrValue(jo, DataConstants.PROVISION_KEY, true),
  574 + getStrValue(jo, DataConstants.PROVISION_SECRET, true)))
  575 + .build();
  576 + }
  577 +
  578 + private static TransportProtos.ProvisionDeviceCredentialsMsg buildProvisionDeviceCredentialsMsg(String provisionKey, String provisionSecret) {
  579 + return TransportProtos.ProvisionDeviceCredentialsMsg.newBuilder()
  580 + .setProvisionDeviceKey(provisionKey)
  581 + .setProvisionDeviceSecret(provisionSecret)
  582 + .build();
  583 + }
  584 +
  585 +
  586 + private static String getStrValue(JsonObject jo, String field, boolean requiredField) {
  587 + if (jo.has(field)) {
  588 + return jo.get(field).getAsString();
  589 + } else {
  590 + if (requiredField) {
  591 + throw new RuntimeException("Failed to find the field " + field + " in JSON body " + jo + "!");
  592 + }
  593 + return "";
  594 + }
  595 + }
501 596 }
... ...
... ... @@ -130,6 +130,9 @@ public class ProtoConverter {
130 130 }
131 131 }
132 132
  133 + public static TransportProtos.ProvisionDeviceRequestMsg convertToProvisionRequestMsg(byte[] bytes) throws InvalidProtocolBufferException {
  134 + return TransportProtos.ProvisionDeviceRequestMsg.parseFrom(bytes);
  135 + }
133 136
134 137 private static List<TransportProtos.KeyValueProto> validateKeyValueProtos(List<TransportProtos.KeyValueProto> kvList) {
135 138 kvList.forEach(keyValueProto -> {
... ...
... ... @@ -34,7 +34,6 @@ import org.thingsboard.server.common.data.id.DeviceProfileId;
34 34 import org.thingsboard.server.common.data.id.RuleChainId;
35 35 import org.thingsboard.server.common.data.id.TenantId;
36 36 import org.thingsboard.server.common.msg.TbMsg;
37   -import org.thingsboard.server.common.msg.TbMsgDataType;
38 37 import org.thingsboard.server.common.msg.TbMsgMetaData;
39 38 import org.thingsboard.server.common.msg.queue.ServiceQueue;
40 39 import org.thingsboard.server.common.msg.queue.ServiceType;
... ... @@ -49,9 +48,10 @@ import org.thingsboard.server.common.transport.TransportServiceCallback;
49 48 import org.thingsboard.server.common.transport.auth.GetOrCreateDeviceFromGatewayResponse;
50 49 import org.thingsboard.server.common.transport.auth.TransportDeviceInfo;
51 50 import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse;
52   -import org.thingsboard.server.common.transport.util.DataDecodingEncodingService;
53 51 import org.thingsboard.server.common.transport.util.JsonUtils;
54 52 import org.thingsboard.server.gen.transport.TransportProtos;
  53 +import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceRequestMsg;
  54 +import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceResponseMsg;
55 55 import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg;
56 56 import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg;
57 57 import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg;
... ... @@ -75,11 +75,9 @@ import org.thingsboard.server.common.stats.StatsType;
75 75
76 76 import javax.annotation.PostConstruct;
77 77 import javax.annotation.PreDestroy;
78   -import java.util.Arrays;
79 78 import java.util.Collections;
80 79 import java.util.List;
81 80 import java.util.Map;
82   -import java.util.Optional;
83 81 import java.util.Random;
84 82 import java.util.UUID;
85 83 import java.util.concurrent.ConcurrentHashMap;
... ... @@ -91,7 +89,6 @@ import java.util.concurrent.ScheduledExecutorService;
91 89 import java.util.concurrent.ScheduledFuture;
92 90 import java.util.concurrent.TimeUnit;
93 91 import java.util.concurrent.atomic.AtomicInteger;
94   -import java.util.function.Function;
95 92
96 93 /**
97 94 * Created by ashvayka on 17.10.18.
... ... @@ -235,6 +232,11 @@ public class DefaultTransportService implements TransportService {
235 232 }
236 233
237 234 @Override
  235 + public ScheduledExecutorService getSchedulerExecutor(){
  236 + return this.schedulerExecutor;
  237 + }
  238 +
  239 + @Override
238 240 public void registerAsyncSession(TransportProtos.SessionInfoProto sessionInfo, SessionMsgListener listener) {
239 241 sessions.putIfAbsent(toSessionId(sessionInfo), new SessionMetaData(sessionInfo, TransportProtos.SessionType.ASYNC, listener));
240 242 }
... ... @@ -333,6 +335,16 @@ public class DefaultTransportService implements TransportService {
333 335 }
334 336
335 337 @Override
  338 + public void process(ProvisionDeviceRequestMsg requestMsg, TransportServiceCallback<ProvisionDeviceResponseMsg> callback) {
  339 + log.trace("Processing msg: {}", requestMsg);
  340 + TbProtoQueueMsg<TransportApiRequestMsg> protoMsg = new TbProtoQueueMsg<>(UUID.randomUUID(), TransportApiRequestMsg.newBuilder().setProvisionDeviceRequestMsg(requestMsg).build());
  341 + ListenableFuture<ProvisionDeviceResponseMsg> response = Futures.transform(transportApiRequestTemplate.send(protoMsg), tmp ->
  342 + tmp.getValue().getProvisionDeviceResponseMsg()
  343 + , MoreExecutors.directExecutor());
  344 + AsyncCallbackTemplate.withCallback(response, callback::onSuccess, callback::onError, transportCallbackExecutor);
  345 + }
  346 +
  347 + @Override
336 348 public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.SubscriptionInfoProto msg, TransportServiceCallback<Void> callback) {
337 349 if (log.isTraceEnabled()) {
338 350 log.trace("[{}] Processing msg: {}", toSessionId(sessionInfo), msg);
... ...
... ... @@ -47,6 +47,7 @@ import org.thingsboard.server.common.data.security.DeviceCredentials;
47 47 import org.thingsboard.server.dao.audit.sink.AuditLogSink;
48 48 import org.thingsboard.server.dao.entity.EntityService;
49 49 import org.thingsboard.server.dao.exception.DataValidationException;
  50 +import org.thingsboard.server.dao.device.provision.ProvisionRequest;
50 51 import org.thingsboard.server.dao.service.DataValidator;
51 52
52 53 import java.io.PrintWriter;
... ... @@ -257,6 +258,13 @@ public class AuditLogServiceImpl implements AuditLogService {
257 258 actionData.put("os", os);
258 259 actionData.put("device", device);
259 260 break;
  261 + case PROVISION_SUCCESS:
  262 + case PROVISION_FAILURE:
  263 + ProvisionRequest request = extractParameter(ProvisionRequest.class, additionalInfo);
  264 + if (request != null) {
  265 + actionData.set("provisionRequest", objectMapper.valueToTree(request));
  266 + }
  267 + break;
260 268 }
261 269 return actionData;
262 270 }
... ...
... ... @@ -214,5 +214,4 @@ public interface DeviceDao extends Dao<Device> {
214 214 * @return the list of device objects
215 215 */
216 216 PageData<Device> findDevicesByTenantIdAndProfileId(UUID tenantId, UUID profileId, PageLink pageLink);
217   -
218 217 }
... ...
... ... @@ -38,5 +38,7 @@ public interface DeviceProfileDao extends Dao<DeviceProfile> {
38 38
39 39 DeviceProfileInfo findDefaultDeviceProfileInfo(TenantId tenantId);
40 40
  41 + DeviceProfile findByProvisionDeviceKey(String provisionDeviceKey);
  42 +
41 43 DeviceProfile findByName(TenantId tenantId, String profileName);
42 44 }
... ...
... ... @@ -26,6 +26,7 @@ import org.springframework.stereotype.Service;
26 26 import org.thingsboard.server.common.data.Device;
27 27 import org.thingsboard.server.common.data.DeviceProfile;
28 28 import org.thingsboard.server.common.data.DeviceProfileInfo;
  29 +import org.thingsboard.server.common.data.DeviceProfileProvisionType;
29 30 import org.thingsboard.server.common.data.DeviceProfileType;
30 31 import org.thingsboard.server.common.data.DeviceTransportType;
31 32 import org.thingsboard.server.common.data.EntitySubtype;
... ... @@ -33,6 +34,7 @@ import org.thingsboard.server.common.data.Tenant;
33 34 import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileConfiguration;
34 35 import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileTransportConfiguration;
35 36 import org.thingsboard.server.common.data.device.profile.DeviceProfileData;
  37 +import org.thingsboard.server.common.data.device.profile.DisabledDeviceProfileProvisionConfiguration;
36 38 import org.thingsboard.server.common.data.id.DeviceProfileId;
37 39 import org.thingsboard.server.common.data.id.TenantId;
38 40 import org.thingsboard.server.common.data.page.PageData;
... ... @@ -112,6 +114,8 @@ public class DeviceProfileServiceImpl extends AbstractEntityService implements D
112 114 ConstraintViolationException e = extractConstraintViolationException(t).orElse(null);
113 115 if (e != null && e.getConstraintName() != null && e.getConstraintName().equalsIgnoreCase("device_profile_name_unq_key")) {
114 116 throw new DataValidationException("Device profile with such name already exists!");
  117 + } else if (e != null && e.getConstraintName() != null && e.getConstraintName().equalsIgnoreCase("device_provision_key_unq_key")) {
  118 + throw new DataValidationException("Device profile with such provision device key already exists!");
115 119 } else {
116 120 throw t;
117 121 }
... ... @@ -210,12 +214,15 @@ public class DeviceProfileServiceImpl extends AbstractEntityService implements D
210 214 deviceProfile.setName(profileName);
211 215 deviceProfile.setType(DeviceProfileType.DEFAULT);
212 216 deviceProfile.setTransportType(DeviceTransportType.DEFAULT);
  217 + deviceProfile.setProvisionType(DeviceProfileProvisionType.DISABLED);
213 218 deviceProfile.setDescription("Default device profile");
214 219 DeviceProfileData deviceProfileData = new DeviceProfileData();
215 220 DefaultDeviceProfileConfiguration configuration = new DefaultDeviceProfileConfiguration();
216 221 DefaultDeviceProfileTransportConfiguration transportConfiguration = new DefaultDeviceProfileTransportConfiguration();
  222 + DisabledDeviceProfileProvisionConfiguration provisionConfiguration = new DisabledDeviceProfileProvisionConfiguration(null);
217 223 deviceProfileData.setConfiguration(configuration);
218 224 deviceProfileData.setTransportConfiguration(transportConfiguration);
  225 + deviceProfileData.setProvisionConfiguration(provisionConfiguration);
219 226 deviceProfile.setProfileData(deviceProfileData);
220 227 return saveDeviceProfile(deviceProfile);
221 228 }
... ...
... ... @@ -40,6 +40,7 @@ import org.thingsboard.server.common.data.EntityType;
40 40 import org.thingsboard.server.common.data.EntityView;
41 41 import org.thingsboard.server.common.data.Tenant;
42 42 import org.thingsboard.server.common.data.device.DeviceSearchQuery;
  43 +import org.thingsboard.server.common.data.device.credentials.BasicMqttCredentials;
43 44 import org.thingsboard.server.common.data.device.data.DefaultDeviceConfiguration;
44 45 import org.thingsboard.server.common.data.device.data.DefaultDeviceTransportConfiguration;
45 46 import org.thingsboard.server.common.data.device.data.DeviceData;
... ... @@ -57,6 +58,9 @@ import org.thingsboard.server.common.data.relation.EntitySearchDirection;
57 58 import org.thingsboard.server.common.data.security.DeviceCredentials;
58 59 import org.thingsboard.server.common.data.security.DeviceCredentialsType;
59 60 import org.thingsboard.server.dao.customer.CustomerDao;
  61 +import org.thingsboard.server.dao.device.provision.ProvisionFailedException;
  62 +import org.thingsboard.server.dao.device.provision.ProvisionRequest;
  63 +import org.thingsboard.server.dao.device.provision.ProvisionResponseStatus;
60 64 import org.thingsboard.server.dao.entity.AbstractEntityService;
61 65 import org.thingsboard.server.dao.entityview.EntityViewService;
62 66 import org.thingsboard.server.dao.event.EventService;
... ... @@ -64,6 +68,7 @@ import org.thingsboard.server.dao.exception.DataValidationException;
64 68 import org.thingsboard.server.dao.service.DataValidator;
65 69 import org.thingsboard.server.dao.service.PaginatedRemover;
66 70 import org.thingsboard.server.dao.tenant.TenantDao;
  71 +import org.thingsboard.server.dao.util.mapping.JacksonUtil;
67 72
68 73 import javax.annotation.Nullable;
69 74 import java.util.ArrayList;
... ... @@ -466,6 +471,50 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe
466 471 return doSaveDevice(device, null);
467 472 }
468 473
  474 + @Override
  475 + @CacheEvict(cacheNames = DEVICE_CACHE, key = "{#profile.tenantId, #provisionRequest.deviceName}")
  476 + @Transactional
  477 + public Device saveDevice(ProvisionRequest provisionRequest, DeviceProfile profile) {
  478 + Device device = new Device();
  479 + device.setName(provisionRequest.getDeviceName());
  480 + device.setType(profile.getName());
  481 + device.setTenantId(profile.getTenantId());
  482 + Device savedDevice = saveDevice(device);
  483 + if (!StringUtils.isEmpty(provisionRequest.getCredentialsData().getToken()) ||
  484 + !StringUtils.isEmpty(provisionRequest.getCredentialsData().getX509CertHash()) ||
  485 + !StringUtils.isEmpty(provisionRequest.getCredentialsData().getUsername()) ||
  486 + !StringUtils.isEmpty(provisionRequest.getCredentialsData().getPassword()) ||
  487 + !StringUtils.isEmpty(provisionRequest.getCredentialsData().getClientId())) {
  488 + DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(savedDevice.getTenantId(), savedDevice.getId());
  489 + if (deviceCredentials == null) {
  490 + deviceCredentials = new DeviceCredentials();
  491 + }
  492 + deviceCredentials.setDeviceId(savedDevice.getId());
  493 + deviceCredentials.setCredentialsType(provisionRequest.getCredentialsType());
  494 + switch (provisionRequest.getCredentialsType()) {
  495 + case ACCESS_TOKEN:
  496 + deviceCredentials.setCredentialsId(provisionRequest.getCredentialsData().getToken());
  497 + break;
  498 + case MQTT_BASIC:
  499 + BasicMqttCredentials mqttCredentials = new BasicMqttCredentials();
  500 + mqttCredentials.setClientId(provisionRequest.getCredentialsData().getClientId());
  501 + mqttCredentials.setUserName(provisionRequest.getCredentialsData().getUsername());
  502 + mqttCredentials.setPassword(provisionRequest.getCredentialsData().getPassword());
  503 + deviceCredentials.setCredentialsValue(JacksonUtil.toString(mqttCredentials));
  504 + break;
  505 + case X509_CERTIFICATE:
  506 + deviceCredentials.setCredentialsValue(provisionRequest.getCredentialsData().getX509CertHash());
  507 + break;
  508 + }
  509 + try {
  510 + deviceCredentialsService.updateDeviceCredentials(savedDevice.getTenantId(), deviceCredentials);
  511 + } catch (Exception e) {
  512 + throw new ProvisionFailedException(ProvisionResponseStatus.FAILURE.name());
  513 + }
  514 + }
  515 + return savedDevice;
  516 + }
  517 +
469 518 private DataValidator<Device> deviceValidator =
470 519 new DataValidator<Device>() {
471 520
... ...
... ... @@ -169,10 +169,12 @@ public class ModelConstants {
169 169 public static final String DEVICE_PROFILE_NAME_PROPERTY = "name";
170 170 public static final String DEVICE_PROFILE_TYPE_PROPERTY = "type";
171 171 public static final String DEVICE_PROFILE_TRANSPORT_TYPE_PROPERTY = "transport_type";
  172 + public static final String DEVICE_PROFILE_PROVISION_TYPE_PROPERTY = "provision_type";
172 173 public static final String DEVICE_PROFILE_PROFILE_DATA_PROPERTY = "profile_data";
173 174 public static final String DEVICE_PROFILE_DESCRIPTION_PROPERTY = "description";
174 175 public static final String DEVICE_PROFILE_IS_DEFAULT_PROPERTY = "is_default";
175 176 public static final String DEVICE_PROFILE_DEFAULT_RULE_CHAIN_ID_PROPERTY = "default_rule_chain_id";
  177 + public static final String DEVICE_PROFILE_PROVISION_DEVICE_KEY = "provision_device_key";
176 178
177 179 /**
178 180 * Cassandra entityView constants.
... ...
... ... @@ -23,6 +23,7 @@ import org.hibernate.annotations.Type;
23 23 import org.hibernate.annotations.TypeDef;
24 24 import org.thingsboard.server.common.data.DeviceProfile;
25 25 import org.thingsboard.server.common.data.DeviceProfileType;
  26 +import org.thingsboard.server.common.data.DeviceProfileProvisionType;
26 27 import org.thingsboard.server.common.data.DeviceTransportType;
27 28 import org.thingsboard.server.common.data.device.profile.DeviceProfileData;
28 29 import org.thingsboard.server.common.data.id.DeviceProfileId;
... ... @@ -62,6 +63,10 @@ public final class DeviceProfileEntity extends BaseSqlEntity<DeviceProfile> impl
62 63 @Column(name = ModelConstants.DEVICE_PROFILE_TRANSPORT_TYPE_PROPERTY)
63 64 private DeviceTransportType transportType;
64 65
  66 + @Enumerated(EnumType.STRING)
  67 + @Column(name = ModelConstants.DEVICE_PROFILE_PROVISION_TYPE_PROPERTY)
  68 + private DeviceProfileProvisionType provisionType;
  69 +
65 70 @Column(name = ModelConstants.DEVICE_PROFILE_DESCRIPTION_PROPERTY)
66 71 private String description;
67 72
... ... @@ -78,6 +83,9 @@ public final class DeviceProfileEntity extends BaseSqlEntity<DeviceProfile> impl
78 83 @Column(name = ModelConstants.DEVICE_PROFILE_PROFILE_DATA_PROPERTY, columnDefinition = "jsonb")
79 84 private JsonNode profileData;
80 85
  86 + @Column(name=ModelConstants.DEVICE_PROFILE_PROVISION_DEVICE_KEY)
  87 + private String provisionDeviceKey;
  88 +
81 89 public DeviceProfileEntity() {
82 90 super();
83 91 }
... ... @@ -93,12 +101,14 @@ public final class DeviceProfileEntity extends BaseSqlEntity<DeviceProfile> impl
93 101 this.name = deviceProfile.getName();
94 102 this.type = deviceProfile.getType();
95 103 this.transportType = deviceProfile.getTransportType();
  104 + this.provisionType = deviceProfile.getProvisionType();
96 105 this.description = deviceProfile.getDescription();
97 106 this.isDefault = deviceProfile.isDefault();
98 107 this.profileData = JacksonUtil.convertValue(deviceProfile.getProfileData(), ObjectNode.class);
99 108 if (deviceProfile.getDefaultRuleChainId() != null) {
100 109 this.defaultRuleChainId = deviceProfile.getDefaultRuleChainId().getId();
101 110 }
  111 + this.provisionDeviceKey = deviceProfile.getProvisionDeviceKey();
102 112 }
103 113
104 114 @Override
... ... @@ -125,12 +135,14 @@ public final class DeviceProfileEntity extends BaseSqlEntity<DeviceProfile> impl
125 135 deviceProfile.setName(name);
126 136 deviceProfile.setType(type);
127 137 deviceProfile.setTransportType(transportType);
  138 + deviceProfile.setProvisionType(provisionType);
128 139 deviceProfile.setDescription(description);
129 140 deviceProfile.setDefault(isDefault);
130 141 deviceProfile.setProfileData(JacksonUtil.convertValue(profileData, DeviceProfileData.class));
131 142 if (defaultRuleChainId != null) {
132 143 deviceProfile.setDefaultRuleChainId(new RuleChainId(defaultRuleChainId));
133 144 }
  145 + deviceProfile.setProvisionDeviceKey(provisionDeviceKey);
134 146 return deviceProfile;
135 147 }
136 148 }
... ...
... ... @@ -20,7 +20,6 @@ import org.springframework.data.domain.Pageable;
20 20 import org.springframework.data.jpa.repository.Query;
21 21 import org.springframework.data.repository.PagingAndSortingRepository;
22 22 import org.springframework.data.repository.query.Param;
23   -import org.thingsboard.server.common.data.DeviceProfile;
24 23 import org.thingsboard.server.common.data.DeviceProfileInfo;
25 24 import org.thingsboard.server.common.data.DeviceTransportType;
26 25 import org.thingsboard.server.dao.model.sql.DeviceProfileEntity;
... ... @@ -66,4 +65,5 @@ public interface DeviceProfileRepository extends PagingAndSortingRepository<Devi
66 65
67 66 DeviceProfileEntity findByTenantIdAndName(UUID id, String profileName);
68 67
  68 + DeviceProfileEntity findByProvisionDeviceKey(@Param("provisionDeviceKey") String provisionDeviceKey);
69 69 }
... ...
... ... @@ -168,5 +168,4 @@ public interface DeviceRepository extends PagingAndSortingRepository<DeviceEntit
168 168 DeviceEntity findByTenantIdAndId(UUID tenantId, UUID id);
169 169
170 170 Long countByDeviceProfileId(UUID deviceProfileId);
171   -
172 171 }
... ...
... ... @@ -92,6 +92,11 @@ public class JpaDeviceProfileDao extends JpaAbstractSearchTextDao<DeviceProfileE
92 92 }
93 93
94 94 @Override
  95 + public DeviceProfile findByProvisionDeviceKey(String provisionDeviceKey) {
  96 + return DaoUtil.getData(deviceProfileRepository.findByProvisionDeviceKey(provisionDeviceKey));
  97 + }
  98 +
  99 + @Override
95 100 public DeviceProfile findByName(TenantId tenantId, String profileName) {
96 101 return DaoUtil.getData(deviceProfileRepository.findByTenantIdAndName(tenantId.getId(), profileName));
97 102 }
... ...
... ... @@ -163,13 +163,16 @@ CREATE TABLE IF NOT EXISTS device_profile (
163 163 name varchar(255),
164 164 type varchar(255),
165 165 transport_type varchar(255),
  166 + provision_type varchar(255),
166 167 profile_data jsonb,
167 168 description varchar,
168 169 search_text varchar(255),
169 170 is_default boolean,
170 171 tenant_id uuid,
171 172 default_rule_chain_id uuid,
  173 + provision_device_key varchar,
172 174 CONSTRAINT device_profile_name_unq_key UNIQUE (tenant_id, name),
  175 + CONSTRAINT device_provision_key_unq_key UNIQUE (provision_device_key),
173 176 CONSTRAINT fk_default_rule_chain_device_profile FOREIGN KEY (default_rule_chain_id) REFERENCES rule_chain(id)
174 177 );
175 178
... ...
... ... @@ -181,13 +181,16 @@ CREATE TABLE IF NOT EXISTS device_profile (
181 181 name varchar(255),
182 182 type varchar(255),
183 183 transport_type varchar(255),
  184 + provision_type varchar(255),
184 185 profile_data jsonb,
185 186 description varchar,
186 187 search_text varchar(255),
187 188 is_default boolean,
188 189 tenant_id uuid,
189 190 default_rule_chain_id uuid,
  191 + provision_device_key varchar,
190 192 CONSTRAINT device_profile_name_unq_key UNIQUE (tenant_id, name),
  193 + CONSTRAINT device_provision_key_unq_key UNIQUE (provision_device_key),
191 194 CONSTRAINT fk_default_rule_chain_device_profile FOREIGN KEY (default_rule_chain_id) REFERENCES rule_chain(id)
192 195 );
193 196
... ...
... ... @@ -105,6 +105,7 @@ import { AlarmRuleConditionComponent } from './profile/alarm/alarm-rule-conditio
105 105 import { FilterTextComponent } from './filter/filter-text.component';
106 106 import { AddDeviceProfileDialogComponent } from './profile/add-device-profile-dialog.component';
107 107 import { RuleChainAutocompleteComponent } from './rule-chain/rule-chain-autocomplete.component';
  108 +import { DeviceProfileProvisionConfigurationComponent } from "./profile/device-profile-provision-configuration.component";
108 109 import { AlarmScheduleComponent } from './profile/alarm/alarm-schedule.component';
109 110 import { DeviceWizardDialogComponent } from './wizard/device-wizard-dialog.component';
110 111 import { DeviceCredentialsComponent } from './device/device-credentials.component';
... ... @@ -202,6 +203,7 @@ import { AlarmRuleConditionDialogComponent } from '@home/components/profile/alar
202 203 AddDeviceProfileDialogComponent,
203 204 RuleChainAutocompleteComponent,
204 205 AlarmScheduleInfoComponent,
  206 + DeviceProfileProvisionConfigurationComponent,
205 207 AlarmScheduleComponent,
206 208 DeviceWizardDialogComponent,
207 209 DeviceCredentialsComponent,
... ... @@ -290,7 +292,9 @@ import { AlarmRuleConditionDialogComponent } from '@home/components/profile/alar
290 292 AlarmScheduleInfoComponent,
291 293 AlarmScheduleComponent,
292 294 AlarmScheduleDialogComponent,
293   - EditAlarmDetailsDialogComponent
  295 + EditAlarmDetailsDialogComponent,
  296 + DeviceProfileProvisionConfigurationComponent,
  297 + AlarmScheduleComponent
294 298 ],
295 299 providers: [
296 300 WidgetComponentService,
... ...
... ... @@ -96,6 +96,14 @@
96 96 </tb-device-profile-alarms>
97 97 </form>
98 98 </mat-step>
  99 + <mat-step [stepControl]="provisionConfigFormGroup" [optional]="true">
  100 + <form [formGroup]="provisionConfigFormGroup" style="padding-bottom: 16px;">
  101 + <ng-template matStepLabel>{{ 'device-profile.device-provisioning' | translate }}</ng-template>
  102 + <tb-device-profile-provision-configuration
  103 + formControlName="provisionConfiguration">
  104 + </tb-device-profile-provision-configuration>
  105 + </form>
  106 + </mat-step>
99 107 </mat-horizontal-stepper>
100 108 </div>
101 109 <div mat-dialog-actions fxLayout="column" fxLayoutAlign="start wrap" fxLayoutGap="8px" style="height: 100px;">
... ...
... ... @@ -36,7 +36,10 @@ import {
36 36 DeviceProfile,
37 37 DeviceProfileType,
38 38 deviceProfileTypeTranslationMap,
39   - DeviceTransportType, deviceTransportTypeHintMap,
  39 + DeviceProvisionConfiguration,
  40 + DeviceProvisionType,
  41 + DeviceTransportType,
  42 + deviceTransportTypeHintMap,
40 43 deviceTransportTypeTranslationMap
41 44 } from '@shared/models/device.models';
42 45 import { DeviceProfileService } from '@core/http/device-profile.service';
... ... @@ -84,6 +87,8 @@ export class AddDeviceProfileDialogComponent extends
84 87
85 88 alarmRulesFormGroup: FormGroup;
86 89
  90 + provisionConfigFormGroup: FormGroup;
  91 +
87 92 constructor(protected store: Store<AppState>,
88 93 protected router: Router,
89 94 @Inject(MAT_DIALOG_DATA) public data: AddDeviceProfileDialogData,
... ... @@ -118,6 +123,14 @@ export class AddDeviceProfileDialogComponent extends
118 123 alarms: [null]
119 124 }
120 125 );
  126 +
  127 + this.provisionConfigFormGroup = this.fb.group(
  128 + {
  129 + provisionConfiguration: [{
  130 + type: DeviceProvisionType.DISABLED
  131 + } as DeviceProvisionConfiguration, [Validators.required]]
  132 + }
  133 + );
121 134 }
122 135
123 136 private deviceProfileTransportTypeChanged() {
... ... @@ -138,7 +151,7 @@ export class AddDeviceProfileDialogComponent extends
138 151 }
139 152
140 153 nextStep() {
141   - if (this.selectedIndex < 2) {
  154 + if (this.selectedIndex < 3) {
142 155 this.addDeviceProfileStepper.next();
143 156 } else {
144 157 this.add();
... ... @@ -153,20 +166,28 @@ export class AddDeviceProfileDialogComponent extends
153 166 return this.transportConfigFormGroup;
154 167 case 2:
155 168 return this.alarmRulesFormGroup;
  169 + case 3:
  170 + return this.provisionConfigFormGroup;
156 171 }
157 172 }
158 173
159 174 add(): void {
160 175 if (this.allValid()) {
  176 + const deviceProvisionConfiguration: DeviceProvisionConfiguration = this.provisionConfigFormGroup.get('provisionConfiguration').value;
  177 + const provisionDeviceKey = deviceProvisionConfiguration.provisionDeviceKey;
  178 + delete deviceProvisionConfiguration.provisionDeviceKey;
161 179 const deviceProfile: DeviceProfile = {
162 180 name: this.deviceProfileDetailsFormGroup.get('name').value,
163 181 type: this.deviceProfileDetailsFormGroup.get('type').value,
164 182 transportType: this.transportConfigFormGroup.get('transportType').value,
  183 + provisionType: deviceProvisionConfiguration.type,
  184 + provisionDeviceKey,
165 185 description: this.deviceProfileDetailsFormGroup.get('description').value,
166 186 profileData: {
167 187 configuration: createDeviceProfileConfiguration(DeviceProfileType.DEFAULT),
168 188 transportConfiguration: this.transportConfigFormGroup.get('transportConfiguration').value,
169   - alarms: this.alarmRulesFormGroup.get('alarms').value
  189 + alarms: this.alarmRulesFormGroup.get('alarms').value,
  190 + provisionConfiguration: deviceProvisionConfiguration
170 191 }
171 192 };
172 193 if (this.deviceProfileDetailsFormGroup.get('defaultRuleChainId').value) {
... ... @@ -188,6 +209,8 @@ export class AddDeviceProfileDialogComponent extends
188 209 return 'device-profile.transport-configuration';
189 210 case 2:
190 211 return 'device-profile.alarm-rules';
  212 + case 3:
  213 + return 'device-profile.device-provisioning';
191 214 }
192 215 }
193 216
... ...
  1 +<!--
  2 +
  3 + Copyright © 2016-2020 The Thingsboard Authors
  4 +
  5 + Licensed under the Apache License, Version 2.0 (the "License");
  6 + you may not use this file except in compliance with the License.
  7 + You may obtain a copy of the License at
  8 +
  9 + http://www.apache.org/licenses/LICENSE-2.0
  10 +
  11 + Unless required by applicable law or agreed to in writing, software
  12 + distributed under the License is distributed on an "AS IS" BASIS,
  13 + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14 + See the License for the specific language governing permissions and
  15 + limitations under the License.
  16 +
  17 +-->
  18 +<div [formGroup]="provisionConfigurationFormGroup">
  19 + <mat-form-field class="mat-block">
  20 + <mat-label translate>device-profile.provision-strategy</mat-label>
  21 + <mat-select formControlName="type" required>
  22 + <mat-option *ngFor="let type of deviceProvisionTypes" [value]="type">
  23 + {{deviceProvisionTypeTranslateMap.get(type) | translate}}
  24 + </mat-option>
  25 + </mat-select>
  26 + <mat-error *ngIf="provisionConfigurationFormGroup.get('type').hasError('required')">
  27 + {{ 'device-profile.provision-strategy-required' | translate }}
  28 + </mat-error>
  29 + </mat-form-field>
  30 + <section *ngIf="provisionConfigurationFormGroup.get('type').value !== deviceProvisionType.DISABLED" fxLayoutGap.gt-xs="8px" fxLayout="row" fxLayout.xs="column">
  31 + <mat-form-field fxFlex class="mat-block">
  32 + <mat-label translate>device-profile.provision-device-key</mat-label>
  33 + <input matInput formControlName="provisionDeviceKey" required/>
  34 + <mat-error *ngIf="provisionConfigurationFormGroup.get('provisionDeviceKey').hasError('required')">
  35 + {{ 'device-profile.provision-device-key-required' | translate }}
  36 + </mat-error>
  37 + </mat-form-field>
  38 + <mat-form-field fxFlex class="mat-block">
  39 + <mat-label translate>device-profile.provision-device-secret</mat-label>
  40 + <input matInput formControlName="provisionDeviceSecret" required/>
  41 + <mat-error *ngIf="provisionConfigurationFormGroup.get('provisionDeviceSecret').hasError('required')">
  42 + {{ 'device-profile.provision-device-secret-required' | translate }}
  43 + </mat-error>
  44 + </mat-form-field>
  45 + </section>
  46 +</div>
... ...
  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 +
  17 +import { Component, forwardRef, Input, OnInit } from "@angular/core";
  18 +import {
  19 + ControlValueAccessor,
  20 + FormBuilder,
  21 + FormControl,
  22 + FormGroup,
  23 + NG_VALIDATORS,
  24 + NG_VALUE_ACCESSOR,
  25 + ValidationErrors,
  26 + Validator,
  27 + Validators
  28 +} from "@angular/forms";
  29 +import { coerceBooleanProperty } from "@angular/cdk/coercion";
  30 +import {
  31 + DeviceProvisionConfiguration,
  32 + DeviceProvisionType,
  33 + deviceProvisionTypeTranslationMap
  34 +} from "@shared/models/device.models";
  35 +import { isDefinedAndNotNull } from "@core/utils";
  36 +
  37 +@Component({
  38 + selector: 'tb-device-profile-provision-configuration',
  39 + templateUrl: './device-profile-provision-configuration.component.html',
  40 + styleUrls: [],
  41 + providers: [
  42 + {
  43 + provide: NG_VALUE_ACCESSOR,
  44 + useExisting: forwardRef(() => DeviceProfileProvisionConfigurationComponent),
  45 + multi: true
  46 + },
  47 + {
  48 + provide: NG_VALIDATORS,
  49 + useExisting: forwardRef(() => DeviceProfileProvisionConfigurationComponent),
  50 + multi: true,
  51 + }
  52 + ]
  53 +})
  54 +export class DeviceProfileProvisionConfigurationComponent implements ControlValueAccessor, OnInit, Validator {
  55 +
  56 + provisionConfigurationFormGroup: FormGroup;
  57 +
  58 + deviceProvisionType = DeviceProvisionType;
  59 + deviceProvisionTypes = Object.keys(DeviceProvisionType);
  60 + deviceProvisionTypeTranslateMap = deviceProvisionTypeTranslationMap;
  61 +
  62 + private requiredValue: boolean;
  63 + get required(): boolean {
  64 + return this.requiredValue;
  65 + }
  66 + @Input()
  67 + set required(value: boolean) {
  68 + this.requiredValue = coerceBooleanProperty(value);
  69 + }
  70 +
  71 + @Input()
  72 + disabled: boolean;
  73 +
  74 + private propagateChange = (v: any) => { };
  75 +
  76 + constructor(private fb: FormBuilder) {
  77 + }
  78 +
  79 + ngOnInit(): void {
  80 + this.provisionConfigurationFormGroup = this.fb.group({
  81 + type: [DeviceProvisionType.DISABLED, Validators.required],
  82 + provisionDeviceSecret: [{value: null, disabled: true}, Validators.required],
  83 + provisionDeviceKey: [{value: null, disabled: true}, Validators.required]
  84 + });
  85 + this.provisionConfigurationFormGroup.get('type').valueChanges.subscribe((type) => {
  86 + if (type === DeviceProvisionType.DISABLED) {
  87 + this.provisionConfigurationFormGroup.get('provisionDeviceSecret').disable({emitEvent: false});
  88 + this.provisionConfigurationFormGroup.get('provisionDeviceSecret').patchValue(null,{emitEvent: false});
  89 + this.provisionConfigurationFormGroup.get('provisionDeviceKey').disable({emitEvent: false});
  90 + this.provisionConfigurationFormGroup.get('provisionDeviceKey').patchValue(null);
  91 + } else {
  92 + this.provisionConfigurationFormGroup.get('provisionDeviceSecret').enable({emitEvent: false});
  93 + this.provisionConfigurationFormGroup.get('provisionDeviceKey').enable({emitEvent: false});
  94 + }
  95 + });
  96 + this.provisionConfigurationFormGroup.valueChanges.subscribe(() => {
  97 + this.updateModel();
  98 + });
  99 + }
  100 +
  101 + registerOnChange(fn: any): void {
  102 + this.propagateChange = fn;
  103 + }
  104 +
  105 + registerOnTouched(fn: any): void {
  106 + }
  107 +
  108 + writeValue(value: DeviceProvisionConfiguration | null): void {
  109 + if (isDefinedAndNotNull(value)){
  110 + this.provisionConfigurationFormGroup.patchValue(value, {emitEvent: false});
  111 + } else {
  112 + this.provisionConfigurationFormGroup.patchValue({type: DeviceProvisionType.DISABLED});
  113 + }
  114 + }
  115 +
  116 + setDisabledState(isDisabled: boolean){
  117 + this.disabled = isDisabled;
  118 + if (this.disabled){
  119 + this.provisionConfigurationFormGroup.disable();
  120 + } else {
  121 + this.provisionConfigurationFormGroup.enable({emitEvent: false});
  122 + }
  123 + }
  124 +
  125 + validate(c: FormControl): ValidationErrors | null {
  126 + return (this.provisionConfigurationFormGroup.valid) ? null : {
  127 + provisionConfiguration: {
  128 + valid: false,
  129 + },
  130 + };
  131 + }
  132 +
  133 + private updateModel(): void {
  134 + let deviceProvisionConfiguration: DeviceProvisionConfiguration = null;
  135 + if (this.provisionConfigurationFormGroup.valid) {
  136 + deviceProvisionConfiguration = this.provisionConfigurationFormGroup.getRawValue();
  137 + }
  138 + this.propagateChange(deviceProvisionConfiguration);
  139 + }
  140 +}
... ...
... ... @@ -24,13 +24,15 @@ import { EntityTableConfig } from '@home/models/entity/entities-table-config.mod
24 24 import { EntityComponent } from '../entity/entity.component';
25 25 import {
26 26 createDeviceProfileConfiguration,
  27 + createDeviceProfileTransportConfiguration,
27 28 DeviceProfile,
28 29 DeviceProfileData,
29 30 DeviceProfileType,
30 31 deviceProfileTypeTranslationMap,
  32 + DeviceProvisionConfiguration,
  33 + DeviceProvisionType,
31 34 DeviceTransportType,
32   - deviceTransportTypeTranslationMap,
33   - createDeviceProfileTransportConfiguration
  35 + deviceTransportTypeTranslationMap
34 36 } from '@shared/models/device.models';
35 37 import { EntityType } from '@shared/models/entity-type.models';
36 38 import { RuleChainId } from '@shared/models/id/rule-chain-id';
... ... @@ -72,15 +74,23 @@ export class DeviceProfileComponent extends EntityComponent<DeviceProfile> {
72 74 }
73 75
74 76 buildForm(entity: DeviceProfile): FormGroup {
  77 + const deviceProvisionConfiguration: DeviceProvisionConfiguration = {
  78 + type: entity?.provisionType ? entity?.provisionType : DeviceProvisionType.DISABLED,
  79 + provisionDeviceKey: entity?.provisionDeviceKey,
  80 + provisionDeviceSecret: entity?.profileData?.provisionConfiguration?.provisionDeviceSecret
  81 + };
75 82 const form = this.fb.group(
76 83 {
77 84 name: [entity ? entity.name : '', [Validators.required]],
78 85 type: [entity ? entity.type : null, [Validators.required]],
79 86 transportType: [entity ? entity.transportType : null, [Validators.required]],
  87 + provisionType: [deviceProvisionConfiguration.type, [Validators.required]],
  88 + provisionDeviceKey: [deviceProvisionConfiguration.provisionDeviceKey],
80 89 profileData: this.fb.group({
81 90 configuration: [entity && !this.isAdd ? entity.profileData?.configuration : {}, Validators.required],
82 91 transportConfiguration: [entity && !this.isAdd ? entity.profileData?.transportConfiguration : {}, Validators.required],
83   - alarms: [entity && !this.isAdd ? entity.profileData?.alarms : []]
  92 + alarms: [entity && !this.isAdd ? entity.profileData?.alarms : []],
  93 + provisionConfiguration: [deviceProvisionConfiguration, Validators.required]
84 94 }),
85 95 defaultRuleChainId: [entity && entity.defaultRuleChainId ? entity.defaultRuleChainId.id : null, []],
86 96 description: [entity ? entity.description : '', []],
... ... @@ -100,6 +110,7 @@ export class DeviceProfileComponent extends EntityComponent<DeviceProfile> {
100 110 if (entity && !entity.id) {
101 111 form.get('type').patchValue(DeviceProfileType.DEFAULT, {emitEvent: true});
102 112 form.get('transportType').patchValue(DeviceTransportType.DEFAULT, {emitEvent: true});
  113 + form.get('provisionType').patchValue(DeviceProvisionType.DISABLED, {emitEvent: true});
103 114 }
104 115 }
105 116
... ... @@ -130,10 +141,22 @@ export class DeviceProfileComponent extends EntityComponent<DeviceProfile> {
130 141 }
131 142
132 143 updateForm(entity: DeviceProfile) {
  144 + const deviceProvisionConfiguration: DeviceProvisionConfiguration = {
  145 + type: entity?.provisionType ? entity?.provisionType : DeviceProvisionType.DISABLED,
  146 + provisionDeviceKey: entity?.provisionDeviceKey,
  147 + provisionDeviceSecret: entity?.profileData?.provisionConfiguration?.provisionDeviceSecret
  148 + };
133 149 this.entityForm.patchValue({name: entity.name});
134 150 this.entityForm.patchValue({type: entity.type}, {emitEvent: false});
135 151 this.entityForm.patchValue({transportType: entity.transportType}, {emitEvent: false});
136   - this.entityForm.patchValue({profileData: entity.profileData});
  152 + this.entityForm.patchValue({provisionType: entity.provisionType}, {emitEvent: false});
  153 + this.entityForm.patchValue({provisionDeviceKey: entity.provisionDeviceKey}, {emitEvent: false});
  154 + this.entityForm.patchValue({profileData: {
  155 + configuration: entity.profileData?.configuration,
  156 + transportConfiguration: entity.profileData?.transportConfiguration,
  157 + alarms: entity.profileData?.alarms,
  158 + provisionConfiguration: deviceProvisionConfiguration
  159 + }});
137 160 this.entityForm.patchValue({defaultRuleChainId: entity.defaultRuleChainId ? entity.defaultRuleChainId.id : null});
138 161 this.entityForm.patchValue({description: entity.description});
139 162 }
... ... @@ -142,6 +165,10 @@ export class DeviceProfileComponent extends EntityComponent<DeviceProfile> {
142 165 if (formValue.defaultRuleChainId) {
143 166 formValue.defaultRuleChainId = new RuleChainId(formValue.defaultRuleChainId);
144 167 }
  168 + const deviceProvisionConfiguration: DeviceProvisionConfiguration = formValue.profileData.provisionConfiguration;
  169 + formValue.provisionType = deviceProvisionConfiguration.type;
  170 + formValue.provisionDeviceKey = deviceProvisionConfiguration.provisionDeviceKey;
  171 + delete deviceProvisionConfiguration.provisionDeviceKey;
145 172 return super.prepareFormValue(formValue);
146 173 }
147 174
... ...
... ... @@ -121,6 +121,14 @@
121 121 </tb-device-profile-alarms>
122 122 </form>
123 123 </mat-step>
  124 + <mat-step [stepControl]="provisionConfigFormGroup" [optional]="true" *ngIf="createProfile">
  125 + <form [formGroup]="provisionConfigFormGroup" style="padding-bottom: 16px;">
  126 + <ng-template matStepLabel>{{ 'device-profile.device-provisioning' | translate }}</ng-template>
  127 + <tb-device-profile-provision-configuration
  128 + formControlName="provisionConfiguration">
  129 + </tb-device-profile-provision-configuration>
  130 + </form>
  131 + </mat-step>
124 132 <mat-step [stepControl]="credentialsFormGroup" [optional]="true">
125 133 <ng-template matStepLabel>{{ 'device.credentials' | translate }}</ng-template>
126 134 <form [formGroup]="credentialsFormGroup" style="padding-bottom: 16px;">
... ...
... ... @@ -25,7 +25,7 @@ import {
25 25 createDeviceProfileConfiguration,
26 26 createDeviceProfileTransportConfiguration,
27 27 DeviceProfile,
28   - DeviceProfileType,
  28 + DeviceProfileType, DeviceProvisionConfiguration, DeviceProvisionType,
29 29 DeviceTransportType, deviceTransportTypeConfigurationInfoMap, deviceTransportTypeHintMap,
30 30 deviceTransportTypeTranslationMap
31 31 } from '@shared/models/device.models';
... ... @@ -75,6 +75,8 @@ export class DeviceWizardDialogComponent extends
75 75
76 76 alarmRulesFormGroup: FormGroup;
77 77
  78 + provisionConfigFormGroup: FormGroup;
  79 +
78 80 credentialsFormGroup: FormGroup;
79 81
80 82 customerFormGroup: FormGroup;
... ... @@ -142,6 +144,14 @@ export class DeviceWizardDialogComponent extends
142 144 }
143 145 );
144 146
  147 + this.provisionConfigFormGroup = this.fb.group(
  148 + {
  149 + provisionConfiguration: [{
  150 + type: DeviceProvisionType.DISABLED
  151 + } as DeviceProvisionConfiguration, [Validators.required]]
  152 + }
  153 + );
  154 +
145 155 this.credentialsFormGroup = this.fb.group({
146 156 setCredential: [false],
147 157 credential: [{value: null, disabled: true}]
... ... @@ -201,7 +211,7 @@ export class DeviceWizardDialogComponent extends
201 211 getFormLabel(index: number): string {
202 212 if (index > 0) {
203 213 if (!this.createProfile) {
204   - index += 2;
  214 + index += 3;
205 215 } else if (!this.createTransportConfiguration) {
206 216 index += 1;
207 217 }
... ... @@ -214,8 +224,10 @@ export class DeviceWizardDialogComponent extends
214 224 case 2:
215 225 return 'device-profile.alarm-rules';
216 226 case 3:
217   - return 'device.credentials';
  227 + return 'device-profile.device-provisioning';
218 228 case 4:
  229 + return 'device.credentials';
  230 + case 5:
219 231 return 'customer.customer';
220 232 }
221 233 }
... ... @@ -246,14 +258,20 @@ export class DeviceWizardDialogComponent extends
246 258
247 259 private createDeviceProfile(): Observable<EntityId> {
248 260 if (this.deviceWizardFormGroup.get('addProfileType').value) {
  261 + const deviceProvisionConfiguration: DeviceProvisionConfiguration = this.provisionConfigFormGroup.get('provisionConfiguration').value;
  262 + const provisionDeviceKey = deviceProvisionConfiguration.provisionDeviceKey;
  263 + delete deviceProvisionConfiguration.provisionDeviceKey;
249 264 const deviceProfile: DeviceProfile = {
250 265 name: this.deviceWizardFormGroup.get('newDeviceProfileTitle').value,
251 266 type: DeviceProfileType.DEFAULT,
252 267 transportType: this.deviceWizardFormGroup.get('transportType').value,
  268 + provisionType: deviceProvisionConfiguration.type,
  269 + provisionDeviceKey,
253 270 profileData: {
254 271 configuration: createDeviceProfileConfiguration(DeviceProfileType.DEFAULT),
255 272 transportConfiguration: this.transportConfigFormGroup.get('transportConfiguration').value,
256   - alarms: this.alarmRulesFormGroup.get('alarms').value
  273 + alarms: this.alarmRulesFormGroup.get('alarms').value,
  274 + provisionConfiguration: deviceProvisionConfiguration
257 275 }
258 276 };
259 277 return this.deviceProfileService.saveDeviceProfile(deviceProfile).pipe(
... ...
... ... @@ -50,6 +50,15 @@
50 50 </div>
51 51 </div>
52 52 </mat-tab>
  53 +<mat-tab *ngIf="entity"
  54 + label="{{ 'device-profile.device-provisioning' | translate }}" #deviceProvisioning="matTab">
  55 + <div class="mat-padding" [formGroup]="detailsForm">
  56 + <div formGroupName="profileData">
  57 + <tb-device-profile-provision-configuration formControlName="provisionConfiguration">
  58 + </tb-device-profile-provision-configuration>
  59 + </div>
  60 + </div>
  61 +</mat-tab>
53 62 <mat-tab *ngIf="false"
54 63 label="{{'device-profile.profile-configuration' | translate }}" #deviceProfile="matTab">
55 64 <div class="mat-padding" [formGroup]="detailsForm">
... ...
... ... @@ -43,6 +43,12 @@ export enum MqttTransportPayloadType {
43 43 PROTOBUF = 'PROTOBUF'
44 44 }
45 45
  46 +export enum DeviceProvisionType {
  47 + DISABLED = 'DISABLED',
  48 + ALLOW_CREATE_NEW_DEVICES = 'ALLOW_CREATE_NEW_DEVICES',
  49 + CHECK_PRE_PROVISIONED_DEVICES = 'CHECK_PRE_PROVISIONED_DEVICES'
  50 +}
  51 +
46 52 export interface DeviceConfigurationFormInfo {
47 53 hasProfileConfiguration: boolean;
48 54 hasDeviceConfiguration: boolean;
... ... @@ -74,6 +80,15 @@ export const deviceTransportTypeTranslationMap = new Map<DeviceTransportType, st
74 80 ]
75 81 );
76 82
  83 +
  84 +export const deviceProvisionTypeTranslationMap = new Map<DeviceProvisionType, string>(
  85 + [
  86 + [DeviceProvisionType.DISABLED, 'device-profile.provision-strategy-disabled'],
  87 + [DeviceProvisionType.ALLOW_CREATE_NEW_DEVICES, 'device-profile.provision-strategy-created-new'],
  88 + [DeviceProvisionType.CHECK_PRE_PROVISIONED_DEVICES, 'device-profile.provision-strategy-check-pre-provisioned']
  89 + ]
  90 +)
  91 +
77 92 export const deviceTransportTypeHintMap = new Map<DeviceTransportType, string>(
78 93 [
79 94 [DeviceTransportType.DEFAULT, 'device-profile.transport-type-default-hint'],
... ... @@ -148,6 +163,12 @@ export interface DeviceProfileTransportConfiguration extends DeviceProfileTransp
148 163 type: DeviceTransportType;
149 164 }
150 165
  166 +export interface DeviceProvisionConfiguration {
  167 + type: DeviceProvisionType;
  168 + provisionDeviceSecret?: string;
  169 + provisionDeviceKey?: string;
  170 +}
  171 +
151 172 export function createDeviceProfileConfiguration(type: DeviceProfileType): DeviceProfileConfiguration {
152 173 let configuration: DeviceProfileConfiguration = null;
153 174 if (type) {
... ... @@ -295,6 +316,7 @@ export interface DeviceProfileData {
295 316 configuration: DeviceProfileConfiguration;
296 317 transportConfiguration: DeviceProfileTransportConfiguration;
297 318 alarms?: Array<DeviceProfileAlarm>;
  319 + provisionConfiguration?: DeviceProvisionConfiguration;
298 320 }
299 321
300 322 export interface DeviceProfile extends BaseData<DeviceProfileId> {
... ... @@ -304,6 +326,8 @@ export interface DeviceProfile extends BaseData<DeviceProfileId> {
304 326 default?: boolean;
305 327 type: DeviceProfileType;
306 328 transportType: DeviceTransportType;
  329 + provisionType: DeviceProvisionType;
  330 + provisionDeviceKey?: string;
307 331 defaultRuleChainId?: RuleChainId;
308 332 profileData: DeviceProfileData;
309 333 }
... ...
... ... @@ -930,6 +930,16 @@
930 930 "alarm-rule-condition": "Alarm rule condition",
931 931 "enter-alarm-rule-condition-prompt": "Please add alarm rule condition",
932 932 "edit-alarm-rule-condition": "Edit alarm rule condition",
  933 + "device-provisioning": "Device provisioning",
  934 + "provision-strategy": "Provision strategy",
  935 + "provision-strategy-required": "Provision strategy is required.",
  936 + "provision-strategy-disabled": "Disabled",
  937 + "provision-strategy-created-new": "Allow create new devices",
  938 + "provision-strategy-check-pre-provisioned": "Check pre provisioned devices",
  939 + "provision-device-key": "Provision device key",
  940 + "provision-device-key-required": "Provision device key is required.",
  941 + "provision-device-secret": "Provision device secret",
  942 + "provision-device-secret-required": "Provision device secret is required.",
933 943 "condition": "Condition",
934 944 "condition-type": "Condition type",
935 945 "condition-type-simple": "Simple",
... ...