Commit d228208072eca2ee1d5610280651618c136d50b8

Authored by zbeacon
1 parent f3e52cbd

Provision init

Showing 27 changed files with 786 additions and 11 deletions
... ... @@ -706,6 +706,12 @@ public abstract class BaseController {
706 706 case ASSIGNED_TO_TENANT:
707 707 msgType = DataConstants.ENTITY_ASSIGNED_TO_TENANT;
708 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 716 if (!StringUtils.isEmpty(msgType)) {
711 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.datastax.oss.driver.api.core.uuid.Uuids;
  19 +import com.fasterxml.jackson.core.JsonProcessingException;
  20 +import com.fasterxml.jackson.databind.node.ObjectNode;
  21 +import com.google.common.util.concurrent.Futures;
  22 +import com.google.common.util.concurrent.ListenableFuture;
  23 +import com.google.common.util.concurrent.MoreExecutors;
  24 +import lombok.extern.slf4j.Slf4j;
  25 +import org.springframework.beans.factory.annotation.Autowired;
  26 +import org.springframework.stereotype.Service;
  27 +import org.springframework.util.StringUtils;
  28 +import org.thingsboard.server.common.data.DataConstants;
  29 +import org.thingsboard.server.common.data.Device;
  30 +import org.thingsboard.server.common.data.DeviceProfile;
  31 +import org.thingsboard.server.common.data.DeviceProfileType;
  32 +import org.thingsboard.server.common.data.audit.ActionType;
  33 +import org.thingsboard.server.common.data.device.profile.ProvisionDeviceProfileConfiguration;
  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.DeviceProfileDao;
  49 +import org.thingsboard.server.dao.device.DeviceProvisionService;
  50 +import org.thingsboard.server.dao.device.DeviceService;
  51 +import org.thingsboard.server.dao.device.provision.ProvisionRequest;
  52 +import org.thingsboard.server.dao.device.provision.ProvisionResponse;
  53 +import org.thingsboard.server.dao.device.provision.ProvisionResponseStatus;
  54 +import org.thingsboard.server.dao.util.mapping.JacksonUtil;
  55 +import org.thingsboard.server.gen.transport.TransportProtos;
  56 +import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg;
  57 +import org.thingsboard.server.queue.TbQueueCallback;
  58 +import org.thingsboard.server.queue.TbQueueProducer;
  59 +import org.thingsboard.server.queue.common.TbProtoQueueMsg;
  60 +import org.thingsboard.server.queue.discovery.PartitionService;
  61 +import org.thingsboard.server.service.state.DeviceStateService;
  62 +
  63 +import java.util.Collections;
  64 +import java.util.List;
  65 +import java.util.Optional;
  66 +import java.util.concurrent.locks.ReentrantLock;
  67 +
  68 +import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID;
  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 static final UserId PROVISION_USER_ID = UserId.fromString(NULL_UUID.toString());
  81 +
  82 + private final ReentrantLock deviceCreationLock = new ReentrantLock();
  83 +
  84 + @Autowired
  85 + DeviceProfileDao deviceProfileDao;
  86 +
  87 + @Autowired
  88 + DeviceService deviceService;
  89 +
  90 + @Autowired
  91 + DeviceCredentialsService deviceCredentialsService;
  92 +
  93 + @Autowired
  94 + AttributesService attributesService;
  95 +
  96 + @Autowired
  97 + DeviceStateService deviceStateService;
  98 +
  99 + @Autowired
  100 + AuditLogService auditLogService;
  101 +
  102 + @Autowired
  103 + PartitionService partitionService;
  104 +
  105 +
  106 + @Override
  107 + public ListenableFuture<ProvisionResponse> provisionDevice(ProvisionRequest provisionRequest) {
  108 + DeviceProfile targetProfile = deviceProfileDao.findProfileByTenantIdAndProfileDataProvisionConfigurationPair(
  109 + TenantId.SYS_TENANT_ID,
  110 + provisionRequest.getCredentials().getProvisionDeviceKey(),
  111 + provisionRequest.getCredentials().getProvisionDeviceSecret());
  112 +
  113 + if (targetProfile.getProfileData().getConfiguration().getType() != DeviceProfileType.PROVISION) {
  114 + return Futures.immediateFuture(new ProvisionResponse(null, ProvisionResponseStatus.NOT_FOUND));
  115 + }
  116 +
  117 + ProvisionDeviceProfileConfiguration currentProfileConfiguration = (ProvisionDeviceProfileConfiguration) targetProfile.getProfileData().getConfiguration();
  118 + if (!new ProvisionDeviceProfileConfiguration(provisionRequest.getCredentials().getProvisionDeviceKey(), provisionRequest.getCredentials().getProvisionDeviceSecret()).equals(currentProfileConfiguration)) {
  119 + return Futures.immediateFuture(new ProvisionResponse(null, ProvisionResponseStatus.NOT_FOUND));
  120 + }
  121 +
  122 + Device device = deviceService.findDeviceByTenantIdAndName(targetProfile.getTenantId(), provisionRequest.getDeviceName());
  123 + switch (currentProfileConfiguration.getStrategy()) {
  124 + case CHECK_NEW_DEVICE:
  125 + if (device == null) {
  126 + return createDevice(provisionRequest, targetProfile);
  127 + } else {
  128 + log.warn("[{}] The device is present and could not be provisioned once more!", device.getName());
  129 + notify(device, provisionRequest, DataConstants.PROVISION_FAILURE, false);
  130 + return Futures.immediateFuture(new ProvisionResponse(null, ProvisionResponseStatus.FAILURE));
  131 + }
  132 + case CHECK_PRE_PROVISIONED_DEVICE:
  133 + if (device == null) {
  134 + log.warn("[{}] Failed to find pre provisioned device!", provisionRequest.getDeviceName());
  135 + return Futures.immediateFuture(new ProvisionResponse(null, ProvisionResponseStatus.FAILURE));
  136 + } else {
  137 + return processProvision(device, provisionRequest);
  138 + }
  139 + default:
  140 + throw new RuntimeException("Strategy is not supported - " + currentProfileConfiguration.getStrategy().name());
  141 + }
  142 + }
  143 +
  144 + private ListenableFuture<ProvisionResponse> processProvision(Device device, ProvisionRequest provisionRequest) {
  145 + ListenableFuture<Optional<AttributeKvEntry>> provisionStateFuture = attributesService.find(device.getTenantId(), device.getId(),
  146 + DataConstants.SERVER_SCOPE, DEVICE_PROVISION_STATE);
  147 + ListenableFuture<Boolean> provisionedFuture = Futures.transformAsync(provisionStateFuture, optionalAtr -> {
  148 + if (optionalAtr.isPresent()) {
  149 + String state = optionalAtr.get().getValueAsString();
  150 + if (state.equals(PROVISIONED_STATE)) {
  151 + return Futures.immediateFuture(true);
  152 + } else {
  153 + log.error("[{}][{}] Unknown provision state: {}!", device.getName(), DEVICE_PROVISION_STATE, state);
  154 + return Futures.immediateCancelledFuture();
  155 + }
  156 + }
  157 + return Futures.transform(saveProvisionStateAttribute(device), input -> false, MoreExecutors.directExecutor());
  158 + }, MoreExecutors.directExecutor());
  159 + if (provisionedFuture.isCancelled()) {
  160 + throw new RuntimeException("Unknown provision state!");
  161 + }
  162 + return Futures.transform(provisionedFuture, provisioned -> {
  163 + if (provisioned) {
  164 + notify(device, provisionRequest, DataConstants.PROVISION_FAILURE, false);
  165 + return new ProvisionResponse(null, ProvisionResponseStatus.FAILURE);
  166 + }
  167 + notify(device, provisionRequest, DataConstants.PROVISION_SUCCESS, true);
  168 + return new ProvisionResponse(deviceCredentialsService.findDeviceCredentialsByDeviceId(device.getTenantId(), device.getId()), ProvisionResponseStatus.SUCCESS);
  169 + }, MoreExecutors.directExecutor());
  170 + }
  171 +
  172 + private ListenableFuture<ProvisionResponse> createDevice(ProvisionRequest provisionRequest, DeviceProfile profile) {
  173 + deviceCreationLock.lock();
  174 + try {
  175 + return processCreateDevice(provisionRequest, profile);
  176 + } finally {
  177 + deviceCreationLock.unlock();
  178 + }
  179 + }
  180 +
  181 + private ListenableFuture<ProvisionResponse> processCreateDevice(ProvisionRequest provisionRequest, DeviceProfile profile) {
  182 + Device device = deviceService.findDeviceByTenantIdAndName(profile.getTenantId(), provisionRequest.getDeviceName());
  183 + if (device == null) {
  184 + Device savedDevice = saveDevice(provisionRequest, profile);
  185 +
  186 + deviceStateService.onDeviceAdded(savedDevice);
  187 + pushDeviceCreatedEventToRuleEngine(savedDevice);
  188 + notify(savedDevice, provisionRequest, DataConstants.PROVISION_SUCCESS, true);
  189 +
  190 + return Futures.transform(saveProvisionStateAttribute(savedDevice), input ->
  191 + new ProvisionResponse(
  192 + getDeviceCredentials(savedDevice, provisionRequest.getX509CertPubKey()),
  193 + ProvisionResponseStatus.SUCCESS), MoreExecutors.directExecutor());
  194 + }
  195 + log.warn("[{}] The device is already provisioned!", device.getName());
  196 + notify(device, provisionRequest, DataConstants.PROVISION_FAILURE, false);
  197 + return Futures.immediateFuture(new ProvisionResponse(null, ProvisionResponseStatus.FAILURE));
  198 + }
  199 +
  200 + private ListenableFuture<List<Void>> saveProvisionStateAttribute(Device device) {
  201 + return attributesService.save(device.getTenantId(), device.getId(), DataConstants.SERVER_SCOPE,
  202 + Collections.singletonList(new BaseAttributeKvEntry(new StringDataEntry(DEVICE_PROVISION_STATE, PROVISIONED_STATE),
  203 + System.currentTimeMillis())));
  204 + }
  205 +
  206 + private Device saveDevice(ProvisionRequest provisionRequest, DeviceProfile profile) {
  207 + Device device = new Device();
  208 + device.setName(provisionRequest.getDeviceName());
  209 + device.setType(provisionRequest.getDeviceType());
  210 + device.setTenantId(profile.getTenantId());
  211 + return deviceService.saveDevice(device);
  212 + }
  213 +
  214 + private DeviceCredentials getDeviceCredentials(Device device, String x509CertPubKey) {
  215 + DeviceCredentials credentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(device.getTenantId(), device.getId());
  216 + if (!StringUtils.isEmpty(x509CertPubKey)) {
  217 + credentials.setCredentialsType(DeviceCredentialsType.X509_CERTIFICATE);
  218 + credentials.setCredentialsValue(x509CertPubKey);
  219 + return deviceCredentialsService.updateDeviceCredentials(device.getTenantId(), credentials);
  220 + }
  221 + return credentials;
  222 + }
  223 +
  224 + private void notify(Device device, ProvisionRequest provisionRequest, String type, boolean success) {
  225 + pushProvisionEventToRuleEngine(provisionRequest, device, type);
  226 + logAction(device.getTenantId(), device, success, provisionRequest);
  227 + }
  228 +
  229 + private void pushProvisionEventToRuleEngine(ProvisionRequest request, Device device, String type) {
  230 + try {
  231 + ObjectNode entityNode = JacksonUtil.OBJECT_MAPPER.valueToTree(request);
  232 + TbMsg msg = new TbMsg(Uuids.timeBased(), type, device.getId(), createTbMsgMetaData(device), JacksonUtil.OBJECT_MAPPER.writeValueAsString(entityNode), null, null, 0L);
  233 + sendToRuleEngine(device.getTenantId(), msg, null);
  234 + } catch (JsonProcessingException | IllegalArgumentException e) {
  235 + log.warn("[{}] Failed to push device action to rule engine: {}", device.getId(), type, e);
  236 + }
  237 + }
  238 +
  239 + private void pushDeviceCreatedEventToRuleEngine(Device device) {
  240 + try {
  241 + ObjectNode entityNode = JacksonUtil.OBJECT_MAPPER.valueToTree(device);
  242 + TbMsg msg = new TbMsg(Uuids.timeBased(), DataConstants.ENTITY_CREATED, device.getId(), createTbMsgMetaData(device), JacksonUtil.OBJECT_MAPPER.writeValueAsString(entityNode), null, null, 0L);
  243 + sendToRuleEngine(device.getTenantId(), msg, null);
  244 + } catch (JsonProcessingException | IllegalArgumentException e) {
  245 + log.warn("[{}] Failed to push device action to rule engine: {}", device.getId(), DataConstants.ENTITY_CREATED, e);
  246 + }
  247 + }
  248 +
  249 + protected void sendToRuleEngine(TenantId tenantId, TbMsg tbMsg, TbQueueCallback callback) {
  250 + TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_RULE_ENGINE, tenantId, tbMsg.getOriginator());
  251 + TransportProtos.ToRuleEngineMsg msg = TransportProtos.ToRuleEngineMsg.newBuilder().setTbMsg(TbMsg.toByteString(tbMsg))
  252 + .setTenantIdMSB(tenantId.getId().getMostSignificantBits())
  253 + .setTenantIdLSB(tenantId.getId().getLeastSignificantBits()).build();
  254 + ruleEngineMsgProducer.send(tpi, new TbProtoQueueMsg<>(tbMsg.getId(), msg), callback);
  255 + }
  256 +
  257 + private TbMsgMetaData createTbMsgMetaData(Device device) {
  258 + TbMsgMetaData metaData = new TbMsgMetaData();
  259 + metaData.putValue("tenantId", device.getTenantId().toString());
  260 + return metaData;
  261 + }
  262 +
  263 + private void logAction(TenantId tenantId, Device device, boolean success, ProvisionRequest provisionRequest) {
  264 + ActionType actionType = success ? ActionType.PROVISION_SUCCESS : ActionType.PROVISION_FAILURE;
  265 + auditLogService.logEntityAction(tenantId, null, null, device.getName(), device.getId(), device, actionType, null, provisionRequest);
  266 + }
  267 +}
... ...
  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.ProvisionDeviceProfileConfiguration;
  21 +
  22 +@Data
  23 +@AllArgsConstructor
  24 +public class ProvisionRequest {
  25 + private String deviceName;
  26 + private String deviceType;
  27 + private String x509CertPubKey;
  28 + private ProvisionDeviceProfileConfiguration credentials;
  29 +}
... ...
  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.security.DeviceCredentials;
  21 +
  22 +@Data
  23 +@AllArgsConstructor
  24 +public class ProvisionResponse {
  25 + private DeviceCredentials deviceCredentials;
  26 + private ProvisionResponseStatus responseStatus;
  27 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.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 63 public static final String ALARM_CLEAR = "ALARM_CLEAR";
64 64 public static final String ENTITY_ASSIGNED_FROM_TENANT = "ENTITY_ASSIGNED_FROM_TENANT";
65 65 public static final String ENTITY_ASSIGNED_TO_TENANT = "ENTITY_ASSIGNED_TO_TENANT";
  66 + public static final String PROVISION_SUCCESS = "PROVISION_SUCCESS";
  67 + public static final String PROVISION_FAILURE = "PROVISION_FAILURE";
66 68
67 69 public static final String RPC_CALL_FROM_SERVER_TO_DEVICE = "RPC_CALL_FROM_SERVER_TO_DEVICE";
68 70
... ... @@ -70,4 +72,11 @@ public class DataConstants {
70 72 public static final String SECRET_KEY_FIELD_NAME = "secretKey";
71 73 public static final String DURATION_MS_FIELD_NAME = "durationMs";
72 74
  75 + public static final String DEVICE_NAME = "deviceName";
  76 + public static final String DEVICE_TYPE = "deviceType";
  77 + public static final String CERT_PUB_KEY = "x509CertPubKey";
  78 +
  79 + public static final String PROVISION_KEY = "provisionDeviceKey";
  80 + public static final String PROVISION_SECRET = "provisionDeviceSecret";
  81 +
73 82 }
... ...
... ... @@ -16,5 +16,6 @@
16 16 package org.thingsboard.server.common.data;
17 17
18 18 public enum DeviceProfileType {
19   - DEFAULT
  19 + DEFAULT,
  20 + PROVISION
20 21 }
... ...
... ... @@ -42,7 +42,9 @@ public enum ActionType {
42 42 LOGOUT(false),
43 43 LOCKOUT(false),
44 44 ASSIGNED_FROM_TENANT(false),
45   - ASSIGNED_TO_TENANT(false);
  45 + ASSIGNED_TO_TENANT(false),
  46 + PROVISION_SUCCESS(false),
  47 + PROVISION_FAILURE(false);
46 48
47 49 private final boolean isRead;
48 50
... ...
... ... @@ -27,7 +27,8 @@ import org.thingsboard.server.common.data.DeviceProfileType;
27 27 include = JsonTypeInfo.As.PROPERTY,
28 28 property = "type")
29 29 @JsonSubTypes({
30   - @JsonSubTypes.Type(value = DefaultDeviceProfileConfiguration.class, name = "DEFAULT")})
  30 + @JsonSubTypes.Type(value = DefaultDeviceProfileConfiguration.class, name = "DEFAULT"),
  31 + @JsonSubTypes.Type(value = ProvisionDeviceProfileConfiguration.class, name = "PROVISION")})
31 32 public interface DeviceProfileConfiguration {
32 33
33 34 @JsonIgnore
... ...
... ... @@ -31,6 +31,8 @@ public class MqttTopics {
31 31 public static final String DEVICE_TELEMETRY_TOPIC = BASE_DEVICE_API_TOPIC + "/telemetry";
32 32 public static final String DEVICE_CLAIM_TOPIC = BASE_DEVICE_API_TOPIC + "/claim";
33 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 37 public static final String BASE_GATEWAY_API_TOPIC = "v1/gateway";
36 38 public static final String GATEWAY_CONNECT_TOPIC = BASE_GATEWAY_API_TOPIC + "/connect";
... ... @@ -41,6 +43,8 @@ public class MqttTopics {
41 43 public static final String GATEWAY_RPC_TOPIC = BASE_GATEWAY_API_TOPIC + "/rpc";
42 44 public static final String GATEWAY_ATTRIBUTES_REQUEST_TOPIC = BASE_GATEWAY_API_TOPIC + "/attributes/request";
43 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 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 com.fasterxml.jackson.annotation.JsonCreator;
  19 +import com.fasterxml.jackson.annotation.JsonProperty;
  20 +import lombok.Data;
  21 +import org.thingsboard.server.common.data.DeviceProfileType;
  22 +
  23 +import java.util.Objects;
  24 +
  25 +@Data
  26 +public class ProvisionDeviceProfileConfiguration implements DeviceProfileConfiguration {
  27 +
  28 + private String provisionDeviceKey;
  29 + private String provisionDeviceSecret;
  30 +
  31 + private ProvisionRequestValidationStrategyType strategy;
  32 +
  33 + @Override
  34 + public DeviceProfileType getType() {
  35 + return DeviceProfileType.PROVISION;
  36 + }
  37 +
  38 + @JsonCreator
  39 + public ProvisionDeviceProfileConfiguration(@JsonProperty("provisionDeviceKey") String provisionProfileKey, @JsonProperty("provisionDeviceSecret") String provisionProfileSecret) {
  40 + this.provisionDeviceKey = provisionProfileKey;
  41 + this.provisionDeviceSecret = provisionProfileSecret;
  42 + }
  43 +
  44 + @Override
  45 + public boolean equals(Object o) {
  46 + if (this == o) return true;
  47 + if (o == null || getClass() != o.getClass()) return false;
  48 + ProvisionDeviceProfileConfiguration that = (ProvisionDeviceProfileConfiguration) o;
  49 + return provisionDeviceKey.equals(that.provisionDeviceKey) &&
  50 + provisionDeviceSecret.equals(that.provisionDeviceSecret);
  51 + }
  52 +
  53 + @Override
  54 + public int hashCode() {
  55 + return Objects.hash(provisionDeviceKey, provisionDeviceSecret);
  56 + }
  57 +}
... ...
  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 ProvisionRequestValidationStrategy {
  22 + private final ProvisionRequestValidationStrategyType validationStrategyType;
  23 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.common.data.device.profile;
  17 +
  18 +public enum ProvisionRequestValidationStrategyType {
  19 + CHECK_NEW_DEVICE, CHECK_PRE_PROVISIONED_DEVICE
  20 +}
... ...
... ... @@ -73,6 +73,11 @@ enum KeyValueType {
73 73 JSON_V = 4;
74 74 }
75 75
  76 +enum CredentialsType {
  77 + ACCESS_TOKEN = 0;
  78 + X509_CERTIFICATE = 1;
  79 +}
  80 +
76 81 message KeyValueProto {
77 82 string key = 1;
78 83 KeyValueType type = 2;
... ... @@ -241,6 +246,36 @@ message ClaimDeviceMsg {
241 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 deviceType = 2;
  260 + string x509CertPubKey = 3;
  261 + ProvisionDeviceCredentialsMsg provisionDeviceCredentialsMsg = 4;
  262 +}
  263 +
  264 +message ProvisionDeviceCredentialsMsg {
  265 + string provisionDeviceKey = 1;
  266 + string provisionDeviceSecret = 2;
  267 +}
  268 +
  269 +message ProvisionDeviceResponseMsg {
  270 + DeviceCredentialsProto deviceCredentials = 1;
  271 + ProvisionResponseStatus provisionResponseStatus = 2;
  272 +}
  273 +
  274 +enum ProvisionResponseStatus {
  275 + SUCCESS = 0;
  276 + NOT_FOUND = 1;
  277 + FAILURE = 2;
  278 +}
244 279 //Used to report session state to tb-Service and persist this state in the cache on the tb-Service level.
245 280 message SubscriptionInfoProto {
246 281 int64 lastActivityTime = 1;
... ... @@ -266,6 +301,7 @@ message TransportToDeviceActorMsg {
266 301 ToDeviceRpcResponseMsg toDeviceRPCCallResponse = 6;
267 302 SubscriptionInfoProto subscriptionInfo = 7;
268 303 ClaimDeviceMsg claimDevice = 8;
  304 + ProvisionDeviceRequestMsg provisionDevice = 9;
269 305 }
270 306
271 307 message TransportToRuleEngineMsg {
... ... @@ -441,6 +477,7 @@ message TransportApiRequestMsg {
441 477 GetTenantRoutingInfoRequestMsg getTenantRoutingInfoRequestMsg = 4;
442 478 GetDeviceProfileRequestMsg getDeviceProfileRequestMsg = 5;
443 479 ValidateBasicMqttCredRequestMsg validateBasicMqttCredRequestMsg = 6;
  480 +// ProvisionDeviceRequestMsg provisionDeviceRequestMsg = 7;
444 481 }
445 482
446 483 /* Response from ThingsBoard Core Service to Transport Service */
... ... @@ -449,6 +486,7 @@ message TransportApiResponseMsg {
449 486 GetOrCreateDeviceFromGatewayResponseMsg getOrCreateDeviceResponseMsg = 2;
450 487 GetTenantRoutingInfoResponseMsg getTenantRoutingInfoResponseMsg = 4;
451 488 GetDeviceProfileResponseMsg getDeviceProfileResponseMsg = 5;
  489 +// ProvisionDeviceResponseMsg provisionDeviceResponseMsg = 6;
452 490 }
453 491
454 492 /* Messages that are handled by ThingsBoard Core Service */
... ... @@ -491,4 +529,5 @@ message ToTransportMsg {
491 529 ToServerRpcResponseMsg toServerResponse = 7;
492 530 DeviceProfileUpdateMsg deviceProfileUpdateMsg = 8;
493 531 DeviceProfileDeleteMsg deviceProfileDeleteMsg = 9;
  532 + ProvisionDeviceResponseMsg provisionResponse = 10;
494 533 }
... ...
... ... @@ -38,7 +38,6 @@ import io.netty.util.ReferenceCountUtil;
38 38 import io.netty.util.concurrent.Future;
39 39 import io.netty.util.concurrent.GenericFutureListener;
40 40 import lombok.extern.slf4j.Slf4j;
41   -import org.springframework.util.StringUtils;
42 41 import org.thingsboard.server.common.data.DeviceProfile;
43 42 import org.thingsboard.server.common.data.DeviceTransportType;
44 43 import org.thingsboard.server.common.data.device.profile.MqttTopics;
... ... @@ -52,9 +51,8 @@ import org.thingsboard.server.common.transport.auth.TransportDeviceInfo;
52 51 import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse;
53 52 import org.thingsboard.server.common.transport.service.DefaultTransportService;
54 53 import org.thingsboard.server.gen.transport.TransportProtos;
  54 +import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceResponseMsg;
55 55 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 56 import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceX509CertRequestMsg;
59 57 import org.thingsboard.server.transport.mqtt.adaptors.MqttTransportAdaptor;
60 58 import org.thingsboard.server.transport.mqtt.session.DeviceSessionCtx;
... ... @@ -66,7 +64,6 @@ import javax.net.ssl.SSLPeerUnverifiedException;
66 64 import javax.security.cert.X509Certificate;
67 65 import java.io.IOException;
68 66 import java.net.InetSocketAddress;
69   -import java.nio.charset.StandardCharsets;
70 67 import java.util.ArrayList;
71 68 import java.util.List;
72 69 import java.util.UUID;
... ... @@ -74,9 +71,9 @@ import java.util.concurrent.ConcurrentHashMap;
74 71 import java.util.concurrent.ConcurrentMap;
75 72
76 73 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 74 import static io.netty.handler.codec.mqtt.MqttConnectReturnCode.CONNECTION_REFUSED_NOT_AUTHORIZED;
79 75 import static io.netty.handler.codec.mqtt.MqttMessageType.CONNACK;
  76 +import static io.netty.handler.codec.mqtt.MqttMessageType.CONNECT;
80 77 import static io.netty.handler.codec.mqtt.MqttMessageType.PINGRESP;
81 78 import static io.netty.handler.codec.mqtt.MqttMessageType.PUBACK;
82 79 import static io.netty.handler.codec.mqtt.MqttMessageType.SUBACK;
... ... @@ -136,10 +133,45 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
136 133 return;
137 134 }
138 135 deviceSessionCtx.setChannel(ctx);
  136 + if (CONNECT.equals(msg.fixedHeader().messageType())) {
  137 + processConnect(ctx, (MqttConnectMessage) msg);
  138 + } else if (deviceSessionCtx.isProvisionOnly()) {
  139 + processProvisionSessionMsg(ctx, msg);
  140 + } else {
  141 + processRegularSessionMsg(ctx, msg);
  142 + }
  143 + }
  144 +
  145 + private void processProvisionSessionMsg(ChannelHandlerContext ctx, MqttMessage msg) {
139 146 switch (msg.fixedHeader().messageType()) {
140   - case CONNECT:
141   - processConnect(ctx, (MqttConnectMessage) msg);
  147 + case PUBLISH:
  148 + MqttPublishMessage mqttMsg = (MqttPublishMessage) msg;
  149 + String topicName = mqttMsg.variableHeader().topicName();
  150 + int msgId = mqttMsg.variableHeader().packetId();
  151 + try {
  152 + if (topicName.equals(MqttTopics.DEVICE_PROVISION_REQUEST_TOPIC)) {
  153 + TransportProtos.ProvisionDeviceRequestMsg provisionRequestMsg = adaptor.convertToProvisionRequestMsg(deviceSessionCtx, mqttMsg);
  154 + transportService.process(deviceSessionCtx.getSessionInfo(), provisionRequestMsg, (TransportServiceCallback) new DeviceProvisionCallback(ctx, msgId, provisionRequestMsg));
  155 + log.trace("[{}][{}] Processing publish msg [{}][{}]!", sessionId, deviceSessionCtx.getDeviceId(), topicName, msgId);
  156 + } else {
  157 + throw new RuntimeException("Unsupported topic for provisioning requests!");
  158 + }
  159 + } catch (RuntimeException | AdaptorException e) {
  160 + log.warn("[{}] Failed to process publish msg [{}][{}]", sessionId, topicName, msgId, e);
  161 + ctx.close();
  162 + }
142 163 break;
  164 + case PINGREQ:
  165 + ctx.writeAndFlush(new MqttMessage(new MqttFixedHeader(PINGRESP, false, AT_MOST_ONCE, false, 0)));
  166 + break;
  167 + case DISCONNECT:
  168 + ctx.close();
  169 + break;
  170 + }
  171 + }
  172 +
  173 + private void processRegularSessionMsg(ChannelHandlerContext ctx, MqttMessage msg) {
  174 + switch (msg.fixedHeader().messageType()) {
143 175 case PUBLISH:
144 176 processPublish(ctx, (MqttPublishMessage) msg);
145 177 break;
... ... @@ -261,6 +293,37 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
261 293 };
262 294 }
263 295
  296 + private class DeviceProvisionCallback implements TransportServiceCallback<ProvisionDeviceResponseMsg> {
  297 + private final ChannelHandlerContext ctx;
  298 + private final int msgId;
  299 + private final TransportProtos.ProvisionDeviceRequestMsg msg;
  300 +
  301 + DeviceProvisionCallback(ChannelHandlerContext ctx, int msgId, TransportProtos.ProvisionDeviceRequestMsg msg) {
  302 + this.ctx = ctx;
  303 + this.msgId = msgId;
  304 + this.msg = msg;
  305 + }
  306 +
  307 + @Override
  308 + public void onSuccess(TransportProtos.ProvisionDeviceResponseMsg provisionResponseMsg) {
  309 + log.trace("[{}] Published msg: {}", sessionId, msg);
  310 + if (msgId > 0) {
  311 + ctx.writeAndFlush(createMqttPubAckMsg(msgId));
  312 + }
  313 + try {
  314 + adaptor.convertToPublish(deviceSessionCtx, provisionResponseMsg).ifPresent(deviceSessionCtx.getChannel()::writeAndFlush);
  315 + } catch (Exception e) {
  316 + log.trace("[{}] Failed to convert device attributes response to MQTT msg", sessionId, e);
  317 + }
  318 + }
  319 +
  320 + @Override
  321 + public void onError(Throwable e) {
  322 + log.trace("[{}] Failed to publish msg: {}", sessionId, msg, e);
  323 + processDisconnect(ctx);
  324 + }
  325 + }
  326 +
264 327 private void processSubscribe(ChannelHandlerContext ctx, MqttSubscribeMessage mqttMsg) {
265 328 if (!checkConnected(ctx, mqttMsg)) {
266 329 return;
... ... @@ -290,6 +353,8 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
290 353 case MqttTopics.GATEWAY_RPC_TOPIC:
291 354 case MqttTopics.GATEWAY_ATTRIBUTES_RESPONSE_TOPIC:
292 355 case MqttTopics.DEVICE_ATTRIBUTES_RESPONSES_TOPIC:
  356 + case MqttTopics.GATEWAY_PROVISION_RESPONSE_TOPIC:
  357 + case MqttTopics.DEVICE_PROVISION_RESPONSE_TOPIC:
293 358 registerSubQoS(topic, grantedQoSList, reqQoS);
294 359 break;
295 360 default:
... ...
... ... @@ -184,6 +184,28 @@ public class JsonMqttAdaptor implements MqttTransportAdaptor {
184 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 209 private MqttPublishMessage createMqttPublishMsg(MqttDeviceAwareSessionContext ctx, String topic, JsonElement json) {
188 210 MqttFixedHeader mqttFixedHeader =
189 211 new MqttFixedHeader(MqttMessageType.PUBLISH, false, ctx.getQoSForTopic(topic), false, 0);
... ...
... ... @@ -24,6 +24,8 @@ import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeRequestM
24 24 import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeResponseMsg;
25 25 import org.thingsboard.server.gen.transport.TransportProtos.PostAttributeMsg;
26 26 import org.thingsboard.server.gen.transport.TransportProtos.PostTelemetryMsg;
  27 +import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceRequestMsg;
  28 +import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceResponseMsg;
27 29 import org.thingsboard.server.gen.transport.TransportProtos.ToDeviceRpcRequestMsg;
28 30 import org.thingsboard.server.gen.transport.TransportProtos.ToDeviceRpcResponseMsg;
29 31 import org.thingsboard.server.gen.transport.TransportProtos.ToServerRpcRequestMsg;
... ... @@ -63,4 +65,10 @@ public interface MqttTransportAdaptor {
63 65
64 66 Optional<MqttMessage> convertToPublish(MqttDeviceAwareSessionContext ctx, ToServerRpcResponseMsg rpcResponse) throws AdaptorException;
65 67
  68 + ProvisionDeviceRequestMsg convertToProvisionRequestMsg(MqttDeviceAwareSessionContext ctx, MqttPublishMessage inbound) throws AdaptorException;
  69 +
  70 + Optional<MqttMessage> convertToPublish(MqttDeviceAwareSessionContext ctx, ProvisionDeviceResponseMsg provisionResponse) throws AdaptorException;
  71 +
  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 17
18 18 import io.netty.channel.ChannelHandlerContext;
19 19 import lombok.Getter;
  20 +import lombok.Setter;
20 21 import lombok.extern.slf4j.Slf4j;
21 22 import org.thingsboard.server.common.data.DeviceProfile;
22 23 import org.thingsboard.server.common.data.DeviceTransportType;
... ... @@ -40,6 +41,10 @@ public class DeviceSessionCtx extends MqttDeviceAwareSessionContext {
40 41 private ChannelHandlerContext channel;
41 42 private final AtomicInteger msgIdSeq = new AtomicInteger(0);
42 43
  44 + @Getter
  45 + @Setter
  46 + private boolean provisionOnly;
  47 +
43 48 private volatile MqttTopicFilter telemetryTopicFilter = MqttTopicFilterFactory.getDefaultTelemetryFilter();
44 49 private volatile MqttTopicFilter attributesTopicFilter = MqttTopicFilterFactory.getDefaultAttributesFilter();
45 50
... ...
... ... @@ -27,6 +27,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.GetTenantRoutingInfo
27 27 import org.thingsboard.server.gen.transport.TransportProtos.GetTenantRoutingInfoResponseMsg;
28 28 import org.thingsboard.server.gen.transport.TransportProtos.PostAttributeMsg;
29 29 import org.thingsboard.server.gen.transport.TransportProtos.PostTelemetryMsg;
  30 +import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceRequestMsg;
30 31 import org.thingsboard.server.gen.transport.TransportProtos.SessionEventMsg;
31 32 import org.thingsboard.server.gen.transport.TransportProtos.SessionInfoProto;
32 33 import org.thingsboard.server.gen.transport.TransportProtos.SubscribeToAttributeUpdatesMsg;
... ... @@ -83,6 +84,8 @@ public interface TransportService {
83 84
84 85 void process(SessionInfoProto sessionInfo, ClaimDeviceMsg msg, TransportServiceCallback<Void> callback);
85 86
  87 + void process(SessionInfoProto sessionInfo, ProvisionDeviceRequestMsg msg, TransportServiceCallback<Void> deviceProvisionCallback);
  88 +
86 89 void registerAsyncSession(SessionInfoProto sessionInfo, SessionMsgListener listener);
87 90
88 91 void registerSyncSession(SessionInfoProto sessionInfo, SessionMsgListener listener, long timeout);
... ... @@ -90,5 +93,4 @@ public interface TransportService {
90 93 void reportActivity(SessionInfoProto sessionInfo);
91 94
92 95 void deregisterSession(SessionInfoProto sessionInfo);
93   -
94 96 }
... ...
... ... @@ -42,6 +42,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.KeyValueProto;
42 42 import org.thingsboard.server.gen.transport.TransportProtos.KeyValueType;
43 43 import org.thingsboard.server.gen.transport.TransportProtos.PostAttributeMsg;
44 44 import org.thingsboard.server.gen.transport.TransportProtos.PostTelemetryMsg;
  45 +import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceResponseMsg;
45 46 import org.thingsboard.server.gen.transport.TransportProtos.TsKvListProto;
46 47 import org.thingsboard.server.gen.transport.TransportProtos.TsKvProto;
47 48
... ... @@ -53,6 +54,7 @@ import java.util.Map;
53 54 import java.util.Map.Entry;
54 55 import java.util.Set;
55 56 import java.util.TreeMap;
  57 +import java.util.UUID;
56 58 import java.util.function.Consumer;
57 59 import java.util.stream.Collectors;
58 60
... ... @@ -397,6 +399,34 @@ 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 DeviceId(
  421 + new UUID(payload.getDeviceCredentials().getDeviceIdMSB(), payload.getDeviceCredentials().getDeviceIdLSB())).toString());
  422 + result.addProperty("credentialsType", payload.getDeviceCredentials().getCredentialsType().name());
  423 + result.addProperty("credentialsId", payload.getDeviceCredentials().getCredentialsId());
  424 + result.addProperty("credentialsValue",
  425 + StringUtils.isEmpty(payload.getDeviceCredentials().getCredentialsValue()) ? null : payload.getDeviceCredentials().getCredentialsValue());
  426 + }
  427 + return result;
  428 + }
  429 +
400 430 public static JsonElement toErrorJson(String errorMsg) {
401 431 JsonObject error = new JsonObject();
402 432 error.addProperty("error", errorMsg);
... ... @@ -498,4 +528,44 @@ public class JsonConverter {
498 528 maxStringValueLength = length;
499 529 }
500 530
  531 + public static TransportProtos.ProvisionDeviceRequestMsg convertToProvisionRequestMsg(String json) {
  532 + JsonElement jsonElement = new JsonParser().parse(json);
  533 + if (jsonElement.isJsonObject()) {
  534 + return buildProvisionRequestMsg(jsonElement.getAsJsonObject());
  535 + } else {
  536 + throw new JsonSyntaxException(CAN_T_PARSE_VALUE + jsonElement);
  537 + }
  538 + }
  539 +
  540 + public static TransportProtos.ProvisionDeviceRequestMsg convertToProvisionRequestMsg(JsonObject jo) {
  541 + return buildProvisionRequestMsg(jo);
  542 + }
  543 +
  544 + private static TransportProtos.ProvisionDeviceRequestMsg buildProvisionRequestMsg(JsonObject jo) {
  545 + return TransportProtos.ProvisionDeviceRequestMsg.newBuilder()
  546 + .setDeviceName(getStrValue(jo, DataConstants.DEVICE_NAME, true))
  547 + .setDeviceType(getStrValue(jo, DataConstants.DEVICE_TYPE, true))
  548 + .setX509CertPubKey(getStrValue(jo, DataConstants.CERT_PUB_KEY, false))
  549 + .setProvisionDeviceCredentialsMsg(buildProvisionDeviceCredentialsMsg(
  550 + getStrValue(jo, DataConstants.PROVISION_KEY, true),
  551 + getStrValue(jo, DataConstants.PROVISION_SECRET, true)))
  552 + .build();
  553 + }
  554 +
  555 + private static TransportProtos.ProvisionDeviceCredentialsMsg buildProvisionDeviceCredentialsMsg(String provisionKey, String provisionSecret) {
  556 + return TransportProtos.ProvisionDeviceCredentialsMsg.newBuilder()
  557 + .setProvisionDeviceKey(provisionKey)
  558 + .setProvisionDeviceSecret(provisionSecret)
  559 + .build();
  560 + }
  561 + private static String getStrValue(JsonObject jo, String field, boolean requiredField) {
  562 + if (jo.has(field)) {
  563 + return jo.get(field).getAsString();
  564 + } else {
  565 + if (requiredField) {
  566 + throw new RuntimeException("Failed to find the field " + field + " in JSON body " + jo + "!");
  567 + }
  568 + return "";
  569 + }
  570 + }
501 571 }
... ...
... ... @@ -485,6 +485,15 @@ public class DefaultTransportService implements TransportService {
485 485 }
486 486
487 487 @Override
  488 + public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.ProvisionDeviceRequestMsg msg, TransportServiceCallback<Void> callback) {
  489 + if (checkLimits(sessionInfo, msg, callback)) {
  490 + reportActivityInternal(sessionInfo);
  491 + sendToDeviceActor(sessionInfo, TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo)
  492 + .setProvisionDevice(msg).build(), callback);
  493 + }
  494 + }
  495 +
  496 + @Override
488 497 public void reportActivity(TransportProtos.SessionInfoProto sessionInfo) {
489 498 reportActivityInternal(sessionInfo);
490 499 }
... ...
... ... @@ -47,6 +47,7 @@ import org.thingsboard.server.common.data.security.DeviceCredentials;
47 47 import org.thingsboard.server.dao.audit.sink.AuditLogSink;
48 48 import org.thingsboard.server.dao.entity.EntityService;
49 49 import org.thingsboard.server.dao.exception.DataValidationException;
  50 +import org.thingsboard.server.dao.device.provision.ProvisionRequest;
50 51 import org.thingsboard.server.dao.service.DataValidator;
51 52
52 53 import java.io.PrintWriter;
... ... @@ -257,6 +258,13 @@ public class AuditLogServiceImpl implements AuditLogService {
257 258 actionData.put("os", os);
258 259 actionData.put("device", device);
259 260 break;
  261 + case PROVISION_SUCCESS:
  262 + case PROVISION_FAILURE:
  263 + ProvisionRequest request = extractParameter(ProvisionRequest.class, additionalInfo);
  264 + if (request != null) {
  265 + actionData.set("provisionRequest", objectMapper.valueToTree(request));
  266 + }
  267 + break;
260 268 }
261 269 return actionData;
262 270 }
... ...
... ... @@ -38,5 +38,9 @@ public interface DeviceProfileDao extends Dao<DeviceProfile> {
38 38
39 39 DeviceProfileInfo findDefaultDeviceProfileInfo(TenantId tenantId);
40 40
  41 + DeviceProfileInfo findProfileInfoByTenantIdAndProfileDataProvisionConfigurationPair(TenantId tenantId, String provisionDeviceKey, String provisionDeviceSecret);
  42 +
  43 + DeviceProfile findProfileByTenantIdAndProfileDataProvisionConfigurationPair(TenantId tenantId, String provisionDeviceKey, String provisionDeviceSecret);
  44 +
41 45 DeviceProfile findByName(TenantId tenantId, String profileName);
42 46 }
... ...
  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 +public class DeviceProvisionServiceImpl {
  19 +}
... ...
... ... @@ -22,6 +22,7 @@ import org.springframework.data.repository.PagingAndSortingRepository;
22 22 import org.springframework.data.repository.query.Param;
23 23 import org.thingsboard.server.common.data.DeviceProfile;
24 24 import org.thingsboard.server.common.data.DeviceProfileInfo;
  25 +import org.thingsboard.server.common.data.DeviceProfileType;
25 26 import org.thingsboard.server.dao.model.sql.DeviceProfileEntity;
26 27
27 28 import java.util.UUID;
... ... @@ -57,4 +58,23 @@ public interface DeviceProfileRepository extends PagingAndSortingRepository<Devi
57 58
58 59 DeviceProfileEntity findByTenantIdAndName(UUID id, String profileName);
59 60
  61 + @Query(value = "SELECT d FROM DeviceProfileEntity d " +
  62 + "WHERE d.tenantId = :tenantId " +
  63 + "AND d.profileData::jsonb->>{'configuration', 'provisionDeviceKey'} = :provisionDeviceKey " +
  64 + "AND d.profileData::jsonb->>{'configuration', 'provisionDeviceSecret' = :provisionDeviceSecret}",
  65 + nativeQuery = true)
  66 + DeviceProfileEntity findProfileByTenantIdAndProfileDataProvisionConfigurationPair(@Param("tenantId") UUID tenantId,
  67 + @Param("provisionDeviceKey") String provisionDeviceKey,
  68 + @Param("provisionDeviceSecret") String provisionDeviceSecret);
  69 +
  70 + @Query(value = "SELECT new org.thingsboard.server.common.data.DeviceProfileInfo(d.id, d.name, d.type, d.transportType) " +
  71 + " FROM DeviceProfileEntity d " +
  72 + "WHERE d.tenantId = :tenantId " +
  73 + "AND d.profileData::jsonb->>{'configuration', 'provisionDeviceKey'} = :provisionDeviceKey " +
  74 + "AND d.profileData::jsonb->>{'configuration', 'provisionDeviceSecret' = :provisionDeviceSecret}",
  75 + nativeQuery = true)
  76 + DeviceProfileInfo findProfileInfoByTenantIdAndProfileDataProvisionConfigurationPair(@Param("tenantId") UUID tenantId,
  77 + @Param("provisionDeviceKey") String provisionDeviceKey,
  78 + @Param("provisionDeviceSecret") String provisionDeviceSecret);
  79 +
60 80 }
... ...
... ... @@ -81,6 +81,16 @@ public class JpaDeviceProfileDao extends JpaAbstractSearchTextDao<DeviceProfileE
81 81 }
82 82
83 83 @Override
  84 + public DeviceProfile findProfileByTenantIdAndProfileDataProvisionConfigurationPair(TenantId tenantId, String provisionDeviceKey, String provisionDeviceSecret) {
  85 + return DaoUtil.getData(deviceProfileRepository.findProfileByTenantIdAndProfileDataProvisionConfigurationPair(tenantId.getId(), provisionDeviceKey, provisionDeviceSecret));
  86 + }
  87 +
  88 + @Override
  89 + public DeviceProfileInfo findProfileInfoByTenantIdAndProfileDataProvisionConfigurationPair(TenantId tenantId, String provisionDeviceKey, String provisionDeviceSecret) {
  90 + return deviceProfileRepository.findProfileInfoByTenantIdAndProfileDataProvisionConfigurationPair(tenantId.getId(), provisionDeviceKey, provisionDeviceSecret);
  91 + }
  92 +
  93 + @Override
84 94 public DeviceProfile findByName(TenantId tenantId, String profileName) {
85 95 return DaoUtil.getData(deviceProfileRepository.findByTenantIdAndName(tenantId.getId(), profileName));
86 96 }
... ...