Commit 2b1317b10fe432fc0ff666ffb38ed9a9d1393852

Authored by Vladyslav Prykhodko
2 parents 1b1dac30 1bb1f5be

Merge branch 'feature/device-provision-3.2-onlyProfileVersion' of https://github…

….com/zbeacon/thingsboard into zbeacon-feature/device-provision-3.2-onlyProfileVersion
Showing 45 changed files with 1020 additions and 19 deletions
@@ -20,13 +20,16 @@ CREATE TABLE IF NOT EXISTS device_profile ( @@ -20,13 +20,16 @@ CREATE TABLE IF NOT EXISTS device_profile (
20 name varchar(255), 20 name varchar(255),
21 type varchar(255), 21 type varchar(255),
22 transport_type varchar(255), 22 transport_type varchar(255),
  23 + provision_type varchar(255),
23 profile_data jsonb, 24 profile_data jsonb,
24 description varchar, 25 description varchar,
25 search_text varchar(255), 26 search_text varchar(255),
26 is_default boolean, 27 is_default boolean,
27 tenant_id uuid, 28 tenant_id uuid,
28 default_rule_chain_id uuid, 29 default_rule_chain_id uuid,
  30 + provision_device_key varchar,
29 CONSTRAINT device_profile_name_unq_key UNIQUE (tenant_id, name), 31 CONSTRAINT device_profile_name_unq_key UNIQUE (tenant_id, name),
  32 + CONSTRAINT device_provision_key_unq_key UNIQUE (provision_device_key),
30 CONSTRAINT fk_default_rule_chain_device_profile FOREIGN KEY (default_rule_chain_id) REFERENCES rule_chain(id) 33 CONSTRAINT fk_default_rule_chain_device_profile FOREIGN KEY (default_rule_chain_id) REFERENCES rule_chain(id)
31 ); 34 );
32 35
@@ -706,6 +706,12 @@ public abstract class BaseController { @@ -706,6 +706,12 @@ public abstract class BaseController {
706 case ASSIGNED_TO_TENANT: 706 case ASSIGNED_TO_TENANT:
707 msgType = DataConstants.ENTITY_ASSIGNED_TO_TENANT; 707 msgType = DataConstants.ENTITY_ASSIGNED_TO_TENANT;
708 break; 708 break;
  709 + case PROVISION_SUCCESS:
  710 + msgType = DataConstants.PROVISION_SUCCESS;
  711 + break;
  712 + case PROVISION_FAILURE:
  713 + msgType = DataConstants.PROVISION_FAILURE;
  714 + break;
709 } 715 }
710 if (!StringUtils.isEmpty(msgType)) { 716 if (!StringUtils.isEmpty(msgType)) {
711 try { 717 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.node.ObjectNode;
  20 +import com.google.common.util.concurrent.Futures;
  21 +import com.google.common.util.concurrent.ListenableFuture;
  22 +import com.google.common.util.concurrent.MoreExecutors;
  23 +import lombok.extern.slf4j.Slf4j;
  24 +import org.springframework.beans.factory.annotation.Autowired;
  25 +import org.springframework.stereotype.Service;
  26 +import org.springframework.util.StringUtils;
  27 +import org.thingsboard.server.common.data.DataConstants;
  28 +import org.thingsboard.server.common.data.Device;
  29 +import org.thingsboard.server.common.data.DeviceProfile;
  30 +import org.thingsboard.server.common.data.audit.ActionType;
  31 +import org.thingsboard.server.common.data.device.profile.AllowCreateNewDevicesDeviceProfileProvisionConfiguration;
  32 +import org.thingsboard.server.common.data.device.profile.CheckPreProvisionedDevicesDeviceProfileProvisionConfiguration;
  33 +import org.thingsboard.server.common.data.id.CustomerId;
  34 +import org.thingsboard.server.common.data.id.TenantId;
  35 +import org.thingsboard.server.common.data.id.UserId;
  36 +import org.thingsboard.server.common.data.kv.AttributeKvEntry;
  37 +import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry;
  38 +import org.thingsboard.server.common.data.kv.StringDataEntry;
  39 +import org.thingsboard.server.common.data.security.DeviceCredentials;
  40 +import org.thingsboard.server.common.data.security.DeviceCredentialsType;
  41 +import org.thingsboard.server.common.msg.TbMsg;
  42 +import org.thingsboard.server.common.msg.TbMsgMetaData;
  43 +import org.thingsboard.server.common.msg.queue.ServiceType;
  44 +import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
  45 +import org.thingsboard.server.dao.attributes.AttributesService;
  46 +import org.thingsboard.server.dao.audit.AuditLogService;
  47 +import org.thingsboard.server.dao.device.DeviceCredentialsService;
  48 +import org.thingsboard.server.dao.device.DeviceDao;
  49 +import org.thingsboard.server.dao.device.DeviceProfileDao;
  50 +import org.thingsboard.server.dao.device.DeviceProvisionService;
  51 +import org.thingsboard.server.dao.device.DeviceService;
  52 +import org.thingsboard.server.dao.device.provision.ProvisionRequest;
  53 +import org.thingsboard.server.dao.device.provision.ProvisionResponse;
  54 +import org.thingsboard.server.dao.device.provision.ProvisionResponseStatus;
  55 +import org.thingsboard.server.dao.util.mapping.JacksonUtil;
  56 +import org.thingsboard.server.gen.transport.TransportProtos;
  57 +import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg;
  58 +import org.thingsboard.server.queue.TbQueueCallback;
  59 +import org.thingsboard.server.queue.TbQueueProducer;
  60 +import org.thingsboard.server.queue.common.TbProtoQueueMsg;
  61 +import org.thingsboard.server.queue.discovery.PartitionService;
  62 +import org.thingsboard.server.queue.provider.TbQueueProducerProvider;
  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.locks.ReentrantLock;
  69 +
  70 +
  71 +@Service
  72 +@Slf4j
  73 +public class DeviceProvisionServiceImpl implements DeviceProvisionService {
  74 +
  75 + protected TbQueueProducer<TbProtoQueueMsg<ToRuleEngineMsg>> ruleEngineMsgProducer;
  76 +
  77 + private static final String DEVICE_PROVISION_STATE = "provisionState";
  78 + private static final String PROVISIONED_STATE = "provisioned";
  79 +
  80 + private final ReentrantLock deviceCreationLock = new ReentrantLock();
  81 +
  82 + @Autowired
  83 + DeviceDao deviceDao;
  84 +
  85 + @Autowired
  86 + DeviceProfileDao deviceProfileDao;
  87 +
  88 + @Autowired
  89 + DeviceService deviceService;
  90 +
  91 + @Autowired
  92 + DeviceCredentialsService deviceCredentialsService;
  93 +
  94 + @Autowired
  95 + AttributesService attributesService;
  96 +
  97 + @Autowired
  98 + DeviceStateService deviceStateService;
  99 +
  100 + @Autowired
  101 + AuditLogService auditLogService;
  102 +
  103 + @Autowired
  104 + PartitionService partitionService;
  105 +
  106 + public DeviceProvisionServiceImpl(TbQueueProducerProvider producerProvider) {
  107 + ruleEngineMsgProducer = producerProvider.getRuleEngineMsgProducer();
  108 + }
  109 +
  110 + @Override
  111 + public ListenableFuture<ProvisionResponse> provisionDevice(ProvisionRequest provisionRequest) {
  112 + String provisionRequestKey = provisionRequest.getCredentials().getProvisionDeviceKey();
  113 + String provisionRequestSecret = provisionRequest.getCredentials().getProvisionDeviceSecret();
  114 +
  115 + if (StringUtils.isEmpty(provisionRequestKey) || StringUtils.isEmpty(provisionRequestSecret)) {
  116 + return Futures.immediateFuture(new ProvisionResponse(null, ProvisionResponseStatus.NOT_FOUND));
  117 + }
  118 +
  119 + DeviceProfile targetProfile = deviceProfileDao.findByProvisionDeviceKey(provisionRequestKey);
  120 +
  121 + if (targetProfile == null) {
  122 + return Futures.immediateFuture(new ProvisionResponse(null, ProvisionResponseStatus.NOT_FOUND));
  123 + }
  124 +
  125 + Device targetDevice = deviceDao.findDeviceByTenantIdAndName(targetProfile.getTenantId().getId(), provisionRequest.getDeviceName()).orElse(null);
  126 +
  127 + switch(targetProfile.getProvisionType()) {
  128 + case ALLOW_CREATE_NEW_DEVICES:
  129 + if (((AllowCreateNewDevicesDeviceProfileProvisionConfiguration) targetProfile.getProfileData().getProvisionConfiguration()).getProvisionDeviceSecret().equals(provisionRequestSecret)){
  130 + if (targetDevice != null) {
  131 + log.warn("[{}] The device is present and could not be provisioned once more!", targetDevice.getName());
  132 + notify(targetDevice, provisionRequest, DataConstants.PROVISION_FAILURE, false);
  133 + return Futures.immediateFuture(new ProvisionResponse(null, ProvisionResponseStatus.FAILURE));
  134 + } else {
  135 + return createDevice(provisionRequest, targetProfile);
  136 + }
  137 + }
  138 + break;
  139 + case CHECK_PRE_PROVISIONED_DEVICES:
  140 + if (((CheckPreProvisionedDevicesDeviceProfileProvisionConfiguration) targetProfile.getProfileData().getProvisionConfiguration()).getProvisionDeviceSecret().equals(provisionRequestSecret)) {
  141 + if (targetDevice != null && targetDevice.getDeviceProfileId().equals(targetProfile.getId())) {
  142 + return processProvision(targetDevice, provisionRequest);
  143 + } else {
  144 + log.warn("[{}] Failed to find pre provisioned device!", provisionRequest.getDeviceName());
  145 + return Futures.immediateFuture(new ProvisionResponse(null, ProvisionResponseStatus.FAILURE));
  146 + }
  147 + }
  148 + break;
  149 + }
  150 + return Futures.immediateFuture(new ProvisionResponse(null, ProvisionResponseStatus.NOT_FOUND));
  151 + }
  152 +
  153 + private ListenableFuture<ProvisionResponse> processProvision(Device device, ProvisionRequest provisionRequest) {
  154 + ListenableFuture<Optional<AttributeKvEntry>> provisionStateFuture = attributesService.find(device.getTenantId(), device.getId(),
  155 + DataConstants.SERVER_SCOPE, DEVICE_PROVISION_STATE);
  156 + ListenableFuture<Boolean> provisionedFuture = Futures.transformAsync(provisionStateFuture, optionalAtr -> {
  157 + if (optionalAtr.isPresent()) {
  158 + String state = optionalAtr.get().getValueAsString();
  159 + if (state.equals(PROVISIONED_STATE)) {
  160 + return Futures.immediateFuture(true);
  161 + } else {
  162 + log.error("[{}][{}] Unknown provision state: {}!", device.getName(), DEVICE_PROVISION_STATE, state);
  163 + return Futures.immediateCancelledFuture();
  164 + }
  165 + }
  166 + return Futures.transform(saveProvisionStateAttribute(device), input -> false, MoreExecutors.directExecutor());
  167 + }, MoreExecutors.directExecutor());
  168 + if (provisionedFuture.isCancelled()) {
  169 + throw new RuntimeException("Unknown provision state!");
  170 + }
  171 + return Futures.transform(provisionedFuture, provisioned -> {
  172 + if (provisioned) {
  173 + notify(device, provisionRequest, DataConstants.PROVISION_FAILURE, false);
  174 + return new ProvisionResponse(null, ProvisionResponseStatus.FAILURE);
  175 + }
  176 + notify(device, provisionRequest, DataConstants.PROVISION_SUCCESS, true);
  177 + return new ProvisionResponse(deviceCredentialsService.findDeviceCredentialsByDeviceId(device.getTenantId(), device.getId()), ProvisionResponseStatus.SUCCESS);
  178 + }, MoreExecutors.directExecutor());
  179 + }
  180 +
  181 + private ListenableFuture<ProvisionResponse> createDevice(ProvisionRequest provisionRequest, DeviceProfile profile) {
  182 + deviceCreationLock.lock();
  183 + try {
  184 + return processCreateDevice(provisionRequest, profile);
  185 + } finally {
  186 + deviceCreationLock.unlock();
  187 + }
  188 + }
  189 +
  190 + private void notify(Device device, ProvisionRequest provisionRequest, String type, boolean success) {
  191 + pushProvisionEventToRuleEngine(provisionRequest, device, type);
  192 + logAction(device.getTenantId(), device.getCustomerId(), device, success, provisionRequest);
  193 + }
  194 +
  195 + private ListenableFuture<ProvisionResponse> processCreateDevice(ProvisionRequest provisionRequest, DeviceProfile profile) {
  196 + Device device = deviceService.findDeviceByTenantIdAndName(profile.getTenantId(), provisionRequest.getDeviceName());
  197 + if (device == null) {
  198 + Device savedDevice = saveDevice(provisionRequest, profile);
  199 +
  200 + deviceStateService.onDeviceAdded(savedDevice);
  201 + pushDeviceCreatedEventToRuleEngine(savedDevice);
  202 + notify(savedDevice, provisionRequest, DataConstants.PROVISION_SUCCESS, true);
  203 +
  204 + return Futures.transform(saveProvisionStateAttribute(savedDevice), input ->
  205 + new ProvisionResponse(
  206 + getDeviceCredentials(savedDevice, provisionRequest.getX509CertPubKey()),
  207 + ProvisionResponseStatus.SUCCESS), MoreExecutors.directExecutor());
  208 + }
  209 + log.warn("[{}] The device is already provisioned!", device.getName());
  210 + notify(device, provisionRequest, DataConstants.PROVISION_FAILURE, false);
  211 + return Futures.immediateFuture(new ProvisionResponse(null, ProvisionResponseStatus.FAILURE));
  212 + }
  213 +
  214 + private ListenableFuture<List<Void>> saveProvisionStateAttribute(Device device) {
  215 + return attributesService.save(device.getTenantId(), device.getId(), DataConstants.SERVER_SCOPE,
  216 + Collections.singletonList(new BaseAttributeKvEntry(new StringDataEntry(DEVICE_PROVISION_STATE, PROVISIONED_STATE),
  217 + System.currentTimeMillis())));
  218 + }
  219 +
  220 + private Device saveDevice(ProvisionRequest provisionRequest, DeviceProfile profile) {
  221 + Device device = new Device();
  222 + device.setName(provisionRequest.getDeviceName());
  223 + device.setType(profile.getName());
  224 + device.setTenantId(profile.getTenantId());
  225 + return deviceService.saveDevice(device);
  226 + }
  227 +
  228 + private DeviceCredentials getDeviceCredentials(Device device, String x509CertPubKey) {
  229 + DeviceCredentials credentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(device.getTenantId(), device.getId());
  230 + if (!StringUtils.isEmpty(x509CertPubKey)) {
  231 + credentials.setCredentialsType(DeviceCredentialsType.X509_CERTIFICATE);
  232 + credentials.setCredentialsValue(x509CertPubKey);
  233 + return deviceCredentialsService.updateDeviceCredentials(device.getTenantId(), credentials);
  234 + }
  235 + return credentials;
  236 + }
  237 +
  238 + private void pushProvisionEventToRuleEngine(ProvisionRequest request, Device device, String type) {
  239 + try {
  240 + ObjectNode entityNode = JacksonUtil.OBJECT_MAPPER.valueToTree(request);
  241 + TbMsg msg = TbMsg.newMsg(type, device.getId(), createTbMsgMetaData(device), JacksonUtil.OBJECT_MAPPER.writeValueAsString(entityNode));
  242 + sendToRuleEngine(device.getTenantId(), msg, null);
  243 + } catch (JsonProcessingException | IllegalArgumentException e) {
  244 + log.warn("[{}] Failed to push device action to rule engine: {}", device.getId(), type, e);
  245 + }
  246 + }
  247 +
  248 + private void pushDeviceCreatedEventToRuleEngine(Device device) {
  249 + try {
  250 + ObjectNode entityNode = JacksonUtil.OBJECT_MAPPER.valueToTree(device);
  251 + TbMsg msg = TbMsg.newMsg(DataConstants.ENTITY_CREATED, device.getId(), createTbMsgMetaData(device), JacksonUtil.OBJECT_MAPPER.writeValueAsString(entityNode));
  252 + sendToRuleEngine(device.getTenantId(), msg, null);
  253 + } catch (JsonProcessingException | IllegalArgumentException e) {
  254 + log.warn("[{}] Failed to push device action to rule engine: {}", device.getId(), DataConstants.ENTITY_CREATED, e);
  255 + }
  256 + }
  257 +
  258 + protected void sendToRuleEngine(TenantId tenantId, TbMsg tbMsg, TbQueueCallback callback) {
  259 + TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_RULE_ENGINE, tenantId, tbMsg.getOriginator());
  260 + TransportProtos.ToRuleEngineMsg msg = TransportProtos.ToRuleEngineMsg.newBuilder().setTbMsg(TbMsg.toByteString(tbMsg))
  261 + .setTenantIdMSB(tenantId.getId().getMostSignificantBits())
  262 + .setTenantIdLSB(tenantId.getId().getLeastSignificantBits()).build();
  263 + ruleEngineMsgProducer.send(tpi, new TbProtoQueueMsg<>(tbMsg.getId(), msg), callback);
  264 + }
  265 +
  266 + private TbMsgMetaData createTbMsgMetaData(Device device) {
  267 + TbMsgMetaData metaData = new TbMsgMetaData();
  268 + metaData.putValue("tenantId", device.getTenantId().toString());
  269 + return metaData;
  270 + }
  271 +
  272 + private void logAction(TenantId tenantId, CustomerId customerId, Device device, boolean success, ProvisionRequest provisionRequest) {
  273 + ActionType actionType = success ? ActionType.PROVISION_SUCCESS : ActionType.PROVISION_FAILURE;
  274 + auditLogService.logEntityAction(tenantId, customerId, new UserId(UserId.NULL_UUID), device.getName(), device.getId(), device, actionType, null, provisionRequest);
  275 + }
  276 +}
@@ -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,7 @@ import org.thingsboard.server.common.data.Device; @@ -31,6 +30,7 @@ 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.profile.ProvisionDeviceProfileCredentials;
34 import org.thingsboard.server.common.data.id.CustomerId; 34 import org.thingsboard.server.common.data.id.CustomerId;
35 import org.thingsboard.server.common.data.id.DeviceId; 35 import org.thingsboard.server.common.data.id.DeviceId;
36 import org.thingsboard.server.common.data.id.DeviceProfileId; 36 import org.thingsboard.server.common.data.id.DeviceProfileId;
@@ -45,17 +45,23 @@ import org.thingsboard.server.common.msg.TbMsgMetaData; @@ -45,17 +45,23 @@ import org.thingsboard.server.common.msg.TbMsgMetaData;
45 import org.thingsboard.server.common.transport.util.DataDecodingEncodingService; 45 import org.thingsboard.server.common.transport.util.DataDecodingEncodingService;
46 import org.thingsboard.server.dao.device.DeviceCredentialsService; 46 import org.thingsboard.server.dao.device.DeviceCredentialsService;
47 import org.thingsboard.server.dao.device.DeviceProfileService; 47 import org.thingsboard.server.dao.device.DeviceProfileService;
  48 +import org.thingsboard.server.dao.device.DeviceProvisionService;
48 import org.thingsboard.server.dao.device.DeviceService; 49 import org.thingsboard.server.dao.device.DeviceService;
  50 +import org.thingsboard.server.dao.device.provision.ProvisionRequest;
  51 +import org.thingsboard.server.dao.device.provision.ProvisionResponse;
  52 +import org.thingsboard.server.dao.device.provision.ProvisionResponseStatus;
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;
52 import org.thingsboard.server.dao.util.mapping.JacksonUtil; 56 import org.thingsboard.server.dao.util.mapping.JacksonUtil;
53 import org.thingsboard.server.gen.transport.TransportProtos; 57 import org.thingsboard.server.gen.transport.TransportProtos;
  58 +import org.thingsboard.server.gen.transport.TransportProtos.CredentialsType;
54 import org.thingsboard.server.gen.transport.TransportProtos.DeviceInfoProto; 59 import org.thingsboard.server.gen.transport.TransportProtos.DeviceInfoProto;
55 import org.thingsboard.server.gen.transport.TransportProtos.GetOrCreateDeviceFromGatewayRequestMsg; 60 import org.thingsboard.server.gen.transport.TransportProtos.GetOrCreateDeviceFromGatewayRequestMsg;
56 import org.thingsboard.server.gen.transport.TransportProtos.GetOrCreateDeviceFromGatewayResponseMsg; 61 import org.thingsboard.server.gen.transport.TransportProtos.GetOrCreateDeviceFromGatewayResponseMsg;
57 import org.thingsboard.server.gen.transport.TransportProtos.GetTenantRoutingInfoRequestMsg; 62 import org.thingsboard.server.gen.transport.TransportProtos.GetTenantRoutingInfoRequestMsg;
58 import org.thingsboard.server.gen.transport.TransportProtos.GetTenantRoutingInfoResponseMsg; 63 import org.thingsboard.server.gen.transport.TransportProtos.GetTenantRoutingInfoResponseMsg;
  64 +import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceRequestMsg;
59 import org.thingsboard.server.gen.transport.TransportProtos.TransportApiRequestMsg; 65 import org.thingsboard.server.gen.transport.TransportProtos.TransportApiRequestMsg;
60 import org.thingsboard.server.gen.transport.TransportProtos.TransportApiResponseMsg; 66 import org.thingsboard.server.gen.transport.TransportProtos.TransportApiResponseMsg;
61 import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceCredentialsResponseMsg; 67 import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceCredentialsResponseMsg;
@@ -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());
@@ -261,6 +273,46 @@ public class DefaultTransportApiService implements TransportApiService { @@ -261,6 +273,46 @@ public class DefaultTransportApiService implements TransportApiService {
261 }, dbCallbackExecutorService); 273 }, dbCallbackExecutorService);
262 } 274 }
263 275
  276 +
  277 + private ListenableFuture<TransportApiResponseMsg> handle(ProvisionDeviceRequestMsg requestMsg) {
  278 + ListenableFuture<ProvisionResponse> provisionResponseFuture = null;
  279 + provisionResponseFuture = deviceProvisionService.provisionDevice(
  280 + new ProvisionRequest(
  281 + requestMsg.getDeviceName(),
  282 + requestMsg.getX509CertPubKey(),
  283 + new ProvisionDeviceProfileCredentials(
  284 + requestMsg.getProvisionDeviceCredentialsMsg().getProvisionDeviceKey(),
  285 + requestMsg.getProvisionDeviceCredentialsMsg().getProvisionDeviceSecret())));
  286 + return Futures.transform(provisionResponseFuture, provisionResponse -> {
  287 + if (provisionResponse.getResponseStatus() == ProvisionResponseStatus.NOT_FOUND) {
  288 + return getTransportApiResponseMsg(TransportProtos.DeviceCredentialsProto.getDefaultInstance(), TransportProtos.ProvisionResponseStatus.NOT_FOUND);
  289 + } else if (provisionResponse.getResponseStatus() == ProvisionResponseStatus.FAILURE) {
  290 + return getTransportApiResponseMsg(TransportProtos.DeviceCredentialsProto.getDefaultInstance(), TransportProtos.ProvisionResponseStatus.FAILURE);
  291 + } else {
  292 + return getTransportApiResponseMsg(getDeviceCredentials(provisionResponse.getDeviceCredentials()), TransportProtos.ProvisionResponseStatus.SUCCESS);
  293 + }
  294 + }, dbCallbackExecutorService);
  295 + }
  296 +
  297 + private TransportApiResponseMsg getTransportApiResponseMsg(TransportProtos.DeviceCredentialsProto deviceCredentials, TransportProtos.ProvisionResponseStatus status) {
  298 + return TransportApiResponseMsg.newBuilder()
  299 + .setProvisionDeviceResponseMsg(TransportProtos.ProvisionDeviceResponseMsg.newBuilder()
  300 + .setDeviceCredentials(deviceCredentials)
  301 + .setProvisionResponseStatus(status)
  302 + .build())
  303 + .build();
  304 + }
  305 +
  306 + private TransportProtos.DeviceCredentialsProto getDeviceCredentials(DeviceCredentials deviceCredentials) {
  307 + return TransportProtos.DeviceCredentialsProto.newBuilder()
  308 + .setDeviceIdMSB(deviceCredentials.getDeviceId().getId().getMostSignificantBits())
  309 + .setDeviceIdLSB(deviceCredentials.getDeviceId().getId().getLeastSignificantBits())
  310 + .setCredentialsType(deviceCredentials.getCredentialsType() == DeviceCredentialsType.ACCESS_TOKEN ?
  311 + CredentialsType.ACCESS_TOKEN : CredentialsType.X509_CERTIFICATE)
  312 + .setCredentialsId(deviceCredentials.getCredentialsId())
  313 + .setCredentialsValue(deviceCredentials.getCredentialsValue() != null ? deviceCredentials.getCredentialsValue() : "")
  314 + .build();
  315 + }
264 private ListenableFuture<TransportApiResponseMsg> handle(GetTenantRoutingInfoRequestMsg requestMsg) { 316 private ListenableFuture<TransportApiResponseMsg> handle(GetTenantRoutingInfoRequestMsg requestMsg) {
265 TenantId tenantId = new TenantId(new UUID(requestMsg.getTenantIdMSB(), requestMsg.getTenantIdLSB())); 317 TenantId tenantId = new TenantId(new UUID(requestMsg.getTenantIdMSB(), requestMsg.getTenantIdLSB()));
266 // TODO: Tenant Profile from cache 318 // 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 {
  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.dao.device.provision.ProvisionRequest;
  20 +import org.thingsboard.server.dao.device.provision.ProvisionResponse;
  21 +
  22 +public interface DeviceProvisionService {
  23 + ListenableFuture<ProvisionResponse> provisionDevice(ProvisionRequest provisionRequest);
  24 +}
  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.profile.ProvisionDeviceProfileCredentials;
  21 +
  22 +@Data
  23 +@AllArgsConstructor
  24 +public class ProvisionRequest {
  25 + private String deviceName;
  26 + private String x509CertPubKey;
  27 + private ProvisionDeviceProfileCredentials credentials;
  28 +}
  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 +
  20 + SUCCESS,
  21 + NOT_FOUND,
  22 + FAILURE
  23 +
  24 +}
@@ -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,12 @@ public class DataConstants { @@ -70,4 +72,12 @@ 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 +
73 } 83 }
@@ -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.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 + @JsonIgnore
  37 + DeviceProfileProvisionType getType();
  38 +
  39 +}
  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 +}
@@ -31,6 +31,8 @@ public class MqttTopics { @@ -31,6 +31,8 @@ public class MqttTopics {
31 public static final String DEVICE_TELEMETRY_TOPIC = BASE_DEVICE_API_TOPIC + "/telemetry"; 31 public static final String DEVICE_TELEMETRY_TOPIC = BASE_DEVICE_API_TOPIC + "/telemetry";
32 public static final String DEVICE_CLAIM_TOPIC = BASE_DEVICE_API_TOPIC + "/claim"; 32 public static final String DEVICE_CLAIM_TOPIC = BASE_DEVICE_API_TOPIC + "/claim";
33 public static final String DEVICE_ATTRIBUTES_TOPIC = BASE_DEVICE_API_TOPIC + "/attributes"; 33 public static final String DEVICE_ATTRIBUTES_TOPIC = BASE_DEVICE_API_TOPIC + "/attributes";
  34 + public static final String DEVICE_PROVISION_REQUEST_TOPIC = BASE_DEVICE_API_TOPIC + "/provision";
  35 + public static final String DEVICE_PROVISION_RESPONSE_TOPIC = BASE_DEVICE_API_TOPIC + "/provision/response";
34 36
35 public static final String BASE_GATEWAY_API_TOPIC = "v1/gateway"; 37 public static final String BASE_GATEWAY_API_TOPIC = "v1/gateway";
36 public static final String GATEWAY_CONNECT_TOPIC = BASE_GATEWAY_API_TOPIC + "/connect"; 38 public static final String GATEWAY_CONNECT_TOPIC = BASE_GATEWAY_API_TOPIC + "/connect";
@@ -41,6 +43,8 @@ public class MqttTopics { @@ -41,6 +43,8 @@ public class MqttTopics {
41 public static final String GATEWAY_RPC_TOPIC = BASE_GATEWAY_API_TOPIC + "/rpc"; 43 public static final String GATEWAY_RPC_TOPIC = BASE_GATEWAY_API_TOPIC + "/rpc";
42 public static final String GATEWAY_ATTRIBUTES_REQUEST_TOPIC = BASE_GATEWAY_API_TOPIC + "/attributes/request"; 44 public static final String GATEWAY_ATTRIBUTES_REQUEST_TOPIC = BASE_GATEWAY_API_TOPIC + "/attributes/request";
43 public static final String GATEWAY_ATTRIBUTES_RESPONSE_TOPIC = BASE_GATEWAY_API_TOPIC + "/attributes/response"; 45 public static final String GATEWAY_ATTRIBUTES_RESPONSE_TOPIC = BASE_GATEWAY_API_TOPIC + "/attributes/response";
  46 + public static final String GATEWAY_PROVISION_REQUEST_TOPIC = BASE_GATEWAY_API_TOPIC + "/provision";
  47 + public static final String GATEWAY_PROVISION_RESPONSE_TOPIC = BASE_GATEWAY_API_TOPIC + "/provision/response";
44 48
45 49
46 private MqttTopics() { 50 private MqttTopics() {
  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,11 @@ enum KeyValueType { @@ -73,6 +73,11 @@ 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 +}
  80 +
76 message KeyValueProto { 81 message KeyValueProto {
77 string key = 1; 82 string key = 1;
78 KeyValueType type = 2; 83 KeyValueType type = 2;
@@ -241,6 +246,35 @@ message ClaimDeviceMsg { @@ -241,6 +246,35 @@ message ClaimDeviceMsg {
241 int64 durationMs = 4; 246 int64 durationMs = 4;
242 } 247 }
243 248
  249 +message DeviceCredentialsProto {
  250 + int64 deviceIdMSB = 1;
  251 + int64 deviceIdLSB = 2;
  252 + CredentialsType credentialsType = 3;
  253 + string credentialsId = 4;
  254 + string credentialsValue = 5;
  255 +}
  256 +
  257 +message ProvisionDeviceRequestMsg {
  258 + string deviceName = 1;
  259 + string x509CertPubKey = 2;
  260 + ProvisionDeviceCredentialsMsg provisionDeviceCredentialsMsg = 3;
  261 +}
  262 +
  263 +message ProvisionDeviceCredentialsMsg {
  264 + string provisionDeviceKey = 1;
  265 + string provisionDeviceSecret = 2;
  266 +}
  267 +
  268 +message ProvisionDeviceResponseMsg {
  269 + DeviceCredentialsProto deviceCredentials = 1;
  270 + ProvisionResponseStatus provisionResponseStatus = 2;
  271 +}
  272 +
  273 +enum ProvisionResponseStatus {
  274 + SUCCESS = 0;
  275 + NOT_FOUND = 1;
  276 + FAILURE = 2;
  277 +}
244 //Used to report session state to tb-Service and persist this state in the cache on the tb-Service level. 278 //Used to report session state to tb-Service and persist this state in the cache on the tb-Service level.
245 message SubscriptionInfoProto { 279 message SubscriptionInfoProto {
246 int64 lastActivityTime = 1; 280 int64 lastActivityTime = 1;
@@ -266,6 +300,7 @@ message TransportToDeviceActorMsg { @@ -266,6 +300,7 @@ message TransportToDeviceActorMsg {
266 ToDeviceRpcResponseMsg toDeviceRPCCallResponse = 6; 300 ToDeviceRpcResponseMsg toDeviceRPCCallResponse = 6;
267 SubscriptionInfoProto subscriptionInfo = 7; 301 SubscriptionInfoProto subscriptionInfo = 7;
268 ClaimDeviceMsg claimDevice = 8; 302 ClaimDeviceMsg claimDevice = 8;
  303 + ProvisionDeviceRequestMsg provisionDevice = 9;
269 } 304 }
270 305
271 message TransportToRuleEngineMsg { 306 message TransportToRuleEngineMsg {
@@ -441,6 +476,7 @@ message TransportApiRequestMsg { @@ -441,6 +476,7 @@ message TransportApiRequestMsg {
441 GetTenantRoutingInfoRequestMsg getTenantRoutingInfoRequestMsg = 4; 476 GetTenantRoutingInfoRequestMsg getTenantRoutingInfoRequestMsg = 4;
442 GetDeviceProfileRequestMsg getDeviceProfileRequestMsg = 5; 477 GetDeviceProfileRequestMsg getDeviceProfileRequestMsg = 5;
443 ValidateBasicMqttCredRequestMsg validateBasicMqttCredRequestMsg = 6; 478 ValidateBasicMqttCredRequestMsg validateBasicMqttCredRequestMsg = 6;
  479 + ProvisionDeviceRequestMsg provisionDeviceRequestMsg = 7;
444 } 480 }
445 481
446 /* Response from ThingsBoard Core Service to Transport Service */ 482 /* Response from ThingsBoard Core Service to Transport Service */
@@ -449,6 +485,7 @@ message TransportApiResponseMsg { @@ -449,6 +485,7 @@ message TransportApiResponseMsg {
449 GetOrCreateDeviceFromGatewayResponseMsg getOrCreateDeviceResponseMsg = 2; 485 GetOrCreateDeviceFromGatewayResponseMsg getOrCreateDeviceResponseMsg = 2;
450 GetTenantRoutingInfoResponseMsg getTenantRoutingInfoResponseMsg = 4; 486 GetTenantRoutingInfoResponseMsg getTenantRoutingInfoResponseMsg = 4;
451 GetDeviceProfileResponseMsg getDeviceProfileResponseMsg = 5; 487 GetDeviceProfileResponseMsg getDeviceProfileResponseMsg = 5;
  488 + ProvisionDeviceResponseMsg provisionDeviceResponseMsg = 6;
452 } 489 }
453 490
454 /* Messages that are handled by ThingsBoard Core Service */ 491 /* Messages that are handled by ThingsBoard Core Service */
@@ -491,4 +528,5 @@ message ToTransportMsg { @@ -491,4 +528,5 @@ message ToTransportMsg {
491 ToServerRpcResponseMsg toServerResponse = 7; 528 ToServerRpcResponseMsg toServerResponse = 7;
492 DeviceProfileUpdateMsg deviceProfileUpdateMsg = 8; 529 DeviceProfileUpdateMsg deviceProfileUpdateMsg = 8;
493 DeviceProfileDeleteMsg deviceProfileDeleteMsg = 9; 530 DeviceProfileDeleteMsg deviceProfileDeleteMsg = 9;
  531 + ProvisionDeviceResponseMsg provisionResponse = 10;
494 } 532 }
@@ -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;
@@ -34,11 +34,12 @@ import io.netty.handler.codec.mqtt.MqttSubscribeMessage; @@ -34,11 +34,12 @@ import io.netty.handler.codec.mqtt.MqttSubscribeMessage;
34 import io.netty.handler.codec.mqtt.MqttTopicSubscription; 34 import io.netty.handler.codec.mqtt.MqttTopicSubscription;
35 import io.netty.handler.codec.mqtt.MqttUnsubscribeMessage; 35 import io.netty.handler.codec.mqtt.MqttUnsubscribeMessage;
36 import io.netty.handler.ssl.SslHandler; 36 import io.netty.handler.ssl.SslHandler;
  37 +import io.netty.util.CharsetUtil;
37 import io.netty.util.ReferenceCountUtil; 38 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;
41 -import org.springframework.util.StringUtils; 42 +import org.thingsboard.server.common.data.DataConstants;
42 import org.thingsboard.server.common.data.DeviceProfile; 43 import org.thingsboard.server.common.data.DeviceProfile;
43 import org.thingsboard.server.common.data.DeviceTransportType; 44 import org.thingsboard.server.common.data.DeviceTransportType;
44 import org.thingsboard.server.common.data.device.profile.MqttTopics; 45 import org.thingsboard.server.common.data.device.profile.MqttTopics;
@@ -52,9 +53,9 @@ import org.thingsboard.server.common.transport.auth.TransportDeviceInfo; @@ -52,9 +53,9 @@ import org.thingsboard.server.common.transport.auth.TransportDeviceInfo;
52 import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; 53 import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse;
53 import org.thingsboard.server.common.transport.service.DefaultTransportService; 54 import org.thingsboard.server.common.transport.service.DefaultTransportService;
54 import org.thingsboard.server.gen.transport.TransportProtos; 55 import org.thingsboard.server.gen.transport.TransportProtos;
  56 +import org.thingsboard.server.gen.transport.TransportProtos.DeviceInfoProto;
  57 +import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceResponseMsg;
55 import org.thingsboard.server.gen.transport.TransportProtos.SessionEvent; 58 import org.thingsboard.server.gen.transport.TransportProtos.SessionEvent;
56 -import org.thingsboard.server.gen.transport.TransportProtos.SessionInfoProto;  
57 -import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceTokenRequestMsg;  
58 import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceX509CertRequestMsg; 59 import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceX509CertRequestMsg;
59 import org.thingsboard.server.transport.mqtt.adaptors.MqttTransportAdaptor; 60 import org.thingsboard.server.transport.mqtt.adaptors.MqttTransportAdaptor;
60 import org.thingsboard.server.transport.mqtt.session.DeviceSessionCtx; 61 import org.thingsboard.server.transport.mqtt.session.DeviceSessionCtx;
@@ -66,7 +67,6 @@ import javax.net.ssl.SSLPeerUnverifiedException; @@ -66,7 +67,6 @@ import javax.net.ssl.SSLPeerUnverifiedException;
66 import javax.security.cert.X509Certificate; 67 import javax.security.cert.X509Certificate;
67 import java.io.IOException; 68 import java.io.IOException;
68 import java.net.InetSocketAddress; 69 import java.net.InetSocketAddress;
69 -import java.nio.charset.StandardCharsets;  
70 import java.util.ArrayList; 70 import java.util.ArrayList;
71 import java.util.List; 71 import java.util.List;
72 import java.util.UUID; 72 import java.util.UUID;
@@ -74,9 +74,9 @@ import java.util.concurrent.ConcurrentHashMap; @@ -74,9 +74,9 @@ import java.util.concurrent.ConcurrentHashMap;
74 import java.util.concurrent.ConcurrentMap; 74 import java.util.concurrent.ConcurrentMap;
75 75
76 import static io.netty.handler.codec.mqtt.MqttConnectReturnCode.CONNECTION_ACCEPTED; 76 import static io.netty.handler.codec.mqtt.MqttConnectReturnCode.CONNECTION_ACCEPTED;
77 -import static io.netty.handler.codec.mqtt.MqttConnectReturnCode.CONNECTION_REFUSED_BAD_USER_NAME_OR_PASSWORD;  
78 import static io.netty.handler.codec.mqtt.MqttConnectReturnCode.CONNECTION_REFUSED_NOT_AUTHORIZED; 77 import static io.netty.handler.codec.mqtt.MqttConnectReturnCode.CONNECTION_REFUSED_NOT_AUTHORIZED;
79 import static io.netty.handler.codec.mqtt.MqttMessageType.CONNACK; 78 import static io.netty.handler.codec.mqtt.MqttMessageType.CONNACK;
  79 +import static io.netty.handler.codec.mqtt.MqttMessageType.CONNECT;
80 import static io.netty.handler.codec.mqtt.MqttMessageType.PINGRESP; 80 import static io.netty.handler.codec.mqtt.MqttMessageType.PINGRESP;
81 import static io.netty.handler.codec.mqtt.MqttMessageType.PUBACK; 81 import static io.netty.handler.codec.mqtt.MqttMessageType.PUBACK;
82 import static io.netty.handler.codec.mqtt.MqttMessageType.SUBACK; 82 import static io.netty.handler.codec.mqtt.MqttMessageType.SUBACK;
@@ -136,10 +136,45 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement @@ -136,10 +136,45 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
136 return; 136 return;
137 } 137 }
138 deviceSessionCtx.setChannel(ctx); 138 deviceSessionCtx.setChannel(ctx);
  139 + if (CONNECT.equals(msg.fixedHeader().messageType())) {
  140 + processConnect(ctx, (MqttConnectMessage) msg);
  141 + } else if (deviceSessionCtx.isProvisionOnly()) {
  142 + processProvisionSessionMsg(ctx, msg);
  143 + } else {
  144 + processRegularSessionMsg(ctx, msg);
  145 + }
  146 + }
  147 +
  148 + private void processProvisionSessionMsg(ChannelHandlerContext ctx, MqttMessage msg) {
139 switch (msg.fixedHeader().messageType()) { 149 switch (msg.fixedHeader().messageType()) {
140 - case CONNECT:  
141 - processConnect(ctx, (MqttConnectMessage) msg); 150 + case PUBLISH:
  151 + MqttPublishMessage mqttMsg = (MqttPublishMessage) msg;
  152 + String topicName = mqttMsg.variableHeader().topicName();
  153 + int msgId = mqttMsg.variableHeader().packetId();
  154 + try {
  155 + if (topicName.equals(MqttTopics.DEVICE_PROVISION_REQUEST_TOPIC)) {
  156 + TransportProtos.ProvisionDeviceRequestMsg provisionRequestMsg = adaptor.convertToProvisionRequestMsg(deviceSessionCtx, mqttMsg);
  157 + transportService.process(provisionRequestMsg, new DeviceProvisionCallback(ctx, msgId, provisionRequestMsg));
  158 + log.trace("[{}][{}] Processing publish msg [{}][{}]!", sessionId, deviceSessionCtx.getDeviceId(), topicName, msgId);
  159 + } else {
  160 + throw new RuntimeException("Unsupported topic for provisioning requests!");
  161 + }
  162 + } catch (RuntimeException | AdaptorException e) {
  163 + log.warn("[{}] Failed to process publish msg [{}][{}]", sessionId, topicName, msgId, e);
  164 + ctx.close();
  165 + }
142 break; 166 break;
  167 + case PINGREQ:
  168 + ctx.writeAndFlush(new MqttMessage(new MqttFixedHeader(PINGRESP, false, AT_MOST_ONCE, false, 0)));
  169 + break;
  170 + case DISCONNECT:
  171 + ctx.close();
  172 + break;
  173 + }
  174 + }
  175 +
  176 + private void processRegularSessionMsg(ChannelHandlerContext ctx, MqttMessage msg) {
  177 + switch (msg.fixedHeader().messageType()) {
143 case PUBLISH: 178 case PUBLISH:
144 processPublish(ctx, (MqttPublishMessage) msg); 179 processPublish(ctx, (MqttPublishMessage) msg);
145 break; 180 break;
@@ -263,6 +298,37 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement @@ -263,6 +298,37 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
263 }; 298 };
264 } 299 }
265 300
  301 + private class DeviceProvisionCallback implements TransportServiceCallback<ProvisionDeviceResponseMsg> {
  302 + private final ChannelHandlerContext ctx;
  303 + private final int msgId;
  304 + private final TransportProtos.ProvisionDeviceRequestMsg msg;
  305 +
  306 + DeviceProvisionCallback(ChannelHandlerContext ctx, int msgId, TransportProtos.ProvisionDeviceRequestMsg msg) {
  307 + this.ctx = ctx;
  308 + this.msgId = msgId;
  309 + this.msg = msg;
  310 + }
  311 +
  312 + @Override
  313 + public void onSuccess(TransportProtos.ProvisionDeviceResponseMsg provisionResponseMsg) {
  314 + log.trace("[{}] Published msg: {}", sessionId, msg);
  315 + if (msgId > 0) {
  316 + ctx.writeAndFlush(createMqttPubAckMsg(msgId));
  317 + }
  318 + try {
  319 + adaptor.convertToPublish(deviceSessionCtx, provisionResponseMsg).ifPresent(deviceSessionCtx.getChannel()::writeAndFlush);
  320 + } catch (Exception e) {
  321 + log.trace("[{}] Failed to convert device attributes response to MQTT msg", sessionId, e);
  322 + }
  323 + }
  324 +
  325 + @Override
  326 + public void onError(Throwable e) {
  327 + log.trace("[{}] Failed to publish msg: {}", sessionId, msg, e);
  328 + processDisconnect(ctx);
  329 + }
  330 + }
  331 +
266 private void processSubscribe(ChannelHandlerContext ctx, MqttSubscribeMessage mqttMsg) { 332 private void processSubscribe(ChannelHandlerContext ctx, MqttSubscribeMessage mqttMsg) {
267 if (!checkConnected(ctx, mqttMsg)) { 333 if (!checkConnected(ctx, mqttMsg)) {
268 return; 334 return;
@@ -292,6 +358,8 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement @@ -292,6 +358,8 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
292 case MqttTopics.GATEWAY_RPC_TOPIC: 358 case MqttTopics.GATEWAY_RPC_TOPIC:
293 case MqttTopics.GATEWAY_ATTRIBUTES_RESPONSE_TOPIC: 359 case MqttTopics.GATEWAY_ATTRIBUTES_RESPONSE_TOPIC:
294 case MqttTopics.DEVICE_ATTRIBUTES_RESPONSES_TOPIC: 360 case MqttTopics.DEVICE_ATTRIBUTES_RESPONSES_TOPIC:
  361 + case MqttTopics.GATEWAY_PROVISION_RESPONSE_TOPIC:
  362 + case MqttTopics.DEVICE_PROVISION_RESPONSE_TOPIC:
295 registerSubQoS(topic, grantedQoSList, reqQoS); 363 registerSubQoS(topic, grantedQoSList, reqQoS);
296 break; 364 break;
297 default: 365 default:
@@ -357,11 +425,18 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement @@ -357,11 +425,18 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
357 425
358 private void processConnect(ChannelHandlerContext ctx, MqttConnectMessage msg) { 426 private void processConnect(ChannelHandlerContext ctx, MqttConnectMessage msg) {
359 log.info("[{}] Processing connect msg for client: {}!", sessionId, msg.payload().clientIdentifier()); 427 log.info("[{}] Processing connect msg for client: {}!", sessionId, msg.payload().clientIdentifier());
360 - X509Certificate cert;  
361 - if (sslHandler != null && (cert = getX509Certificate()) != null) {  
362 - processX509CertConnect(ctx, cert); 428 + String userName = msg.payload().userName();
  429 + if (DataConstants.PROVISION.equals(userName)) {
  430 + deviceSessionCtx.setProvisionOnly(true);
  431 + ctx.writeAndFlush(createMqttConnAckMsg(CONNECTION_ACCEPTED));
363 } else { 432 } else {
364 - processAuthTokenConnect(ctx, msg); 433 + X509Certificate cert;
  434 +
  435 + if (sslHandler != null && (cert = getX509Certificate()) != null) {
  436 + processX509CertConnect(ctx, cert);
  437 + } else {
  438 + processAuthTokenConnect(ctx, msg);
  439 + }
365 } 440 }
366 } 441 }
367 442
@@ -184,6 +184,28 @@ public class JsonMqttAdaptor implements MqttTransportAdaptor { @@ -184,6 +184,28 @@ public class JsonMqttAdaptor implements MqttTransportAdaptor {
184 return Optional.of(createMqttPublishMsg(ctx, MqttTopics.DEVICE_RPC_RESPONSE_TOPIC + rpcResponse.getRequestId(), JsonConverter.toJson(rpcResponse))); 184 return Optional.of(createMqttPublishMsg(ctx, MqttTopics.DEVICE_RPC_RESPONSE_TOPIC + rpcResponse.getRequestId(), JsonConverter.toJson(rpcResponse)));
185 } 185 }
186 186
  187 + @Override
  188 + public Optional<MqttMessage> convertToPublish(MqttDeviceAwareSessionContext ctx, TransportProtos.ProvisionDeviceResponseMsg provisionResponse) {
  189 + return Optional.of(createMqttPublishMsg(ctx, MqttTopics.DEVICE_PROVISION_RESPONSE_TOPIC, JsonConverter.toJson(provisionResponse)));
  190 + }
  191 +
  192 + @Override
  193 + public TransportProtos.ProvisionDeviceRequestMsg convertToProvisionRequestMsg(MqttDeviceAwareSessionContext ctx, MqttPublishMessage inbound) throws AdaptorException {
  194 + String payload = validatePayload(ctx.getSessionId(), inbound.payload(), false);
  195 + try {
  196 + return JsonConverter.convertToProvisionRequestMsg(payload);
  197 + } catch (IllegalStateException | JsonSyntaxException ex) {
  198 + throw new AdaptorException(ex);
  199 + }
  200 + }
  201 +
  202 + @Override
  203 + public Optional<MqttMessage> convertToGatewayPublish(MqttDeviceAwareSessionContext ctx, TransportProtos.ProvisionDeviceResponseMsg responseMsg, int requestId) {
  204 + return Optional.of(createMqttPublishMsg(ctx,
  205 + MqttTopics.GATEWAY_PROVISION_REQUEST_TOPIC,
  206 + JsonConverter.toJson(responseMsg, requestId)));
  207 + }
  208 +
187 private MqttPublishMessage createMqttPublishMsg(MqttDeviceAwareSessionContext ctx, String topic, JsonElement json) { 209 private MqttPublishMessage createMqttPublishMsg(MqttDeviceAwareSessionContext ctx, String topic, JsonElement json) {
188 MqttFixedHeader mqttFixedHeader = 210 MqttFixedHeader mqttFixedHeader =
189 new MqttFixedHeader(MqttMessageType.PUBLISH, false, ctx.getQoSForTopic(topic), false, 0); 211 new MqttFixedHeader(MqttMessageType.PUBLISH, false, ctx.getQoSForTopic(topic), false, 0);
@@ -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,10 @@ public interface MqttTransportAdaptor { @@ -63,4 +65,10 @@ 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 +
  72 + Optional<MqttMessage> convertToGatewayPublish(MqttDeviceAwareSessionContext ctx, ProvisionDeviceResponseMsg provisionResponse, int requestId) throws AdaptorException;
  73 +
66 } 74 }
@@ -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;
@@ -40,6 +41,10 @@ public class DeviceSessionCtx extends MqttDeviceAwareSessionContext { @@ -40,6 +41,10 @@ public class DeviceSessionCtx extends MqttDeviceAwareSessionContext {
40 private ChannelHandlerContext channel; 41 private ChannelHandlerContext channel;
41 private final AtomicInteger msgIdSeq = new AtomicInteger(0); 42 private final AtomicInteger msgIdSeq = new AtomicInteger(0);
42 43
  44 + @Getter
  45 + @Setter
  46 + private boolean provisionOnly;
  47 +
43 private volatile MqttTopicFilter telemetryTopicFilter = MqttTopicFilterFactory.getDefaultTelemetryFilter(); 48 private volatile MqttTopicFilter telemetryTopicFilter = MqttTopicFilterFactory.getDefaultTelemetryFilter();
44 private volatile MqttTopicFilter attributesTopicFilter = MqttTopicFilterFactory.getDefaultAttributesFilter(); 49 private volatile MqttTopicFilter attributesTopicFilter = MqttTopicFilterFactory.getDefaultAttributesFilter();
45 50
@@ -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;
@@ -57,6 +59,9 @@ public interface TransportService { @@ -57,6 +59,9 @@ public interface TransportService {
57 void process(GetOrCreateDeviceFromGatewayRequestMsg msg, 59 void process(GetOrCreateDeviceFromGatewayRequestMsg msg,
58 TransportServiceCallback<GetOrCreateDeviceFromGatewayResponse> callback); 60 TransportServiceCallback<GetOrCreateDeviceFromGatewayResponse> callback);
59 61
  62 + void process(ProvisionDeviceRequestMsg msg,
  63 + TransportServiceCallback<ProvisionDeviceResponseMsg> callback);
  64 +
60 void getDeviceProfile(DeviceProfileId deviceProfileId, TransportServiceCallback<DeviceProfile> callback); 65 void getDeviceProfile(DeviceProfileId deviceProfileId, TransportServiceCallback<DeviceProfile> callback);
61 66
62 void onProfileUpdate(DeviceProfile deviceProfile); 67 void onProfileUpdate(DeviceProfile deviceProfile);
@@ -90,5 +95,4 @@ public interface TransportService { @@ -90,5 +95,4 @@ public interface TransportService {
90 void reportActivity(SessionInfoProto sessionInfo); 95 void reportActivity(SessionInfoProto sessionInfo);
91 96
92 void deregisterSession(SessionInfoProto sessionInfo); 97 void deregisterSession(SessionInfoProto sessionInfo);
93 -  
94 } 98 }
@@ -42,6 +42,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.KeyValueProto; @@ -42,6 +42,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.KeyValueProto;
42 import org.thingsboard.server.gen.transport.TransportProtos.KeyValueType; 42 import org.thingsboard.server.gen.transport.TransportProtos.KeyValueType;
43 import org.thingsboard.server.gen.transport.TransportProtos.PostAttributeMsg; 43 import org.thingsboard.server.gen.transport.TransportProtos.PostAttributeMsg;
44 import org.thingsboard.server.gen.transport.TransportProtos.PostTelemetryMsg; 44 import org.thingsboard.server.gen.transport.TransportProtos.PostTelemetryMsg;
  45 +import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceResponseMsg;
45 import org.thingsboard.server.gen.transport.TransportProtos.TsKvListProto; 46 import org.thingsboard.server.gen.transport.TransportProtos.TsKvListProto;
46 import org.thingsboard.server.gen.transport.TransportProtos.TsKvProto; 47 import org.thingsboard.server.gen.transport.TransportProtos.TsKvProto;
47 48
@@ -53,6 +54,7 @@ import java.util.Map; @@ -53,6 +54,7 @@ import java.util.Map;
53 import java.util.Map.Entry; 54 import java.util.Map.Entry;
54 import java.util.Set; 55 import java.util.Set;
55 import java.util.TreeMap; 56 import java.util.TreeMap;
  57 +import java.util.UUID;
56 import java.util.function.Consumer; 58 import java.util.function.Consumer;
57 import java.util.stream.Collectors; 59 import java.util.stream.Collectors;
58 60
@@ -397,6 +399,33 @@ public class JsonConverter { @@ -397,6 +399,33 @@ public class JsonConverter {
397 } 399 }
398 } 400 }
399 401
  402 + public static JsonObject toJson(ProvisionDeviceResponseMsg payload) {
  403 + return toJson(payload, false, 0);
  404 + }
  405 +
  406 + public static JsonObject toJson(ProvisionDeviceResponseMsg payload, int requestId) {
  407 + return toJson(payload, true, requestId);
  408 + }
  409 +
  410 + private static JsonObject toJson(ProvisionDeviceResponseMsg payload, boolean toGateway, int requestId) {
  411 + JsonObject result = new JsonObject();
  412 + if (payload.getProvisionResponseStatus() == TransportProtos.ProvisionResponseStatus.NOT_FOUND) {
  413 + result.addProperty("errorMsg", "Provision data was not found!");
  414 + } else if (payload.getProvisionResponseStatus() == TransportProtos.ProvisionResponseStatus.FAILURE) {
  415 + result.addProperty("errorMsg", "Failed to provision device!");
  416 + } else {
  417 + if (toGateway) {
  418 + result.addProperty("id", requestId);
  419 + }
  420 + result.addProperty("deviceId", new UUID(payload.getDeviceCredentials().getDeviceIdMSB(), payload.getDeviceCredentials().getDeviceIdLSB()).toString());
  421 + result.addProperty("credentialsType", payload.getDeviceCredentials().getCredentialsType().name());
  422 + result.addProperty("credentialsId", payload.getDeviceCredentials().getCredentialsId());
  423 + result.addProperty("credentialsValue",
  424 + StringUtils.isEmpty(payload.getDeviceCredentials().getCredentialsValue()) ? null : payload.getDeviceCredentials().getCredentialsValue());
  425 + }
  426 + return result;
  427 + }
  428 +
400 public static JsonElement toErrorJson(String errorMsg) { 429 public static JsonElement toErrorJson(String errorMsg) {
401 JsonObject error = new JsonObject(); 430 JsonObject error = new JsonObject();
402 error.addProperty("error", errorMsg); 431 error.addProperty("error", errorMsg);
@@ -498,4 +527,43 @@ public class JsonConverter { @@ -498,4 +527,43 @@ public class JsonConverter {
498 maxStringValueLength = length; 527 maxStringValueLength = length;
499 } 528 }
500 529
  530 + public static TransportProtos.ProvisionDeviceRequestMsg convertToProvisionRequestMsg(String json) {
  531 + JsonElement jsonElement = new JsonParser().parse(json);
  532 + if (jsonElement.isJsonObject()) {
  533 + return buildProvisionRequestMsg(jsonElement.getAsJsonObject());
  534 + } else {
  535 + throw new JsonSyntaxException(CAN_T_PARSE_VALUE + jsonElement);
  536 + }
  537 + }
  538 +
  539 + public static TransportProtos.ProvisionDeviceRequestMsg convertToProvisionRequestMsg(JsonObject jo) {
  540 + return buildProvisionRequestMsg(jo);
  541 + }
  542 +
  543 + private static TransportProtos.ProvisionDeviceRequestMsg buildProvisionRequestMsg(JsonObject jo) {
  544 + return TransportProtos.ProvisionDeviceRequestMsg.newBuilder()
  545 + .setDeviceName(getStrValue(jo, DataConstants.DEVICE_NAME, true))
  546 + .setX509CertPubKey(getStrValue(jo, DataConstants.CERT_PUB_KEY, false))
  547 + .setProvisionDeviceCredentialsMsg(buildProvisionDeviceCredentialsMsg(
  548 + getStrValue(jo, DataConstants.PROVISION_KEY, true),
  549 + getStrValue(jo, DataConstants.PROVISION_SECRET, true)))
  550 + .build();
  551 + }
  552 +
  553 + private static TransportProtos.ProvisionDeviceCredentialsMsg buildProvisionDeviceCredentialsMsg(String provisionKey, String provisionSecret) {
  554 + return TransportProtos.ProvisionDeviceCredentialsMsg.newBuilder()
  555 + .setProvisionDeviceKey(provisionKey)
  556 + .setProvisionDeviceSecret(provisionSecret)
  557 + .build();
  558 + }
  559 + private static String getStrValue(JsonObject jo, String field, boolean requiredField) {
  560 + if (jo.has(field)) {
  561 + return jo.get(field).getAsString();
  562 + } else {
  563 + if (requiredField) {
  564 + throw new RuntimeException("Failed to find the field " + field + " in JSON body " + jo + "!");
  565 + }
  566 + return "";
  567 + }
  568 + }
501 } 569 }
@@ -52,6 +52,8 @@ import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsRes @@ -52,6 +52,8 @@ import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsRes
52 import org.thingsboard.server.common.transport.util.DataDecodingEncodingService; 52 import org.thingsboard.server.common.transport.util.DataDecodingEncodingService;
53 import org.thingsboard.server.common.transport.util.JsonUtils; 53 import org.thingsboard.server.common.transport.util.JsonUtils;
54 import org.thingsboard.server.gen.transport.TransportProtos; 54 import org.thingsboard.server.gen.transport.TransportProtos;
  55 +import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceRequestMsg;
  56 +import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceResponseMsg;
55 import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg; 57 import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg;
56 import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg; 58 import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg;
57 import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg; 59 import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg;
@@ -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 }
@@ -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.dao.model.sql.DeviceProfileEntity; 24 import org.thingsboard.server.dao.model.sql.DeviceProfileEntity;
26 25
@@ -57,4 +56,5 @@ public interface DeviceProfileRepository extends PagingAndSortingRepository<Devi @@ -57,4 +56,5 @@ public interface DeviceProfileRepository extends PagingAndSortingRepository<Devi
57 56
58 DeviceProfileEntity findByTenantIdAndName(UUID id, String profileName); 57 DeviceProfileEntity findByTenantIdAndName(UUID id, String profileName);
59 58
  59 + DeviceProfileEntity findByProvisionDeviceKey(@Param("provisionDeviceKey") String provisionDeviceKey);
60 } 60 }
@@ -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 }
@@ -81,6 +81,11 @@ public class JpaDeviceProfileDao extends JpaAbstractSearchTextDao<DeviceProfileE @@ -81,6 +81,11 @@ public class JpaDeviceProfileDao extends JpaAbstractSearchTextDao<DeviceProfileE
81 } 81 }
82 82
83 @Override 83 @Override
  84 + public DeviceProfile findByProvisionDeviceKey(String provisionDeviceKey) {
  85 + return DaoUtil.getData(deviceProfileRepository.findByProvisionDeviceKey(provisionDeviceKey));
  86 + }
  87 +
  88 + @Override
84 public DeviceProfile findByName(TenantId tenantId, String profileName) { 89 public DeviceProfile findByName(TenantId tenantId, String profileName) {
85 return DaoUtil.getData(deviceProfileRepository.findByTenantIdAndName(tenantId.getId(), profileName)); 90 return DaoUtil.getData(deviceProfileRepository.findByTenantIdAndName(tenantId.getId(), profileName));
86 } 91 }
@@ -152,13 +152,16 @@ CREATE TABLE IF NOT EXISTS device_profile ( @@ -152,13 +152,16 @@ CREATE TABLE IF NOT EXISTS device_profile (
152 name varchar(255), 152 name varchar(255),
153 type varchar(255), 153 type varchar(255),
154 transport_type varchar(255), 154 transport_type varchar(255),
  155 + provision_type varchar(255),
155 profile_data jsonb, 156 profile_data jsonb,
156 description varchar, 157 description varchar,
157 search_text varchar(255), 158 search_text varchar(255),
158 is_default boolean, 159 is_default boolean,
159 tenant_id uuid, 160 tenant_id uuid,
160 default_rule_chain_id uuid, 161 default_rule_chain_id uuid,
  162 + provision_device_key varchar,
161 CONSTRAINT device_profile_name_unq_key UNIQUE (tenant_id, name), 163 CONSTRAINT device_profile_name_unq_key UNIQUE (tenant_id, name),
  164 + CONSTRAINT device_provision_key_unq_key UNIQUE (provision_device_key),
162 CONSTRAINT fk_default_rule_chain_device_profile FOREIGN KEY (default_rule_chain_id) REFERENCES rule_chain(id) 165 CONSTRAINT fk_default_rule_chain_device_profile FOREIGN KEY (default_rule_chain_id) REFERENCES rule_chain(id)
163 ); 166 );
164 167
@@ -170,13 +170,16 @@ CREATE TABLE IF NOT EXISTS device_profile ( @@ -170,13 +170,16 @@ CREATE TABLE IF NOT EXISTS device_profile (
170 name varchar(255), 170 name varchar(255),
171 type varchar(255), 171 type varchar(255),
172 transport_type varchar(255), 172 transport_type varchar(255),
  173 + provision_type varchar(255),
173 profile_data jsonb, 174 profile_data jsonb,
174 description varchar, 175 description varchar,
175 search_text varchar(255), 176 search_text varchar(255),
176 is_default boolean, 177 is_default boolean,
177 tenant_id uuid, 178 tenant_id uuid,
178 default_rule_chain_id uuid, 179 default_rule_chain_id uuid,
  180 + provision_device_key varchar,
179 CONSTRAINT device_profile_name_unq_key UNIQUE (tenant_id, name), 181 CONSTRAINT device_profile_name_unq_key UNIQUE (tenant_id, name),
  182 + CONSTRAINT device_provision_key_unq_key UNIQUE (provision_device_key),
180 CONSTRAINT fk_default_rule_chain_device_profile FOREIGN KEY (default_rule_chain_id) REFERENCES rule_chain(id) 183 CONSTRAINT fk_default_rule_chain_device_profile FOREIGN KEY (default_rule_chain_id) REFERENCES rule_chain(id)
181 ); 184 );
182 185