Added strategy checking

This commit is contained in:
zbeacon 2020-09-22 08:36:08 +03:00
parent d228208072
commit 15be6d60a3
9 changed files with 157 additions and 48 deletions

View File

@ -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<ProvisionResponse> 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<ProvisionResponse> 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);
}
}

View File

@ -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;
}

View File

@ -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";
}

View File

@ -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);
}
}

View File

@ -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;

View File

@ -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);
}
}
}

View File

@ -215,4 +215,6 @@ public interface DeviceDao extends Dao<Device> {
*/
PageData<Device> findDevicesByTenantIdAndProfileId(UUID tenantId, UUID profileId, PageLink pageLink);
Device findDeviceByTenantIdAndDeviceDataProvisionConfigurationPair(TenantId tenantId, String provisionDeviceKey, String provisionDeviceSecret);
}

View File

@ -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<DeviceEntit
Long countByDeviceProfileId(UUID deviceProfileId);
@Query(value = "SELECT d FROM Device d " +
"WHERE d.tenant_id = :tenantId " +
"AND d.device_data::jsonb->>('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);
}

View File

@ -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<DeviceEntity, Device>
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<EntitySubtype> convertTenantDeviceTypesToDto(UUID tenantId, List<String> types) {
List<EntitySubtype> list = Collections.emptyList();
if (types != null && !types.isEmpty()) {