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,13 +89,16 @@ CREATE TABLE IF NOT EXISTS device_profile (
89 name varchar(255), 89 name varchar(255),
90 type varchar(255), 90 type varchar(255),
91 transport_type varchar(255), 91 transport_type varchar(255),
  92 + provision_type varchar(255),
92 profile_data jsonb, 93 profile_data jsonb,
93 description varchar, 94 description varchar,
94 search_text varchar(255), 95 search_text varchar(255),
95 is_default boolean, 96 is_default boolean,
96 tenant_id uuid, 97 tenant_id uuid,
97 default_rule_chain_id uuid, 98 default_rule_chain_id uuid,
  99 + provision_device_key varchar,
98 CONSTRAINT device_profile_name_unq_key UNIQUE (tenant_id, name), 100 CONSTRAINT device_profile_name_unq_key UNIQUE (tenant_id, name),
  101 + CONSTRAINT device_provision_key_unq_key UNIQUE (provision_device_key),
99 CONSTRAINT fk_default_rule_chain_device_profile FOREIGN KEY (default_rule_chain_id) REFERENCES rule_chain(id) 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,6 +700,12 @@ public abstract class BaseController {
700 case ASSIGNED_TO_TENANT: 700 case ASSIGNED_TO_TENANT:
701 msgType = DataConstants.ENTITY_ASSIGNED_TO_TENANT; 701 msgType = DataConstants.ENTITY_ASSIGNED_TO_TENANT;
702 break; 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 if (!StringUtils.isEmpty(msgType)) { 710 if (!StringUtils.isEmpty(msgType)) {
705 try { 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,7 +23,6 @@ import com.google.common.util.concurrent.ListenableFuture;
23 import com.google.common.util.concurrent.MoreExecutors; 23 import com.google.common.util.concurrent.MoreExecutors;
24 import com.google.protobuf.ByteString; 24 import com.google.protobuf.ByteString;
25 import lombok.extern.slf4j.Slf4j; 25 import lombok.extern.slf4j.Slf4j;
26 -import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;  
27 import org.springframework.stereotype.Service; 26 import org.springframework.stereotype.Service;
28 import org.springframework.util.StringUtils; 27 import org.springframework.util.StringUtils;
29 import org.thingsboard.server.common.data.DataConstants; 28 import org.thingsboard.server.common.data.DataConstants;
@@ -31,6 +30,8 @@ import org.thingsboard.server.common.data.Device; @@ -31,6 +30,8 @@ import org.thingsboard.server.common.data.Device;
31 import org.thingsboard.server.common.data.DeviceProfile; 30 import org.thingsboard.server.common.data.DeviceProfile;
32 import org.thingsboard.server.common.data.TenantProfile; 31 import org.thingsboard.server.common.data.TenantProfile;
33 import org.thingsboard.server.common.data.device.credentials.BasicMqttCredentials; 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 import org.thingsboard.server.common.data.id.CustomerId; 35 import org.thingsboard.server.common.data.id.CustomerId;
35 import org.thingsboard.server.common.data.id.DeviceId; 36 import org.thingsboard.server.common.data.id.DeviceId;
36 import org.thingsboard.server.common.data.id.DeviceProfileId; 37 import org.thingsboard.server.common.data.id.DeviceProfileId;
@@ -45,7 +46,10 @@ import org.thingsboard.server.common.msg.TbMsgMetaData; @@ -45,7 +46,10 @@ import org.thingsboard.server.common.msg.TbMsgMetaData;
45 import org.thingsboard.server.common.transport.util.DataDecodingEncodingService; 46 import org.thingsboard.server.common.transport.util.DataDecodingEncodingService;
46 import org.thingsboard.server.dao.device.DeviceCredentialsService; 47 import org.thingsboard.server.dao.device.DeviceCredentialsService;
47 import org.thingsboard.server.dao.device.DeviceProfileService; 48 import org.thingsboard.server.dao.device.DeviceProfileService;
  49 +import org.thingsboard.server.dao.device.DeviceProvisionService;
48 import org.thingsboard.server.dao.device.DeviceService; 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 import org.thingsboard.server.dao.relation.RelationService; 53 import org.thingsboard.server.dao.relation.RelationService;
50 import org.thingsboard.server.dao.tenant.TenantProfileService; 54 import org.thingsboard.server.dao.tenant.TenantProfileService;
51 import org.thingsboard.server.dao.tenant.TenantService; 55 import org.thingsboard.server.dao.tenant.TenantService;
@@ -56,6 +60,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.GetOrCreateDeviceFro @@ -56,6 +60,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.GetOrCreateDeviceFro
56 import org.thingsboard.server.gen.transport.TransportProtos.GetOrCreateDeviceFromGatewayResponseMsg; 60 import org.thingsboard.server.gen.transport.TransportProtos.GetOrCreateDeviceFromGatewayResponseMsg;
57 import org.thingsboard.server.gen.transport.TransportProtos.GetTenantRoutingInfoRequestMsg; 61 import org.thingsboard.server.gen.transport.TransportProtos.GetTenantRoutingInfoRequestMsg;
58 import org.thingsboard.server.gen.transport.TransportProtos.GetTenantRoutingInfoResponseMsg; 62 import org.thingsboard.server.gen.transport.TransportProtos.GetTenantRoutingInfoResponseMsg;
  63 +import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceRequestMsg;
59 import org.thingsboard.server.gen.transport.TransportProtos.TransportApiRequestMsg; 64 import org.thingsboard.server.gen.transport.TransportProtos.TransportApiRequestMsg;
60 import org.thingsboard.server.gen.transport.TransportProtos.TransportApiResponseMsg; 65 import org.thingsboard.server.gen.transport.TransportProtos.TransportApiResponseMsg;
61 import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceCredentialsResponseMsg; 66 import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceCredentialsResponseMsg;
@@ -63,6 +68,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceTokenR @@ -63,6 +68,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceTokenR
63 import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceX509CertRequestMsg; 68 import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceX509CertRequestMsg;
64 import org.thingsboard.server.queue.common.TbProtoQueueMsg; 69 import org.thingsboard.server.queue.common.TbProtoQueueMsg;
65 import org.thingsboard.server.queue.util.TbCoreComponent; 70 import org.thingsboard.server.queue.util.TbCoreComponent;
  71 +import org.thingsboard.server.dao.device.provision.ProvisionFailedException;
66 import org.thingsboard.server.service.executors.DbCallbackExecutorService; 72 import org.thingsboard.server.service.executors.DbCallbackExecutorService;
67 import org.thingsboard.server.service.queue.TbClusterService; 73 import org.thingsboard.server.service.queue.TbClusterService;
68 import org.thingsboard.server.service.state.DeviceStateService; 74 import org.thingsboard.server.service.state.DeviceStateService;
@@ -94,6 +100,7 @@ public class DefaultTransportApiService implements TransportApiService { @@ -94,6 +100,7 @@ public class DefaultTransportApiService implements TransportApiService {
94 private final DbCallbackExecutorService dbCallbackExecutorService; 100 private final DbCallbackExecutorService dbCallbackExecutorService;
95 private final TbClusterService tbClusterService; 101 private final TbClusterService tbClusterService;
96 private final DataDecodingEncodingService dataDecodingEncodingService; 102 private final DataDecodingEncodingService dataDecodingEncodingService;
  103 + private final DeviceProvisionService deviceProvisionService;
97 104
98 105
99 private final ConcurrentMap<String, ReentrantLock> deviceCreationLocks = new ConcurrentHashMap<>(); 106 private final ConcurrentMap<String, ReentrantLock> deviceCreationLocks = new ConcurrentHashMap<>();
@@ -102,7 +109,8 @@ public class DefaultTransportApiService implements TransportApiService { @@ -102,7 +109,8 @@ public class DefaultTransportApiService implements TransportApiService {
102 TenantProfileService tenantProfileService, DeviceService deviceService, 109 TenantProfileService tenantProfileService, DeviceService deviceService,
103 RelationService relationService, DeviceCredentialsService deviceCredentialsService, 110 RelationService relationService, DeviceCredentialsService deviceCredentialsService,
104 DeviceStateService deviceStateService, DbCallbackExecutorService dbCallbackExecutorService, 111 DeviceStateService deviceStateService, DbCallbackExecutorService dbCallbackExecutorService,
105 - TbClusterService tbClusterService, DataDecodingEncodingService dataDecodingEncodingService) { 112 + TbClusterService tbClusterService, DataDecodingEncodingService dataDecodingEncodingService,
  113 + DeviceProvisionService deviceProvisionService) {
106 this.deviceProfileService = deviceProfileService; 114 this.deviceProfileService = deviceProfileService;
107 this.tenantService = tenantService; 115 this.tenantService = tenantService;
108 this.tenantProfileService = tenantProfileService; 116 this.tenantProfileService = tenantProfileService;
@@ -113,6 +121,7 @@ public class DefaultTransportApiService implements TransportApiService { @@ -113,6 +121,7 @@ public class DefaultTransportApiService implements TransportApiService {
113 this.dbCallbackExecutorService = dbCallbackExecutorService; 121 this.dbCallbackExecutorService = dbCallbackExecutorService;
114 this.tbClusterService = tbClusterService; 122 this.tbClusterService = tbClusterService;
115 this.dataDecodingEncodingService = dataDecodingEncodingService; 123 this.dataDecodingEncodingService = dataDecodingEncodingService;
  124 + this.deviceProvisionService = deviceProvisionService;
116 } 125 }
117 126
118 @Override 127 @Override
@@ -139,6 +148,9 @@ public class DefaultTransportApiService implements TransportApiService { @@ -139,6 +148,9 @@ public class DefaultTransportApiService implements TransportApiService {
139 } else if (transportApiRequestMsg.hasGetDeviceProfileRequestMsg()) { 148 } else if (transportApiRequestMsg.hasGetDeviceProfileRequestMsg()) {
140 return Futures.transform(handle(transportApiRequestMsg.getGetDeviceProfileRequestMsg()), 149 return Futures.transform(handle(transportApiRequestMsg.getGetDeviceProfileRequestMsg()),
141 value -> new TbProtoQueueMsg<>(tbProtoQueueMsg.getKey(), value, tbProtoQueueMsg.getHeaders()), MoreExecutors.directExecutor()); 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 return Futures.transform(getEmptyTransportApiResponseFuture(), 155 return Futures.transform(getEmptyTransportApiResponseFuture(),
144 value -> new TbProtoQueueMsg<>(tbProtoQueueMsg.getKey(), value, tbProtoQueueMsg.getHeaders()), MoreExecutors.directExecutor()); 156 value -> new TbProtoQueueMsg<>(tbProtoQueueMsg.getKey(), value, tbProtoQueueMsg.getHeaders()), MoreExecutors.directExecutor());
@@ -263,6 +275,50 @@ public class DefaultTransportApiService implements TransportApiService { @@ -263,6 +275,50 @@ public class DefaultTransportApiService implements TransportApiService {
263 }, dbCallbackExecutorService); 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 private ListenableFuture<TransportApiResponseMsg> handle(GetTenantRoutingInfoRequestMsg requestMsg) { 322 private ListenableFuture<TransportApiResponseMsg> handle(GetTenantRoutingInfoRequestMsg requestMsg) {
267 TenantId tenantId = new TenantId(new UUID(requestMsg.getTenantIdMSB(), requestMsg.getTenantIdLSB())); 323 TenantId tenantId = new TenantId(new UUID(requestMsg.getTenantIdMSB(), requestMsg.getTenantIdLSB()));
268 // TODO: Tenant Profile from cache 324 // TODO: Tenant Profile from cache
@@ -68,6 +68,7 @@ import org.thingsboard.server.common.data.User; @@ -68,6 +68,7 @@ import org.thingsboard.server.common.data.User;
68 import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileConfiguration; 68 import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileConfiguration;
69 import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileTransportConfiguration; 69 import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileTransportConfiguration;
70 import org.thingsboard.server.common.data.device.profile.DeviceProfileData; 70 import org.thingsboard.server.common.data.device.profile.DeviceProfileData;
  71 +import org.thingsboard.server.common.data.device.profile.ProvisionDeviceProfileCredentials;
71 import org.thingsboard.server.common.data.id.HasId; 72 import org.thingsboard.server.common.data.id.HasId;
72 import org.thingsboard.server.common.data.id.RuleChainId; 73 import org.thingsboard.server.common.data.id.RuleChainId;
73 import org.thingsboard.server.common.data.id.TenantId; 74 import org.thingsboard.server.common.data.id.TenantId;
@@ -28,6 +28,7 @@ import org.thingsboard.server.common.data.DeviceProfileType; @@ -28,6 +28,7 @@ import org.thingsboard.server.common.data.DeviceProfileType;
28 import org.thingsboard.server.common.data.DeviceTransportType; 28 import org.thingsboard.server.common.data.DeviceTransportType;
29 import org.thingsboard.server.common.data.Tenant; 29 import org.thingsboard.server.common.data.Tenant;
30 import org.thingsboard.server.common.data.User; 30 import org.thingsboard.server.common.data.User;
  31 +import org.thingsboard.server.common.data.device.profile.ProvisionDeviceProfileCredentials;
31 import org.thingsboard.server.common.data.page.PageData; 32 import org.thingsboard.server.common.data.page.PageData;
32 import org.thingsboard.server.common.data.page.PageLink; 33 import org.thingsboard.server.common.data.page.PageLink;
33 import org.thingsboard.server.common.data.security.Authority; 34 import org.thingsboard.server.common.data.security.Authority;
@@ -153,6 +154,17 @@ public abstract class BaseDeviceProfileControllerTest extends AbstractController @@ -153,6 +154,17 @@ public abstract class BaseDeviceProfileControllerTest extends AbstractController
153 .andExpect(statusReason(containsString("Device profile with such name already exists"))); 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 @Ignore 168 @Ignore
157 @Test 169 @Test
158 public void testChangeDeviceProfileTypeWithExistingDevices() throws Exception { 170 public void testChangeDeviceProfileTypeWithExistingDevices() throws Exception {
@@ -26,13 +26,18 @@ import org.junit.Assert; @@ -26,13 +26,18 @@ import org.junit.Assert;
26 import org.springframework.util.StringUtils; 26 import org.springframework.util.StringUtils;
27 import org.thingsboard.server.common.data.Device; 27 import org.thingsboard.server.common.data.Device;
28 import org.thingsboard.server.common.data.DeviceProfile; 28 import org.thingsboard.server.common.data.DeviceProfile;
  29 +import org.thingsboard.server.common.data.DeviceProfileProvisionType;
29 import org.thingsboard.server.common.data.DeviceProfileType; 30 import org.thingsboard.server.common.data.DeviceProfileType;
30 import org.thingsboard.server.common.data.DeviceTransportType; 31 import org.thingsboard.server.common.data.DeviceTransportType;
31 import org.thingsboard.server.common.data.Tenant; 32 import org.thingsboard.server.common.data.Tenant;
32 import org.thingsboard.server.common.data.TransportPayloadType; 33 import org.thingsboard.server.common.data.TransportPayloadType;
33 import org.thingsboard.server.common.data.User; 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 import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileConfiguration; 37 import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileConfiguration;
35 import org.thingsboard.server.common.data.device.profile.DeviceProfileData; 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 import org.thingsboard.server.common.data.device.profile.MqttDeviceProfileTransportConfiguration; 41 import org.thingsboard.server.common.data.device.profile.MqttDeviceProfileTransportConfiguration;
37 import org.thingsboard.server.common.data.security.Authority; 42 import org.thingsboard.server.common.data.security.Authority;
38 import org.thingsboard.server.common.data.security.DeviceCredentials; 43 import org.thingsboard.server.common.data.security.DeviceCredentials;
@@ -64,7 +69,18 @@ public abstract class AbstractMqttIntegrationTest extends AbstractControllerTest @@ -64,7 +69,18 @@ public abstract class AbstractMqttIntegrationTest extends AbstractControllerTest
64 protected Device savedGateway; 69 protected Device savedGateway;
65 protected String gatewayAccessToken; 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 loginSysAdmin(); 84 loginSysAdmin();
69 85
70 Tenant tenant = new Tenant(); 86 Tenant tenant = new Tenant();
@@ -93,7 +109,7 @@ public abstract class AbstractMqttIntegrationTest extends AbstractControllerTest @@ -93,7 +109,7 @@ public abstract class AbstractMqttIntegrationTest extends AbstractControllerTest
93 gateway.setAdditionalInfo(additionalInfo); 109 gateway.setAdditionalInfo(additionalInfo);
94 110
95 if (payloadType != null) { 111 if (payloadType != null) {
96 - DeviceProfile mqttDeviceProfile = createMqttDeviceProfile(payloadType, telemetryTopic, attributesTopic); 112 + DeviceProfile mqttDeviceProfile = createMqttDeviceProfile(payloadType, telemetryTopic, attributesTopic, provisionType, provisionKey, provisionSecret);
97 DeviceProfile savedDeviceProfile = doPost("/api/deviceProfile", mqttDeviceProfile, DeviceProfile.class); 113 DeviceProfile savedDeviceProfile = doPost("/api/deviceProfile", mqttDeviceProfile, DeviceProfile.class);
98 device.setType(savedDeviceProfile.getName()); 114 device.setType(savedDeviceProfile.getName());
99 device.setDeviceProfileId(savedDeviceProfile.getId()); 115 device.setDeviceProfileId(savedDeviceProfile.getId());
@@ -183,11 +199,17 @@ public abstract class AbstractMqttIntegrationTest extends AbstractControllerTest @@ -183,11 +199,17 @@ public abstract class AbstractMqttIntegrationTest extends AbstractControllerTest
183 return keyValueProtoBuilder.build(); 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 DeviceProfile deviceProfile = new DeviceProfile(); 207 DeviceProfile deviceProfile = new DeviceProfile();
188 deviceProfile.setName(transportPayloadType.name()); 208 deviceProfile.setName(transportPayloadType.name());
189 deviceProfile.setType(DeviceProfileType.DEFAULT); 209 deviceProfile.setType(DeviceProfileType.DEFAULT);
190 deviceProfile.setTransportType(DeviceTransportType.MQTT); 210 deviceProfile.setTransportType(DeviceTransportType.MQTT);
  211 + deviceProfile.setProvisionType(provisionType);
  212 + deviceProfile.setProvisionDeviceKey(provisionKey);
191 deviceProfile.setDescription(transportPayloadType.name() + " Test"); 213 deviceProfile.setDescription(transportPayloadType.name() + " Test");
192 DeviceProfileData deviceProfileData = new DeviceProfileData(); 214 DeviceProfileData deviceProfileData = new DeviceProfileData();
193 DefaultDeviceProfileConfiguration configuration = new DefaultDeviceProfileConfiguration(); 215 DefaultDeviceProfileConfiguration configuration = new DefaultDeviceProfileConfiguration();
@@ -200,6 +222,19 @@ public abstract class AbstractMqttIntegrationTest extends AbstractControllerTest @@ -200,6 +222,19 @@ public abstract class AbstractMqttIntegrationTest extends AbstractControllerTest
200 transportConfiguration.setDeviceAttributesTopic(attributesTopic); 222 transportConfiguration.setDeviceAttributesTopic(attributesTopic);
201 } 223 }
202 deviceProfileData.setTransportConfiguration(transportConfiguration); 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 deviceProfileData.setConfiguration(configuration); 238 deviceProfileData.setConfiguration(configuration);
204 deviceProfile.setProfileData(deviceProfileData); 239 deviceProfile.setProfileData(deviceProfileData);
205 deviceProfile.setDefault(false); 240 deviceProfile.setDefault(false);
@@ -31,7 +31,8 @@ import java.util.Arrays; @@ -31,7 +31,8 @@ import java.util.Arrays;
31 "org.thingsboard.server.mqtt.telemetry.attributes.sql.*Test", 31 "org.thingsboard.server.mqtt.telemetry.attributes.sql.*Test",
32 "org.thingsboard.server.mqtt.attributes.updates.sql.*Test", 32 "org.thingsboard.server.mqtt.attributes.updates.sql.*Test",
33 "org.thingsboard.server.mqtt.attributes.request.sql.*Test", 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 public class MqttSqlTestSuite { 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,6 +18,7 @@ package org.thingsboard.server.dao.device;
18 import com.google.common.util.concurrent.ListenableFuture; 18 import com.google.common.util.concurrent.ListenableFuture;
19 import org.thingsboard.server.common.data.Device; 19 import org.thingsboard.server.common.data.Device;
20 import org.thingsboard.server.common.data.DeviceInfo; 20 import org.thingsboard.server.common.data.DeviceInfo;
  21 +import org.thingsboard.server.common.data.DeviceProfile;
21 import org.thingsboard.server.common.data.EntitySubtype; 22 import org.thingsboard.server.common.data.EntitySubtype;
22 import org.thingsboard.server.common.data.device.DeviceSearchQuery; 23 import org.thingsboard.server.common.data.device.DeviceSearchQuery;
23 import org.thingsboard.server.common.data.id.CustomerId; 24 import org.thingsboard.server.common.data.id.CustomerId;
@@ -26,6 +27,7 @@ import org.thingsboard.server.common.data.id.DeviceProfileId; @@ -26,6 +27,7 @@ import org.thingsboard.server.common.data.id.DeviceProfileId;
26 import org.thingsboard.server.common.data.id.TenantId; 27 import org.thingsboard.server.common.data.id.TenantId;
27 import org.thingsboard.server.common.data.page.PageData; 28 import org.thingsboard.server.common.data.page.PageData;
28 import org.thingsboard.server.common.data.page.PageLink; 29 import org.thingsboard.server.common.data.page.PageLink;
  30 +import org.thingsboard.server.dao.device.provision.ProvisionRequest;
29 31
30 import java.util.List; 32 import java.util.List;
31 33
@@ -83,4 +85,6 @@ public interface DeviceService { @@ -83,4 +85,6 @@ public interface DeviceService {
83 85
84 Device assignDeviceToTenant(TenantId tenantId, Device device); 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,6 +63,8 @@ public class DataConstants {
63 public static final String ALARM_CLEAR = "ALARM_CLEAR"; 63 public static final String ALARM_CLEAR = "ALARM_CLEAR";
64 public static final String ENTITY_ASSIGNED_FROM_TENANT = "ENTITY_ASSIGNED_FROM_TENANT"; 64 public static final String ENTITY_ASSIGNED_FROM_TENANT = "ENTITY_ASSIGNED_FROM_TENANT";
65 public static final String ENTITY_ASSIGNED_TO_TENANT = "ENTITY_ASSIGNED_TO_TENANT"; 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 public static final String RPC_CALL_FROM_SERVER_TO_DEVICE = "RPC_CALL_FROM_SERVER_TO_DEVICE"; 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,4 +72,18 @@ public class DataConstants {
70 public static final String SECRET_KEY_FIELD_NAME = "secretKey"; 72 public static final String SECRET_KEY_FIELD_NAME = "secretKey";
71 public static final String DURATION_MS_FIELD_NAME = "durationMs"; 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,10 +41,12 @@ public class DeviceProfile extends SearchTextBased<DeviceProfileId> implements H
41 private boolean isDefault; 41 private boolean isDefault;
42 private DeviceProfileType type; 42 private DeviceProfileType type;
43 private DeviceTransportType transportType; 43 private DeviceTransportType transportType;
  44 + private DeviceProfileProvisionType provisionType;
44 private RuleChainId defaultRuleChainId; 45 private RuleChainId defaultRuleChainId;
45 private transient DeviceProfileData profileData; 46 private transient DeviceProfileData profileData;
46 @JsonIgnore 47 @JsonIgnore
47 private byte[] profileDataBytes; 48 private byte[] profileDataBytes;
  49 + private String provisionDeviceKey;
48 50
49 public DeviceProfile() { 51 public DeviceProfile() {
50 super(); 52 super();
@@ -62,6 +64,7 @@ public class DeviceProfile extends SearchTextBased<DeviceProfileId> implements H @@ -62,6 +64,7 @@ public class DeviceProfile extends SearchTextBased<DeviceProfileId> implements H
62 this.isDefault = deviceProfile.isDefault(); 64 this.isDefault = deviceProfile.isDefault();
63 this.defaultRuleChainId = deviceProfile.getDefaultRuleChainId(); 65 this.defaultRuleChainId = deviceProfile.getDefaultRuleChainId();
64 this.setProfileData(deviceProfile.getProfileData()); 66 this.setProfileData(deviceProfile.getProfileData());
  67 + this.provisionDeviceKey = deviceProfile.getProvisionDeviceKey();
65 } 68 }
66 69
67 @Override 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,7 +42,9 @@ public enum ActionType {
42 LOGOUT(false), 42 LOGOUT(false),
43 LOCKOUT(false), 43 LOCKOUT(false),
44 ASSIGNED_FROM_TENANT(false), 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 private final boolean isRead; 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,6 +24,7 @@ public class DeviceProfileData {
24 24
25 private DeviceProfileConfiguration configuration; 25 private DeviceProfileConfiguration configuration;
26 private DeviceProfileTransportConfiguration transportConfiguration; 26 private DeviceProfileTransportConfiguration transportConfiguration;
  27 + private DeviceProfileProvisionConfiguration provisionConfiguration;
27 private List<DeviceProfileAlarm> alarms; 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,6 +20,8 @@ package org.thingsboard.server.common.data.device.profile;
20 */ 20 */
21 public class MqttTopics { 21 public class MqttTopics {
22 22
  23 + private static final String REQUEST = "/request";
  24 + private static final String RESPONSE = "/response";
23 private static final String RPC = "/rpc"; 25 private static final String RPC = "/rpc";
24 private static final String CONNECT = "/connect"; 26 private static final String CONNECT = "/connect";
25 private static final String DISCONNECT = "/disconnect"; 27 private static final String DISCONNECT = "/disconnect";
@@ -27,11 +29,13 @@ public class MqttTopics { @@ -27,11 +29,13 @@ public class MqttTopics {
27 private static final String ATTRIBUTES = "/attributes"; 29 private static final String ATTRIBUTES = "/attributes";
28 private static final String CLAIM = "/claim"; 30 private static final String CLAIM = "/claim";
29 private static final String SUB_TOPIC = "+"; 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 private static final String DEVICE_ATTRIBUTES_RESPONSE = ATTRIBUTES_RESPONSE + "/"; 40 private static final String DEVICE_ATTRIBUTES_RESPONSE = ATTRIBUTES_RESPONSE + "/";
37 private static final String DEVICE_ATTRIBUTES_REQUEST = ATTRIBUTES_REQUEST + "/"; 41 private static final String DEVICE_ATTRIBUTES_REQUEST = ATTRIBUTES_REQUEST + "/";
@@ -50,6 +54,8 @@ public class MqttTopics { @@ -50,6 +54,8 @@ public class MqttTopics {
50 public static final String DEVICE_TELEMETRY_TOPIC = BASE_DEVICE_API_TOPIC + TELEMETRY; 54 public static final String DEVICE_TELEMETRY_TOPIC = BASE_DEVICE_API_TOPIC + TELEMETRY;
51 public static final String DEVICE_CLAIM_TOPIC = BASE_DEVICE_API_TOPIC + CLAIM; 55 public static final String DEVICE_CLAIM_TOPIC = BASE_DEVICE_API_TOPIC + CLAIM;
52 public static final String DEVICE_ATTRIBUTES_TOPIC = BASE_DEVICE_API_TOPIC + ATTRIBUTES; 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 // V1_JSON gateway topics 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,5 +16,5 @@
16 package org.thingsboard.server.common.msg.session; 16 package org.thingsboard.server.common.msg.session;
17 17
18 public enum FeatureType { 18 public enum FeatureType {
19 - ATTRIBUTES, TELEMETRY, RPC, CLAIM 19 + ATTRIBUTES, TELEMETRY, RPC, CLAIM, PROVISION
20 } 20 }
@@ -73,6 +73,12 @@ enum KeyValueType { @@ -73,6 +73,12 @@ enum KeyValueType {
73 JSON_V = 4; 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 message KeyValueProto { 82 message KeyValueProto {
77 string key = 1; 83 string key = 1;
78 KeyValueType type = 2; 84 KeyValueType type = 2;
@@ -241,6 +247,43 @@ message ClaimDeviceMsg { @@ -241,6 +247,43 @@ message ClaimDeviceMsg {
241 int64 durationMs = 4; 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 //Used to report session state to tb-Service and persist this state in the cache on the tb-Service level. 287 //Used to report session state to tb-Service and persist this state in the cache on the tb-Service level.
245 message SubscriptionInfoProto { 288 message SubscriptionInfoProto {
246 int64 lastActivityTime = 1; 289 int64 lastActivityTime = 1;
@@ -266,6 +309,7 @@ message TransportToDeviceActorMsg { @@ -266,6 +309,7 @@ message TransportToDeviceActorMsg {
266 ToDeviceRpcResponseMsg toDeviceRPCCallResponse = 6; 309 ToDeviceRpcResponseMsg toDeviceRPCCallResponse = 6;
267 SubscriptionInfoProto subscriptionInfo = 7; 310 SubscriptionInfoProto subscriptionInfo = 7;
268 ClaimDeviceMsg claimDevice = 8; 311 ClaimDeviceMsg claimDevice = 8;
  312 + ProvisionDeviceRequestMsg provisionDevice = 9;
269 } 313 }
270 314
271 message TransportToRuleEngineMsg { 315 message TransportToRuleEngineMsg {
@@ -441,6 +485,7 @@ message TransportApiRequestMsg { @@ -441,6 +485,7 @@ message TransportApiRequestMsg {
441 GetTenantRoutingInfoRequestMsg getTenantRoutingInfoRequestMsg = 4; 485 GetTenantRoutingInfoRequestMsg getTenantRoutingInfoRequestMsg = 4;
442 GetDeviceProfileRequestMsg getDeviceProfileRequestMsg = 5; 486 GetDeviceProfileRequestMsg getDeviceProfileRequestMsg = 5;
443 ValidateBasicMqttCredRequestMsg validateBasicMqttCredRequestMsg = 6; 487 ValidateBasicMqttCredRequestMsg validateBasicMqttCredRequestMsg = 6;
  488 + ProvisionDeviceRequestMsg provisionDeviceRequestMsg = 7;
444 } 489 }
445 490
446 /* Response from ThingsBoard Core Service to Transport Service */ 491 /* Response from ThingsBoard Core Service to Transport Service */
@@ -449,6 +494,7 @@ message TransportApiResponseMsg { @@ -449,6 +494,7 @@ message TransportApiResponseMsg {
449 GetOrCreateDeviceFromGatewayResponseMsg getOrCreateDeviceResponseMsg = 2; 494 GetOrCreateDeviceFromGatewayResponseMsg getOrCreateDeviceResponseMsg = 2;
450 GetTenantRoutingInfoResponseMsg getTenantRoutingInfoResponseMsg = 4; 495 GetTenantRoutingInfoResponseMsg getTenantRoutingInfoResponseMsg = 4;
451 GetDeviceProfileResponseMsg getDeviceProfileResponseMsg = 5; 496 GetDeviceProfileResponseMsg getDeviceProfileResponseMsg = 5;
  497 + ProvisionDeviceResponseMsg provisionDeviceResponseMsg = 6;
452 } 498 }
453 499
454 /* Messages that are handled by ThingsBoard Core Service */ 500 /* Messages that are handled by ThingsBoard Core Service */
@@ -491,4 +537,5 @@ message ToTransportMsg { @@ -491,4 +537,5 @@ message ToTransportMsg {
491 ToServerRpcResponseMsg toServerResponse = 7; 537 ToServerRpcResponseMsg toServerResponse = 7;
492 DeviceProfileUpdateMsg deviceProfileUpdateMsg = 8; 538 DeviceProfileUpdateMsg deviceProfileUpdateMsg = 8;
493 DeviceProfileDeleteMsg deviceProfileDeleteMsg = 9; 539 DeviceProfileDeleteMsg deviceProfileDeleteMsg = 9;
  540 + ProvisionDeviceResponseMsg provisionResponse = 10;
494 } 541 }
@@ -24,6 +24,7 @@ import org.eclipse.californium.core.network.ExchangeObserver; @@ -24,6 +24,7 @@ import org.eclipse.californium.core.network.ExchangeObserver;
24 import org.eclipse.californium.core.server.resources.CoapExchange; 24 import org.eclipse.californium.core.server.resources.CoapExchange;
25 import org.eclipse.californium.core.server.resources.Resource; 25 import org.eclipse.californium.core.server.resources.Resource;
26 import org.springframework.util.ReflectionUtils; 26 import org.springframework.util.ReflectionUtils;
  27 +import org.thingsboard.server.common.data.DataConstants;
27 import org.thingsboard.server.common.data.DeviceTransportType; 28 import org.thingsboard.server.common.data.DeviceTransportType;
28 import org.thingsboard.server.common.data.security.DeviceTokenCredentials; 29 import org.thingsboard.server.common.data.security.DeviceTokenCredentials;
29 import org.thingsboard.server.common.msg.session.FeatureType; 30 import org.thingsboard.server.common.msg.session.FeatureType;
@@ -33,9 +34,11 @@ import org.thingsboard.server.common.transport.TransportContext; @@ -33,9 +34,11 @@ import org.thingsboard.server.common.transport.TransportContext;
33 import org.thingsboard.server.common.transport.TransportService; 34 import org.thingsboard.server.common.transport.TransportService;
34 import org.thingsboard.server.common.transport.TransportServiceCallback; 35 import org.thingsboard.server.common.transport.TransportServiceCallback;
35 import org.thingsboard.server.common.transport.adaptor.AdaptorException; 36 import org.thingsboard.server.common.transport.adaptor.AdaptorException;
  37 +import org.thingsboard.server.common.transport.adaptor.JsonConverter;
36 import org.thingsboard.server.common.transport.auth.SessionInfoCreator; 38 import org.thingsboard.server.common.transport.auth.SessionInfoCreator;
37 import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; 39 import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse;
38 import org.thingsboard.server.gen.transport.TransportProtos; 40 import org.thingsboard.server.gen.transport.TransportProtos;
  41 +import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceResponseMsg;
39 42
40 import java.lang.reflect.Field; 43 import java.lang.reflect.Field;
41 import java.util.List; 44 import java.util.List;
@@ -130,10 +133,25 @@ public class CoapTransportResource extends CoapResource { @@ -130,10 +133,25 @@ public class CoapTransportResource extends CoapResource {
130 case CLAIM: 133 case CLAIM:
131 processRequest(exchange, SessionMsgType.CLAIM_REQUEST); 134 processRequest(exchange, SessionMsgType.CLAIM_REQUEST);
132 break; 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 private void processRequest(CoapExchange exchange, SessionMsgType type) { 155 private void processRequest(CoapExchange exchange, SessionMsgType type) {
138 log.trace("Processing {}", exchange.advanced().getRequest()); 156 log.trace("Processing {}", exchange.advanced().getRequest());
139 exchange.accept(); 157 exchange.accept();
@@ -274,6 +292,8 @@ public class CoapTransportResource extends CoapResource { @@ -274,6 +292,8 @@ public class CoapTransportResource extends CoapResource {
274 try { 292 try {
275 if (uriPath.size() >= FEATURE_TYPE_POSITION) { 293 if (uriPath.size() >= FEATURE_TYPE_POSITION) {
276 return Optional.of(FeatureType.valueOf(uriPath.get(FEATURE_TYPE_POSITION - 1).toUpperCase())); 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 } catch (RuntimeException e) { 298 } catch (RuntimeException e) {
279 log.warn("Failed to decode feature type: {}", uriPath); 299 log.warn("Failed to decode feature type: {}", uriPath);
@@ -325,6 +345,25 @@ public class CoapTransportResource extends CoapResource { @@ -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 private static class CoapOkCallback implements TransportServiceCallback<Void> { 367 private static class CoapOkCallback implements TransportServiceCallback<Void> {
329 private final CoapExchange exchange; 368 private final CoapExchange exchange;
330 369
@@ -19,6 +19,7 @@ import org.eclipse.californium.core.coap.Request; @@ -19,6 +19,7 @@ import org.eclipse.californium.core.coap.Request;
19 import org.eclipse.californium.core.coap.Response; 19 import org.eclipse.californium.core.coap.Response;
20 import org.thingsboard.server.common.transport.adaptor.AdaptorException; 20 import org.thingsboard.server.common.transport.adaptor.AdaptorException;
21 import org.thingsboard.server.gen.transport.TransportProtos; 21 import org.thingsboard.server.gen.transport.TransportProtos;
  22 +import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceRequestMsg;
22 import org.thingsboard.server.transport.coap.CoapTransportResource; 23 import org.thingsboard.server.transport.coap.CoapTransportResource;
23 24
24 import java.util.UUID; 25 import java.util.UUID;
@@ -45,4 +46,6 @@ public interface CoapTransportAdaptor { @@ -45,4 +46,6 @@ public interface CoapTransportAdaptor {
45 46
46 Response convertToPublish(CoapTransportResource.CoapSessionListener coapSessionListener, TransportProtos.ToServerRpcResponseMsg msg) throws AdaptorException; 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,6 +124,16 @@ public class JsonCoapAdaptor implements CoapTransportAdaptor {
124 } 124 }
125 125
126 @Override 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 public Response convertToPublish(CoapTransportResource.CoapSessionListener session, TransportProtos.GetAttributeResponseMsg msg) throws AdaptorException { 137 public Response convertToPublish(CoapTransportResource.CoapSessionListener session, TransportProtos.GetAttributeResponseMsg msg) throws AdaptorException {
128 if (msg.getClientAttributeListCount() == 0 && msg.getSharedAttributeListCount() == 0) { 138 if (msg.getClientAttributeListCount() == 0 && msg.getSharedAttributeListCount() == 0) {
129 return new Response(CoAP.ResponseCode.NOT_FOUND); 139 return new Response(CoAP.ResponseCode.NOT_FOUND);
@@ -44,6 +44,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.AttributeUpdateNotif @@ -44,6 +44,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.AttributeUpdateNotif
44 import org.thingsboard.server.gen.transport.TransportProtos.DeviceInfoProto; 44 import org.thingsboard.server.gen.transport.TransportProtos.DeviceInfoProto;
45 import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeRequestMsg; 45 import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeRequestMsg;
46 import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeResponseMsg; 46 import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeResponseMsg;
  47 +import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceResponseMsg;
47 import org.thingsboard.server.gen.transport.TransportProtos.SessionCloseNotificationProto; 48 import org.thingsboard.server.gen.transport.TransportProtos.SessionCloseNotificationProto;
48 import org.thingsboard.server.gen.transport.TransportProtos.SessionInfoProto; 49 import org.thingsboard.server.gen.transport.TransportProtos.SessionInfoProto;
49 import org.thingsboard.server.gen.transport.TransportProtos.SubscribeToAttributeUpdatesMsg; 50 import org.thingsboard.server.gen.transport.TransportProtos.SubscribeToAttributeUpdatesMsg;
@@ -203,6 +204,14 @@ public class DeviceApiController { @@ -203,6 +204,14 @@ public class DeviceApiController {
203 return responseWriter; 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 private static class DeviceAuthCallback implements TransportServiceCallback<ValidateDeviceCredentialsResponse> { 215 private static class DeviceAuthCallback implements TransportServiceCallback<ValidateDeviceCredentialsResponse> {
207 private final TransportContext transportContext; 216 private final TransportContext transportContext;
208 private final DeferredResult<ResponseEntity> responseWriter; 217 private final DeferredResult<ResponseEntity> responseWriter;
@@ -230,6 +239,25 @@ public class DeviceApiController { @@ -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 private static class SessionCloseOnErrorCallback implements TransportServiceCallback<Void> { 261 private static class SessionCloseOnErrorCallback implements TransportServiceCallback<Void> {
234 private final TransportService transportService; 262 private final TransportService transportService;
235 private final SessionInfoProto sessionInfo; 263 private final SessionInfoProto sessionInfo;
@@ -16,6 +16,7 @@ @@ -16,6 +16,7 @@
16 package org.thingsboard.server.transport.mqtt; 16 package org.thingsboard.server.transport.mqtt;
17 17
18 import com.fasterxml.jackson.databind.JsonNode; 18 import com.fasterxml.jackson.databind.JsonNode;
  19 +import com.google.gson.JsonParseException;
19 import io.netty.channel.ChannelHandlerContext; 20 import io.netty.channel.ChannelHandlerContext;
20 import io.netty.channel.ChannelInboundHandlerAdapter; 21 import io.netty.channel.ChannelInboundHandlerAdapter;
21 import io.netty.handler.codec.mqtt.MqttConnAckMessage; 22 import io.netty.handler.codec.mqtt.MqttConnAckMessage;
@@ -38,8 +39,10 @@ import io.netty.util.ReferenceCountUtil; @@ -38,8 +39,10 @@ import io.netty.util.ReferenceCountUtil;
38 import io.netty.util.concurrent.Future; 39 import io.netty.util.concurrent.Future;
39 import io.netty.util.concurrent.GenericFutureListener; 40 import io.netty.util.concurrent.GenericFutureListener;
40 import lombok.extern.slf4j.Slf4j; 41 import lombok.extern.slf4j.Slf4j;
  42 +import org.thingsboard.server.common.data.DataConstants;
41 import org.thingsboard.server.common.data.DeviceProfile; 43 import org.thingsboard.server.common.data.DeviceProfile;
42 import org.thingsboard.server.common.data.DeviceTransportType; 44 import org.thingsboard.server.common.data.DeviceTransportType;
  45 +import org.thingsboard.server.common.data.TransportPayloadType;
43 import org.thingsboard.server.common.data.device.profile.MqttTopics; 46 import org.thingsboard.server.common.data.device.profile.MqttTopics;
44 import org.thingsboard.server.common.msg.EncryptionUtil; 47 import org.thingsboard.server.common.msg.EncryptionUtil;
45 import org.thingsboard.server.common.transport.SessionMsgListener; 48 import org.thingsboard.server.common.transport.SessionMsgListener;
@@ -51,6 +54,7 @@ import org.thingsboard.server.common.transport.auth.TransportDeviceInfo; @@ -51,6 +54,7 @@ import org.thingsboard.server.common.transport.auth.TransportDeviceInfo;
51 import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; 54 import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse;
52 import org.thingsboard.server.common.transport.service.DefaultTransportService; 55 import org.thingsboard.server.common.transport.service.DefaultTransportService;
53 import org.thingsboard.server.gen.transport.TransportProtos; 56 import org.thingsboard.server.gen.transport.TransportProtos;
  57 +import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceResponseMsg;
54 import org.thingsboard.server.gen.transport.TransportProtos.SessionEvent; 58 import org.thingsboard.server.gen.transport.TransportProtos.SessionEvent;
55 import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceX509CertRequestMsg; 59 import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceX509CertRequestMsg;
56 import org.thingsboard.server.transport.mqtt.adaptors.MqttTransportAdaptor; 60 import org.thingsboard.server.transport.mqtt.adaptors.MqttTransportAdaptor;
@@ -68,11 +72,12 @@ import java.util.List; @@ -68,11 +72,12 @@ import java.util.List;
68 import java.util.UUID; 72 import java.util.UUID;
69 import java.util.concurrent.ConcurrentHashMap; 73 import java.util.concurrent.ConcurrentHashMap;
70 import java.util.concurrent.ConcurrentMap; 74 import java.util.concurrent.ConcurrentMap;
71 -import java.util.Date; 75 +import java.util.concurrent.TimeUnit;
72 76
73 import static io.netty.handler.codec.mqtt.MqttConnectReturnCode.CONNECTION_ACCEPTED; 77 import static io.netty.handler.codec.mqtt.MqttConnectReturnCode.CONNECTION_ACCEPTED;
74 import static io.netty.handler.codec.mqtt.MqttConnectReturnCode.CONNECTION_REFUSED_NOT_AUTHORIZED; 78 import static io.netty.handler.codec.mqtt.MqttConnectReturnCode.CONNECTION_REFUSED_NOT_AUTHORIZED;
75 import static io.netty.handler.codec.mqtt.MqttMessageType.CONNACK; 79 import static io.netty.handler.codec.mqtt.MqttMessageType.CONNACK;
  80 +import static io.netty.handler.codec.mqtt.MqttMessageType.CONNECT;
76 import static io.netty.handler.codec.mqtt.MqttMessageType.PINGRESP; 81 import static io.netty.handler.codec.mqtt.MqttMessageType.PINGRESP;
77 import static io.netty.handler.codec.mqtt.MqttMessageType.PUBACK; 82 import static io.netty.handler.codec.mqtt.MqttMessageType.PUBACK;
78 import static io.netty.handler.codec.mqtt.MqttMessageType.SUBACK; 83 import static io.netty.handler.codec.mqtt.MqttMessageType.SUBACK;
@@ -130,10 +135,58 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement @@ -130,10 +135,58 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
130 return; 135 return;
131 } 136 }
132 deviceSessionCtx.setChannel(ctx); 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 switch (msg.fixedHeader().messageType()) { 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 break; 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 case PUBLISH: 190 case PUBLISH:
138 processPublish(ctx, (MqttPublishMessage) msg); 191 processPublish(ctx, (MqttPublishMessage) msg);
139 break; 192 break;
@@ -257,6 +310,42 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement @@ -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 private void processSubscribe(ChannelHandlerContext ctx, MqttSubscribeMessage mqttMsg) { 349 private void processSubscribe(ChannelHandlerContext ctx, MqttSubscribeMessage mqttMsg) {
261 if (!checkConnected(ctx, mqttMsg)) { 350 if (!checkConnected(ctx, mqttMsg)) {
262 return; 351 return;
@@ -286,6 +375,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement @@ -286,6 +375,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
286 case MqttTopics.GATEWAY_ATTRIBUTES_TOPIC: 375 case MqttTopics.GATEWAY_ATTRIBUTES_TOPIC:
287 case MqttTopics.GATEWAY_RPC_TOPIC: 376 case MqttTopics.GATEWAY_RPC_TOPIC:
288 case MqttTopics.GATEWAY_ATTRIBUTES_RESPONSE_TOPIC: 377 case MqttTopics.GATEWAY_ATTRIBUTES_RESPONSE_TOPIC:
  378 + case MqttTopics.DEVICE_PROVISION_RESPONSE_TOPIC:
289 registerSubQoS(topic, grantedQoSList, reqQoS); 379 registerSubQoS(topic, grantedQoSList, reqQoS);
290 break; 380 break;
291 default: 381 default:
@@ -351,11 +441,18 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement @@ -351,11 +441,18 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
351 441
352 private void processConnect(ChannelHandlerContext ctx, MqttConnectMessage msg) { 442 private void processConnect(ChannelHandlerContext ctx, MqttConnectMessage msg) {
353 log.info("[{}] Processing connect msg for client: {}!", sessionId, msg.payload().clientIdentifier()); 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 } else { 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,7 +484,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
387 484
388 private void processX509CertConnect(ChannelHandlerContext ctx, X509Certificate cert) { 485 private void processX509CertConnect(ChannelHandlerContext ctx, X509Certificate cert) {
389 try { 486 try {
390 - if(!context.isSkipValidityCheckForClientCert()){ 487 + if (!context.isSkipValidityCheckForClientCert()) {
391 cert.checkValidity(); 488 cert.checkValidity();
392 } 489 }
393 String strCert = SslUtil.getX509CertificateString(cert); 490 String strCert = SslUtil.getX509CertificateString(cert);
@@ -88,6 +88,16 @@ public class JsonMqttAdaptor implements MqttTransportAdaptor { @@ -88,6 +88,16 @@ public class JsonMqttAdaptor implements MqttTransportAdaptor {
88 } 88 }
89 89
90 @Override 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 public TransportProtos.GetAttributeRequestMsg convertToGetAttributes(MqttDeviceAwareSessionContext ctx, MqttPublishMessage inbound) throws AdaptorException { 101 public TransportProtos.GetAttributeRequestMsg convertToGetAttributes(MqttDeviceAwareSessionContext ctx, MqttPublishMessage inbound) throws AdaptorException {
92 return processGetAttributeRequestMsg(inbound, MqttTopics.DEVICE_ATTRIBUTES_REQUEST_TOPIC_PREFIX); 102 return processGetAttributeRequestMsg(inbound, MqttTopics.DEVICE_ATTRIBUTES_REQUEST_TOPIC_PREFIX);
93 } 103 }
@@ -138,6 +148,11 @@ public class JsonMqttAdaptor implements MqttTransportAdaptor { @@ -138,6 +148,11 @@ public class JsonMqttAdaptor implements MqttTransportAdaptor {
138 return Optional.of(createMqttPublishMsg(ctx, MqttTopics.DEVICE_RPC_RESPONSE_TOPIC + rpcResponse.getRequestId(), JsonConverter.toJson(rpcResponse))); 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 public static JsonElement validateJsonPayload(UUID sessionId, ByteBuf payloadData) throws AdaptorException { 156 public static JsonElement validateJsonPayload(UUID sessionId, ByteBuf payloadData) throws AdaptorException {
142 String payload = validatePayload(sessionId, payloadData, false); 157 String payload = validatePayload(sessionId, payloadData, false);
143 try { 158 try {
@@ -24,6 +24,8 @@ import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeRequestM @@ -24,6 +24,8 @@ import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeRequestM
24 import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeResponseMsg; 24 import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeResponseMsg;
25 import org.thingsboard.server.gen.transport.TransportProtos.PostAttributeMsg; 25 import org.thingsboard.server.gen.transport.TransportProtos.PostAttributeMsg;
26 import org.thingsboard.server.gen.transport.TransportProtos.PostTelemetryMsg; 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 import org.thingsboard.server.gen.transport.TransportProtos.ToDeviceRpcRequestMsg; 29 import org.thingsboard.server.gen.transport.TransportProtos.ToDeviceRpcRequestMsg;
28 import org.thingsboard.server.gen.transport.TransportProtos.ToDeviceRpcResponseMsg; 30 import org.thingsboard.server.gen.transport.TransportProtos.ToDeviceRpcResponseMsg;
29 import org.thingsboard.server.gen.transport.TransportProtos.ToServerRpcRequestMsg; 31 import org.thingsboard.server.gen.transport.TransportProtos.ToServerRpcRequestMsg;
@@ -63,4 +65,8 @@ public interface MqttTransportAdaptor { @@ -63,4 +65,8 @@ public interface MqttTransportAdaptor {
63 65
64 Optional<MqttMessage> convertToPublish(MqttDeviceAwareSessionContext ctx, ToServerRpcResponseMsg rpcResponse) throws AdaptorException; 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,6 +32,7 @@ import org.thingsboard.server.common.transport.adaptor.AdaptorException;
32 import org.thingsboard.server.common.transport.adaptor.ProtoConverter; 32 import org.thingsboard.server.common.transport.adaptor.ProtoConverter;
33 import org.thingsboard.server.gen.transport.TransportApiProtos; 33 import org.thingsboard.server.gen.transport.TransportApiProtos;
34 import org.thingsboard.server.gen.transport.TransportProtos; 34 import org.thingsboard.server.gen.transport.TransportProtos;
  35 +import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceResponseMsg;
35 import org.thingsboard.server.transport.mqtt.session.MqttDeviceAwareSessionContext; 36 import org.thingsboard.server.transport.mqtt.session.MqttDeviceAwareSessionContext;
36 37
37 import java.util.Optional; 38 import java.util.Optional;
@@ -109,6 +110,17 @@ public class ProtoMqttAdaptor implements MqttTransportAdaptor { @@ -109,6 +110,17 @@ public class ProtoMqttAdaptor implements MqttTransportAdaptor {
109 } 110 }
110 111
111 @Override 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 public Optional<MqttMessage> convertToPublish(MqttDeviceAwareSessionContext ctx, TransportProtos.GetAttributeResponseMsg responseMsg) throws AdaptorException { 124 public Optional<MqttMessage> convertToPublish(MqttDeviceAwareSessionContext ctx, TransportProtos.GetAttributeResponseMsg responseMsg) throws AdaptorException {
113 if (!StringUtils.isEmpty(responseMsg.getError())) { 125 if (!StringUtils.isEmpty(responseMsg.getError())) {
114 throw new AdaptorException(responseMsg.getError()); 126 throw new AdaptorException(responseMsg.getError());
@@ -140,6 +152,11 @@ public class ProtoMqttAdaptor implements MqttTransportAdaptor { @@ -140,6 +152,11 @@ public class ProtoMqttAdaptor implements MqttTransportAdaptor {
140 } 152 }
141 153
142 @Override 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 public Optional<MqttMessage> convertToGatewayPublish(MqttDeviceAwareSessionContext ctx, String deviceName, TransportProtos.GetAttributeResponseMsg responseMsg) throws AdaptorException { 160 public Optional<MqttMessage> convertToGatewayPublish(MqttDeviceAwareSessionContext ctx, String deviceName, TransportProtos.GetAttributeResponseMsg responseMsg) throws AdaptorException {
144 if (!StringUtils.isEmpty(responseMsg.getError())) { 161 if (!StringUtils.isEmpty(responseMsg.getError())) {
145 throw new AdaptorException(responseMsg.getError()); 162 throw new AdaptorException(responseMsg.getError());
@@ -17,6 +17,7 @@ package org.thingsboard.server.transport.mqtt.session; @@ -17,6 +17,7 @@ package org.thingsboard.server.transport.mqtt.session;
17 17
18 import io.netty.channel.ChannelHandlerContext; 18 import io.netty.channel.ChannelHandlerContext;
19 import lombok.Getter; 19 import lombok.Getter;
  20 +import lombok.Setter;
20 import lombok.extern.slf4j.Slf4j; 21 import lombok.extern.slf4j.Slf4j;
21 import org.thingsboard.server.common.data.DeviceProfile; 22 import org.thingsboard.server.common.data.DeviceProfile;
22 import org.thingsboard.server.common.data.DeviceTransportType; 23 import org.thingsboard.server.common.data.DeviceTransportType;
@@ -46,10 +47,18 @@ public class DeviceSessionCtx extends MqttDeviceAwareSessionContext { @@ -46,10 +47,18 @@ public class DeviceSessionCtx extends MqttDeviceAwareSessionContext {
46 47
47 private final AtomicInteger msgIdSeq = new AtomicInteger(0); 48 private final AtomicInteger msgIdSeq = new AtomicInteger(0);
48 49
  50 + @Getter
  51 + @Setter
  52 + private boolean provisionOnly = false;
  53 +
49 private volatile MqttTopicFilter telemetryTopicFilter = MqttTopicFilterFactory.getDefaultTelemetryFilter(); 54 private volatile MqttTopicFilter telemetryTopicFilter = MqttTopicFilterFactory.getDefaultTelemetryFilter();
50 private volatile MqttTopicFilter attributesTopicFilter = MqttTopicFilterFactory.getDefaultAttributesFilter(); 55 private volatile MqttTopicFilter attributesTopicFilter = MqttTopicFilterFactory.getDefaultAttributesFilter();
51 private volatile TransportPayloadType payloadType = TransportPayloadType.JSON; 56 private volatile TransportPayloadType payloadType = TransportPayloadType.JSON;
52 57
  58 + @Getter
  59 + @Setter
  60 + private TransportPayloadType provisionPayloadType = payloadType;
  61 +
53 public DeviceSessionCtx(UUID sessionId, ConcurrentMap<MqttTopicMatcher, Integer> mqttQoSMap, MqttTransportContext context) { 62 public DeviceSessionCtx(UUID sessionId, ConcurrentMap<MqttTopicMatcher, Integer> mqttQoSMap, MqttTransportContext context) {
54 super(sessionId, mqttQoSMap); 63 super(sessionId, mqttQoSMap);
55 this.context = context; 64 this.context = context;
@@ -27,6 +27,8 @@ import org.thingsboard.server.gen.transport.TransportProtos.GetTenantRoutingInfo @@ -27,6 +27,8 @@ import org.thingsboard.server.gen.transport.TransportProtos.GetTenantRoutingInfo
27 import org.thingsboard.server.gen.transport.TransportProtos.GetTenantRoutingInfoResponseMsg; 27 import org.thingsboard.server.gen.transport.TransportProtos.GetTenantRoutingInfoResponseMsg;
28 import org.thingsboard.server.gen.transport.TransportProtos.PostAttributeMsg; 28 import org.thingsboard.server.gen.transport.TransportProtos.PostAttributeMsg;
29 import org.thingsboard.server.gen.transport.TransportProtos.PostTelemetryMsg; 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 import org.thingsboard.server.gen.transport.TransportProtos.SessionEventMsg; 32 import org.thingsboard.server.gen.transport.TransportProtos.SessionEventMsg;
31 import org.thingsboard.server.gen.transport.TransportProtos.SessionInfoProto; 33 import org.thingsboard.server.gen.transport.TransportProtos.SessionInfoProto;
32 import org.thingsboard.server.gen.transport.TransportProtos.SubscribeToAttributeUpdatesMsg; 34 import org.thingsboard.server.gen.transport.TransportProtos.SubscribeToAttributeUpdatesMsg;
@@ -38,6 +40,8 @@ import org.thingsboard.server.gen.transport.TransportProtos.ValidateBasicMqttCre @@ -38,6 +40,8 @@ import org.thingsboard.server.gen.transport.TransportProtos.ValidateBasicMqttCre
38 import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceTokenRequestMsg; 40 import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceTokenRequestMsg;
39 import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceX509CertRequestMsg; 41 import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceX509CertRequestMsg;
40 42
  43 +import java.util.concurrent.ScheduledExecutorService;
  44 +
41 /** 45 /**
42 * Created by ashvayka on 04.10.18. 46 * Created by ashvayka on 04.10.18.
43 */ 47 */
@@ -57,6 +61,9 @@ public interface TransportService { @@ -57,6 +61,9 @@ public interface TransportService {
57 void process(GetOrCreateDeviceFromGatewayRequestMsg msg, 61 void process(GetOrCreateDeviceFromGatewayRequestMsg msg,
58 TransportServiceCallback<GetOrCreateDeviceFromGatewayResponse> callback); 62 TransportServiceCallback<GetOrCreateDeviceFromGatewayResponse> callback);
59 63
  64 + void process(ProvisionDeviceRequestMsg msg,
  65 + TransportServiceCallback<ProvisionDeviceResponseMsg> callback);
  66 +
60 void getDeviceProfile(DeviceProfileId deviceProfileId, TransportServiceCallback<DeviceProfile> callback); 67 void getDeviceProfile(DeviceProfileId deviceProfileId, TransportServiceCallback<DeviceProfile> callback);
61 68
62 void onProfileUpdate(DeviceProfile deviceProfile); 69 void onProfileUpdate(DeviceProfile deviceProfile);
@@ -83,6 +90,8 @@ public interface TransportService { @@ -83,6 +90,8 @@ public interface TransportService {
83 90
84 void process(SessionInfoProto sessionInfo, ClaimDeviceMsg msg, TransportServiceCallback<Void> callback); 91 void process(SessionInfoProto sessionInfo, ClaimDeviceMsg msg, TransportServiceCallback<Void> callback);
85 92
  93 + ScheduledExecutorService getSchedulerExecutor();
  94 +
86 void registerAsyncSession(SessionInfoProto sessionInfo, SessionMsgListener listener); 95 void registerAsyncSession(SessionInfoProto sessionInfo, SessionMsgListener listener);
87 96
88 void registerSyncSession(SessionInfoProto sessionInfo, SessionMsgListener listener, long timeout); 97 void registerSyncSession(SessionInfoProto sessionInfo, SessionMsgListener listener, long timeout);
@@ -90,5 +99,4 @@ public interface TransportService { @@ -90,5 +99,4 @@ public interface TransportService {
90 void reportActivity(SessionInfoProto sessionInfo); 99 void reportActivity(SessionInfoProto sessionInfo);
91 100
92 void deregisterSession(SessionInfoProto sessionInfo); 101 void deregisterSession(SessionInfoProto sessionInfo);
93 -  
94 } 102 }
@@ -37,13 +37,19 @@ import org.thingsboard.server.common.data.kv.StringDataEntry; @@ -37,13 +37,19 @@ import org.thingsboard.server.common.data.kv.StringDataEntry;
37 import org.thingsboard.server.gen.transport.TransportProtos; 37 import org.thingsboard.server.gen.transport.TransportProtos;
38 import org.thingsboard.server.gen.transport.TransportProtos.AttributeUpdateNotificationMsg; 38 import org.thingsboard.server.gen.transport.TransportProtos.AttributeUpdateNotificationMsg;
39 import org.thingsboard.server.gen.transport.TransportProtos.ClaimDeviceMsg; 39 import org.thingsboard.server.gen.transport.TransportProtos.ClaimDeviceMsg;
  40 +import org.thingsboard.server.gen.transport.TransportProtos.CredentialsType;
40 import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeResponseMsg; 41 import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeResponseMsg;
41 import org.thingsboard.server.gen.transport.TransportProtos.KeyValueProto; 42 import org.thingsboard.server.gen.transport.TransportProtos.KeyValueProto;
42 import org.thingsboard.server.gen.transport.TransportProtos.KeyValueType; 43 import org.thingsboard.server.gen.transport.TransportProtos.KeyValueType;
43 import org.thingsboard.server.gen.transport.TransportProtos.PostAttributeMsg; 44 import org.thingsboard.server.gen.transport.TransportProtos.PostAttributeMsg;
44 import org.thingsboard.server.gen.transport.TransportProtos.PostTelemetryMsg; 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 import org.thingsboard.server.gen.transport.TransportProtos.TsKvListProto; 48 import org.thingsboard.server.gen.transport.TransportProtos.TsKvListProto;
46 import org.thingsboard.server.gen.transport.TransportProtos.TsKvProto; 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 import java.util.ArrayList; 54 import java.util.ArrayList;
49 import java.util.HashMap; 55 import java.util.HashMap;
@@ -53,6 +59,7 @@ import java.util.Map; @@ -53,6 +59,7 @@ import java.util.Map;
53 import java.util.Map.Entry; 59 import java.util.Map.Entry;
54 import java.util.Set; 60 import java.util.Set;
55 import java.util.TreeMap; 61 import java.util.TreeMap;
  62 +import java.util.UUID;
56 import java.util.function.Consumer; 63 import java.util.function.Consumer;
57 import java.util.stream.Collectors; 64 import java.util.stream.Collectors;
58 65
@@ -397,6 +404,36 @@ public class JsonConverter { @@ -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 public static JsonElement toErrorJson(String errorMsg) { 437 public static JsonElement toErrorJson(String errorMsg) {
401 JsonObject error = new JsonObject(); 438 JsonObject error = new JsonObject();
402 error.addProperty("error", errorMsg); 439 error.addProperty("error", errorMsg);
@@ -410,6 +447,13 @@ public class JsonConverter { @@ -410,6 +447,13 @@ public class JsonConverter {
410 return result; 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 public static Set<AttributeKvEntry> convertToAttributes(JsonElement element) { 457 public static Set<AttributeKvEntry> convertToAttributes(JsonElement element) {
414 Set<AttributeKvEntry> result = new HashSet<>(); 458 Set<AttributeKvEntry> result = new HashSet<>();
415 long ts = System.currentTimeMillis(); 459 long ts = System.currentTimeMillis();
@@ -498,4 +542,55 @@ public class JsonConverter { @@ -498,4 +542,55 @@ public class JsonConverter {
498 maxStringValueLength = length; 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,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 private static List<TransportProtos.KeyValueProto> validateKeyValueProtos(List<TransportProtos.KeyValueProto> kvList) { 137 private static List<TransportProtos.KeyValueProto> validateKeyValueProtos(List<TransportProtos.KeyValueProto> kvList) {
135 kvList.forEach(keyValueProto -> { 138 kvList.forEach(keyValueProto -> {
@@ -34,7 +34,6 @@ import org.thingsboard.server.common.data.id.DeviceProfileId; @@ -34,7 +34,6 @@ import org.thingsboard.server.common.data.id.DeviceProfileId;
34 import org.thingsboard.server.common.data.id.RuleChainId; 34 import org.thingsboard.server.common.data.id.RuleChainId;
35 import org.thingsboard.server.common.data.id.TenantId; 35 import org.thingsboard.server.common.data.id.TenantId;
36 import org.thingsboard.server.common.msg.TbMsg; 36 import org.thingsboard.server.common.msg.TbMsg;
37 -import org.thingsboard.server.common.msg.TbMsgDataType;  
38 import org.thingsboard.server.common.msg.TbMsgMetaData; 37 import org.thingsboard.server.common.msg.TbMsgMetaData;
39 import org.thingsboard.server.common.msg.queue.ServiceQueue; 38 import org.thingsboard.server.common.msg.queue.ServiceQueue;
40 import org.thingsboard.server.common.msg.queue.ServiceType; 39 import org.thingsboard.server.common.msg.queue.ServiceType;
@@ -49,9 +48,10 @@ import org.thingsboard.server.common.transport.TransportServiceCallback; @@ -49,9 +48,10 @@ import org.thingsboard.server.common.transport.TransportServiceCallback;
49 import org.thingsboard.server.common.transport.auth.GetOrCreateDeviceFromGatewayResponse; 48 import org.thingsboard.server.common.transport.auth.GetOrCreateDeviceFromGatewayResponse;
50 import org.thingsboard.server.common.transport.auth.TransportDeviceInfo; 49 import org.thingsboard.server.common.transport.auth.TransportDeviceInfo;
51 import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; 50 import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse;
52 -import org.thingsboard.server.common.transport.util.DataDecodingEncodingService;  
53 import org.thingsboard.server.common.transport.util.JsonUtils; 51 import org.thingsboard.server.common.transport.util.JsonUtils;
54 import org.thingsboard.server.gen.transport.TransportProtos; 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 import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg; 55 import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg;
56 import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg; 56 import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg;
57 import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg; 57 import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg;
@@ -75,11 +75,9 @@ import org.thingsboard.server.common.stats.StatsType; @@ -75,11 +75,9 @@ import org.thingsboard.server.common.stats.StatsType;
75 75
76 import javax.annotation.PostConstruct; 76 import javax.annotation.PostConstruct;
77 import javax.annotation.PreDestroy; 77 import javax.annotation.PreDestroy;
78 -import java.util.Arrays;  
79 import java.util.Collections; 78 import java.util.Collections;
80 import java.util.List; 79 import java.util.List;
81 import java.util.Map; 80 import java.util.Map;
82 -import java.util.Optional;  
83 import java.util.Random; 81 import java.util.Random;
84 import java.util.UUID; 82 import java.util.UUID;
85 import java.util.concurrent.ConcurrentHashMap; 83 import java.util.concurrent.ConcurrentHashMap;
@@ -91,7 +89,6 @@ import java.util.concurrent.ScheduledExecutorService; @@ -91,7 +89,6 @@ import java.util.concurrent.ScheduledExecutorService;
91 import java.util.concurrent.ScheduledFuture; 89 import java.util.concurrent.ScheduledFuture;
92 import java.util.concurrent.TimeUnit; 90 import java.util.concurrent.TimeUnit;
93 import java.util.concurrent.atomic.AtomicInteger; 91 import java.util.concurrent.atomic.AtomicInteger;
94 -import java.util.function.Function;  
95 92
96 /** 93 /**
97 * Created by ashvayka on 17.10.18. 94 * Created by ashvayka on 17.10.18.
@@ -235,6 +232,11 @@ public class DefaultTransportService implements TransportService { @@ -235,6 +232,11 @@ public class DefaultTransportService implements TransportService {
235 } 232 }
236 233
237 @Override 234 @Override
  235 + public ScheduledExecutorService getSchedulerExecutor(){
  236 + return this.schedulerExecutor;
  237 + }
  238 +
  239 + @Override
238 public void registerAsyncSession(TransportProtos.SessionInfoProto sessionInfo, SessionMsgListener listener) { 240 public void registerAsyncSession(TransportProtos.SessionInfoProto sessionInfo, SessionMsgListener listener) {
239 sessions.putIfAbsent(toSessionId(sessionInfo), new SessionMetaData(sessionInfo, TransportProtos.SessionType.ASYNC, listener)); 241 sessions.putIfAbsent(toSessionId(sessionInfo), new SessionMetaData(sessionInfo, TransportProtos.SessionType.ASYNC, listener));
240 } 242 }
@@ -333,6 +335,16 @@ public class DefaultTransportService implements TransportService { @@ -333,6 +335,16 @@ public class DefaultTransportService implements TransportService {
333 } 335 }
334 336
335 @Override 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 public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.SubscriptionInfoProto msg, TransportServiceCallback<Void> callback) { 348 public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.SubscriptionInfoProto msg, TransportServiceCallback<Void> callback) {
337 if (log.isTraceEnabled()) { 349 if (log.isTraceEnabled()) {
338 log.trace("[{}] Processing msg: {}", toSessionId(sessionInfo), msg); 350 log.trace("[{}] Processing msg: {}", toSessionId(sessionInfo), msg);
@@ -47,6 +47,7 @@ import org.thingsboard.server.common.data.security.DeviceCredentials; @@ -47,6 +47,7 @@ import org.thingsboard.server.common.data.security.DeviceCredentials;
47 import org.thingsboard.server.dao.audit.sink.AuditLogSink; 47 import org.thingsboard.server.dao.audit.sink.AuditLogSink;
48 import org.thingsboard.server.dao.entity.EntityService; 48 import org.thingsboard.server.dao.entity.EntityService;
49 import org.thingsboard.server.dao.exception.DataValidationException; 49 import org.thingsboard.server.dao.exception.DataValidationException;
  50 +import org.thingsboard.server.dao.device.provision.ProvisionRequest;
50 import org.thingsboard.server.dao.service.DataValidator; 51 import org.thingsboard.server.dao.service.DataValidator;
51 52
52 import java.io.PrintWriter; 53 import java.io.PrintWriter;
@@ -257,6 +258,13 @@ public class AuditLogServiceImpl implements AuditLogService { @@ -257,6 +258,13 @@ public class AuditLogServiceImpl implements AuditLogService {
257 actionData.put("os", os); 258 actionData.put("os", os);
258 actionData.put("device", device); 259 actionData.put("device", device);
259 break; 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 return actionData; 269 return actionData;
262 } 270 }
@@ -214,5 +214,4 @@ public interface DeviceDao extends Dao<Device> { @@ -214,5 +214,4 @@ public interface DeviceDao extends Dao<Device> {
214 * @return the list of device objects 214 * @return the list of device objects
215 */ 215 */
216 PageData<Device> findDevicesByTenantIdAndProfileId(UUID tenantId, UUID profileId, PageLink pageLink); 216 PageData<Device> findDevicesByTenantIdAndProfileId(UUID tenantId, UUID profileId, PageLink pageLink);
217 -  
218 } 217 }
@@ -38,5 +38,7 @@ public interface DeviceProfileDao extends Dao<DeviceProfile> { @@ -38,5 +38,7 @@ public interface DeviceProfileDao extends Dao<DeviceProfile> {
38 38
39 DeviceProfileInfo findDefaultDeviceProfileInfo(TenantId tenantId); 39 DeviceProfileInfo findDefaultDeviceProfileInfo(TenantId tenantId);
40 40
  41 + DeviceProfile findByProvisionDeviceKey(String provisionDeviceKey);
  42 +
41 DeviceProfile findByName(TenantId tenantId, String profileName); 43 DeviceProfile findByName(TenantId tenantId, String profileName);
42 } 44 }
@@ -26,6 +26,7 @@ import org.springframework.stereotype.Service; @@ -26,6 +26,7 @@ import org.springframework.stereotype.Service;
26 import org.thingsboard.server.common.data.Device; 26 import org.thingsboard.server.common.data.Device;
27 import org.thingsboard.server.common.data.DeviceProfile; 27 import org.thingsboard.server.common.data.DeviceProfile;
28 import org.thingsboard.server.common.data.DeviceProfileInfo; 28 import org.thingsboard.server.common.data.DeviceProfileInfo;
  29 +import org.thingsboard.server.common.data.DeviceProfileProvisionType;
29 import org.thingsboard.server.common.data.DeviceProfileType; 30 import org.thingsboard.server.common.data.DeviceProfileType;
30 import org.thingsboard.server.common.data.DeviceTransportType; 31 import org.thingsboard.server.common.data.DeviceTransportType;
31 import org.thingsboard.server.common.data.EntitySubtype; 32 import org.thingsboard.server.common.data.EntitySubtype;
@@ -33,6 +34,7 @@ import org.thingsboard.server.common.data.Tenant; @@ -33,6 +34,7 @@ import org.thingsboard.server.common.data.Tenant;
33 import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileConfiguration; 34 import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileConfiguration;
34 import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileTransportConfiguration; 35 import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileTransportConfiguration;
35 import org.thingsboard.server.common.data.device.profile.DeviceProfileData; 36 import org.thingsboard.server.common.data.device.profile.DeviceProfileData;
  37 +import org.thingsboard.server.common.data.device.profile.DisabledDeviceProfileProvisionConfiguration;
36 import org.thingsboard.server.common.data.id.DeviceProfileId; 38 import org.thingsboard.server.common.data.id.DeviceProfileId;
37 import org.thingsboard.server.common.data.id.TenantId; 39 import org.thingsboard.server.common.data.id.TenantId;
38 import org.thingsboard.server.common.data.page.PageData; 40 import org.thingsboard.server.common.data.page.PageData;
@@ -112,6 +114,8 @@ public class DeviceProfileServiceImpl extends AbstractEntityService implements D @@ -112,6 +114,8 @@ public class DeviceProfileServiceImpl extends AbstractEntityService implements D
112 ConstraintViolationException e = extractConstraintViolationException(t).orElse(null); 114 ConstraintViolationException e = extractConstraintViolationException(t).orElse(null);
113 if (e != null && e.getConstraintName() != null && e.getConstraintName().equalsIgnoreCase("device_profile_name_unq_key")) { 115 if (e != null && e.getConstraintName() != null && e.getConstraintName().equalsIgnoreCase("device_profile_name_unq_key")) {
114 throw new DataValidationException("Device profile with such name already exists!"); 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 } else { 119 } else {
116 throw t; 120 throw t;
117 } 121 }
@@ -210,12 +214,15 @@ public class DeviceProfileServiceImpl extends AbstractEntityService implements D @@ -210,12 +214,15 @@ public class DeviceProfileServiceImpl extends AbstractEntityService implements D
210 deviceProfile.setName(profileName); 214 deviceProfile.setName(profileName);
211 deviceProfile.setType(DeviceProfileType.DEFAULT); 215 deviceProfile.setType(DeviceProfileType.DEFAULT);
212 deviceProfile.setTransportType(DeviceTransportType.DEFAULT); 216 deviceProfile.setTransportType(DeviceTransportType.DEFAULT);
  217 + deviceProfile.setProvisionType(DeviceProfileProvisionType.DISABLED);
213 deviceProfile.setDescription("Default device profile"); 218 deviceProfile.setDescription("Default device profile");
214 DeviceProfileData deviceProfileData = new DeviceProfileData(); 219 DeviceProfileData deviceProfileData = new DeviceProfileData();
215 DefaultDeviceProfileConfiguration configuration = new DefaultDeviceProfileConfiguration(); 220 DefaultDeviceProfileConfiguration configuration = new DefaultDeviceProfileConfiguration();
216 DefaultDeviceProfileTransportConfiguration transportConfiguration = new DefaultDeviceProfileTransportConfiguration(); 221 DefaultDeviceProfileTransportConfiguration transportConfiguration = new DefaultDeviceProfileTransportConfiguration();
  222 + DisabledDeviceProfileProvisionConfiguration provisionConfiguration = new DisabledDeviceProfileProvisionConfiguration(null);
217 deviceProfileData.setConfiguration(configuration); 223 deviceProfileData.setConfiguration(configuration);
218 deviceProfileData.setTransportConfiguration(transportConfiguration); 224 deviceProfileData.setTransportConfiguration(transportConfiguration);
  225 + deviceProfileData.setProvisionConfiguration(provisionConfiguration);
219 deviceProfile.setProfileData(deviceProfileData); 226 deviceProfile.setProfileData(deviceProfileData);
220 return saveDeviceProfile(deviceProfile); 227 return saveDeviceProfile(deviceProfile);
221 } 228 }
@@ -40,6 +40,7 @@ import org.thingsboard.server.common.data.EntityType; @@ -40,6 +40,7 @@ import org.thingsboard.server.common.data.EntityType;
40 import org.thingsboard.server.common.data.EntityView; 40 import org.thingsboard.server.common.data.EntityView;
41 import org.thingsboard.server.common.data.Tenant; 41 import org.thingsboard.server.common.data.Tenant;
42 import org.thingsboard.server.common.data.device.DeviceSearchQuery; 42 import org.thingsboard.server.common.data.device.DeviceSearchQuery;
  43 +import org.thingsboard.server.common.data.device.credentials.BasicMqttCredentials;
43 import org.thingsboard.server.common.data.device.data.DefaultDeviceConfiguration; 44 import org.thingsboard.server.common.data.device.data.DefaultDeviceConfiguration;
44 import org.thingsboard.server.common.data.device.data.DefaultDeviceTransportConfiguration; 45 import org.thingsboard.server.common.data.device.data.DefaultDeviceTransportConfiguration;
45 import org.thingsboard.server.common.data.device.data.DeviceData; 46 import org.thingsboard.server.common.data.device.data.DeviceData;
@@ -57,6 +58,9 @@ import org.thingsboard.server.common.data.relation.EntitySearchDirection; @@ -57,6 +58,9 @@ import org.thingsboard.server.common.data.relation.EntitySearchDirection;
57 import org.thingsboard.server.common.data.security.DeviceCredentials; 58 import org.thingsboard.server.common.data.security.DeviceCredentials;
58 import org.thingsboard.server.common.data.security.DeviceCredentialsType; 59 import org.thingsboard.server.common.data.security.DeviceCredentialsType;
59 import org.thingsboard.server.dao.customer.CustomerDao; 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 import org.thingsboard.server.dao.entity.AbstractEntityService; 64 import org.thingsboard.server.dao.entity.AbstractEntityService;
61 import org.thingsboard.server.dao.entityview.EntityViewService; 65 import org.thingsboard.server.dao.entityview.EntityViewService;
62 import org.thingsboard.server.dao.event.EventService; 66 import org.thingsboard.server.dao.event.EventService;
@@ -64,6 +68,7 @@ import org.thingsboard.server.dao.exception.DataValidationException; @@ -64,6 +68,7 @@ import org.thingsboard.server.dao.exception.DataValidationException;
64 import org.thingsboard.server.dao.service.DataValidator; 68 import org.thingsboard.server.dao.service.DataValidator;
65 import org.thingsboard.server.dao.service.PaginatedRemover; 69 import org.thingsboard.server.dao.service.PaginatedRemover;
66 import org.thingsboard.server.dao.tenant.TenantDao; 70 import org.thingsboard.server.dao.tenant.TenantDao;
  71 +import org.thingsboard.server.dao.util.mapping.JacksonUtil;
67 72
68 import javax.annotation.Nullable; 73 import javax.annotation.Nullable;
69 import java.util.ArrayList; 74 import java.util.ArrayList;
@@ -466,6 +471,50 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe @@ -466,6 +471,50 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe
466 return doSaveDevice(device, null); 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 private DataValidator<Device> deviceValidator = 518 private DataValidator<Device> deviceValidator =
470 new DataValidator<Device>() { 519 new DataValidator<Device>() {
471 520
@@ -169,10 +169,12 @@ public class ModelConstants { @@ -169,10 +169,12 @@ public class ModelConstants {
169 public static final String DEVICE_PROFILE_NAME_PROPERTY = "name"; 169 public static final String DEVICE_PROFILE_NAME_PROPERTY = "name";
170 public static final String DEVICE_PROFILE_TYPE_PROPERTY = "type"; 170 public static final String DEVICE_PROFILE_TYPE_PROPERTY = "type";
171 public static final String DEVICE_PROFILE_TRANSPORT_TYPE_PROPERTY = "transport_type"; 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 public static final String DEVICE_PROFILE_PROFILE_DATA_PROPERTY = "profile_data"; 173 public static final String DEVICE_PROFILE_PROFILE_DATA_PROPERTY = "profile_data";
173 public static final String DEVICE_PROFILE_DESCRIPTION_PROPERTY = "description"; 174 public static final String DEVICE_PROFILE_DESCRIPTION_PROPERTY = "description";
174 public static final String DEVICE_PROFILE_IS_DEFAULT_PROPERTY = "is_default"; 175 public static final String DEVICE_PROFILE_IS_DEFAULT_PROPERTY = "is_default";
175 public static final String DEVICE_PROFILE_DEFAULT_RULE_CHAIN_ID_PROPERTY = "default_rule_chain_id"; 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 * Cassandra entityView constants. 180 * Cassandra entityView constants.
@@ -23,6 +23,7 @@ import org.hibernate.annotations.Type; @@ -23,6 +23,7 @@ import org.hibernate.annotations.Type;
23 import org.hibernate.annotations.TypeDef; 23 import org.hibernate.annotations.TypeDef;
24 import org.thingsboard.server.common.data.DeviceProfile; 24 import org.thingsboard.server.common.data.DeviceProfile;
25 import org.thingsboard.server.common.data.DeviceProfileType; 25 import org.thingsboard.server.common.data.DeviceProfileType;
  26 +import org.thingsboard.server.common.data.DeviceProfileProvisionType;
26 import org.thingsboard.server.common.data.DeviceTransportType; 27 import org.thingsboard.server.common.data.DeviceTransportType;
27 import org.thingsboard.server.common.data.device.profile.DeviceProfileData; 28 import org.thingsboard.server.common.data.device.profile.DeviceProfileData;
28 import org.thingsboard.server.common.data.id.DeviceProfileId; 29 import org.thingsboard.server.common.data.id.DeviceProfileId;
@@ -62,6 +63,10 @@ public final class DeviceProfileEntity extends BaseSqlEntity<DeviceProfile> impl @@ -62,6 +63,10 @@ public final class DeviceProfileEntity extends BaseSqlEntity<DeviceProfile> impl
62 @Column(name = ModelConstants.DEVICE_PROFILE_TRANSPORT_TYPE_PROPERTY) 63 @Column(name = ModelConstants.DEVICE_PROFILE_TRANSPORT_TYPE_PROPERTY)
63 private DeviceTransportType transportType; 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 @Column(name = ModelConstants.DEVICE_PROFILE_DESCRIPTION_PROPERTY) 70 @Column(name = ModelConstants.DEVICE_PROFILE_DESCRIPTION_PROPERTY)
66 private String description; 71 private String description;
67 72
@@ -78,6 +83,9 @@ public final class DeviceProfileEntity extends BaseSqlEntity<DeviceProfile> impl @@ -78,6 +83,9 @@ public final class DeviceProfileEntity extends BaseSqlEntity<DeviceProfile> impl
78 @Column(name = ModelConstants.DEVICE_PROFILE_PROFILE_DATA_PROPERTY, columnDefinition = "jsonb") 83 @Column(name = ModelConstants.DEVICE_PROFILE_PROFILE_DATA_PROPERTY, columnDefinition = "jsonb")
79 private JsonNode profileData; 84 private JsonNode profileData;
80 85
  86 + @Column(name=ModelConstants.DEVICE_PROFILE_PROVISION_DEVICE_KEY)
  87 + private String provisionDeviceKey;
  88 +
81 public DeviceProfileEntity() { 89 public DeviceProfileEntity() {
82 super(); 90 super();
83 } 91 }
@@ -93,12 +101,14 @@ public final class DeviceProfileEntity extends BaseSqlEntity<DeviceProfile> impl @@ -93,12 +101,14 @@ public final class DeviceProfileEntity extends BaseSqlEntity<DeviceProfile> impl
93 this.name = deviceProfile.getName(); 101 this.name = deviceProfile.getName();
94 this.type = deviceProfile.getType(); 102 this.type = deviceProfile.getType();
95 this.transportType = deviceProfile.getTransportType(); 103 this.transportType = deviceProfile.getTransportType();
  104 + this.provisionType = deviceProfile.getProvisionType();
96 this.description = deviceProfile.getDescription(); 105 this.description = deviceProfile.getDescription();
97 this.isDefault = deviceProfile.isDefault(); 106 this.isDefault = deviceProfile.isDefault();
98 this.profileData = JacksonUtil.convertValue(deviceProfile.getProfileData(), ObjectNode.class); 107 this.profileData = JacksonUtil.convertValue(deviceProfile.getProfileData(), ObjectNode.class);
99 if (deviceProfile.getDefaultRuleChainId() != null) { 108 if (deviceProfile.getDefaultRuleChainId() != null) {
100 this.defaultRuleChainId = deviceProfile.getDefaultRuleChainId().getId(); 109 this.defaultRuleChainId = deviceProfile.getDefaultRuleChainId().getId();
101 } 110 }
  111 + this.provisionDeviceKey = deviceProfile.getProvisionDeviceKey();
102 } 112 }
103 113
104 @Override 114 @Override
@@ -125,12 +135,14 @@ public final class DeviceProfileEntity extends BaseSqlEntity<DeviceProfile> impl @@ -125,12 +135,14 @@ public final class DeviceProfileEntity extends BaseSqlEntity<DeviceProfile> impl
125 deviceProfile.setName(name); 135 deviceProfile.setName(name);
126 deviceProfile.setType(type); 136 deviceProfile.setType(type);
127 deviceProfile.setTransportType(transportType); 137 deviceProfile.setTransportType(transportType);
  138 + deviceProfile.setProvisionType(provisionType);
128 deviceProfile.setDescription(description); 139 deviceProfile.setDescription(description);
129 deviceProfile.setDefault(isDefault); 140 deviceProfile.setDefault(isDefault);
130 deviceProfile.setProfileData(JacksonUtil.convertValue(profileData, DeviceProfileData.class)); 141 deviceProfile.setProfileData(JacksonUtil.convertValue(profileData, DeviceProfileData.class));
131 if (defaultRuleChainId != null) { 142 if (defaultRuleChainId != null) {
132 deviceProfile.setDefaultRuleChainId(new RuleChainId(defaultRuleChainId)); 143 deviceProfile.setDefaultRuleChainId(new RuleChainId(defaultRuleChainId));
133 } 144 }
  145 + deviceProfile.setProvisionDeviceKey(provisionDeviceKey);
134 return deviceProfile; 146 return deviceProfile;
135 } 147 }
136 } 148 }
@@ -20,7 +20,6 @@ import org.springframework.data.domain.Pageable; @@ -20,7 +20,6 @@ import org.springframework.data.domain.Pageable;
20 import org.springframework.data.jpa.repository.Query; 20 import org.springframework.data.jpa.repository.Query;
21 import org.springframework.data.repository.PagingAndSortingRepository; 21 import org.springframework.data.repository.PagingAndSortingRepository;
22 import org.springframework.data.repository.query.Param; 22 import org.springframework.data.repository.query.Param;
23 -import org.thingsboard.server.common.data.DeviceProfile;  
24 import org.thingsboard.server.common.data.DeviceProfileInfo; 23 import org.thingsboard.server.common.data.DeviceProfileInfo;
25 import org.thingsboard.server.common.data.DeviceTransportType; 24 import org.thingsboard.server.common.data.DeviceTransportType;
26 import org.thingsboard.server.dao.model.sql.DeviceProfileEntity; 25 import org.thingsboard.server.dao.model.sql.DeviceProfileEntity;
@@ -66,4 +65,5 @@ public interface DeviceProfileRepository extends PagingAndSortingRepository<Devi @@ -66,4 +65,5 @@ public interface DeviceProfileRepository extends PagingAndSortingRepository<Devi
66 65
67 DeviceProfileEntity findByTenantIdAndName(UUID id, String profileName); 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,5 +168,4 @@ public interface DeviceRepository extends PagingAndSortingRepository<DeviceEntit
168 DeviceEntity findByTenantIdAndId(UUID tenantId, UUID id); 168 DeviceEntity findByTenantIdAndId(UUID tenantId, UUID id);
169 169
170 Long countByDeviceProfileId(UUID deviceProfileId); 170 Long countByDeviceProfileId(UUID deviceProfileId);
171 -  
172 } 171 }
@@ -92,6 +92,11 @@ public class JpaDeviceProfileDao extends JpaAbstractSearchTextDao<DeviceProfileE @@ -92,6 +92,11 @@ public class JpaDeviceProfileDao extends JpaAbstractSearchTextDao<DeviceProfileE
92 } 92 }
93 93
94 @Override 94 @Override
  95 + public DeviceProfile findByProvisionDeviceKey(String provisionDeviceKey) {
  96 + return DaoUtil.getData(deviceProfileRepository.findByProvisionDeviceKey(provisionDeviceKey));
  97 + }
  98 +
  99 + @Override
95 public DeviceProfile findByName(TenantId tenantId, String profileName) { 100 public DeviceProfile findByName(TenantId tenantId, String profileName) {
96 return DaoUtil.getData(deviceProfileRepository.findByTenantIdAndName(tenantId.getId(), profileName)); 101 return DaoUtil.getData(deviceProfileRepository.findByTenantIdAndName(tenantId.getId(), profileName));
97 } 102 }
@@ -163,13 +163,16 @@ CREATE TABLE IF NOT EXISTS device_profile ( @@ -163,13 +163,16 @@ CREATE TABLE IF NOT EXISTS device_profile (
163 name varchar(255), 163 name varchar(255),
164 type varchar(255), 164 type varchar(255),
165 transport_type varchar(255), 165 transport_type varchar(255),
  166 + provision_type varchar(255),
166 profile_data jsonb, 167 profile_data jsonb,
167 description varchar, 168 description varchar,
168 search_text varchar(255), 169 search_text varchar(255),
169 is_default boolean, 170 is_default boolean,
170 tenant_id uuid, 171 tenant_id uuid,
171 default_rule_chain_id uuid, 172 default_rule_chain_id uuid,
  173 + provision_device_key varchar,
172 CONSTRAINT device_profile_name_unq_key UNIQUE (tenant_id, name), 174 CONSTRAINT device_profile_name_unq_key UNIQUE (tenant_id, name),
  175 + CONSTRAINT device_provision_key_unq_key UNIQUE (provision_device_key),
173 CONSTRAINT fk_default_rule_chain_device_profile FOREIGN KEY (default_rule_chain_id) REFERENCES rule_chain(id) 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,13 +181,16 @@ CREATE TABLE IF NOT EXISTS device_profile (
181 name varchar(255), 181 name varchar(255),
182 type varchar(255), 182 type varchar(255),
183 transport_type varchar(255), 183 transport_type varchar(255),
  184 + provision_type varchar(255),
184 profile_data jsonb, 185 profile_data jsonb,
185 description varchar, 186 description varchar,
186 search_text varchar(255), 187 search_text varchar(255),
187 is_default boolean, 188 is_default boolean,
188 tenant_id uuid, 189 tenant_id uuid,
189 default_rule_chain_id uuid, 190 default_rule_chain_id uuid,
  191 + provision_device_key varchar,
190 CONSTRAINT device_profile_name_unq_key UNIQUE (tenant_id, name), 192 CONSTRAINT device_profile_name_unq_key UNIQUE (tenant_id, name),
  193 + CONSTRAINT device_provision_key_unq_key UNIQUE (provision_device_key),
191 CONSTRAINT fk_default_rule_chain_device_profile FOREIGN KEY (default_rule_chain_id) REFERENCES rule_chain(id) 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,6 +105,7 @@ import { AlarmRuleConditionComponent } from './profile/alarm/alarm-rule-conditio
105 import { FilterTextComponent } from './filter/filter-text.component'; 105 import { FilterTextComponent } from './filter/filter-text.component';
106 import { AddDeviceProfileDialogComponent } from './profile/add-device-profile-dialog.component'; 106 import { AddDeviceProfileDialogComponent } from './profile/add-device-profile-dialog.component';
107 import { RuleChainAutocompleteComponent } from './rule-chain/rule-chain-autocomplete.component'; 107 import { RuleChainAutocompleteComponent } from './rule-chain/rule-chain-autocomplete.component';
  108 +import { DeviceProfileProvisionConfigurationComponent } from "./profile/device-profile-provision-configuration.component";
108 import { AlarmScheduleComponent } from './profile/alarm/alarm-schedule.component'; 109 import { AlarmScheduleComponent } from './profile/alarm/alarm-schedule.component';
109 import { DeviceWizardDialogComponent } from './wizard/device-wizard-dialog.component'; 110 import { DeviceWizardDialogComponent } from './wizard/device-wizard-dialog.component';
110 import { DeviceCredentialsComponent } from './device/device-credentials.component'; 111 import { DeviceCredentialsComponent } from './device/device-credentials.component';
@@ -202,6 +203,7 @@ import { AlarmRuleConditionDialogComponent } from '@home/components/profile/alar @@ -202,6 +203,7 @@ import { AlarmRuleConditionDialogComponent } from '@home/components/profile/alar
202 AddDeviceProfileDialogComponent, 203 AddDeviceProfileDialogComponent,
203 RuleChainAutocompleteComponent, 204 RuleChainAutocompleteComponent,
204 AlarmScheduleInfoComponent, 205 AlarmScheduleInfoComponent,
  206 + DeviceProfileProvisionConfigurationComponent,
205 AlarmScheduleComponent, 207 AlarmScheduleComponent,
206 DeviceWizardDialogComponent, 208 DeviceWizardDialogComponent,
207 DeviceCredentialsComponent, 209 DeviceCredentialsComponent,
@@ -290,7 +292,9 @@ import { AlarmRuleConditionDialogComponent } from '@home/components/profile/alar @@ -290,7 +292,9 @@ import { AlarmRuleConditionDialogComponent } from '@home/components/profile/alar
290 AlarmScheduleInfoComponent, 292 AlarmScheduleInfoComponent,
291 AlarmScheduleComponent, 293 AlarmScheduleComponent,
292 AlarmScheduleDialogComponent, 294 AlarmScheduleDialogComponent,
293 - EditAlarmDetailsDialogComponent 295 + EditAlarmDetailsDialogComponent,
  296 + DeviceProfileProvisionConfigurationComponent,
  297 + AlarmScheduleComponent
294 ], 298 ],
295 providers: [ 299 providers: [
296 WidgetComponentService, 300 WidgetComponentService,
@@ -96,6 +96,14 @@ @@ -96,6 +96,14 @@
96 </tb-device-profile-alarms> 96 </tb-device-profile-alarms>
97 </form> 97 </form>
98 </mat-step> 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 </mat-horizontal-stepper> 107 </mat-horizontal-stepper>
100 </div> 108 </div>
101 <div mat-dialog-actions fxLayout="column" fxLayoutAlign="start wrap" fxLayoutGap="8px" style="height: 100px;"> 109 <div mat-dialog-actions fxLayout="column" fxLayoutAlign="start wrap" fxLayoutGap="8px" style="height: 100px;">
@@ -36,7 +36,10 @@ import { @@ -36,7 +36,10 @@ import {
36 DeviceProfile, 36 DeviceProfile,
37 DeviceProfileType, 37 DeviceProfileType,
38 deviceProfileTypeTranslationMap, 38 deviceProfileTypeTranslationMap,
39 - DeviceTransportType, deviceTransportTypeHintMap, 39 + DeviceProvisionConfiguration,
  40 + DeviceProvisionType,
  41 + DeviceTransportType,
  42 + deviceTransportTypeHintMap,
40 deviceTransportTypeTranslationMap 43 deviceTransportTypeTranslationMap
41 } from '@shared/models/device.models'; 44 } from '@shared/models/device.models';
42 import { DeviceProfileService } from '@core/http/device-profile.service'; 45 import { DeviceProfileService } from '@core/http/device-profile.service';
@@ -84,6 +87,8 @@ export class AddDeviceProfileDialogComponent extends @@ -84,6 +87,8 @@ export class AddDeviceProfileDialogComponent extends
84 87
85 alarmRulesFormGroup: FormGroup; 88 alarmRulesFormGroup: FormGroup;
86 89
  90 + provisionConfigFormGroup: FormGroup;
  91 +
87 constructor(protected store: Store<AppState>, 92 constructor(protected store: Store<AppState>,
88 protected router: Router, 93 protected router: Router,
89 @Inject(MAT_DIALOG_DATA) public data: AddDeviceProfileDialogData, 94 @Inject(MAT_DIALOG_DATA) public data: AddDeviceProfileDialogData,
@@ -118,6 +123,14 @@ export class AddDeviceProfileDialogComponent extends @@ -118,6 +123,14 @@ export class AddDeviceProfileDialogComponent extends
118 alarms: [null] 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 private deviceProfileTransportTypeChanged() { 136 private deviceProfileTransportTypeChanged() {
@@ -138,7 +151,7 @@ export class AddDeviceProfileDialogComponent extends @@ -138,7 +151,7 @@ export class AddDeviceProfileDialogComponent extends
138 } 151 }
139 152
140 nextStep() { 153 nextStep() {
141 - if (this.selectedIndex < 2) { 154 + if (this.selectedIndex < 3) {
142 this.addDeviceProfileStepper.next(); 155 this.addDeviceProfileStepper.next();
143 } else { 156 } else {
144 this.add(); 157 this.add();
@@ -153,20 +166,28 @@ export class AddDeviceProfileDialogComponent extends @@ -153,20 +166,28 @@ export class AddDeviceProfileDialogComponent extends
153 return this.transportConfigFormGroup; 166 return this.transportConfigFormGroup;
154 case 2: 167 case 2:
155 return this.alarmRulesFormGroup; 168 return this.alarmRulesFormGroup;
  169 + case 3:
  170 + return this.provisionConfigFormGroup;
156 } 171 }
157 } 172 }
158 173
159 add(): void { 174 add(): void {
160 if (this.allValid()) { 175 if (this.allValid()) {
  176 + const deviceProvisionConfiguration: DeviceProvisionConfiguration = this.provisionConfigFormGroup.get('provisionConfiguration').value;
  177 + const provisionDeviceKey = deviceProvisionConfiguration.provisionDeviceKey;
  178 + delete deviceProvisionConfiguration.provisionDeviceKey;
161 const deviceProfile: DeviceProfile = { 179 const deviceProfile: DeviceProfile = {
162 name: this.deviceProfileDetailsFormGroup.get('name').value, 180 name: this.deviceProfileDetailsFormGroup.get('name').value,
163 type: this.deviceProfileDetailsFormGroup.get('type').value, 181 type: this.deviceProfileDetailsFormGroup.get('type').value,
164 transportType: this.transportConfigFormGroup.get('transportType').value, 182 transportType: this.transportConfigFormGroup.get('transportType').value,
  183 + provisionType: deviceProvisionConfiguration.type,
  184 + provisionDeviceKey,
165 description: this.deviceProfileDetailsFormGroup.get('description').value, 185 description: this.deviceProfileDetailsFormGroup.get('description').value,
166 profileData: { 186 profileData: {
167 configuration: createDeviceProfileConfiguration(DeviceProfileType.DEFAULT), 187 configuration: createDeviceProfileConfiguration(DeviceProfileType.DEFAULT),
168 transportConfiguration: this.transportConfigFormGroup.get('transportConfiguration').value, 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 if (this.deviceProfileDetailsFormGroup.get('defaultRuleChainId').value) { 193 if (this.deviceProfileDetailsFormGroup.get('defaultRuleChainId').value) {
@@ -188,6 +209,8 @@ export class AddDeviceProfileDialogComponent extends @@ -188,6 +209,8 @@ export class AddDeviceProfileDialogComponent extends
188 return 'device-profile.transport-configuration'; 209 return 'device-profile.transport-configuration';
189 case 2: 210 case 2:
190 return 'device-profile.alarm-rules'; 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,13 +24,15 @@ import { EntityTableConfig } from '@home/models/entity/entities-table-config.mod
24 import { EntityComponent } from '../entity/entity.component'; 24 import { EntityComponent } from '../entity/entity.component';
25 import { 25 import {
26 createDeviceProfileConfiguration, 26 createDeviceProfileConfiguration,
  27 + createDeviceProfileTransportConfiguration,
27 DeviceProfile, 28 DeviceProfile,
28 DeviceProfileData, 29 DeviceProfileData,
29 DeviceProfileType, 30 DeviceProfileType,
30 deviceProfileTypeTranslationMap, 31 deviceProfileTypeTranslationMap,
  32 + DeviceProvisionConfiguration,
  33 + DeviceProvisionType,
31 DeviceTransportType, 34 DeviceTransportType,
32 - deviceTransportTypeTranslationMap,  
33 - createDeviceProfileTransportConfiguration 35 + deviceTransportTypeTranslationMap
34 } from '@shared/models/device.models'; 36 } from '@shared/models/device.models';
35 import { EntityType } from '@shared/models/entity-type.models'; 37 import { EntityType } from '@shared/models/entity-type.models';
36 import { RuleChainId } from '@shared/models/id/rule-chain-id'; 38 import { RuleChainId } from '@shared/models/id/rule-chain-id';
@@ -72,15 +74,23 @@ export class DeviceProfileComponent extends EntityComponent<DeviceProfile> { @@ -72,15 +74,23 @@ export class DeviceProfileComponent extends EntityComponent<DeviceProfile> {
72 } 74 }
73 75
74 buildForm(entity: DeviceProfile): FormGroup { 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 const form = this.fb.group( 82 const form = this.fb.group(
76 { 83 {
77 name: [entity ? entity.name : '', [Validators.required]], 84 name: [entity ? entity.name : '', [Validators.required]],
78 type: [entity ? entity.type : null, [Validators.required]], 85 type: [entity ? entity.type : null, [Validators.required]],
79 transportType: [entity ? entity.transportType : null, [Validators.required]], 86 transportType: [entity ? entity.transportType : null, [Validators.required]],
  87 + provisionType: [deviceProvisionConfiguration.type, [Validators.required]],
  88 + provisionDeviceKey: [deviceProvisionConfiguration.provisionDeviceKey],
80 profileData: this.fb.group({ 89 profileData: this.fb.group({
81 configuration: [entity && !this.isAdd ? entity.profileData?.configuration : {}, Validators.required], 90 configuration: [entity && !this.isAdd ? entity.profileData?.configuration : {}, Validators.required],
82 transportConfiguration: [entity && !this.isAdd ? entity.profileData?.transportConfiguration : {}, Validators.required], 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 defaultRuleChainId: [entity && entity.defaultRuleChainId ? entity.defaultRuleChainId.id : null, []], 95 defaultRuleChainId: [entity && entity.defaultRuleChainId ? entity.defaultRuleChainId.id : null, []],
86 description: [entity ? entity.description : '', []], 96 description: [entity ? entity.description : '', []],
@@ -100,6 +110,7 @@ export class DeviceProfileComponent extends EntityComponent<DeviceProfile> { @@ -100,6 +110,7 @@ export class DeviceProfileComponent extends EntityComponent<DeviceProfile> {
100 if (entity && !entity.id) { 110 if (entity && !entity.id) {
101 form.get('type').patchValue(DeviceProfileType.DEFAULT, {emitEvent: true}); 111 form.get('type').patchValue(DeviceProfileType.DEFAULT, {emitEvent: true});
102 form.get('transportType').patchValue(DeviceTransportType.DEFAULT, {emitEvent: true}); 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,10 +141,22 @@ export class DeviceProfileComponent extends EntityComponent<DeviceProfile> {
130 } 141 }
131 142
132 updateForm(entity: DeviceProfile) { 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 this.entityForm.patchValue({name: entity.name}); 149 this.entityForm.patchValue({name: entity.name});
134 this.entityForm.patchValue({type: entity.type}, {emitEvent: false}); 150 this.entityForm.patchValue({type: entity.type}, {emitEvent: false});
135 this.entityForm.patchValue({transportType: entity.transportType}, {emitEvent: false}); 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 this.entityForm.patchValue({defaultRuleChainId: entity.defaultRuleChainId ? entity.defaultRuleChainId.id : null}); 160 this.entityForm.patchValue({defaultRuleChainId: entity.defaultRuleChainId ? entity.defaultRuleChainId.id : null});
138 this.entityForm.patchValue({description: entity.description}); 161 this.entityForm.patchValue({description: entity.description});
139 } 162 }
@@ -142,6 +165,10 @@ export class DeviceProfileComponent extends EntityComponent<DeviceProfile> { @@ -142,6 +165,10 @@ export class DeviceProfileComponent extends EntityComponent<DeviceProfile> {
142 if (formValue.defaultRuleChainId) { 165 if (formValue.defaultRuleChainId) {
143 formValue.defaultRuleChainId = new RuleChainId(formValue.defaultRuleChainId); 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 return super.prepareFormValue(formValue); 172 return super.prepareFormValue(formValue);
146 } 173 }
147 174
@@ -121,6 +121,14 @@ @@ -121,6 +121,14 @@
121 </tb-device-profile-alarms> 121 </tb-device-profile-alarms>
122 </form> 122 </form>
123 </mat-step> 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 <mat-step [stepControl]="credentialsFormGroup" [optional]="true"> 132 <mat-step [stepControl]="credentialsFormGroup" [optional]="true">
125 <ng-template matStepLabel>{{ 'device.credentials' | translate }}</ng-template> 133 <ng-template matStepLabel>{{ 'device.credentials' | translate }}</ng-template>
126 <form [formGroup]="credentialsFormGroup" style="padding-bottom: 16px;"> 134 <form [formGroup]="credentialsFormGroup" style="padding-bottom: 16px;">
@@ -25,7 +25,7 @@ import { @@ -25,7 +25,7 @@ import {
25 createDeviceProfileConfiguration, 25 createDeviceProfileConfiguration,
26 createDeviceProfileTransportConfiguration, 26 createDeviceProfileTransportConfiguration,
27 DeviceProfile, 27 DeviceProfile,
28 - DeviceProfileType, 28 + DeviceProfileType, DeviceProvisionConfiguration, DeviceProvisionType,
29 DeviceTransportType, deviceTransportTypeConfigurationInfoMap, deviceTransportTypeHintMap, 29 DeviceTransportType, deviceTransportTypeConfigurationInfoMap, deviceTransportTypeHintMap,
30 deviceTransportTypeTranslationMap 30 deviceTransportTypeTranslationMap
31 } from '@shared/models/device.models'; 31 } from '@shared/models/device.models';
@@ -75,6 +75,8 @@ export class DeviceWizardDialogComponent extends @@ -75,6 +75,8 @@ export class DeviceWizardDialogComponent extends
75 75
76 alarmRulesFormGroup: FormGroup; 76 alarmRulesFormGroup: FormGroup;
77 77
  78 + provisionConfigFormGroup: FormGroup;
  79 +
78 credentialsFormGroup: FormGroup; 80 credentialsFormGroup: FormGroup;
79 81
80 customerFormGroup: FormGroup; 82 customerFormGroup: FormGroup;
@@ -142,6 +144,14 @@ export class DeviceWizardDialogComponent extends @@ -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 this.credentialsFormGroup = this.fb.group({ 155 this.credentialsFormGroup = this.fb.group({
146 setCredential: [false], 156 setCredential: [false],
147 credential: [{value: null, disabled: true}] 157 credential: [{value: null, disabled: true}]
@@ -201,7 +211,7 @@ export class DeviceWizardDialogComponent extends @@ -201,7 +211,7 @@ export class DeviceWizardDialogComponent extends
201 getFormLabel(index: number): string { 211 getFormLabel(index: number): string {
202 if (index > 0) { 212 if (index > 0) {
203 if (!this.createProfile) { 213 if (!this.createProfile) {
204 - index += 2; 214 + index += 3;
205 } else if (!this.createTransportConfiguration) { 215 } else if (!this.createTransportConfiguration) {
206 index += 1; 216 index += 1;
207 } 217 }
@@ -214,8 +224,10 @@ export class DeviceWizardDialogComponent extends @@ -214,8 +224,10 @@ export class DeviceWizardDialogComponent extends
214 case 2: 224 case 2:
215 return 'device-profile.alarm-rules'; 225 return 'device-profile.alarm-rules';
216 case 3: 226 case 3:
217 - return 'device.credentials'; 227 + return 'device-profile.device-provisioning';
218 case 4: 228 case 4:
  229 + return 'device.credentials';
  230 + case 5:
219 return 'customer.customer'; 231 return 'customer.customer';
220 } 232 }
221 } 233 }
@@ -246,14 +258,20 @@ export class DeviceWizardDialogComponent extends @@ -246,14 +258,20 @@ export class DeviceWizardDialogComponent extends
246 258
247 private createDeviceProfile(): Observable<EntityId> { 259 private createDeviceProfile(): Observable<EntityId> {
248 if (this.deviceWizardFormGroup.get('addProfileType').value) { 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 const deviceProfile: DeviceProfile = { 264 const deviceProfile: DeviceProfile = {
250 name: this.deviceWizardFormGroup.get('newDeviceProfileTitle').value, 265 name: this.deviceWizardFormGroup.get('newDeviceProfileTitle').value,
251 type: DeviceProfileType.DEFAULT, 266 type: DeviceProfileType.DEFAULT,
252 transportType: this.deviceWizardFormGroup.get('transportType').value, 267 transportType: this.deviceWizardFormGroup.get('transportType').value,
  268 + provisionType: deviceProvisionConfiguration.type,
  269 + provisionDeviceKey,
253 profileData: { 270 profileData: {
254 configuration: createDeviceProfileConfiguration(DeviceProfileType.DEFAULT), 271 configuration: createDeviceProfileConfiguration(DeviceProfileType.DEFAULT),
255 transportConfiguration: this.transportConfigFormGroup.get('transportConfiguration').value, 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 return this.deviceProfileService.saveDeviceProfile(deviceProfile).pipe( 277 return this.deviceProfileService.saveDeviceProfile(deviceProfile).pipe(
@@ -50,6 +50,15 @@ @@ -50,6 +50,15 @@
50 </div> 50 </div>
51 </div> 51 </div>
52 </mat-tab> 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 <mat-tab *ngIf="false" 62 <mat-tab *ngIf="false"
54 label="{{'device-profile.profile-configuration' | translate }}" #deviceProfile="matTab"> 63 label="{{'device-profile.profile-configuration' | translate }}" #deviceProfile="matTab">
55 <div class="mat-padding" [formGroup]="detailsForm"> 64 <div class="mat-padding" [formGroup]="detailsForm">
@@ -43,6 +43,12 @@ export enum MqttTransportPayloadType { @@ -43,6 +43,12 @@ export enum MqttTransportPayloadType {
43 PROTOBUF = 'PROTOBUF' 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 export interface DeviceConfigurationFormInfo { 52 export interface DeviceConfigurationFormInfo {
47 hasProfileConfiguration: boolean; 53 hasProfileConfiguration: boolean;
48 hasDeviceConfiguration: boolean; 54 hasDeviceConfiguration: boolean;
@@ -74,6 +80,15 @@ export const deviceTransportTypeTranslationMap = new Map<DeviceTransportType, st @@ -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 export const deviceTransportTypeHintMap = new Map<DeviceTransportType, string>( 92 export const deviceTransportTypeHintMap = new Map<DeviceTransportType, string>(
78 [ 93 [
79 [DeviceTransportType.DEFAULT, 'device-profile.transport-type-default-hint'], 94 [DeviceTransportType.DEFAULT, 'device-profile.transport-type-default-hint'],
@@ -148,6 +163,12 @@ export interface DeviceProfileTransportConfiguration extends DeviceProfileTransp @@ -148,6 +163,12 @@ export interface DeviceProfileTransportConfiguration extends DeviceProfileTransp
148 type: DeviceTransportType; 163 type: DeviceTransportType;
149 } 164 }
150 165
  166 +export interface DeviceProvisionConfiguration {
  167 + type: DeviceProvisionType;
  168 + provisionDeviceSecret?: string;
  169 + provisionDeviceKey?: string;
  170 +}
  171 +
151 export function createDeviceProfileConfiguration(type: DeviceProfileType): DeviceProfileConfiguration { 172 export function createDeviceProfileConfiguration(type: DeviceProfileType): DeviceProfileConfiguration {
152 let configuration: DeviceProfileConfiguration = null; 173 let configuration: DeviceProfileConfiguration = null;
153 if (type) { 174 if (type) {
@@ -295,6 +316,7 @@ export interface DeviceProfileData { @@ -295,6 +316,7 @@ export interface DeviceProfileData {
295 configuration: DeviceProfileConfiguration; 316 configuration: DeviceProfileConfiguration;
296 transportConfiguration: DeviceProfileTransportConfiguration; 317 transportConfiguration: DeviceProfileTransportConfiguration;
297 alarms?: Array<DeviceProfileAlarm>; 318 alarms?: Array<DeviceProfileAlarm>;
  319 + provisionConfiguration?: DeviceProvisionConfiguration;
298 } 320 }
299 321
300 export interface DeviceProfile extends BaseData<DeviceProfileId> { 322 export interface DeviceProfile extends BaseData<DeviceProfileId> {
@@ -304,6 +326,8 @@ export interface DeviceProfile extends BaseData<DeviceProfileId> { @@ -304,6 +326,8 @@ export interface DeviceProfile extends BaseData<DeviceProfileId> {
304 default?: boolean; 326 default?: boolean;
305 type: DeviceProfileType; 327 type: DeviceProfileType;
306 transportType: DeviceTransportType; 328 transportType: DeviceTransportType;
  329 + provisionType: DeviceProvisionType;
  330 + provisionDeviceKey?: string;
307 defaultRuleChainId?: RuleChainId; 331 defaultRuleChainId?: RuleChainId;
308 profileData: DeviceProfileData; 332 profileData: DeviceProfileData;
309 } 333 }
@@ -930,6 +930,16 @@ @@ -930,6 +930,16 @@
930 "alarm-rule-condition": "Alarm rule condition", 930 "alarm-rule-condition": "Alarm rule condition",
931 "enter-alarm-rule-condition-prompt": "Please add alarm rule condition", 931 "enter-alarm-rule-condition-prompt": "Please add alarm rule condition",
932 "edit-alarm-rule-condition": "Edit alarm rule condition", 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 "condition": "Condition", 943 "condition": "Condition",
934 "condition-type": "Condition type", 944 "condition-type": "Condition type",
935 "condition-type-simple": "Simple", 945 "condition-type-simple": "Simple",