From 15be6d60a314d2dbaf9ad09dd4ed05a55349d364 Mon Sep 17 00:00:00 2001 From: zbeacon Date: Tue, 22 Sep 2020 08:36:08 +0300 Subject: [PATCH] Added strategy checking --- .../device/DeviceProvisionServiceImpl.java | 97 +++++++++++-------- .../device/provision/ProvisionRequest.java | 4 +- .../server/common/data/DataConstants.java | 7 +- .../data/ProvisionDeviceConfiguration.java | 57 +++++++++++ .../ProvisionDeviceProfileConfiguration.java | 2 + .../transport/mqtt/MqttTransportHandler.java | 19 +++- .../server/dao/device/DeviceDao.java | 2 + .../dao/sql/device/DeviceRepository.java | 10 ++ .../server/dao/sql/device/JpaDeviceDao.java | 7 ++ 9 files changed, 157 insertions(+), 48 deletions(-) create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/device/data/ProvisionDeviceConfiguration.java diff --git a/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java b/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java index e7a64c046b..739af86c13 100644 --- a/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java +++ b/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java @@ -15,7 +15,6 @@ */ package org.thingsboard.server.service.device; -import com.datastax.oss.driver.api.core.uuid.Uuids; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.node.ObjectNode; import com.google.common.util.concurrent.Futures; @@ -30,9 +29,11 @@ import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceProfileType; import org.thingsboard.server.common.data.audit.ActionType; +import org.thingsboard.server.common.data.device.data.ProvisionDeviceConfiguration; import org.thingsboard.server.common.data.device.profile.ProvisionDeviceProfileConfiguration; +import org.thingsboard.server.common.data.device.profile.ProvisionRequestValidationStrategyType; +import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.kv.AttributeKvEntry; import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry; import org.thingsboard.server.common.data.kv.StringDataEntry; @@ -45,6 +46,7 @@ import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; import org.thingsboard.server.dao.attributes.AttributesService; import org.thingsboard.server.dao.audit.AuditLogService; import org.thingsboard.server.dao.device.DeviceCredentialsService; +import org.thingsboard.server.dao.device.DeviceDao; import org.thingsboard.server.dao.device.DeviceProfileDao; import org.thingsboard.server.dao.device.DeviceProvisionService; import org.thingsboard.server.dao.device.DeviceService; @@ -65,8 +67,6 @@ import java.util.List; import java.util.Optional; import java.util.concurrent.locks.ReentrantLock; -import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID; - @Service @Slf4j @@ -77,10 +77,11 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { private static final String DEVICE_PROVISION_STATE = "provisionState"; private static final String PROVISIONED_STATE = "provisioned"; - private static final UserId PROVISION_USER_ID = UserId.fromString(NULL_UUID.toString()); - private final ReentrantLock deviceCreationLock = new ReentrantLock(); + @Autowired + DeviceDao deviceDao; + @Autowired DeviceProfileDao deviceProfileDao; @@ -105,39 +106,52 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { @Override public ListenableFuture provisionDevice(ProvisionRequest provisionRequest) { - DeviceProfile targetProfile = deviceProfileDao.findProfileByTenantIdAndProfileDataProvisionConfigurationPair( + Device targetDevice = deviceDao.findDeviceByTenantIdAndDeviceDataProvisionConfigurationPair( TenantId.SYS_TENANT_ID, provisionRequest.getCredentials().getProvisionDeviceKey(), provisionRequest.getCredentials().getProvisionDeviceSecret()); - if (targetProfile.getProfileData().getConfiguration().getType() != DeviceProfileType.PROVISION) { - return Futures.immediateFuture(new ProvisionResponse(null, ProvisionResponseStatus.NOT_FOUND)); - } + if (targetDevice != null) { + if (targetDevice.getDeviceData().getConfiguration().getType() != DeviceProfileType.PROVISION) { + return Futures.immediateFuture(new ProvisionResponse(null, ProvisionResponseStatus.NOT_FOUND)); + } - ProvisionDeviceProfileConfiguration currentProfileConfiguration = (ProvisionDeviceProfileConfiguration) targetProfile.getProfileData().getConfiguration(); - if (!new ProvisionDeviceProfileConfiguration(provisionRequest.getCredentials().getProvisionDeviceKey(), provisionRequest.getCredentials().getProvisionDeviceSecret()).equals(currentProfileConfiguration)) { - return Futures.immediateFuture(new ProvisionResponse(null, ProvisionResponseStatus.NOT_FOUND)); - } + DeviceProfile targetProfile = deviceProfileDao.findById(TenantId.SYS_TENANT_ID, targetDevice.getDeviceProfileId().getId()); - Device device = deviceService.findDeviceByTenantIdAndName(targetProfile.getTenantId(), provisionRequest.getDeviceName()); - switch (currentProfileConfiguration.getStrategy()) { - case CHECK_NEW_DEVICE: - if (device == null) { - return createDevice(provisionRequest, targetProfile); - } else { - log.warn("[{}] The device is present and could not be provisioned once more!", device.getName()); - notify(device, provisionRequest, DataConstants.PROVISION_FAILURE, false); + ProvisionDeviceConfiguration currentProfileConfiguration = (ProvisionDeviceConfiguration) targetDevice.getDeviceData().getConfiguration(); + if (!new ProvisionDeviceConfiguration(provisionRequest.getCredentials().getProvisionDeviceKey(), provisionRequest.getCredentials().getProvisionDeviceSecret()).equals(currentProfileConfiguration)) { + return Futures.immediateFuture(new ProvisionResponse(null, ProvisionResponseStatus.NOT_FOUND)); + } + ProvisionRequestValidationStrategyType targetStrategy = getStrategy(targetProfile); + switch (targetStrategy) { + case CHECK_NEW_DEVICE: + log.warn("[{}] The device is present and could not be provisioned once more!", targetDevice.getName()); + notify(targetDevice, provisionRequest, DataConstants.PROVISION_FAILURE, false); return Futures.immediateFuture(new ProvisionResponse(null, ProvisionResponseStatus.FAILURE)); - } - case CHECK_PRE_PROVISIONED_DEVICE: - if (device == null) { + case CHECK_PRE_PROVISIONED_DEVICE: + return processProvision(targetDevice, provisionRequest); + default: + throw new RuntimeException("Strategy is not supported - " + targetStrategy.name()); + } + } else { + DeviceProfile targetProfile = deviceProfileDao.findProfileByTenantIdAndProfileDataProvisionConfigurationPair( + TenantId.SYS_TENANT_ID, + provisionRequest.getCredentials().getProvisionDeviceKey(), + provisionRequest.getCredentials().getProvisionDeviceSecret() + ); + if (targetProfile.getProfileData().getConfiguration().getType() != DeviceProfileType.PROVISION) { + return Futures.immediateFuture(new ProvisionResponse(null, ProvisionResponseStatus.NOT_FOUND)); + } + ProvisionRequestValidationStrategyType targetStrategy = getStrategy(targetProfile); + switch (targetStrategy) { + case CHECK_NEW_DEVICE: + return createDevice(provisionRequest, targetProfile); + case CHECK_PRE_PROVISIONED_DEVICE: log.warn("[{}] Failed to find pre provisioned device!", provisionRequest.getDeviceName()); return Futures.immediateFuture(new ProvisionResponse(null, ProvisionResponseStatus.FAILURE)); - } else { - return processProvision(device, provisionRequest); - } - default: - throw new RuntimeException("Strategy is not supported - " + currentProfileConfiguration.getStrategy().name()); + default: + throw new RuntimeException("Strategy is not supported - " + targetStrategy.name()); + } } } @@ -178,6 +192,16 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { } } + private void notify(Device device, ProvisionRequest provisionRequest, String type, boolean success) { + pushProvisionEventToRuleEngine(provisionRequest, device, type); + logAction(device.getTenantId(), device.getCustomerId(), device, success, provisionRequest); + } + + private ProvisionRequestValidationStrategyType getStrategy(DeviceProfile profile) { + return ((ProvisionDeviceProfileConfiguration) profile.getProfileData().getConfiguration()).getStrategy(); + + } + private ListenableFuture processCreateDevice(ProvisionRequest provisionRequest, DeviceProfile profile) { Device device = deviceService.findDeviceByTenantIdAndName(profile.getTenantId(), provisionRequest.getDeviceName()); if (device == null) { @@ -221,15 +245,10 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { return credentials; } - private void notify(Device device, ProvisionRequest provisionRequest, String type, boolean success) { - pushProvisionEventToRuleEngine(provisionRequest, device, type); - logAction(device.getTenantId(), device, success, provisionRequest); - } - private void pushProvisionEventToRuleEngine(ProvisionRequest request, Device device, String type) { try { ObjectNode entityNode = JacksonUtil.OBJECT_MAPPER.valueToTree(request); - TbMsg msg = new TbMsg(Uuids.timeBased(), type, device.getId(), createTbMsgMetaData(device), JacksonUtil.OBJECT_MAPPER.writeValueAsString(entityNode), null, null, 0L); + TbMsg msg = TbMsg.newMsg(type, device.getId(), createTbMsgMetaData(device), JacksonUtil.OBJECT_MAPPER.writeValueAsString(entityNode)); sendToRuleEngine(device.getTenantId(), msg, null); } catch (JsonProcessingException | IllegalArgumentException e) { log.warn("[{}] Failed to push device action to rule engine: {}", device.getId(), type, e); @@ -239,7 +258,7 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { private void pushDeviceCreatedEventToRuleEngine(Device device) { try { ObjectNode entityNode = JacksonUtil.OBJECT_MAPPER.valueToTree(device); - TbMsg msg = new TbMsg(Uuids.timeBased(), DataConstants.ENTITY_CREATED, device.getId(), createTbMsgMetaData(device), JacksonUtil.OBJECT_MAPPER.writeValueAsString(entityNode), null, null, 0L); + TbMsg msg = TbMsg.newMsg(DataConstants.ENTITY_CREATED, device.getId(), createTbMsgMetaData(device), JacksonUtil.OBJECT_MAPPER.writeValueAsString(entityNode)); sendToRuleEngine(device.getTenantId(), msg, null); } catch (JsonProcessingException | IllegalArgumentException e) { log.warn("[{}] Failed to push device action to rule engine: {}", device.getId(), DataConstants.ENTITY_CREATED, e); @@ -260,8 +279,8 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { return metaData; } - private void logAction(TenantId tenantId, Device device, boolean success, ProvisionRequest provisionRequest) { + private void logAction(TenantId tenantId, CustomerId customerId, Device device, boolean success, ProvisionRequest provisionRequest) { ActionType actionType = success ? ActionType.PROVISION_SUCCESS : ActionType.PROVISION_FAILURE; - auditLogService.logEntityAction(tenantId, null, null, device.getName(), device.getId(), device, actionType, null, provisionRequest); + auditLogService.logEntityAction(tenantId, customerId, null, device.getName(), device.getId(), device, actionType, null, provisionRequest); } } diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/provision/ProvisionRequest.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/provision/ProvisionRequest.java index 45eb7f5998..3889cd5ab6 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/provision/ProvisionRequest.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/provision/ProvisionRequest.java @@ -17,7 +17,7 @@ package org.thingsboard.server.dao.device.provision; import lombok.AllArgsConstructor; import lombok.Data; -import org.thingsboard.server.common.data.device.profile.ProvisionDeviceProfileConfiguration; +import org.thingsboard.server.common.data.device.data.ProvisionDeviceConfiguration; @Data @AllArgsConstructor @@ -25,5 +25,5 @@ public class ProvisionRequest { private String deviceName; private String deviceType; private String x509CertPubKey; - private ProvisionDeviceProfileConfiguration credentials; + private ProvisionDeviceConfiguration credentials; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java b/common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java index bd9f4d26b0..d2c4aaf60b 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java @@ -72,11 +72,12 @@ public class DataConstants { public static final String SECRET_KEY_FIELD_NAME = "secretKey"; public static final String DURATION_MS_FIELD_NAME = "durationMs"; + public static final String PROVISION = "provision"; + public static final String PROVISION_KEY = "provisionDeviceKey"; + public static final String PROVISION_SECRET = "provisionDeviceSecret"; + public static final String DEVICE_NAME = "deviceName"; public static final String DEVICE_TYPE = "deviceType"; public static final String CERT_PUB_KEY = "x509CertPubKey"; - public static final String PROVISION_KEY = "provisionDeviceKey"; - public static final String PROVISION_SECRET = "provisionDeviceSecret"; - } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/data/ProvisionDeviceConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/data/ProvisionDeviceConfiguration.java new file mode 100644 index 0000000000..a4930d367d --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/data/ProvisionDeviceConfiguration.java @@ -0,0 +1,57 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.device.data; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import org.thingsboard.server.common.data.DeviceProfileType; +import org.thingsboard.server.common.data.device.profile.DeviceProfileConfiguration; +import org.thingsboard.server.common.data.device.profile.ProvisionRequestValidationStrategyType; + +import java.util.Objects; + +@Data +public class ProvisionDeviceConfiguration implements DeviceConfiguration { + + private String provisionDeviceKey; + private String provisionDeviceSecret; + + @Override + public DeviceProfileType getType() { + return DeviceProfileType.PROVISION; + } + + @JsonCreator + public ProvisionDeviceConfiguration(@JsonProperty("provisionDeviceKey") String provisionProfileKey, @JsonProperty("provisionDeviceSecret") String provisionProfileSecret) { + this.provisionDeviceKey = provisionProfileKey; + this.provisionDeviceSecret = provisionProfileSecret; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ProvisionDeviceConfiguration that = (ProvisionDeviceConfiguration) o; + return provisionDeviceKey.equals(that.provisionDeviceKey) && + provisionDeviceSecret.equals(that.provisionDeviceSecret); + } + + @Override + public int hashCode() { + return Objects.hash(provisionDeviceKey, provisionDeviceSecret); + } +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/ProvisionDeviceProfileConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/ProvisionDeviceProfileConfiguration.java index ee20a51628..44a66f6714 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/ProvisionDeviceProfileConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/ProvisionDeviceProfileConfiguration.java @@ -19,6 +19,8 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Data; import org.thingsboard.server.common.data.DeviceProfileType; +import org.thingsboard.server.common.data.device.credentials.BasicMqttCredentials; +import org.thingsboard.server.common.data.device.data.ProvisionDeviceConfiguration; import java.util.Objects; diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java index be913992e8..85206cf1d1 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java @@ -34,10 +34,12 @@ import io.netty.handler.codec.mqtt.MqttSubscribeMessage; import io.netty.handler.codec.mqtt.MqttTopicSubscription; import io.netty.handler.codec.mqtt.MqttUnsubscribeMessage; import io.netty.handler.ssl.SslHandler; +import io.netty.util.CharsetUtil; import io.netty.util.ReferenceCountUtil; import io.netty.util.concurrent.Future; import io.netty.util.concurrent.GenericFutureListener; import lombok.extern.slf4j.Slf4j; +import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.device.profile.MqttTopics; @@ -51,6 +53,7 @@ import org.thingsboard.server.common.transport.auth.TransportDeviceInfo; import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; import org.thingsboard.server.common.transport.service.DefaultTransportService; import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.gen.transport.TransportProtos.DeviceInfoProto; import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceResponseMsg; import org.thingsboard.server.gen.transport.TransportProtos.SessionEvent; import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceX509CertRequestMsg; @@ -420,11 +423,19 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement private void processConnect(ChannelHandlerContext ctx, MqttConnectMessage msg) { log.info("[{}] Processing connect msg for client: {}!", sessionId, msg.payload().clientIdentifier()); - X509Certificate cert; - if (sslHandler != null && (cert = getX509Certificate()) != null) { - processX509CertConnect(ctx, cert); + String userName = msg.payload().userName(); + if (DataConstants.PROVISION.equals(userName)) { + deviceSessionCtx.setDeviceInfo(new TransportDeviceInfo()); + deviceSessionCtx.setProvisionOnly(true); + ctx.writeAndFlush(createMqttConnAckMsg(CONNECTION_ACCEPTED)); } else { - processAuthTokenConnect(ctx, msg); + X509Certificate cert; + + if (sslHandler != null && (cert = getX509Certificate()) != null) { + processX509CertConnect(ctx, cert); + } else { + processAuthTokenConnect(ctx, msg); + } } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceDao.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceDao.java index bbc1735c9f..7cf392c556 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceDao.java @@ -215,4 +215,6 @@ public interface DeviceDao extends Dao { */ PageData findDevicesByTenantIdAndProfileId(UUID tenantId, UUID profileId, PageLink pageLink); + Device findDeviceByTenantIdAndDeviceDataProvisionConfigurationPair(TenantId tenantId, String provisionDeviceKey, String provisionDeviceSecret); + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceRepository.java index 02f62d3358..aec705e519 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceRepository.java @@ -20,8 +20,10 @@ import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.PagingAndSortingRepository; import org.springframework.data.repository.query.Param; +import org.thingsboard.server.common.data.DeviceProfileInfo; import org.thingsboard.server.dao.model.sql.DeviceEntity; import org.thingsboard.server.dao.model.sql.DeviceInfoEntity; +import org.thingsboard.server.dao.model.sql.DeviceProfileEntity; import java.util.List; import java.util.UUID; @@ -169,4 +171,12 @@ public interface DeviceRepository extends PagingAndSortingRepository>('configuration', 'provisionDeviceKey') = :provisionDeviceKey " + + "AND d.device_data::jsonb->>('configuration', 'provisionDeviceSecret') = :provisionDeviceSecret", + nativeQuery = true) + DeviceEntity findDeviceByTenantIdAndDeviceDataProvisionConfigurationPair(@Param("tenantId") UUID tenantId, + @Param("provisionDeviceKey") String provisionDeviceKey, + @Param("provisionDeviceSecret") String provisionDeviceSecret); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceDao.java index b3eeea6360..6372e23e16 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceDao.java @@ -22,6 +22,8 @@ import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceInfo; +import org.thingsboard.server.common.data.DeviceProfile; +import org.thingsboard.server.common.data.DeviceProfileInfo; import org.thingsboard.server.common.data.EntitySubtype; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.id.TenantId; @@ -219,6 +221,11 @@ public class JpaDeviceDao extends JpaAbstractSearchTextDao return deviceRepository.countByDeviceProfileId(deviceProfileId); } + @Override + public Device findDeviceByTenantIdAndDeviceDataProvisionConfigurationPair(TenantId tenantId, String provisionDeviceKey, String provisionDeviceSecret) { + return DaoUtil.getData(deviceRepository.findDeviceByTenantIdAndDeviceDataProvisionConfigurationPair(tenantId.getId(), provisionDeviceKey, provisionDeviceSecret)); + } + private List convertTenantDeviceTypesToDto(UUID tenantId, List types) { List list = Collections.emptyList(); if (types != null && !types.isEmpty()) {