From d228208072eca2ee1d5610280651618c136d50b8 Mon Sep 17 00:00:00 2001 From: zbeacon Date: Fri, 18 Sep 2020 16:11:49 +0300 Subject: [PATCH 01/48] Provision init --- .../server/controller/BaseController.java | 6 + .../device/DeviceProvisionServiceImpl.java | 267 ++++++++++++++++++ .../dao/device/DeviceProvisionService.java | 24 ++ .../device/provision/ProvisionRequest.java | 29 ++ .../device/provision/ProvisionResponse.java | 27 ++ .../provision/ProvisionResponseStatus.java | 24 ++ .../server/common/data/DataConstants.java | 9 + .../server/common/data/DeviceProfileType.java | 3 +- .../server/common/data/audit/ActionType.java | 4 +- .../profile/DeviceProfileConfiguration.java | 3 +- .../data/device/profile/MqttTopics.java | 4 + .../ProvisionDeviceProfileConfiguration.java | 57 ++++ .../ProvisionRequestValidationStrategy.java | 23 ++ ...rovisionRequestValidationStrategyType.java | 20 ++ common/queue/src/main/proto/queue.proto | 39 +++ .../transport/mqtt/MqttTransportHandler.java | 79 +++++- .../mqtt/adaptors/JsonMqttAdaptor.java | 22 ++ .../mqtt/adaptors/MqttTransportAdaptor.java | 8 + .../mqtt/session/DeviceSessionCtx.java | 5 + .../common/transport/TransportService.java | 4 +- .../transport/adaptor/JsonConverter.java | 70 +++++ .../service/DefaultTransportService.java | 9 + .../server/dao/audit/AuditLogServiceImpl.java | 8 + .../server/dao/device/DeviceProfileDao.java | 4 + .../device/DeviceProvisionServiceImpl.java | 19 ++ .../sql/device/DeviceProfileRepository.java | 20 ++ .../dao/sql/device/JpaDeviceProfileDao.java | 10 + 27 files changed, 786 insertions(+), 11 deletions(-) create mode 100644 application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java create mode 100644 common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceProvisionService.java create mode 100644 common/dao-api/src/main/java/org/thingsboard/server/dao/device/provision/ProvisionRequest.java create mode 100644 common/dao-api/src/main/java/org/thingsboard/server/dao/device/provision/ProvisionResponse.java create mode 100644 common/dao-api/src/main/java/org/thingsboard/server/dao/device/provision/ProvisionResponseStatus.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/device/profile/ProvisionDeviceProfileConfiguration.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/device/profile/ProvisionRequestValidationStrategy.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/device/profile/ProvisionRequestValidationStrategyType.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/device/DeviceProvisionServiceImpl.java diff --git a/application/src/main/java/org/thingsboard/server/controller/BaseController.java b/application/src/main/java/org/thingsboard/server/controller/BaseController.java index 8434f67d1d..7b68b18bd6 100644 --- a/application/src/main/java/org/thingsboard/server/controller/BaseController.java +++ b/application/src/main/java/org/thingsboard/server/controller/BaseController.java @@ -706,6 +706,12 @@ public abstract class BaseController { case ASSIGNED_TO_TENANT: msgType = DataConstants.ENTITY_ASSIGNED_TO_TENANT; break; + case PROVISION_SUCCESS: + msgType = DataConstants.PROVISION_SUCCESS; + break; + case PROVISION_FAILURE: + msgType = DataConstants.PROVISION_FAILURE; + break; } if (!StringUtils.isEmpty(msgType)) { try { 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 new file mode 100644 index 0000000000..e7a64c046b --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java @@ -0,0 +1,267 @@ +/** + * 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.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; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.MoreExecutors; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; +import org.thingsboard.server.common.data.DataConstants; +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.profile.ProvisionDeviceProfileConfiguration; +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; +import org.thingsboard.server.common.data.security.DeviceCredentials; +import org.thingsboard.server.common.data.security.DeviceCredentialsType; +import org.thingsboard.server.common.msg.TbMsg; +import org.thingsboard.server.common.msg.TbMsgMetaData; +import org.thingsboard.server.common.msg.queue.ServiceType; +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.DeviceProfileDao; +import org.thingsboard.server.dao.device.DeviceProvisionService; +import org.thingsboard.server.dao.device.DeviceService; +import org.thingsboard.server.dao.device.provision.ProvisionRequest; +import org.thingsboard.server.dao.device.provision.ProvisionResponse; +import org.thingsboard.server.dao.device.provision.ProvisionResponseStatus; +import org.thingsboard.server.dao.util.mapping.JacksonUtil; +import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg; +import org.thingsboard.server.queue.TbQueueCallback; +import org.thingsboard.server.queue.TbQueueProducer; +import org.thingsboard.server.queue.common.TbProtoQueueMsg; +import org.thingsboard.server.queue.discovery.PartitionService; +import org.thingsboard.server.service.state.DeviceStateService; + +import java.util.Collections; +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 +public class DeviceProvisionServiceImpl implements DeviceProvisionService { + + protected TbQueueProducer> ruleEngineMsgProducer; + + 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 + DeviceProfileDao deviceProfileDao; + + @Autowired + DeviceService deviceService; + + @Autowired + DeviceCredentialsService deviceCredentialsService; + + @Autowired + AttributesService attributesService; + + @Autowired + DeviceStateService deviceStateService; + + @Autowired + AuditLogService auditLogService; + + @Autowired + PartitionService partitionService; + + + @Override + public ListenableFuture provisionDevice(ProvisionRequest provisionRequest) { + 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)); + } + + 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)); + } + + 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); + return Futures.immediateFuture(new ProvisionResponse(null, ProvisionResponseStatus.FAILURE)); + } + case CHECK_PRE_PROVISIONED_DEVICE: + if (device == null) { + 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()); + } + } + + private ListenableFuture processProvision(Device device, ProvisionRequest provisionRequest) { + ListenableFuture> provisionStateFuture = attributesService.find(device.getTenantId(), device.getId(), + DataConstants.SERVER_SCOPE, DEVICE_PROVISION_STATE); + ListenableFuture provisionedFuture = Futures.transformAsync(provisionStateFuture, optionalAtr -> { + if (optionalAtr.isPresent()) { + String state = optionalAtr.get().getValueAsString(); + if (state.equals(PROVISIONED_STATE)) { + return Futures.immediateFuture(true); + } else { + log.error("[{}][{}] Unknown provision state: {}!", device.getName(), DEVICE_PROVISION_STATE, state); + return Futures.immediateCancelledFuture(); + } + } + return Futures.transform(saveProvisionStateAttribute(device), input -> false, MoreExecutors.directExecutor()); + }, MoreExecutors.directExecutor()); + if (provisionedFuture.isCancelled()) { + throw new RuntimeException("Unknown provision state!"); + } + return Futures.transform(provisionedFuture, provisioned -> { + if (provisioned) { + notify(device, provisionRequest, DataConstants.PROVISION_FAILURE, false); + return new ProvisionResponse(null, ProvisionResponseStatus.FAILURE); + } + notify(device, provisionRequest, DataConstants.PROVISION_SUCCESS, true); + return new ProvisionResponse(deviceCredentialsService.findDeviceCredentialsByDeviceId(device.getTenantId(), device.getId()), ProvisionResponseStatus.SUCCESS); + }, MoreExecutors.directExecutor()); + } + + private ListenableFuture createDevice(ProvisionRequest provisionRequest, DeviceProfile profile) { + deviceCreationLock.lock(); + try { + return processCreateDevice(provisionRequest, profile); + } finally { + deviceCreationLock.unlock(); + } + } + + private ListenableFuture processCreateDevice(ProvisionRequest provisionRequest, DeviceProfile profile) { + Device device = deviceService.findDeviceByTenantIdAndName(profile.getTenantId(), provisionRequest.getDeviceName()); + if (device == null) { + Device savedDevice = saveDevice(provisionRequest, profile); + + deviceStateService.onDeviceAdded(savedDevice); + pushDeviceCreatedEventToRuleEngine(savedDevice); + notify(savedDevice, provisionRequest, DataConstants.PROVISION_SUCCESS, true); + + return Futures.transform(saveProvisionStateAttribute(savedDevice), input -> + new ProvisionResponse( + getDeviceCredentials(savedDevice, provisionRequest.getX509CertPubKey()), + ProvisionResponseStatus.SUCCESS), MoreExecutors.directExecutor()); + } + log.warn("[{}] The device is already provisioned!", device.getName()); + notify(device, provisionRequest, DataConstants.PROVISION_FAILURE, false); + return Futures.immediateFuture(new ProvisionResponse(null, ProvisionResponseStatus.FAILURE)); + } + + private ListenableFuture> saveProvisionStateAttribute(Device device) { + return attributesService.save(device.getTenantId(), device.getId(), DataConstants.SERVER_SCOPE, + Collections.singletonList(new BaseAttributeKvEntry(new StringDataEntry(DEVICE_PROVISION_STATE, PROVISIONED_STATE), + System.currentTimeMillis()))); + } + + private Device saveDevice(ProvisionRequest provisionRequest, DeviceProfile profile) { + Device device = new Device(); + device.setName(provisionRequest.getDeviceName()); + device.setType(provisionRequest.getDeviceType()); + device.setTenantId(profile.getTenantId()); + return deviceService.saveDevice(device); + } + + private DeviceCredentials getDeviceCredentials(Device device, String x509CertPubKey) { + DeviceCredentials credentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(device.getTenantId(), device.getId()); + if (!StringUtils.isEmpty(x509CertPubKey)) { + credentials.setCredentialsType(DeviceCredentialsType.X509_CERTIFICATE); + credentials.setCredentialsValue(x509CertPubKey); + return deviceCredentialsService.updateDeviceCredentials(device.getTenantId(), credentials); + } + 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); + sendToRuleEngine(device.getTenantId(), msg, null); + } catch (JsonProcessingException | IllegalArgumentException e) { + log.warn("[{}] Failed to push device action to rule engine: {}", device.getId(), type, e); + } + } + + 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); + 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); + } + } + + protected void sendToRuleEngine(TenantId tenantId, TbMsg tbMsg, TbQueueCallback callback) { + TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_RULE_ENGINE, tenantId, tbMsg.getOriginator()); + TransportProtos.ToRuleEngineMsg msg = TransportProtos.ToRuleEngineMsg.newBuilder().setTbMsg(TbMsg.toByteString(tbMsg)) + .setTenantIdMSB(tenantId.getId().getMostSignificantBits()) + .setTenantIdLSB(tenantId.getId().getLeastSignificantBits()).build(); + ruleEngineMsgProducer.send(tpi, new TbProtoQueueMsg<>(tbMsg.getId(), msg), callback); + } + + private TbMsgMetaData createTbMsgMetaData(Device device) { + TbMsgMetaData metaData = new TbMsgMetaData(); + metaData.putValue("tenantId", device.getTenantId().toString()); + return metaData; + } + + private void logAction(TenantId tenantId, 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); + } +} diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceProvisionService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceProvisionService.java new file mode 100644 index 0000000000..28456fb4fe --- /dev/null +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceProvisionService.java @@ -0,0 +1,24 @@ +/** + * 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.dao.device; + +import com.google.common.util.concurrent.ListenableFuture; +import org.thingsboard.server.dao.device.provision.ProvisionRequest; +import org.thingsboard.server.dao.device.provision.ProvisionResponse; + +public interface DeviceProvisionService { + ListenableFuture provisionDevice(ProvisionRequest 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 new file mode 100644 index 0000000000..45eb7f5998 --- /dev/null +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/provision/ProvisionRequest.java @@ -0,0 +1,29 @@ +/** + * 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.dao.device.provision; + +import lombok.AllArgsConstructor; +import lombok.Data; +import org.thingsboard.server.common.data.device.profile.ProvisionDeviceProfileConfiguration; + +@Data +@AllArgsConstructor +public class ProvisionRequest { + private String deviceName; + private String deviceType; + private String x509CertPubKey; + private ProvisionDeviceProfileConfiguration credentials; +} diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/provision/ProvisionResponse.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/provision/ProvisionResponse.java new file mode 100644 index 0000000000..509823eeb2 --- /dev/null +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/provision/ProvisionResponse.java @@ -0,0 +1,27 @@ +/** + * 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.dao.device.provision; + +import lombok.AllArgsConstructor; +import lombok.Data; +import org.thingsboard.server.common.data.security.DeviceCredentials; + +@Data +@AllArgsConstructor +public class ProvisionResponse { + private DeviceCredentials deviceCredentials; + private ProvisionResponseStatus responseStatus; +} diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/provision/ProvisionResponseStatus.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/provision/ProvisionResponseStatus.java new file mode 100644 index 0000000000..3c993b0e14 --- /dev/null +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/provision/ProvisionResponseStatus.java @@ -0,0 +1,24 @@ +/** + * 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.dao.device.provision; + +public enum ProvisionResponseStatus { + + SUCCESS, + NOT_FOUND, + FAILURE + +} 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 8dc492093c..bd9f4d26b0 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 @@ -63,6 +63,8 @@ public class DataConstants { public static final String ALARM_CLEAR = "ALARM_CLEAR"; public static final String ENTITY_ASSIGNED_FROM_TENANT = "ENTITY_ASSIGNED_FROM_TENANT"; public static final String ENTITY_ASSIGNED_TO_TENANT = "ENTITY_ASSIGNED_TO_TENANT"; + public static final String PROVISION_SUCCESS = "PROVISION_SUCCESS"; + public static final String PROVISION_FAILURE = "PROVISION_FAILURE"; public static final String RPC_CALL_FROM_SERVER_TO_DEVICE = "RPC_CALL_FROM_SERVER_TO_DEVICE"; @@ -70,4 +72,11 @@ 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 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/DeviceProfileType.java b/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfileType.java index 93ca102082..218a5aa5b4 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfileType.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfileType.java @@ -16,5 +16,6 @@ package org.thingsboard.server.common.data; public enum DeviceProfileType { - DEFAULT + DEFAULT, + PROVISION } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/audit/ActionType.java b/common/data/src/main/java/org/thingsboard/server/common/data/audit/ActionType.java index 14a38c810c..e30e20090d 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/audit/ActionType.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/audit/ActionType.java @@ -42,7 +42,9 @@ public enum ActionType { LOGOUT(false), LOCKOUT(false), ASSIGNED_FROM_TENANT(false), - ASSIGNED_TO_TENANT(false); + ASSIGNED_TO_TENANT(false), + PROVISION_SUCCESS(false), + PROVISION_FAILURE(false); private final boolean isRead; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileConfiguration.java index 3bb3d29c34..aca6d391da 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileConfiguration.java @@ -27,7 +27,8 @@ import org.thingsboard.server.common.data.DeviceProfileType; include = JsonTypeInfo.As.PROPERTY, property = "type") @JsonSubTypes({ - @JsonSubTypes.Type(value = DefaultDeviceProfileConfiguration.class, name = "DEFAULT")}) + @JsonSubTypes.Type(value = DefaultDeviceProfileConfiguration.class, name = "DEFAULT"), + @JsonSubTypes.Type(value = ProvisionDeviceProfileConfiguration.class, name = "PROVISION")}) public interface DeviceProfileConfiguration { @JsonIgnore diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttTopics.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttTopics.java index 7aa8ddf63c..10e73af309 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttTopics.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttTopics.java @@ -31,6 +31,8 @@ public class MqttTopics { public static final String DEVICE_TELEMETRY_TOPIC = BASE_DEVICE_API_TOPIC + "/telemetry"; public static final String DEVICE_CLAIM_TOPIC = BASE_DEVICE_API_TOPIC + "/claim"; public static final String DEVICE_ATTRIBUTES_TOPIC = BASE_DEVICE_API_TOPIC + "/attributes"; + public static final String DEVICE_PROVISION_REQUEST_TOPIC = BASE_DEVICE_API_TOPIC + "/provision"; + public static final String DEVICE_PROVISION_RESPONSE_TOPIC = BASE_DEVICE_API_TOPIC + "/provision/response"; public static final String BASE_GATEWAY_API_TOPIC = "v1/gateway"; public static final String GATEWAY_CONNECT_TOPIC = BASE_GATEWAY_API_TOPIC + "/connect"; @@ -41,6 +43,8 @@ public class MqttTopics { public static final String GATEWAY_RPC_TOPIC = BASE_GATEWAY_API_TOPIC + "/rpc"; public static final String GATEWAY_ATTRIBUTES_REQUEST_TOPIC = BASE_GATEWAY_API_TOPIC + "/attributes/request"; public static final String GATEWAY_ATTRIBUTES_RESPONSE_TOPIC = BASE_GATEWAY_API_TOPIC + "/attributes/response"; + public static final String GATEWAY_PROVISION_REQUEST_TOPIC = BASE_GATEWAY_API_TOPIC + "/provision"; + public static final String GATEWAY_PROVISION_RESPONSE_TOPIC = BASE_GATEWAY_API_TOPIC + "/provision/response"; private MqttTopics() { 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 new file mode 100644 index 0000000000..ee20a51628 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/ProvisionDeviceProfileConfiguration.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.profile; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import org.thingsboard.server.common.data.DeviceProfileType; + +import java.util.Objects; + +@Data +public class ProvisionDeviceProfileConfiguration implements DeviceProfileConfiguration { + + private String provisionDeviceKey; + private String provisionDeviceSecret; + + private ProvisionRequestValidationStrategyType strategy; + + @Override + public DeviceProfileType getType() { + return DeviceProfileType.PROVISION; + } + + @JsonCreator + public ProvisionDeviceProfileConfiguration(@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; + ProvisionDeviceProfileConfiguration that = (ProvisionDeviceProfileConfiguration) 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/ProvisionRequestValidationStrategy.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/ProvisionRequestValidationStrategy.java new file mode 100644 index 0000000000..759940b763 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/ProvisionRequestValidationStrategy.java @@ -0,0 +1,23 @@ +/** + * 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.profile; + +import lombok.Data; + +@Data +public class ProvisionRequestValidationStrategy { + private final ProvisionRequestValidationStrategyType validationStrategyType; +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/ProvisionRequestValidationStrategyType.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/ProvisionRequestValidationStrategyType.java new file mode 100644 index 0000000000..9a4005612a --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/ProvisionRequestValidationStrategyType.java @@ -0,0 +1,20 @@ +/** + * 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.profile; + +public enum ProvisionRequestValidationStrategyType { + CHECK_NEW_DEVICE, CHECK_PRE_PROVISIONED_DEVICE +} diff --git a/common/queue/src/main/proto/queue.proto b/common/queue/src/main/proto/queue.proto index 331c17b9d7..4caf51a7b4 100644 --- a/common/queue/src/main/proto/queue.proto +++ b/common/queue/src/main/proto/queue.proto @@ -73,6 +73,11 @@ enum KeyValueType { JSON_V = 4; } +enum CredentialsType { + ACCESS_TOKEN = 0; + X509_CERTIFICATE = 1; +} + message KeyValueProto { string key = 1; KeyValueType type = 2; @@ -241,6 +246,36 @@ message ClaimDeviceMsg { int64 durationMs = 4; } +message DeviceCredentialsProto { + int64 deviceIdMSB = 1; + int64 deviceIdLSB = 2; + CredentialsType credentialsType = 3; + string credentialsId = 4; + string credentialsValue = 5; +} + +message ProvisionDeviceRequestMsg { + string deviceName = 1; + string deviceType = 2; + string x509CertPubKey = 3; + ProvisionDeviceCredentialsMsg provisionDeviceCredentialsMsg = 4; +} + +message ProvisionDeviceCredentialsMsg { + string provisionDeviceKey = 1; + string provisionDeviceSecret = 2; +} + +message ProvisionDeviceResponseMsg { + DeviceCredentialsProto deviceCredentials = 1; + ProvisionResponseStatus provisionResponseStatus = 2; +} + +enum ProvisionResponseStatus { + SUCCESS = 0; + NOT_FOUND = 1; + FAILURE = 2; +} //Used to report session state to tb-Service and persist this state in the cache on the tb-Service level. message SubscriptionInfoProto { int64 lastActivityTime = 1; @@ -266,6 +301,7 @@ message TransportToDeviceActorMsg { ToDeviceRpcResponseMsg toDeviceRPCCallResponse = 6; SubscriptionInfoProto subscriptionInfo = 7; ClaimDeviceMsg claimDevice = 8; + ProvisionDeviceRequestMsg provisionDevice = 9; } message TransportToRuleEngineMsg { @@ -441,6 +477,7 @@ message TransportApiRequestMsg { GetTenantRoutingInfoRequestMsg getTenantRoutingInfoRequestMsg = 4; GetDeviceProfileRequestMsg getDeviceProfileRequestMsg = 5; ValidateBasicMqttCredRequestMsg validateBasicMqttCredRequestMsg = 6; +// ProvisionDeviceRequestMsg provisionDeviceRequestMsg = 7; } /* Response from ThingsBoard Core Service to Transport Service */ @@ -449,6 +486,7 @@ message TransportApiResponseMsg { GetOrCreateDeviceFromGatewayResponseMsg getOrCreateDeviceResponseMsg = 2; GetTenantRoutingInfoResponseMsg getTenantRoutingInfoResponseMsg = 4; GetDeviceProfileResponseMsg getDeviceProfileResponseMsg = 5; +// ProvisionDeviceResponseMsg provisionDeviceResponseMsg = 6; } /* Messages that are handled by ThingsBoard Core Service */ @@ -491,4 +529,5 @@ message ToTransportMsg { ToServerRpcResponseMsg toServerResponse = 7; DeviceProfileUpdateMsg deviceProfileUpdateMsg = 8; DeviceProfileDeleteMsg deviceProfileDeleteMsg = 9; + ProvisionDeviceResponseMsg provisionResponse = 10; } 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 866a4f7627..be913992e8 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 @@ -38,7 +38,6 @@ import io.netty.util.ReferenceCountUtil; import io.netty.util.concurrent.Future; import io.netty.util.concurrent.GenericFutureListener; import lombok.extern.slf4j.Slf4j; -import org.springframework.util.StringUtils; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.device.profile.MqttTopics; @@ -52,9 +51,8 @@ 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.ProvisionDeviceResponseMsg; import org.thingsboard.server.gen.transport.TransportProtos.SessionEvent; -import org.thingsboard.server.gen.transport.TransportProtos.SessionInfoProto; -import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceTokenRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceX509CertRequestMsg; import org.thingsboard.server.transport.mqtt.adaptors.MqttTransportAdaptor; import org.thingsboard.server.transport.mqtt.session.DeviceSessionCtx; @@ -66,7 +64,6 @@ import javax.net.ssl.SSLPeerUnverifiedException; import javax.security.cert.X509Certificate; import java.io.IOException; import java.net.InetSocketAddress; -import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; import java.util.UUID; @@ -74,9 +71,9 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import static io.netty.handler.codec.mqtt.MqttConnectReturnCode.CONNECTION_ACCEPTED; -import static io.netty.handler.codec.mqtt.MqttConnectReturnCode.CONNECTION_REFUSED_BAD_USER_NAME_OR_PASSWORD; import static io.netty.handler.codec.mqtt.MqttConnectReturnCode.CONNECTION_REFUSED_NOT_AUTHORIZED; import static io.netty.handler.codec.mqtt.MqttMessageType.CONNACK; +import static io.netty.handler.codec.mqtt.MqttMessageType.CONNECT; import static io.netty.handler.codec.mqtt.MqttMessageType.PINGRESP; import static io.netty.handler.codec.mqtt.MqttMessageType.PUBACK; import static io.netty.handler.codec.mqtt.MqttMessageType.SUBACK; @@ -136,10 +133,45 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement return; } deviceSessionCtx.setChannel(ctx); + if (CONNECT.equals(msg.fixedHeader().messageType())) { + processConnect(ctx, (MqttConnectMessage) msg); + } else if (deviceSessionCtx.isProvisionOnly()) { + processProvisionSessionMsg(ctx, msg); + } else { + processRegularSessionMsg(ctx, msg); + } + } + + private void processProvisionSessionMsg(ChannelHandlerContext ctx, MqttMessage msg) { switch (msg.fixedHeader().messageType()) { - case CONNECT: - processConnect(ctx, (MqttConnectMessage) msg); + case PUBLISH: + MqttPublishMessage mqttMsg = (MqttPublishMessage) msg; + String topicName = mqttMsg.variableHeader().topicName(); + int msgId = mqttMsg.variableHeader().packetId(); + try { + if (topicName.equals(MqttTopics.DEVICE_PROVISION_REQUEST_TOPIC)) { + TransportProtos.ProvisionDeviceRequestMsg provisionRequestMsg = adaptor.convertToProvisionRequestMsg(deviceSessionCtx, mqttMsg); + transportService.process(deviceSessionCtx.getSessionInfo(), provisionRequestMsg, (TransportServiceCallback) new DeviceProvisionCallback(ctx, msgId, provisionRequestMsg)); + log.trace("[{}][{}] Processing publish msg [{}][{}]!", sessionId, deviceSessionCtx.getDeviceId(), topicName, msgId); + } else { + throw new RuntimeException("Unsupported topic for provisioning requests!"); + } + } catch (RuntimeException | AdaptorException e) { + log.warn("[{}] Failed to process publish msg [{}][{}]", sessionId, topicName, msgId, e); + ctx.close(); + } break; + case PINGREQ: + ctx.writeAndFlush(new MqttMessage(new MqttFixedHeader(PINGRESP, false, AT_MOST_ONCE, false, 0))); + break; + case DISCONNECT: + ctx.close(); + break; + } + } + + private void processRegularSessionMsg(ChannelHandlerContext ctx, MqttMessage msg) { + switch (msg.fixedHeader().messageType()) { case PUBLISH: processPublish(ctx, (MqttPublishMessage) msg); break; @@ -261,6 +293,37 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement }; } + private class DeviceProvisionCallback implements TransportServiceCallback { + private final ChannelHandlerContext ctx; + private final int msgId; + private final TransportProtos.ProvisionDeviceRequestMsg msg; + + DeviceProvisionCallback(ChannelHandlerContext ctx, int msgId, TransportProtos.ProvisionDeviceRequestMsg msg) { + this.ctx = ctx; + this.msgId = msgId; + this.msg = msg; + } + + @Override + public void onSuccess(TransportProtos.ProvisionDeviceResponseMsg provisionResponseMsg) { + log.trace("[{}] Published msg: {}", sessionId, msg); + if (msgId > 0) { + ctx.writeAndFlush(createMqttPubAckMsg(msgId)); + } + try { + adaptor.convertToPublish(deviceSessionCtx, provisionResponseMsg).ifPresent(deviceSessionCtx.getChannel()::writeAndFlush); + } catch (Exception e) { + log.trace("[{}] Failed to convert device attributes response to MQTT msg", sessionId, e); + } + } + + @Override + public void onError(Throwable e) { + log.trace("[{}] Failed to publish msg: {}", sessionId, msg, e); + processDisconnect(ctx); + } + } + private void processSubscribe(ChannelHandlerContext ctx, MqttSubscribeMessage mqttMsg) { if (!checkConnected(ctx, mqttMsg)) { return; @@ -290,6 +353,8 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement case MqttTopics.GATEWAY_RPC_TOPIC: case MqttTopics.GATEWAY_ATTRIBUTES_RESPONSE_TOPIC: case MqttTopics.DEVICE_ATTRIBUTES_RESPONSES_TOPIC: + case MqttTopics.GATEWAY_PROVISION_RESPONSE_TOPIC: + case MqttTopics.DEVICE_PROVISION_RESPONSE_TOPIC: registerSubQoS(topic, grantedQoSList, reqQoS); break; default: diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/JsonMqttAdaptor.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/JsonMqttAdaptor.java index 7ba6fbeea6..50e36a1c4c 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/JsonMqttAdaptor.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/JsonMqttAdaptor.java @@ -184,6 +184,28 @@ public class JsonMqttAdaptor implements MqttTransportAdaptor { return Optional.of(createMqttPublishMsg(ctx, MqttTopics.DEVICE_RPC_RESPONSE_TOPIC + rpcResponse.getRequestId(), JsonConverter.toJson(rpcResponse))); } + @Override + public Optional convertToPublish(MqttDeviceAwareSessionContext ctx, TransportProtos.ProvisionDeviceResponseMsg provisionResponse) { + return Optional.of(createMqttPublishMsg(ctx, MqttTopics.DEVICE_PROVISION_RESPONSE_TOPIC, JsonConverter.toJson(provisionResponse))); + } + + @Override + public TransportProtos.ProvisionDeviceRequestMsg convertToProvisionRequestMsg(MqttDeviceAwareSessionContext ctx, MqttPublishMessage inbound) throws AdaptorException { + String payload = validatePayload(ctx.getSessionId(), inbound.payload(), false); + try { + return JsonConverter.convertToProvisionRequestMsg(payload); + } catch (IllegalStateException | JsonSyntaxException ex) { + throw new AdaptorException(ex); + } + } + + @Override + public Optional convertToGatewayPublish(MqttDeviceAwareSessionContext ctx, TransportProtos.ProvisionDeviceResponseMsg responseMsg, int requestId) { + return Optional.of(createMqttPublishMsg(ctx, + MqttTopics.GATEWAY_PROVISION_REQUEST_TOPIC, + JsonConverter.toJson(responseMsg, requestId))); + } + private MqttPublishMessage createMqttPublishMsg(MqttDeviceAwareSessionContext ctx, String topic, JsonElement json) { MqttFixedHeader mqttFixedHeader = new MqttFixedHeader(MqttMessageType.PUBLISH, false, ctx.getQoSForTopic(topic), false, 0); diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/MqttTransportAdaptor.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/MqttTransportAdaptor.java index d4d36320f3..84a22ca198 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/MqttTransportAdaptor.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/MqttTransportAdaptor.java @@ -24,6 +24,8 @@ import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeRequestM import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeResponseMsg; import org.thingsboard.server.gen.transport.TransportProtos.PostAttributeMsg; import org.thingsboard.server.gen.transport.TransportProtos.PostTelemetryMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceRequestMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceResponseMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToDeviceRpcRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToDeviceRpcResponseMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToServerRpcRequestMsg; @@ -63,4 +65,10 @@ public interface MqttTransportAdaptor { Optional convertToPublish(MqttDeviceAwareSessionContext ctx, ToServerRpcResponseMsg rpcResponse) throws AdaptorException; + ProvisionDeviceRequestMsg convertToProvisionRequestMsg(MqttDeviceAwareSessionContext ctx, MqttPublishMessage inbound) throws AdaptorException; + + Optional convertToPublish(MqttDeviceAwareSessionContext ctx, ProvisionDeviceResponseMsg provisionResponse) throws AdaptorException; + + Optional convertToGatewayPublish(MqttDeviceAwareSessionContext ctx, ProvisionDeviceResponseMsg provisionResponse, int requestId) throws AdaptorException; + } diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/DeviceSessionCtx.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/DeviceSessionCtx.java index 3f5e0dc7ad..3faad734d0 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/DeviceSessionCtx.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/DeviceSessionCtx.java @@ -17,6 +17,7 @@ package org.thingsboard.server.transport.mqtt.session; import io.netty.channel.ChannelHandlerContext; import lombok.Getter; +import lombok.Setter; import lombok.extern.slf4j.Slf4j; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceTransportType; @@ -40,6 +41,10 @@ public class DeviceSessionCtx extends MqttDeviceAwareSessionContext { private ChannelHandlerContext channel; private final AtomicInteger msgIdSeq = new AtomicInteger(0); + @Getter + @Setter + private boolean provisionOnly; + private volatile MqttTopicFilter telemetryTopicFilter = MqttTopicFilterFactory.getDefaultTelemetryFilter(); private volatile MqttTopicFilter attributesTopicFilter = MqttTopicFilterFactory.getDefaultAttributesFilter(); diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java index 3fc8ed96d1..04da376ff3 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java @@ -27,6 +27,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.GetTenantRoutingInfo import org.thingsboard.server.gen.transport.TransportProtos.GetTenantRoutingInfoResponseMsg; import org.thingsboard.server.gen.transport.TransportProtos.PostAttributeMsg; import org.thingsboard.server.gen.transport.TransportProtos.PostTelemetryMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.SessionEventMsg; import org.thingsboard.server.gen.transport.TransportProtos.SessionInfoProto; import org.thingsboard.server.gen.transport.TransportProtos.SubscribeToAttributeUpdatesMsg; @@ -83,6 +84,8 @@ public interface TransportService { void process(SessionInfoProto sessionInfo, ClaimDeviceMsg msg, TransportServiceCallback callback); + void process(SessionInfoProto sessionInfo, ProvisionDeviceRequestMsg msg, TransportServiceCallback deviceProvisionCallback); + void registerAsyncSession(SessionInfoProto sessionInfo, SessionMsgListener listener); void registerSyncSession(SessionInfoProto sessionInfo, SessionMsgListener listener, long timeout); @@ -90,5 +93,4 @@ public interface TransportService { void reportActivity(SessionInfoProto sessionInfo); void deregisterSession(SessionInfoProto sessionInfo); - } diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java index 428dfb0912..c63c4d7bb8 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java @@ -42,6 +42,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.KeyValueProto; import org.thingsboard.server.gen.transport.TransportProtos.KeyValueType; import org.thingsboard.server.gen.transport.TransportProtos.PostAttributeMsg; import org.thingsboard.server.gen.transport.TransportProtos.PostTelemetryMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceResponseMsg; import org.thingsboard.server.gen.transport.TransportProtos.TsKvListProto; import org.thingsboard.server.gen.transport.TransportProtos.TsKvProto; @@ -53,6 +54,7 @@ import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.TreeMap; +import java.util.UUID; import java.util.function.Consumer; import java.util.stream.Collectors; @@ -397,6 +399,34 @@ public class JsonConverter { } } + public static JsonObject toJson(ProvisionDeviceResponseMsg payload) { + return toJson(payload, false, 0); + } + + public static JsonObject toJson(ProvisionDeviceResponseMsg payload, int requestId) { + return toJson(payload, true, requestId); + } + + private static JsonObject toJson(ProvisionDeviceResponseMsg payload, boolean toGateway, int requestId) { + JsonObject result = new JsonObject(); + if (payload.getProvisionResponseStatus() == TransportProtos.ProvisionResponseStatus.NOT_FOUND) { + result.addProperty("errorMsg", "Provision data was not found!"); + } else if (payload.getProvisionResponseStatus() == TransportProtos.ProvisionResponseStatus.FAILURE) { + result.addProperty("errorMsg", "Failed to provision device!"); + } else { + if (toGateway) { + result.addProperty("id", requestId); + } + result.addProperty("deviceId", new DeviceId( + new UUID(payload.getDeviceCredentials().getDeviceIdMSB(), payload.getDeviceCredentials().getDeviceIdLSB())).toString()); + result.addProperty("credentialsType", payload.getDeviceCredentials().getCredentialsType().name()); + result.addProperty("credentialsId", payload.getDeviceCredentials().getCredentialsId()); + result.addProperty("credentialsValue", + StringUtils.isEmpty(payload.getDeviceCredentials().getCredentialsValue()) ? null : payload.getDeviceCredentials().getCredentialsValue()); + } + return result; + } + public static JsonElement toErrorJson(String errorMsg) { JsonObject error = new JsonObject(); error.addProperty("error", errorMsg); @@ -498,4 +528,44 @@ public class JsonConverter { maxStringValueLength = length; } + public static TransportProtos.ProvisionDeviceRequestMsg convertToProvisionRequestMsg(String json) { + JsonElement jsonElement = new JsonParser().parse(json); + if (jsonElement.isJsonObject()) { + return buildProvisionRequestMsg(jsonElement.getAsJsonObject()); + } else { + throw new JsonSyntaxException(CAN_T_PARSE_VALUE + jsonElement); + } + } + + public static TransportProtos.ProvisionDeviceRequestMsg convertToProvisionRequestMsg(JsonObject jo) { + return buildProvisionRequestMsg(jo); + } + + private static TransportProtos.ProvisionDeviceRequestMsg buildProvisionRequestMsg(JsonObject jo) { + return TransportProtos.ProvisionDeviceRequestMsg.newBuilder() + .setDeviceName(getStrValue(jo, DataConstants.DEVICE_NAME, true)) + .setDeviceType(getStrValue(jo, DataConstants.DEVICE_TYPE, true)) + .setX509CertPubKey(getStrValue(jo, DataConstants.CERT_PUB_KEY, false)) + .setProvisionDeviceCredentialsMsg(buildProvisionDeviceCredentialsMsg( + getStrValue(jo, DataConstants.PROVISION_KEY, true), + getStrValue(jo, DataConstants.PROVISION_SECRET, true))) + .build(); + } + + private static TransportProtos.ProvisionDeviceCredentialsMsg buildProvisionDeviceCredentialsMsg(String provisionKey, String provisionSecret) { + return TransportProtos.ProvisionDeviceCredentialsMsg.newBuilder() + .setProvisionDeviceKey(provisionKey) + .setProvisionDeviceSecret(provisionSecret) + .build(); + } + private static String getStrValue(JsonObject jo, String field, boolean requiredField) { + if (jo.has(field)) { + return jo.get(field).getAsString(); + } else { + if (requiredField) { + throw new RuntimeException("Failed to find the field " + field + " in JSON body " + jo + "!"); + } + return ""; + } + } } diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java index 72727e39de..35bfd00cb0 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java @@ -484,6 +484,15 @@ public class DefaultTransportService implements TransportService { } } + @Override + public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.ProvisionDeviceRequestMsg msg, TransportServiceCallback callback) { + if (checkLimits(sessionInfo, msg, callback)) { + reportActivityInternal(sessionInfo); + sendToDeviceActor(sessionInfo, TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo) + .setProvisionDevice(msg).build(), callback); + } + } + @Override public void reportActivity(TransportProtos.SessionInfoProto sessionInfo) { reportActivityInternal(sessionInfo); diff --git a/dao/src/main/java/org/thingsboard/server/dao/audit/AuditLogServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/audit/AuditLogServiceImpl.java index 467e957f74..6b3f1650a2 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/audit/AuditLogServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/audit/AuditLogServiceImpl.java @@ -47,6 +47,7 @@ import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.dao.audit.sink.AuditLogSink; import org.thingsboard.server.dao.entity.EntityService; import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.dao.device.provision.ProvisionRequest; import org.thingsboard.server.dao.service.DataValidator; import java.io.PrintWriter; @@ -257,6 +258,13 @@ public class AuditLogServiceImpl implements AuditLogService { actionData.put("os", os); actionData.put("device", device); break; + case PROVISION_SUCCESS: + case PROVISION_FAILURE: + ProvisionRequest request = extractParameter(ProvisionRequest.class, additionalInfo); + if (request != null) { + actionData.set("provisionRequest", objectMapper.valueToTree(request)); + } + break; } return actionData; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileDao.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileDao.java index 267aff358e..5b118180d8 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileDao.java @@ -38,5 +38,9 @@ public interface DeviceProfileDao extends Dao { DeviceProfileInfo findDefaultDeviceProfileInfo(TenantId tenantId); + DeviceProfileInfo findProfileInfoByTenantIdAndProfileDataProvisionConfigurationPair(TenantId tenantId, String provisionDeviceKey, String provisionDeviceSecret); + + DeviceProfile findProfileByTenantIdAndProfileDataProvisionConfigurationPair(TenantId tenantId, String provisionDeviceKey, String provisionDeviceSecret); + DeviceProfile findByName(TenantId tenantId, String profileName); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProvisionServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProvisionServiceImpl.java new file mode 100644 index 0000000000..e9e1fcd182 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProvisionServiceImpl.java @@ -0,0 +1,19 @@ +/** + * 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.dao.device; + +public class DeviceProvisionServiceImpl { +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceProfileRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceProfileRepository.java index 8116b711d5..97736cdb46 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceProfileRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceProfileRepository.java @@ -22,6 +22,7 @@ import org.springframework.data.repository.PagingAndSortingRepository; import org.springframework.data.repository.query.Param; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceProfileInfo; +import org.thingsboard.server.common.data.DeviceProfileType; import org.thingsboard.server.dao.model.sql.DeviceProfileEntity; import java.util.UUID; @@ -57,4 +58,23 @@ public interface DeviceProfileRepository extends PagingAndSortingRepository>{'configuration', 'provisionDeviceKey'} = :provisionDeviceKey " + + "AND d.profileData::jsonb->>{'configuration', 'provisionDeviceSecret' = :provisionDeviceSecret}", + nativeQuery = true) + DeviceProfileEntity findProfileByTenantIdAndProfileDataProvisionConfigurationPair(@Param("tenantId") UUID tenantId, + @Param("provisionDeviceKey") String provisionDeviceKey, + @Param("provisionDeviceSecret") String provisionDeviceSecret); + + @Query(value = "SELECT new org.thingsboard.server.common.data.DeviceProfileInfo(d.id, d.name, d.type, d.transportType) " + + " FROM DeviceProfileEntity d " + + "WHERE d.tenantId = :tenantId " + + "AND d.profileData::jsonb->>{'configuration', 'provisionDeviceKey'} = :provisionDeviceKey " + + "AND d.profileData::jsonb->>{'configuration', 'provisionDeviceSecret' = :provisionDeviceSecret}", + nativeQuery = true) + DeviceProfileInfo findProfileInfoByTenantIdAndProfileDataProvisionConfigurationPair(@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/JpaDeviceProfileDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceProfileDao.java index 9399d27304..b21f8a0ee6 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceProfileDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceProfileDao.java @@ -80,6 +80,16 @@ public class JpaDeviceProfileDao extends JpaAbstractSearchTextDao Date: Tue, 22 Sep 2020 08:36:08 +0300 Subject: [PATCH 02/48] 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()) { From 8a8695f260e0b98615c711a4c0d6b77819a2c26a Mon Sep 17 00:00:00 2001 From: zbeacon Date: Wed, 23 Sep 2020 14:24:44 +0300 Subject: [PATCH 03/48] Working version for provision feature with provision credentials on device level --- .../device/DeviceProvisionServiceImpl.java | 121 +++++++++++------- .../transport/DefaultTransportApiService.java | 58 ++++++++- .../src/main/resources/thingsboard.yml | 2 + .../data/device/data/DeviceConfiguration.java | 3 +- .../common/msg/session/FeatureType.java | 2 +- common/queue/src/main/proto/queue.proto | 4 +- .../transport/coap/CoapTransportResource.java | 39 ++++++ .../coap/adaptors/CoapTransportAdaptor.java | 3 + .../coap/adaptors/JsonCoapAdaptor.java | 10 ++ .../transport/http/DeviceApiController.java | 28 ++++ .../transport/mqtt/MqttTransportHandler.java | 7 +- .../common/transport/TransportService.java | 6 +- .../service/DefaultTransportService.java | 21 +-- .../server/dao/device/DeviceDao.java | 2 +- .../server/dao/device/DeviceProfileDao.java | 5 +- .../sql/device/DeviceProfileRepository.java | 29 ++--- .../dao/sql/device/DeviceRepository.java | 16 +-- .../server/dao/sql/device/JpaDeviceDao.java | 6 +- .../dao/sql/device/JpaDeviceProfileDao.java | 10 +- 19 files changed, 267 insertions(+), 105 deletions(-) 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 739af86c13..2a32e86f11 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 @@ -34,6 +34,7 @@ import org.thingsboard.server.common.data.device.profile.ProvisionDeviceProfileC 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; @@ -60,6 +61,7 @@ import org.thingsboard.server.queue.TbQueueCallback; import org.thingsboard.server.queue.TbQueueProducer; import org.thingsboard.server.queue.common.TbProtoQueueMsg; import org.thingsboard.server.queue.discovery.PartitionService; +import org.thingsboard.server.queue.provider.TbQueueProducerProvider; import org.thingsboard.server.service.state.DeviceStateService; import java.util.Collections; @@ -103,55 +105,88 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { @Autowired PartitionService partitionService; + public DeviceProvisionServiceImpl(TbQueueProducerProvider producerProvider) { + ruleEngineMsgProducer = producerProvider.getRuleEngineMsgProducer(); + } @Override public ListenableFuture provisionDevice(ProvisionRequest provisionRequest) { - Device targetDevice = deviceDao.findDeviceByTenantIdAndDeviceDataProvisionConfigurationPair( - TenantId.SYS_TENANT_ID, - provisionRequest.getCredentials().getProvisionDeviceKey(), - provisionRequest.getCredentials().getProvisionDeviceSecret()); + String provisionRequestKey = provisionRequest.getCredentials().getProvisionDeviceKey(); + String provisionRequestSecret = provisionRequest.getCredentials().getProvisionDeviceSecret(); - if (targetDevice != null) { - if (targetDevice.getDeviceData().getConfiguration().getType() != DeviceProfileType.PROVISION) { - return Futures.immediateFuture(new ProvisionResponse(null, ProvisionResponseStatus.NOT_FOUND)); - } + if (StringUtils.isEmpty(provisionRequestKey) || StringUtils.isEmpty(provisionRequestSecret)) { + return Futures.immediateFuture(new ProvisionResponse(null, ProvisionResponseStatus.NOT_FOUND)); + } - DeviceProfile targetProfile = deviceProfileDao.findById(TenantId.SYS_TENANT_ID, targetDevice.getDeviceProfileId().getId()); + Device targetDevice = deviceDao.findDeviceByProfileNameAndDeviceDataProvisionConfigurationPair( + provisionRequest.getDeviceType(), + provisionRequestKey, + provisionRequestSecret + ).orElse(null); - 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: - return processProvision(targetDevice, provisionRequest); - default: - throw new RuntimeException("Strategy is not supported - " + targetStrategy.name()); - } + if (targetDevice != null) { + return processProvisionDeviceWithKeySecretPairExists(provisionRequest, provisionRequestKey, provisionRequestSecret, targetDevice); } 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)); - default: - throw new RuntimeException("Strategy is not supported - " + targetStrategy.name()); - } + return processProvisionDeviceWithKeySecretPairNotExists(provisionRequest, provisionRequestKey, provisionRequestSecret); + } + } + + private ListenableFuture processProvisionDeviceWithKeySecretPairExists(ProvisionRequest provisionRequest, String provisionRequestKey, String provisionRequestSecret, Device targetDevice) { + if (targetDevice.getDeviceData().getConfiguration().getType() != DeviceProfileType.PROVISION) { + return Futures.immediateFuture(new ProvisionResponse(null, ProvisionResponseStatus.NOT_FOUND)); + } + + DeviceProfile targetProfile = deviceProfileDao.findById(targetDevice.getTenantId(), targetDevice.getDeviceProfileId().getId()); + + if (targetProfile == null || targetProfile.getProfileData().getConfiguration().getType() != DeviceProfileType.PROVISION) { + return Futures.immediateFuture(new ProvisionResponse(null, ProvisionResponseStatus.NOT_FOUND)); + } + + ProvisionDeviceConfiguration currentDeviceConfiguration = (ProvisionDeviceConfiguration) targetDevice.getDeviceData().getConfiguration(); + + if (!new ProvisionDeviceConfiguration(provisionRequestKey, provisionRequestSecret).equals(currentDeviceConfiguration)) { + 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: + return processProvision(targetDevice, provisionRequest); + default: + throw new RuntimeException("Strategy is not supported - " + targetStrategy.name()); + } + } + + private ListenableFuture processProvisionDeviceWithKeySecretPairNotExists(ProvisionRequest provisionRequest, String provisionRequestKey, String provisionRequestSecret){ + DeviceProfile targetProfile = deviceProfileDao.findProfileByProfileNameAndProfileDataProvisionConfigurationPair( + provisionRequest.getDeviceType(), + provisionRequestKey, + provisionRequestSecret + ); + + if (targetProfile == null || 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: + ProvisionDeviceProfileConfiguration currentDeviceProfileConfiguration = (ProvisionDeviceProfileConfiguration) targetProfile.getProfileData().getConfiguration(); + if(new ProvisionDeviceProfileConfiguration(provisionRequestKey, provisionRequestSecret).equals(currentDeviceProfileConfiguration)) { + Optional optionalDevice = deviceDao.findDeviceByTenantIdAndName(targetProfile.getTenantId().getId(), provisionRequest.getDeviceName()); + if (optionalDevice.isPresent()) { + return processProvision(optionalDevice.get(), provisionRequest); + } + } + log.warn("[{}] Failed to find pre provisioned device!", provisionRequest.getDeviceName()); + return Futures.immediateFuture(new ProvisionResponse(null, ProvisionResponseStatus.FAILURE)); + default: + throw new RuntimeException("Strategy is not supported - " + targetStrategy.name()); } } @@ -281,6 +316,6 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { 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, customerId, null, device.getName(), device.getId(), device, actionType, null, provisionRequest); + auditLogService.logEntityAction(tenantId, customerId, new UserId(UserId.NULL_UUID), device.getName(), device.getId(), device, actionType, null, provisionRequest); } } diff --git a/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java b/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java index 16c7c894da..fb3ba8a4b4 100644 --- a/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java +++ b/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java @@ -22,6 +22,7 @@ import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.MoreExecutors; import com.google.protobuf.ByteString; +import com.google.protobuf.InvalidProtocolBufferException; import lombok.extern.slf4j.Slf4j; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.stereotype.Service; @@ -31,6 +32,7 @@ import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.device.credentials.BasicMqttCredentials; +import org.thingsboard.server.common.data.device.data.ProvisionDeviceConfiguration; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceProfileId; @@ -45,17 +47,24 @@ import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.common.transport.util.DataDecodingEncodingService; import org.thingsboard.server.dao.device.DeviceCredentialsService; import org.thingsboard.server.dao.device.DeviceProfileService; +import org.thingsboard.server.dao.device.DeviceProvisionService; import org.thingsboard.server.dao.device.DeviceService; +import org.thingsboard.server.dao.device.provision.ProvisionRequest; +import org.thingsboard.server.dao.device.provision.ProvisionResponse; +import org.thingsboard.server.dao.device.provision.ProvisionResponseStatus; import org.thingsboard.server.dao.relation.RelationService; import org.thingsboard.server.dao.tenant.TenantProfileService; import org.thingsboard.server.dao.tenant.TenantService; import org.thingsboard.server.dao.util.mapping.JacksonUtil; import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.gen.transport.TransportProtos.CredentialsType; import org.thingsboard.server.gen.transport.TransportProtos.DeviceInfoProto; import org.thingsboard.server.gen.transport.TransportProtos.GetOrCreateDeviceFromGatewayRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.GetOrCreateDeviceFromGatewayResponseMsg; import org.thingsboard.server.gen.transport.TransportProtos.GetTenantRoutingInfoRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.GetTenantRoutingInfoResponseMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceCredentialsMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.TransportApiRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.TransportApiResponseMsg; import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceCredentialsResponseMsg; @@ -94,6 +103,7 @@ public class DefaultTransportApiService implements TransportApiService { private final DbCallbackExecutorService dbCallbackExecutorService; private final TbClusterService tbClusterService; private final DataDecodingEncodingService dataDecodingEncodingService; + private final DeviceProvisionService deviceProvisionService; private final ConcurrentMap deviceCreationLocks = new ConcurrentHashMap<>(); @@ -102,7 +112,8 @@ public class DefaultTransportApiService implements TransportApiService { TenantProfileService tenantProfileService, DeviceService deviceService, RelationService relationService, DeviceCredentialsService deviceCredentialsService, DeviceStateService deviceStateService, DbCallbackExecutorService dbCallbackExecutorService, - TbClusterService tbClusterService, DataDecodingEncodingService dataDecodingEncodingService) { + TbClusterService tbClusterService, DataDecodingEncodingService dataDecodingEncodingService, + DeviceProvisionService deviceProvisionService) { this.deviceProfileService = deviceProfileService; this.tenantService = tenantService; this.tenantProfileService = tenantProfileService; @@ -113,6 +124,7 @@ public class DefaultTransportApiService implements TransportApiService { this.dbCallbackExecutorService = dbCallbackExecutorService; this.tbClusterService = tbClusterService; this.dataDecodingEncodingService = dataDecodingEncodingService; + this.deviceProvisionService = deviceProvisionService; } @Override @@ -139,6 +151,9 @@ public class DefaultTransportApiService implements TransportApiService { } else if (transportApiRequestMsg.hasGetDeviceProfileRequestMsg()) { return Futures.transform(handle(transportApiRequestMsg.getGetDeviceProfileRequestMsg()), value -> new TbProtoQueueMsg<>(tbProtoQueueMsg.getKey(), value, tbProtoQueueMsg.getHeaders()), MoreExecutors.directExecutor()); + } else if (transportApiRequestMsg.hasProvisionDeviceRequestMsg()) { + return Futures.transform(handle(transportApiRequestMsg.getProvisionDeviceRequestMsg()), + value -> new TbProtoQueueMsg<>(tbProtoQueueMsg.getKey(), value, tbProtoQueueMsg.getHeaders()), MoreExecutors.directExecutor()); } return Futures.transform(getEmptyTransportApiResponseFuture(), value -> new TbProtoQueueMsg<>(tbProtoQueueMsg.getKey(), value, tbProtoQueueMsg.getHeaders()), MoreExecutors.directExecutor()); @@ -261,6 +276,47 @@ public class DefaultTransportApiService implements TransportApiService { }, dbCallbackExecutorService); } + + private ListenableFuture handle(ProvisionDeviceRequestMsg requestMsg) { + ListenableFuture provisionResponseFuture = null; + provisionResponseFuture = deviceProvisionService.provisionDevice( + new ProvisionRequest( + requestMsg.getDeviceName(), + requestMsg.getDeviceType(), + requestMsg.getX509CertPubKey(), + new ProvisionDeviceConfiguration( + requestMsg.getProvisionDeviceCredentialsMsg().getProvisionDeviceKey(), + requestMsg.getProvisionDeviceCredentialsMsg().getProvisionDeviceSecret()))); + return Futures.transform(provisionResponseFuture, provisionResponse -> { + if (provisionResponse.getResponseStatus() == ProvisionResponseStatus.NOT_FOUND) { + return getTransportApiResponseMsg(TransportProtos.DeviceCredentialsProto.getDefaultInstance(), TransportProtos.ProvisionResponseStatus.NOT_FOUND); + } else if (provisionResponse.getResponseStatus() == ProvisionResponseStatus.FAILURE) { + return getTransportApiResponseMsg(TransportProtos.DeviceCredentialsProto.getDefaultInstance(), TransportProtos.ProvisionResponseStatus.FAILURE); + } else { + return getTransportApiResponseMsg(getDeviceCredentials(provisionResponse.getDeviceCredentials()), TransportProtos.ProvisionResponseStatus.SUCCESS); + } + }, dbCallbackExecutorService); + } + + private TransportApiResponseMsg getTransportApiResponseMsg(TransportProtos.DeviceCredentialsProto deviceCredentials, TransportProtos.ProvisionResponseStatus status) { + return TransportApiResponseMsg.newBuilder() + .setProvisionDeviceResponseMsg(TransportProtos.ProvisionDeviceResponseMsg.newBuilder() + .setDeviceCredentials(deviceCredentials) + .setProvisionResponseStatus(status) + .build()) + .build(); + } + + private TransportProtos.DeviceCredentialsProto getDeviceCredentials(DeviceCredentials deviceCredentials) { + return TransportProtos.DeviceCredentialsProto.newBuilder() + .setDeviceIdMSB(deviceCredentials.getDeviceId().getId().getMostSignificantBits()) + .setDeviceIdLSB(deviceCredentials.getDeviceId().getId().getLeastSignificantBits()) + .setCredentialsType(deviceCredentials.getCredentialsType() == DeviceCredentialsType.ACCESS_TOKEN ? + CredentialsType.ACCESS_TOKEN : CredentialsType.X509_CERTIFICATE) + .setCredentialsId(deviceCredentials.getCredentialsId()) + .setCredentialsValue(deviceCredentials.getCredentialsValue() != null ? deviceCredentials.getCredentialsValue() : "") + .build(); + } private ListenableFuture handle(GetTenantRoutingInfoRequestMsg requestMsg) { TenantId tenantId = new TenantId(new UUID(requestMsg.getTenantIdMSB(), requestMsg.getTenantIdLSB())); // TODO: Tenant Profile from cache diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index 8e691ba48e..cab87e9680 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -473,7 +473,9 @@ spring: enabled: "true" jpa: open-in-view: "false" + show-sql: "true" hibernate: + format_sql: "true" ddl-auto: "none" database-platform: "${SPRING_JPA_DATABASE_PLATFORM:org.hibernate.dialect.PostgreSQLDialect}" datasource: diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/data/DeviceConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/data/DeviceConfiguration.java index 1ea2ee4f97..5c9a116abe 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/data/DeviceConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/data/DeviceConfiguration.java @@ -27,7 +27,8 @@ import org.thingsboard.server.common.data.DeviceProfileType; include = JsonTypeInfo.As.PROPERTY, property = "type") @JsonSubTypes({ - @JsonSubTypes.Type(value = DefaultDeviceConfiguration.class, name = "DEFAULT")}) + @JsonSubTypes.Type(value = DefaultDeviceConfiguration.class, name = "DEFAULT"), + @JsonSubTypes.Type(value = ProvisionDeviceConfiguration.class, name = "PROVISION")}) public interface DeviceConfiguration { @JsonIgnore diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/session/FeatureType.java b/common/message/src/main/java/org/thingsboard/server/common/msg/session/FeatureType.java index 9f421c0e73..58289cc94b 100644 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/session/FeatureType.java +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/session/FeatureType.java @@ -16,5 +16,5 @@ package org.thingsboard.server.common.msg.session; public enum FeatureType { - ATTRIBUTES, TELEMETRY, RPC, CLAIM + ATTRIBUTES, TELEMETRY, RPC, CLAIM, PROVISION } diff --git a/common/queue/src/main/proto/queue.proto b/common/queue/src/main/proto/queue.proto index 4caf51a7b4..007c17f3d3 100644 --- a/common/queue/src/main/proto/queue.proto +++ b/common/queue/src/main/proto/queue.proto @@ -477,7 +477,7 @@ message TransportApiRequestMsg { GetTenantRoutingInfoRequestMsg getTenantRoutingInfoRequestMsg = 4; GetDeviceProfileRequestMsg getDeviceProfileRequestMsg = 5; ValidateBasicMqttCredRequestMsg validateBasicMqttCredRequestMsg = 6; -// ProvisionDeviceRequestMsg provisionDeviceRequestMsg = 7; + ProvisionDeviceRequestMsg provisionDeviceRequestMsg = 7; } /* Response from ThingsBoard Core Service to Transport Service */ @@ -486,7 +486,7 @@ message TransportApiResponseMsg { GetOrCreateDeviceFromGatewayResponseMsg getOrCreateDeviceResponseMsg = 2; GetTenantRoutingInfoResponseMsg getTenantRoutingInfoResponseMsg = 4; GetDeviceProfileResponseMsg getDeviceProfileResponseMsg = 5; -// ProvisionDeviceResponseMsg provisionDeviceResponseMsg = 6; + ProvisionDeviceResponseMsg provisionDeviceResponseMsg = 6; } /* Messages that are handled by ThingsBoard Core Service */ diff --git a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java index b846bac38c..63af515c00 100644 --- a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java +++ b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java @@ -24,6 +24,7 @@ import org.eclipse.californium.core.network.ExchangeObserver; import org.eclipse.californium.core.server.resources.CoapExchange; import org.eclipse.californium.core.server.resources.Resource; import org.springframework.util.ReflectionUtils; +import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.security.DeviceTokenCredentials; import org.thingsboard.server.common.msg.session.FeatureType; @@ -33,9 +34,11 @@ import org.thingsboard.server.common.transport.TransportContext; import org.thingsboard.server.common.transport.TransportService; import org.thingsboard.server.common.transport.TransportServiceCallback; import org.thingsboard.server.common.transport.adaptor.AdaptorException; +import org.thingsboard.server.common.transport.adaptor.JsonConverter; import org.thingsboard.server.common.transport.auth.SessionInfoCreator; import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceResponseMsg; import java.lang.reflect.Field; import java.util.List; @@ -130,10 +133,25 @@ public class CoapTransportResource extends CoapResource { case CLAIM: processRequest(exchange, SessionMsgType.CLAIM_REQUEST); break; + case PROVISION: + processProvision(exchange); + break; } } } + private void processProvision(CoapExchange exchange) { + log.trace("Processing {}", exchange.advanced().getRequest()); + exchange.accept(); + try { + transportService.process(transportContext.getAdaptor().convertToProvisionRequestMsg(UUID.randomUUID(), exchange.advanced().getRequest()), + new DeviceProvisionCallback(exchange)); + } catch (AdaptorException e) { + log.trace("Failed to decode message: ", e); + exchange.respond(ResponseCode.BAD_REQUEST); + } + } + private void processRequest(CoapExchange exchange, SessionMsgType type) { log.trace("Processing {}", exchange.advanced().getRequest()); exchange.accept(); @@ -274,6 +292,8 @@ public class CoapTransportResource extends CoapResource { try { if (uriPath.size() >= FEATURE_TYPE_POSITION) { return Optional.of(FeatureType.valueOf(uriPath.get(FEATURE_TYPE_POSITION - 1).toUpperCase())); + } else if (uriPath.size() == 3 && uriPath.contains(DataConstants.PROVISION)) { + return Optional.of(FeatureType.valueOf(DataConstants.PROVISION.toUpperCase())); } } catch (RuntimeException e) { log.warn("Failed to decode feature type: {}", uriPath); @@ -325,6 +345,25 @@ public class CoapTransportResource extends CoapResource { } } + private static class DeviceProvisionCallback implements TransportServiceCallback { + private final CoapExchange exchange; + + DeviceProvisionCallback(CoapExchange exchange) { + this.exchange = exchange; + } + + @Override + public void onSuccess(TransportProtos.ProvisionDeviceResponseMsg msg) { + exchange.respond(JsonConverter.toJson(msg).toString()); + } + + @Override + public void onError(Throwable e) { + log.warn("Failed to process request", e); + exchange.respond(ResponseCode.INTERNAL_SERVER_ERROR); + } + } + private static class CoapOkCallback implements TransportServiceCallback { private final CoapExchange exchange; diff --git a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/adaptors/CoapTransportAdaptor.java b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/adaptors/CoapTransportAdaptor.java index 82c0b80547..fd010a7141 100644 --- a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/adaptors/CoapTransportAdaptor.java +++ b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/adaptors/CoapTransportAdaptor.java @@ -19,6 +19,7 @@ import org.eclipse.californium.core.coap.Request; import org.eclipse.californium.core.coap.Response; import org.thingsboard.server.common.transport.adaptor.AdaptorException; import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceRequestMsg; import org.thingsboard.server.transport.coap.CoapTransportResource; import java.util.UUID; @@ -45,4 +46,6 @@ public interface CoapTransportAdaptor { Response convertToPublish(CoapTransportResource.CoapSessionListener coapSessionListener, TransportProtos.ToServerRpcResponseMsg msg) throws AdaptorException; + ProvisionDeviceRequestMsg convertToProvisionRequestMsg(UUID sessionId, Request inbound) throws AdaptorException; + } diff --git a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/adaptors/JsonCoapAdaptor.java b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/adaptors/JsonCoapAdaptor.java index 5c9c471570..28292f6c20 100644 --- a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/adaptors/JsonCoapAdaptor.java +++ b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/adaptors/JsonCoapAdaptor.java @@ -123,6 +123,16 @@ public class JsonCoapAdaptor implements CoapTransportAdaptor { return response; } + @Override + public TransportProtos.ProvisionDeviceRequestMsg convertToProvisionRequestMsg(UUID sessionId, Request inbound) throws AdaptorException { + String payload = validatePayload(sessionId, inbound, false); + try { + return JsonConverter.convertToProvisionRequestMsg(payload); + } catch (IllegalStateException | JsonSyntaxException ex) { + throw new AdaptorException(ex); + } + } + @Override public Response convertToPublish(CoapTransportResource.CoapSessionListener session, TransportProtos.GetAttributeResponseMsg msg) throws AdaptorException { if (msg.getClientAttributeListCount() == 0 && msg.getSharedAttributeListCount() == 0) { diff --git a/common/transport/http/src/main/java/org/thingsboard/server/transport/http/DeviceApiController.java b/common/transport/http/src/main/java/org/thingsboard/server/transport/http/DeviceApiController.java index 404024b202..cdc2791839 100644 --- a/common/transport/http/src/main/java/org/thingsboard/server/transport/http/DeviceApiController.java +++ b/common/transport/http/src/main/java/org/thingsboard/server/transport/http/DeviceApiController.java @@ -44,6 +44,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.AttributeUpdateNotif import org.thingsboard.server.gen.transport.TransportProtos.DeviceInfoProto; import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeResponseMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceResponseMsg; import org.thingsboard.server.gen.transport.TransportProtos.SessionCloseNotificationProto; import org.thingsboard.server.gen.transport.TransportProtos.SessionInfoProto; import org.thingsboard.server.gen.transport.TransportProtos.SubscribeToAttributeUpdatesMsg; @@ -203,6 +204,14 @@ public class DeviceApiController { return responseWriter; } + @RequestMapping(value = "/provision", method = RequestMethod.POST) + public DeferredResult provisionDevice(@RequestBody String json, HttpServletRequest httpRequest) { + DeferredResult responseWriter = new DeferredResult<>(); + transportContext.getTransportService().process(JsonConverter.convertToProvisionRequestMsg(json), + new DeviceProvisionCallback(responseWriter)); + return responseWriter; + } + private static class DeviceAuthCallback implements TransportServiceCallback { private final TransportContext transportContext; private final DeferredResult responseWriter; @@ -230,6 +239,25 @@ public class DeviceApiController { } } + private static class DeviceProvisionCallback implements TransportServiceCallback { + private final DeferredResult responseWriter; + + DeviceProvisionCallback(DeferredResult responseWriter) { + this.responseWriter = responseWriter; + } + + @Override + public void onSuccess(ProvisionDeviceResponseMsg msg) { + responseWriter.setResult(new ResponseEntity<>(JsonConverter.toJson(msg).toString(), HttpStatus.OK)); + } + + @Override + public void onError(Throwable e) { + log.warn("Failed to process request", e); + responseWriter.setResult(new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR)); + } + } + private static class SessionCloseOnErrorCallback implements TransportServiceCallback { private final TransportService transportService; private final SessionInfoProto sessionInfo; 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 85206cf1d1..4fc41d6267 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 @@ -154,7 +154,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement try { if (topicName.equals(MqttTopics.DEVICE_PROVISION_REQUEST_TOPIC)) { TransportProtos.ProvisionDeviceRequestMsg provisionRequestMsg = adaptor.convertToProvisionRequestMsg(deviceSessionCtx, mqttMsg); - transportService.process(deviceSessionCtx.getSessionInfo(), provisionRequestMsg, (TransportServiceCallback) new DeviceProvisionCallback(ctx, msgId, provisionRequestMsg)); + transportService.process(provisionRequestMsg, new DeviceProvisionCallback(ctx, msgId, provisionRequestMsg)); log.trace("[{}][{}] Processing publish msg [{}][{}]!", sessionId, deviceSessionCtx.getDeviceId(), topicName, msgId); } else { throw new RuntimeException("Unsupported topic for provisioning requests!"); @@ -167,6 +167,10 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement case PINGREQ: ctx.writeAndFlush(new MqttMessage(new MqttFixedHeader(PINGRESP, false, AT_MOST_ONCE, false, 0))); break; +// case SUBSCRIBE: +// deviceSessionCtx.setDeviceInfo(TransportDeviceInfo); +// processSubscribe(ctx, (MqttSubscribeMessage) msg); +// break; case DISCONNECT: ctx.close(); break; @@ -425,7 +429,6 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement log.info("[{}] Processing connect msg for client: {}!", sessionId, msg.payload().clientIdentifier()); String userName = msg.payload().userName(); if (DataConstants.PROVISION.equals(userName)) { - deviceSessionCtx.setDeviceInfo(new TransportDeviceInfo()); deviceSessionCtx.setProvisionOnly(true); ctx.writeAndFlush(createMqttConnAckMsg(CONNECTION_ACCEPTED)); } else { diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java index 04da376ff3..995b9eca91 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java @@ -28,6 +28,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.GetTenantRoutingInfo import org.thingsboard.server.gen.transport.TransportProtos.PostAttributeMsg; import org.thingsboard.server.gen.transport.TransportProtos.PostTelemetryMsg; import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceRequestMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceResponseMsg; import org.thingsboard.server.gen.transport.TransportProtos.SessionEventMsg; import org.thingsboard.server.gen.transport.TransportProtos.SessionInfoProto; import org.thingsboard.server.gen.transport.TransportProtos.SubscribeToAttributeUpdatesMsg; @@ -58,6 +59,9 @@ public interface TransportService { void process(GetOrCreateDeviceFromGatewayRequestMsg msg, TransportServiceCallback callback); + void process(ProvisionDeviceRequestMsg msg, + TransportServiceCallback callback); + void getDeviceProfile(DeviceProfileId deviceProfileId, TransportServiceCallback callback); void onProfileUpdate(DeviceProfile deviceProfile); @@ -84,8 +88,6 @@ public interface TransportService { void process(SessionInfoProto sessionInfo, ClaimDeviceMsg msg, TransportServiceCallback callback); - void process(SessionInfoProto sessionInfo, ProvisionDeviceRequestMsg msg, TransportServiceCallback deviceProvisionCallback); - void registerAsyncSession(SessionInfoProto sessionInfo, SessionMsgListener listener); void registerSyncSession(SessionInfoProto sessionInfo, SessionMsgListener listener, long timeout); diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java index 35bfd00cb0..e9be0155a7 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java @@ -52,6 +52,8 @@ import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsRes import org.thingsboard.server.common.transport.util.DataDecodingEncodingService; import org.thingsboard.server.common.transport.util.JsonUtils; import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceRequestMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceResponseMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg; @@ -332,6 +334,16 @@ public class DefaultTransportService implements TransportService { return tdi; } + @Override + public void process(ProvisionDeviceRequestMsg requestMsg, TransportServiceCallback callback) { + log.trace("Processing msg: {}", requestMsg); + TbProtoQueueMsg protoMsg = new TbProtoQueueMsg<>(UUID.randomUUID(), TransportApiRequestMsg.newBuilder().setProvisionDeviceRequestMsg(requestMsg).build()); + ListenableFuture response = Futures.transform(transportApiRequestTemplate.send(protoMsg), tmp -> + tmp.getValue().getProvisionDeviceResponseMsg() + , MoreExecutors.directExecutor()); + AsyncCallbackTemplate.withCallback(response, callback::onSuccess, callback::onError, transportCallbackExecutor); + } + @Override public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.SubscriptionInfoProto msg, TransportServiceCallback callback) { if (log.isTraceEnabled()) { @@ -484,15 +496,6 @@ public class DefaultTransportService implements TransportService { } } - @Override - public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.ProvisionDeviceRequestMsg msg, TransportServiceCallback callback) { - if (checkLimits(sessionInfo, msg, callback)) { - reportActivityInternal(sessionInfo); - sendToDeviceActor(sessionInfo, TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo) - .setProvisionDevice(msg).build(), callback); - } - } - @Override public void reportActivity(TransportProtos.SessionInfoProto sessionInfo) { reportActivityInternal(sessionInfo); 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 7cf392c556..eb66de7009 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,6 +215,6 @@ public interface DeviceDao extends Dao { */ PageData findDevicesByTenantIdAndProfileId(UUID tenantId, UUID profileId, PageLink pageLink); - Device findDeviceByTenantIdAndDeviceDataProvisionConfigurationPair(TenantId tenantId, String provisionDeviceKey, String provisionDeviceSecret); + Optional findDeviceByProfileNameAndDeviceDataProvisionConfigurationPair(String profileName, String provisionDeviceKey, String provisionDeviceSecret); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileDao.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileDao.java index 5b118180d8..3c5a9269a1 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileDao.java @@ -22,6 +22,7 @@ import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.dao.Dao; +import java.util.Optional; import java.util.UUID; public interface DeviceProfileDao extends Dao { @@ -38,9 +39,7 @@ public interface DeviceProfileDao extends Dao { DeviceProfileInfo findDefaultDeviceProfileInfo(TenantId tenantId); - DeviceProfileInfo findProfileInfoByTenantIdAndProfileDataProvisionConfigurationPair(TenantId tenantId, String provisionDeviceKey, String provisionDeviceSecret); - - DeviceProfile findProfileByTenantIdAndProfileDataProvisionConfigurationPair(TenantId tenantId, String provisionDeviceKey, String provisionDeviceSecret); + DeviceProfile findProfileByProfileNameAndProfileDataProvisionConfigurationPair(String profileName, String provisionDeviceKey, String provisionDeviceSecret); DeviceProfile findByName(TenantId tenantId, String profileName); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceProfileRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceProfileRepository.java index 97736cdb46..d528650c10 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceProfileRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceProfileRepository.java @@ -20,9 +20,7 @@ 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.DeviceProfile; import org.thingsboard.server.common.data.DeviceProfileInfo; -import org.thingsboard.server.common.data.DeviceProfileType; import org.thingsboard.server.dao.model.sql.DeviceProfileEntity; import java.util.UUID; @@ -58,23 +56,14 @@ public interface DeviceProfileRepository extends PagingAndSortingRepository>{'configuration', 'provisionDeviceKey'} = :provisionDeviceKey " + - "AND d.profileData::jsonb->>{'configuration', 'provisionDeviceSecret' = :provisionDeviceSecret}", + @Query(value = "SELECT d.* FROM device_profile as d " + + "WHERE d.name = :profileName " + + "AND d.profile_data->'configuration'->>'provisionDeviceKey' IS NOT NULL " + + "AND d.profile_data->'configuration'->>'provisionDeviceSecret' IS NOT NULL " + + "AND d.profile_data->'configuration'->>'provisionDeviceKey' = :provisionDeviceKey " + + "AND d.profile_data->'configuration'->>'provisionDeviceSecret' = :provisionDeviceSecret", nativeQuery = true) - DeviceProfileEntity findProfileByTenantIdAndProfileDataProvisionConfigurationPair(@Param("tenantId") UUID tenantId, - @Param("provisionDeviceKey") String provisionDeviceKey, - @Param("provisionDeviceSecret") String provisionDeviceSecret); - - @Query(value = "SELECT new org.thingsboard.server.common.data.DeviceProfileInfo(d.id, d.name, d.type, d.transportType) " + - " FROM DeviceProfileEntity d " + - "WHERE d.tenantId = :tenantId " + - "AND d.profileData::jsonb->>{'configuration', 'provisionDeviceKey'} = :provisionDeviceKey " + - "AND d.profileData::jsonb->>{'configuration', 'provisionDeviceSecret' = :provisionDeviceSecret}", - nativeQuery = true) - DeviceProfileInfo findProfileInfoByTenantIdAndProfileDataProvisionConfigurationPair(@Param("tenantId") UUID tenantId, - @Param("provisionDeviceKey") String provisionDeviceKey, - @Param("provisionDeviceSecret") String provisionDeviceSecret); - + DeviceProfileEntity findProfileByProfileNameAndProfileDataProvisionConfigurationPair(@Param("profileName") String profileName, + @Param("provisionDeviceKey") String provisionDeviceKey, + @Param("provisionDeviceSecret") 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 aec705e519..8ad229850a 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,10 +20,8 @@ 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; @@ -171,12 +169,12 @@ public interface DeviceRepository extends PagingAndSortingRepository>('configuration', 'provisionDeviceKey') = :provisionDeviceKey " + - "AND d.device_data::jsonb->>('configuration', 'provisionDeviceSecret') = :provisionDeviceSecret", + @Query(value = "SELECT * FROM Device as d " + + "WHERE d.device_data->'configuration'->>'provisionDeviceKey' = :provisionDeviceKey " + + "AND d.device_data->'configuration'->>'provisionDeviceSecret' = :provisionDeviceSecret " + + "AND d.type = :profileName", nativeQuery = true) - DeviceEntity findDeviceByTenantIdAndDeviceDataProvisionConfigurationPair(@Param("tenantId") UUID tenantId, - @Param("provisionDeviceKey") String provisionDeviceKey, - @Param("provisionDeviceSecret") String provisionDeviceSecret); + DeviceEntity findDeviceByProfileNameAndDeviceDataProvisionConfigurationPair(@Param("profileName") String profileName, + @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 6372e23e16..1cbb9bc24c 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,8 +22,6 @@ 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; @@ -222,8 +220,8 @@ public class JpaDeviceDao extends JpaAbstractSearchTextDao } @Override - public Device findDeviceByTenantIdAndDeviceDataProvisionConfigurationPair(TenantId tenantId, String provisionDeviceKey, String provisionDeviceSecret) { - return DaoUtil.getData(deviceRepository.findDeviceByTenantIdAndDeviceDataProvisionConfigurationPair(tenantId.getId(), provisionDeviceKey, provisionDeviceSecret)); + public Optional findDeviceByProfileNameAndDeviceDataProvisionConfigurationPair(String profileName, String provisionDeviceKey, String provisionDeviceSecret) { + return Optional.ofNullable(DaoUtil.getData(deviceRepository.findDeviceByProfileNameAndDeviceDataProvisionConfigurationPair(profileName, provisionDeviceKey, provisionDeviceSecret))); } private List convertTenantDeviceTypesToDto(UUID tenantId, List types) { diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceProfileDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceProfileDao.java index b21f8a0ee6..6fa5768603 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceProfileDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceProfileDao.java @@ -29,6 +29,7 @@ import org.thingsboard.server.dao.model.sql.DeviceProfileEntity; import org.thingsboard.server.dao.sql.JpaAbstractSearchTextDao; import java.util.Objects; +import java.util.Optional; import java.util.UUID; @Component @@ -81,13 +82,8 @@ public class JpaDeviceProfileDao extends JpaAbstractSearchTextDao Date: Wed, 23 Sep 2020 15:19:06 +0300 Subject: [PATCH 04/48] Working version with provision data only in device profile --- .../device/DeviceProvisionServiceImpl.java | 75 +++++-------------- 1 file changed, 19 insertions(+), 56 deletions(-) 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 2a32e86f11..0f2254a805 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 @@ -31,6 +31,7 @@ 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.ProvisionRequestValidationStrategy; 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; @@ -118,75 +119,37 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { return Futures.immediateFuture(new ProvisionResponse(null, ProvisionResponseStatus.NOT_FOUND)); } - Device targetDevice = deviceDao.findDeviceByProfileNameAndDeviceDataProvisionConfigurationPair( + DeviceProfile targetProfile = deviceProfileDao.findProfileByProfileNameAndProfileDataProvisionConfigurationPair( provisionRequest.getDeviceType(), provisionRequestKey, - provisionRequestSecret - ).orElse(null); - - if (targetDevice != null) { - return processProvisionDeviceWithKeySecretPairExists(provisionRequest, provisionRequestKey, provisionRequestSecret, targetDevice); - } else { - return processProvisionDeviceWithKeySecretPairNotExists(provisionRequest, provisionRequestKey, provisionRequestSecret); - } - } - - private ListenableFuture processProvisionDeviceWithKeySecretPairExists(ProvisionRequest provisionRequest, String provisionRequestKey, String provisionRequestSecret, Device targetDevice) { - if (targetDevice.getDeviceData().getConfiguration().getType() != DeviceProfileType.PROVISION) { - return Futures.immediateFuture(new ProvisionResponse(null, ProvisionResponseStatus.NOT_FOUND)); - } - - DeviceProfile targetProfile = deviceProfileDao.findById(targetDevice.getTenantId(), targetDevice.getDeviceProfileId().getId()); + provisionRequestSecret); if (targetProfile == null || targetProfile.getProfileData().getConfiguration().getType() != DeviceProfileType.PROVISION) { return Futures.immediateFuture(new ProvisionResponse(null, ProvisionResponseStatus.NOT_FOUND)); } - ProvisionDeviceConfiguration currentDeviceConfiguration = (ProvisionDeviceConfiguration) targetDevice.getDeviceData().getConfiguration(); - - if (!new ProvisionDeviceConfiguration(provisionRequestKey, provisionRequestSecret).equals(currentDeviceConfiguration)) { - 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: - return processProvision(targetDevice, provisionRequest); - default: - throw new RuntimeException("Strategy is not supported - " + targetStrategy.name()); - } - } + ProvisionRequestValidationStrategyType validationStrategy = getStrategy(targetProfile); - private ListenableFuture processProvisionDeviceWithKeySecretPairNotExists(ProvisionRequest provisionRequest, String provisionRequestKey, String provisionRequestSecret){ - DeviceProfile targetProfile = deviceProfileDao.findProfileByProfileNameAndProfileDataProvisionConfigurationPair( - provisionRequest.getDeviceType(), - provisionRequestKey, - provisionRequestSecret - ); + Device targetDevice = deviceDao.findDeviceByTenantIdAndName(targetProfile.getTenantId().getId(), provisionRequest.getDeviceName()).orElse(null); - if (targetProfile == null || targetProfile.getProfileData().getConfiguration().getType() != DeviceProfileType.PROVISION) { - return Futures.immediateFuture(new ProvisionResponse(null, ProvisionResponseStatus.NOT_FOUND)); - } - ProvisionRequestValidationStrategyType targetStrategy = getStrategy(targetProfile); - switch (targetStrategy) { + switch(validationStrategy) { case CHECK_NEW_DEVICE: - return createDevice(provisionRequest, targetProfile); + if (targetDevice != null) { + 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)); + } else { + return createDevice(provisionRequest, targetProfile); + } case CHECK_PRE_PROVISIONED_DEVICE: - ProvisionDeviceProfileConfiguration currentDeviceProfileConfiguration = (ProvisionDeviceProfileConfiguration) targetProfile.getProfileData().getConfiguration(); - if(new ProvisionDeviceProfileConfiguration(provisionRequestKey, provisionRequestSecret).equals(currentDeviceProfileConfiguration)) { - Optional optionalDevice = deviceDao.findDeviceByTenantIdAndName(targetProfile.getTenantId().getId(), provisionRequest.getDeviceName()); - if (optionalDevice.isPresent()) { - return processProvision(optionalDevice.get(), provisionRequest); - } + if (targetDevice != null){ + return processProvision(targetDevice, provisionRequest); + } else { + log.warn("[{}] Failed to find pre provisioned device!", provisionRequest.getDeviceName()); + return Futures.immediateFuture(new ProvisionResponse(null, ProvisionResponseStatus.FAILURE)); } - log.warn("[{}] Failed to find pre provisioned device!", provisionRequest.getDeviceName()); - return Futures.immediateFuture(new ProvisionResponse(null, ProvisionResponseStatus.FAILURE)); default: - throw new RuntimeException("Strategy is not supported - " + targetStrategy.name()); + throw new RuntimeException("Strategy is not supported - " + validationStrategy.name()); } } From 1e44303473848ea1be55c195b3d6250992017f99 Mon Sep 17 00:00:00 2001 From: zbeacon Date: Mon, 28 Sep 2020 14:24:46 +0300 Subject: [PATCH 05/48] removed debuging --- application/src/main/resources/thingsboard.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index cab87e9680..8e691ba48e 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -473,9 +473,7 @@ spring: enabled: "true" jpa: open-in-view: "false" - show-sql: "true" hibernate: - format_sql: "true" ddl-auto: "none" database-platform: "${SPRING_JPA_DATABASE_PLATFORM:org.hibernate.dialect.PostgreSQLDialect}" datasource: From 70b8647ef483d194b7fbfcf21c24e2ed4a42a6ae Mon Sep 17 00:00:00 2001 From: zbeacon Date: Mon, 28 Sep 2020 15:16:38 +0300 Subject: [PATCH 06/48] Refactoring to use provision data only from device profile --- .../device/DeviceProvisionServiceImpl.java | 2 - .../transport/DefaultTransportApiService.java | 7 +-- .../device/provision/ProvisionRequest.java | 4 +- .../data/device/data/DeviceConfiguration.java | 2 +- .../data/ProvisionDeviceConfiguration.java | 57 ------------------- .../ProvisionDeviceProfileConfiguration.java | 2 - 6 files changed, 5 insertions(+), 69 deletions(-) delete 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 0f2254a805..80d4c25798 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 @@ -29,9 +29,7 @@ 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.ProvisionRequestValidationStrategy; 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; diff --git a/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java b/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java index fb3ba8a4b4..e2dcbe5416 100644 --- a/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java +++ b/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java @@ -22,9 +22,7 @@ import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.MoreExecutors; import com.google.protobuf.ByteString; -import com.google.protobuf.InvalidProtocolBufferException; import lombok.extern.slf4j.Slf4j; -import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; import org.thingsboard.server.common.data.DataConstants; @@ -32,7 +30,7 @@ import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.device.credentials.BasicMqttCredentials; -import org.thingsboard.server.common.data.device.data.ProvisionDeviceConfiguration; +import org.thingsboard.server.common.data.device.profile.ProvisionDeviceProfileConfiguration; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceProfileId; @@ -63,7 +61,6 @@ import org.thingsboard.server.gen.transport.TransportProtos.GetOrCreateDeviceFro import org.thingsboard.server.gen.transport.TransportProtos.GetOrCreateDeviceFromGatewayResponseMsg; import org.thingsboard.server.gen.transport.TransportProtos.GetTenantRoutingInfoRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.GetTenantRoutingInfoResponseMsg; -import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceCredentialsMsg; import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.TransportApiRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.TransportApiResponseMsg; @@ -284,7 +281,7 @@ public class DefaultTransportApiService implements TransportApiService { requestMsg.getDeviceName(), requestMsg.getDeviceType(), requestMsg.getX509CertPubKey(), - new ProvisionDeviceConfiguration( + new ProvisionDeviceProfileConfiguration( requestMsg.getProvisionDeviceCredentialsMsg().getProvisionDeviceKey(), requestMsg.getProvisionDeviceCredentialsMsg().getProvisionDeviceSecret()))); return Futures.transform(provisionResponseFuture, provisionResponse -> { 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 3889cd5ab6..45eb7f5998 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.data.ProvisionDeviceConfiguration; +import org.thingsboard.server.common.data.device.profile.ProvisionDeviceProfileConfiguration; @Data @AllArgsConstructor @@ -25,5 +25,5 @@ public class ProvisionRequest { private String deviceName; private String deviceType; private String x509CertPubKey; - private ProvisionDeviceConfiguration credentials; + private ProvisionDeviceProfileConfiguration credentials; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/data/DeviceConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/data/DeviceConfiguration.java index 5c9a116abe..18d233caa0 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/data/DeviceConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/data/DeviceConfiguration.java @@ -28,7 +28,7 @@ import org.thingsboard.server.common.data.DeviceProfileType; property = "type") @JsonSubTypes({ @JsonSubTypes.Type(value = DefaultDeviceConfiguration.class, name = "DEFAULT"), - @JsonSubTypes.Type(value = ProvisionDeviceConfiguration.class, name = "PROVISION")}) + @JsonSubTypes.Type(value = DefaultDeviceConfiguration.class, name = "PROVISION")}) public interface DeviceConfiguration { @JsonIgnore 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 deleted file mode 100644 index a4930d367d..0000000000 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/data/ProvisionDeviceConfiguration.java +++ /dev/null @@ -1,57 +0,0 @@ -/** - * 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 44a66f6714..ee20a51628 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,8 +19,6 @@ 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; From 263dd8f9b3074b98792d8872f97ac7c1aa07b052 Mon Sep 17 00:00:00 2001 From: zbeacon Date: Mon, 28 Sep 2020 15:48:23 +0300 Subject: [PATCH 07/48] Removed provision info from device dao --- .../org/thingsboard/server/dao/device/DeviceDao.java | 3 --- .../server/dao/sql/device/DeviceRepository.java | 9 --------- .../thingsboard/server/dao/sql/device/JpaDeviceDao.java | 5 ----- 3 files changed, 17 deletions(-) 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 eb66de7009..54ca245ee8 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 @@ -214,7 +214,4 @@ public interface DeviceDao extends Dao { * @return the list of device objects */ PageData findDevicesByTenantIdAndProfileId(UUID tenantId, UUID profileId, PageLink pageLink); - - Optional findDeviceByProfileNameAndDeviceDataProvisionConfigurationPair(String profileName, 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 8ad229850a..b6ead2cdc3 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 @@ -168,13 +168,4 @@ public interface DeviceRepository extends PagingAndSortingRepository'configuration'->>'provisionDeviceKey' = :provisionDeviceKey " + - "AND d.device_data->'configuration'->>'provisionDeviceSecret' = :provisionDeviceSecret " + - "AND d.type = :profileName", - nativeQuery = true) - DeviceEntity findDeviceByProfileNameAndDeviceDataProvisionConfigurationPair(@Param("profileName") String profileName, - @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 1cbb9bc24c..b3eeea6360 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 @@ -219,11 +219,6 @@ public class JpaDeviceDao extends JpaAbstractSearchTextDao return deviceRepository.countByDeviceProfileId(deviceProfileId); } - @Override - public Optional findDeviceByProfileNameAndDeviceDataProvisionConfigurationPair(String profileName, String provisionDeviceKey, String provisionDeviceSecret) { - return Optional.ofNullable(DaoUtil.getData(deviceRepository.findDeviceByProfileNameAndDeviceDataProvisionConfigurationPair(profileName, provisionDeviceKey, provisionDeviceSecret))); - } - private List convertTenantDeviceTypesToDto(UUID tenantId, List types) { List list = Collections.emptyList(); if (types != null && !types.isEmpty()) { From b80fc6592bb207718ca601d9adf4e779d04ae542 Mon Sep 17 00:00:00 2001 From: zbeacon Date: Mon, 28 Sep 2020 15:54:13 +0300 Subject: [PATCH 08/48] Refactoring --- .../device/DeviceProvisionServiceImpl.java | 19 ------------------- 1 file changed, 19 deletions(-) delete mode 100644 dao/src/main/java/org/thingsboard/server/dao/device/DeviceProvisionServiceImpl.java diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProvisionServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProvisionServiceImpl.java deleted file mode 100644 index e9e1fcd182..0000000000 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProvisionServiceImpl.java +++ /dev/null @@ -1,19 +0,0 @@ -/** - * 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.dao.device; - -public class DeviceProvisionServiceImpl { -} From 950907d3980f9602f8b10a53cf036f5e5698118b Mon Sep 17 00:00:00 2001 From: zbeacon Date: Tue, 29 Sep 2020 11:30:32 +0300 Subject: [PATCH 09/48] Refactoring according to comments from @YevhenBondarenko --- .../server/dao/device/provision/ProvisionResponse.java | 6 ++---- .../server/transport/mqtt/MqttTransportHandler.java | 4 ---- .../server/common/transport/adaptor/JsonConverter.java | 3 +-- .../org/thingsboard/server/dao/device/DeviceProfileDao.java | 1 - .../server/dao/sql/device/JpaDeviceProfileDao.java | 1 - 5 files changed, 3 insertions(+), 12 deletions(-) diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/provision/ProvisionResponse.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/provision/ProvisionResponse.java index 509823eeb2..d38b53b134 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/provision/ProvisionResponse.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/provision/ProvisionResponse.java @@ -15,13 +15,11 @@ */ package org.thingsboard.server.dao.device.provision; -import lombok.AllArgsConstructor; import lombok.Data; import org.thingsboard.server.common.data.security.DeviceCredentials; @Data -@AllArgsConstructor public class ProvisionResponse { - private DeviceCredentials deviceCredentials; - private ProvisionResponseStatus responseStatus; + private final DeviceCredentials deviceCredentials; + private final ProvisionResponseStatus responseStatus; } 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 37738b89a6..127bceb10a 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 @@ -167,10 +167,6 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement case PINGREQ: ctx.writeAndFlush(new MqttMessage(new MqttFixedHeader(PINGRESP, false, AT_MOST_ONCE, false, 0))); break; -// case SUBSCRIBE: -// deviceSessionCtx.setDeviceInfo(TransportDeviceInfo); -// processSubscribe(ctx, (MqttSubscribeMessage) msg); -// break; case DISCONNECT: ctx.close(); break; diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java index c63c4d7bb8..13dda0a10a 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java @@ -417,8 +417,7 @@ public class JsonConverter { if (toGateway) { result.addProperty("id", requestId); } - result.addProperty("deviceId", new DeviceId( - new UUID(payload.getDeviceCredentials().getDeviceIdMSB(), payload.getDeviceCredentials().getDeviceIdLSB())).toString()); + result.addProperty("deviceId", new UUID(payload.getDeviceCredentials().getDeviceIdMSB(), payload.getDeviceCredentials().getDeviceIdLSB()).toString()); result.addProperty("credentialsType", payload.getDeviceCredentials().getCredentialsType().name()); result.addProperty("credentialsId", payload.getDeviceCredentials().getCredentialsId()); result.addProperty("credentialsValue", diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileDao.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileDao.java index 3c5a9269a1..2d65448d02 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileDao.java @@ -22,7 +22,6 @@ import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.dao.Dao; -import java.util.Optional; import java.util.UUID; public interface DeviceProfileDao extends Dao { diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceProfileDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceProfileDao.java index 6fa5768603..3a812e78da 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceProfileDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceProfileDao.java @@ -29,7 +29,6 @@ import org.thingsboard.server.dao.model.sql.DeviceProfileEntity; import org.thingsboard.server.dao.sql.JpaAbstractSearchTextDao; import java.util.Objects; -import java.util.Optional; import java.util.UUID; @Component From 2704696f2bc6a1abdf55176ddc8183c4655df8c2 Mon Sep 17 00:00:00 2001 From: zbeacon Date: Thu, 1 Oct 2020 12:23:12 +0300 Subject: [PATCH 10/48] Moved provisionDeviceKey as regular column --- .../upgrade/3.1.2/schema_update_before.sql | 3 + .../device/DeviceProvisionServiceImpl.java | 32 ++++------- .../transport/DefaultTransportApiService.java | 5 +- .../server/controller/AbstractWebTest.java | 1 + .../BaseDeviceProfileControllerTest.java | 12 ++++ .../device/provision/ProvisionRequest.java | 5 +- .../server/common/data/DeviceProfile.java | 3 + ...e.java => DeviceProfileProvisionType.java} | 8 ++- .../server/common/data/DeviceProfileType.java | 3 +- .../data/device/data/DeviceConfiguration.java | 3 +- ...esDeviceProfileProvisionConfiguration.java | 31 ++++++++++ ...esDeviceProfileProvisionConfiguration.java | 31 ++++++++++ .../profile/DeviceProfileConfiguration.java | 3 +- .../device/profile/DeviceProfileData.java | 1 + .../DeviceProfileProvisionConfiguration.java | 39 +++++++++++++ ...edDeviceProfileProvisionConfiguration.java | 31 ++++++++++ .../ProvisionDeviceProfileConfiguration.java | 57 ------------------- ...=> ProvisionDeviceProfileCredentials.java} | 5 +- common/queue/src/main/proto/queue.proto | 5 +- .../transport/adaptor/JsonConverter.java | 1 - .../server/dao/device/DeviceProfileDao.java | 2 +- .../dao/device/DeviceProfileServiceImpl.java | 7 +++ .../server/dao/model/ModelConstants.java | 2 + .../dao/model/sql/DeviceProfileEntity.java | 12 ++++ .../sql/device/DeviceProfileRepository.java | 14 ++--- .../dao/sql/device/JpaDeviceProfileDao.java | 4 +- .../resources/sql/schema-entities-hsql.sql | 3 + .../main/resources/sql/schema-entities.sql | 3 + 28 files changed, 215 insertions(+), 111 deletions(-) rename common/data/src/main/java/org/thingsboard/server/common/data/{device/profile/ProvisionRequestValidationStrategyType.java => DeviceProfileProvisionType.java} (79%) create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AllowCreateNewDevicesDeviceProfileProvisionConfiguration.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/device/profile/CheckPreProvisionedDevicesDeviceProfileProvisionConfiguration.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileProvisionConfiguration.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DisabledDeviceProfileProvisionConfiguration.java delete mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/device/profile/ProvisionDeviceProfileConfiguration.java rename common/data/src/main/java/org/thingsboard/server/common/data/device/profile/{ProvisionRequestValidationStrategy.java => ProvisionDeviceProfileCredentials.java} (83%) diff --git a/application/src/main/data/upgrade/3.1.2/schema_update_before.sql b/application/src/main/data/upgrade/3.1.2/schema_update_before.sql index c1591e7831..8f1d2f69a2 100644 --- a/application/src/main/data/upgrade/3.1.2/schema_update_before.sql +++ b/application/src/main/data/upgrade/3.1.2/schema_update_before.sql @@ -20,13 +20,16 @@ CREATE TABLE IF NOT EXISTS device_profile ( name varchar(255), type varchar(255), transport_type varchar(255), + provision_type varchar(255), profile_data jsonb, description varchar, search_text varchar(255), is_default boolean, tenant_id uuid, default_rule_chain_id uuid, + provision_device_key varchar, CONSTRAINT device_profile_name_unq_key UNIQUE (tenant_id, name), + CONSTRAINT device_provision_key_unq_key UNIQUE (provision_device_key), CONSTRAINT fk_default_rule_chain_device_profile FOREIGN KEY (default_rule_chain_id) REFERENCES rule_chain(id) ); 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 80d4c25798..091e5099ce 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 @@ -27,10 +27,8 @@ import org.springframework.util.StringUtils; import org.thingsboard.server.common.data.DataConstants; 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.DeviceProfileProvisionType; import org.thingsboard.server.common.data.audit.ActionType; -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; @@ -117,21 +115,18 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { return Futures.immediateFuture(new ProvisionResponse(null, ProvisionResponseStatus.NOT_FOUND)); } - DeviceProfile targetProfile = deviceProfileDao.findProfileByProfileNameAndProfileDataProvisionConfigurationPair( - provisionRequest.getDeviceType(), - provisionRequestKey, - provisionRequestSecret); + DeviceProfile targetProfile = deviceProfileDao.findByProvisionDeviceKeyAndProvisionDeviceSecret(provisionRequestKey, provisionRequestSecret); - if (targetProfile == null || targetProfile.getProfileData().getConfiguration().getType() != DeviceProfileType.PROVISION) { + if (targetProfile == null || + !(targetProfile.getProvisionType() != DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES || + targetProfile.getProvisionType() != DeviceProfileProvisionType.CHECK_PRE_PROVISIONED_DEVICES)) { return Futures.immediateFuture(new ProvisionResponse(null, ProvisionResponseStatus.NOT_FOUND)); } - ProvisionRequestValidationStrategyType validationStrategy = getStrategy(targetProfile); - Device targetDevice = deviceDao.findDeviceByTenantIdAndName(targetProfile.getTenantId().getId(), provisionRequest.getDeviceName()).orElse(null); - switch(validationStrategy) { - case CHECK_NEW_DEVICE: + switch(targetProfile.getProvisionType()) { + case ALLOW_CREATE_NEW_DEVICES: if (targetDevice != null) { log.warn("[{}] The device is present and could not be provisioned once more!", targetDevice.getName()); notify(targetDevice, provisionRequest, DataConstants.PROVISION_FAILURE, false); @@ -139,15 +134,15 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { } else { return createDevice(provisionRequest, targetProfile); } - case CHECK_PRE_PROVISIONED_DEVICE: - if (targetDevice != null){ + case CHECK_PRE_PROVISIONED_DEVICES: + if (targetDevice != null && targetDevice.getDeviceProfileId().equals(targetProfile.getId())){ return processProvision(targetDevice, provisionRequest); } else { log.warn("[{}] Failed to find pre provisioned device!", provisionRequest.getDeviceName()); return Futures.immediateFuture(new ProvisionResponse(null, ProvisionResponseStatus.FAILURE)); } default: - throw new RuntimeException("Strategy is not supported - " + validationStrategy.name()); + throw new RuntimeException("Strategy is not supported - " + targetProfile.getProvisionType().name()); } } @@ -193,11 +188,6 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { 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) { @@ -226,7 +216,7 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { private Device saveDevice(ProvisionRequest provisionRequest, DeviceProfile profile) { Device device = new Device(); device.setName(provisionRequest.getDeviceName()); - device.setType(provisionRequest.getDeviceType()); + device.setType(profile.getName()); device.setTenantId(profile.getTenantId()); return deviceService.saveDevice(device); } diff --git a/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java b/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java index e2dcbe5416..927afc3c8a 100644 --- a/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java +++ b/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java @@ -30,7 +30,7 @@ import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.device.credentials.BasicMqttCredentials; -import org.thingsboard.server.common.data.device.profile.ProvisionDeviceProfileConfiguration; +import org.thingsboard.server.common.data.device.profile.ProvisionDeviceProfileCredentials; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceProfileId; @@ -279,9 +279,8 @@ public class DefaultTransportApiService implements TransportApiService { provisionResponseFuture = deviceProvisionService.provisionDevice( new ProvisionRequest( requestMsg.getDeviceName(), - requestMsg.getDeviceType(), requestMsg.getX509CertPubKey(), - new ProvisionDeviceProfileConfiguration( + new ProvisionDeviceProfileCredentials( requestMsg.getProvisionDeviceCredentialsMsg().getProvisionDeviceKey(), requestMsg.getProvisionDeviceCredentialsMsg().getProvisionDeviceSecret()))); return Futures.transform(provisionResponseFuture, provisionResponse -> { diff --git a/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java b/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java index a7b796cace..da71c1e2df 100644 --- a/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java @@ -68,6 +68,7 @@ import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileConfiguration; import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.device.profile.DeviceProfileData; +import org.thingsboard.server.common.data.device.profile.ProvisionDeviceProfileCredentials; import org.thingsboard.server.common.data.id.HasId; import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.TenantId; diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseDeviceProfileControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseDeviceProfileControllerTest.java index b2334d7c46..3376c16573 100644 --- a/application/src/test/java/org/thingsboard/server/controller/BaseDeviceProfileControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/BaseDeviceProfileControllerTest.java @@ -28,6 +28,7 @@ import org.thingsboard.server.common.data.DeviceProfileType; import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.User; +import org.thingsboard.server.common.data.device.profile.ProvisionDeviceProfileCredentials; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.security.Authority; @@ -153,6 +154,17 @@ public abstract class BaseDeviceProfileControllerTest extends AbstractController .andExpect(statusReason(containsString("Device profile with such name already exists"))); } + @Test + public void testSaveDeviceProfileWithSameProvisionDeviceKey() throws Exception { + DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile"); + deviceProfile.setProvisionDeviceKey("testProvisionDeviceKey"); + doPost("/api/deviceProfile", deviceProfile).andExpect(status().isOk()); + DeviceProfile deviceProfile2 = this.createDeviceProfile("Device Profile 2"); + deviceProfile2.setProvisionDeviceKey("testProvisionDeviceKey"); + doPost("/api/deviceProfile", deviceProfile2).andExpect(status().isBadRequest()) + .andExpect(statusReason(containsString("Device profile with such provision device key already exists"))); + } + @Ignore @Test public void testChangeDeviceProfileTypeWithExistingDevices() throws Exception { 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..1c743fd855 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,13 +17,12 @@ 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.profile.ProvisionDeviceProfileCredentials; @Data @AllArgsConstructor public class ProvisionRequest { private String deviceName; - private String deviceType; private String x509CertPubKey; - private ProvisionDeviceProfileConfiguration credentials; + private ProvisionDeviceProfileCredentials credentials; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfile.java b/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfile.java index 097d64b198..10990bc436 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfile.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfile.java @@ -41,10 +41,12 @@ public class DeviceProfile extends SearchTextBased implements H private boolean isDefault; private DeviceProfileType type; private DeviceTransportType transportType; + private DeviceProfileProvisionType provisionType; private RuleChainId defaultRuleChainId; private transient DeviceProfileData profileData; @JsonIgnore private byte[] profileDataBytes; + private String provisionDeviceKey; public DeviceProfile() { super(); @@ -62,6 +64,7 @@ public class DeviceProfile extends SearchTextBased implements H this.isDefault = deviceProfile.isDefault(); this.defaultRuleChainId = deviceProfile.getDefaultRuleChainId(); this.setProfileData(deviceProfile.getProfileData()); + this.provisionDeviceKey = deviceProfile.getProvisionDeviceKey(); } @Override diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/ProvisionRequestValidationStrategyType.java b/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfileProvisionType.java similarity index 79% rename from common/data/src/main/java/org/thingsboard/server/common/data/device/profile/ProvisionRequestValidationStrategyType.java rename to common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfileProvisionType.java index 9a4005612a..f33c705d52 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/ProvisionRequestValidationStrategyType.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfileProvisionType.java @@ -13,8 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.common.data.device.profile; +package org.thingsboard.server.common.data; -public enum ProvisionRequestValidationStrategyType { - CHECK_NEW_DEVICE, CHECK_PRE_PROVISIONED_DEVICE +public enum DeviceProfileProvisionType { + DISABLED, + ALLOW_CREATE_NEW_DEVICES, + CHECK_PRE_PROVISIONED_DEVICES } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfileType.java b/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfileType.java index 218a5aa5b4..93ca102082 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfileType.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfileType.java @@ -16,6 +16,5 @@ package org.thingsboard.server.common.data; public enum DeviceProfileType { - DEFAULT, - PROVISION + DEFAULT } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/data/DeviceConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/data/DeviceConfiguration.java index 18d233caa0..1ea2ee4f97 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/data/DeviceConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/data/DeviceConfiguration.java @@ -27,8 +27,7 @@ import org.thingsboard.server.common.data.DeviceProfileType; include = JsonTypeInfo.As.PROPERTY, property = "type") @JsonSubTypes({ - @JsonSubTypes.Type(value = DefaultDeviceConfiguration.class, name = "DEFAULT"), - @JsonSubTypes.Type(value = DefaultDeviceConfiguration.class, name = "PROVISION")}) + @JsonSubTypes.Type(value = DefaultDeviceConfiguration.class, name = "DEFAULT")}) public interface DeviceConfiguration { @JsonIgnore diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AllowCreateNewDevicesDeviceProfileProvisionConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AllowCreateNewDevicesDeviceProfileProvisionConfiguration.java new file mode 100644 index 0000000000..8414caa400 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AllowCreateNewDevicesDeviceProfileProvisionConfiguration.java @@ -0,0 +1,31 @@ +/** + * 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.profile; + +import lombok.Data; +import org.thingsboard.server.common.data.DeviceProfileProvisionType; + +@Data +public class AllowCreateNewDevicesDeviceProfileProvisionConfiguration implements DeviceProfileProvisionConfiguration { + + private final String provisionDeviceSecret; + + @Override + public DeviceProfileProvisionType getType() { + return DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES; + } + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/CheckPreProvisionedDevicesDeviceProfileProvisionConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/CheckPreProvisionedDevicesDeviceProfileProvisionConfiguration.java new file mode 100644 index 0000000000..40af29ea51 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/CheckPreProvisionedDevicesDeviceProfileProvisionConfiguration.java @@ -0,0 +1,31 @@ +/** + * 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.profile; + +import lombok.Data; +import org.thingsboard.server.common.data.DeviceProfileProvisionType; + +@Data +public class CheckPreProvisionedDevicesDeviceProfileProvisionConfiguration implements DeviceProfileProvisionConfiguration { + + private final String provisionDeviceSecret; + + @Override + public DeviceProfileProvisionType getType() { + return DeviceProfileProvisionType.CHECK_PRE_PROVISIONED_DEVICES; + } + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileConfiguration.java index aca6d391da..3bb3d29c34 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileConfiguration.java @@ -27,8 +27,7 @@ import org.thingsboard.server.common.data.DeviceProfileType; include = JsonTypeInfo.As.PROPERTY, property = "type") @JsonSubTypes({ - @JsonSubTypes.Type(value = DefaultDeviceProfileConfiguration.class, name = "DEFAULT"), - @JsonSubTypes.Type(value = ProvisionDeviceProfileConfiguration.class, name = "PROVISION")}) + @JsonSubTypes.Type(value = DefaultDeviceProfileConfiguration.class, name = "DEFAULT")}) public interface DeviceProfileConfiguration { @JsonIgnore diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileData.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileData.java index 275e6269d6..7cb3304934 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileData.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileData.java @@ -24,6 +24,7 @@ public class DeviceProfileData { private DeviceProfileConfiguration configuration; private DeviceProfileTransportConfiguration transportConfiguration; + private DeviceProfileProvisionConfiguration provisionConfiguration; private List alarms; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileProvisionConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileProvisionConfiguration.java new file mode 100644 index 0000000000..140c7334ec --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileProvisionConfiguration.java @@ -0,0 +1,39 @@ +/** + * 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.profile; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import org.thingsboard.server.common.data.DeviceProfileProvisionType; + + +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonTypeInfo( + use = JsonTypeInfo.Id.NAME, + include = JsonTypeInfo.As.PROPERTY, + property = "type") +@JsonSubTypes({ + @JsonSubTypes.Type(value = DisabledDeviceProfileProvisionConfiguration.class, name = "DISABLED"), + @JsonSubTypes.Type(value = AllowCreateNewDevicesDeviceProfileProvisionConfiguration.class, name = "ALLOW_CREATE_NEW_DEVICES"), + @JsonSubTypes.Type(value = CheckPreProvisionedDevicesDeviceProfileProvisionConfiguration.class, name = "CHECK_PRE_PROVISIONED_DEVICES")}) +public interface DeviceProfileProvisionConfiguration { + + @JsonIgnore + DeviceProfileProvisionType getType(); + +} \ No newline at end of file diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DisabledDeviceProfileProvisionConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DisabledDeviceProfileProvisionConfiguration.java new file mode 100644 index 0000000000..01876c810d --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DisabledDeviceProfileProvisionConfiguration.java @@ -0,0 +1,31 @@ +/** + * 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.profile; + +import lombok.Data; +import org.thingsboard.server.common.data.DeviceProfileProvisionType; + +@Data +public class DisabledDeviceProfileProvisionConfiguration implements DeviceProfileProvisionConfiguration { + + private final String provisionDeviceSecret; + + @Override + public DeviceProfileProvisionType getType() { + return DeviceProfileProvisionType.DISABLED; + } + +} 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 deleted file mode 100644 index ee20a51628..0000000000 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/ProvisionDeviceProfileConfiguration.java +++ /dev/null @@ -1,57 +0,0 @@ -/** - * 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.profile; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; -import lombok.Data; -import org.thingsboard.server.common.data.DeviceProfileType; - -import java.util.Objects; - -@Data -public class ProvisionDeviceProfileConfiguration implements DeviceProfileConfiguration { - - private String provisionDeviceKey; - private String provisionDeviceSecret; - - private ProvisionRequestValidationStrategyType strategy; - - @Override - public DeviceProfileType getType() { - return DeviceProfileType.PROVISION; - } - - @JsonCreator - public ProvisionDeviceProfileConfiguration(@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; - ProvisionDeviceProfileConfiguration that = (ProvisionDeviceProfileConfiguration) 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/ProvisionRequestValidationStrategy.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/ProvisionDeviceProfileCredentials.java similarity index 83% rename from common/data/src/main/java/org/thingsboard/server/common/data/device/profile/ProvisionRequestValidationStrategy.java rename to common/data/src/main/java/org/thingsboard/server/common/data/device/profile/ProvisionDeviceProfileCredentials.java index 759940b763..659da71488 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/ProvisionRequestValidationStrategy.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/ProvisionDeviceProfileCredentials.java @@ -18,6 +18,7 @@ package org.thingsboard.server.common.data.device.profile; import lombok.Data; @Data -public class ProvisionRequestValidationStrategy { - private final ProvisionRequestValidationStrategyType validationStrategyType; +public class ProvisionDeviceProfileCredentials { + private final String provisionDeviceKey; + private final String provisionDeviceSecret; } diff --git a/common/queue/src/main/proto/queue.proto b/common/queue/src/main/proto/queue.proto index 007c17f3d3..bf308d922e 100644 --- a/common/queue/src/main/proto/queue.proto +++ b/common/queue/src/main/proto/queue.proto @@ -256,9 +256,8 @@ message DeviceCredentialsProto { message ProvisionDeviceRequestMsg { string deviceName = 1; - string deviceType = 2; - string x509CertPubKey = 3; - ProvisionDeviceCredentialsMsg provisionDeviceCredentialsMsg = 4; + string x509CertPubKey = 2; + ProvisionDeviceCredentialsMsg provisionDeviceCredentialsMsg = 3; } message ProvisionDeviceCredentialsMsg { diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java index 13dda0a10a..f710efd44a 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java @@ -543,7 +543,6 @@ public class JsonConverter { private static TransportProtos.ProvisionDeviceRequestMsg buildProvisionRequestMsg(JsonObject jo) { return TransportProtos.ProvisionDeviceRequestMsg.newBuilder() .setDeviceName(getStrValue(jo, DataConstants.DEVICE_NAME, true)) - .setDeviceType(getStrValue(jo, DataConstants.DEVICE_TYPE, true)) .setX509CertPubKey(getStrValue(jo, DataConstants.CERT_PUB_KEY, false)) .setProvisionDeviceCredentialsMsg(buildProvisionDeviceCredentialsMsg( getStrValue(jo, DataConstants.PROVISION_KEY, true), diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileDao.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileDao.java index 2d65448d02..74a251e3ff 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileDao.java @@ -38,7 +38,7 @@ public interface DeviceProfileDao extends Dao { DeviceProfileInfo findDefaultDeviceProfileInfo(TenantId tenantId); - DeviceProfile findProfileByProfileNameAndProfileDataProvisionConfigurationPair(String profileName, String provisionDeviceKey, String provisionDeviceSecret); + DeviceProfile findByProvisionDeviceKeyAndProvisionDeviceSecret(String provisionDeviceKey, String provisionDeviceSecret); DeviceProfile findByName(TenantId tenantId, String profileName); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java index b40ad102eb..d83408aead 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java @@ -26,6 +26,7 @@ import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceProfileInfo; +import org.thingsboard.server.common.data.DeviceProfileProvisionType; import org.thingsboard.server.common.data.DeviceProfileType; import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.EntitySubtype; @@ -33,6 +34,7 @@ import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileConfiguration; import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.device.profile.DeviceProfileData; +import org.thingsboard.server.common.data.device.profile.DisabledDeviceProfileProvisionConfiguration; import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; @@ -112,6 +114,8 @@ public class DeviceProfileServiceImpl extends AbstractEntityService implements D ConstraintViolationException e = extractConstraintViolationException(t).orElse(null); if (e != null && e.getConstraintName() != null && e.getConstraintName().equalsIgnoreCase("device_profile_name_unq_key")) { throw new DataValidationException("Device profile with such name already exists!"); + } else if (e != null && e.getConstraintName() != null && e.getConstraintName().equalsIgnoreCase("device_provision_key_unq_key")) { + throw new DataValidationException("Device profile with such provision device key already exists!"); } else { throw t; } @@ -210,12 +214,15 @@ public class DeviceProfileServiceImpl extends AbstractEntityService implements D deviceProfile.setName(profileName); deviceProfile.setType(DeviceProfileType.DEFAULT); deviceProfile.setTransportType(DeviceTransportType.DEFAULT); + deviceProfile.setProvisionType(DeviceProfileProvisionType.DISABLED); deviceProfile.setDescription("Default device profile"); DeviceProfileData deviceProfileData = new DeviceProfileData(); DefaultDeviceProfileConfiguration configuration = new DefaultDeviceProfileConfiguration(); DefaultDeviceProfileTransportConfiguration transportConfiguration = new DefaultDeviceProfileTransportConfiguration(); + DisabledDeviceProfileProvisionConfiguration provisionConfiguration = new DisabledDeviceProfileProvisionConfiguration(null); deviceProfileData.setConfiguration(configuration); deviceProfileData.setTransportConfiguration(transportConfiguration); + deviceProfileData.setProvisionConfiguration(provisionConfiguration); deviceProfile.setProfileData(deviceProfileData); return saveDeviceProfile(deviceProfile); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java index b62dac2d44..1f5a4b34d7 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java @@ -169,10 +169,12 @@ public class ModelConstants { public static final String DEVICE_PROFILE_NAME_PROPERTY = "name"; public static final String DEVICE_PROFILE_TYPE_PROPERTY = "type"; public static final String DEVICE_PROFILE_TRANSPORT_TYPE_PROPERTY = "transport_type"; + public static final String DEVICE_PROFILE_PROVISION_TYPE_PROPERTY = "provision_type"; public static final String DEVICE_PROFILE_PROFILE_DATA_PROPERTY = "profile_data"; public static final String DEVICE_PROFILE_DESCRIPTION_PROPERTY = "description"; public static final String DEVICE_PROFILE_IS_DEFAULT_PROPERTY = "is_default"; public static final String DEVICE_PROFILE_DEFAULT_RULE_CHAIN_ID_PROPERTY = "default_rule_chain_id"; + public static final String DEVICE_PROFILE_PROVISION_DEVICE_KEY = "provision_device_key"; /** * Cassandra entityView constants. diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/DeviceProfileEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/DeviceProfileEntity.java index 27e77d4eca..264d0725ae 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/DeviceProfileEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/DeviceProfileEntity.java @@ -23,6 +23,7 @@ import org.hibernate.annotations.Type; import org.hibernate.annotations.TypeDef; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceProfileType; +import org.thingsboard.server.common.data.DeviceProfileProvisionType; import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.device.profile.DeviceProfileData; import org.thingsboard.server.common.data.id.DeviceProfileId; @@ -62,6 +63,10 @@ public final class DeviceProfileEntity extends BaseSqlEntity impl @Column(name = ModelConstants.DEVICE_PROFILE_TRANSPORT_TYPE_PROPERTY) private DeviceTransportType transportType; + @Enumerated(EnumType.STRING) + @Column(name = ModelConstants.DEVICE_PROFILE_PROVISION_TYPE_PROPERTY) + private DeviceProfileProvisionType provisionType; + @Column(name = ModelConstants.DEVICE_PROFILE_DESCRIPTION_PROPERTY) private String description; @@ -78,6 +83,9 @@ public final class DeviceProfileEntity extends BaseSqlEntity impl @Column(name = ModelConstants.DEVICE_PROFILE_PROFILE_DATA_PROPERTY, columnDefinition = "jsonb") private JsonNode profileData; + @Column(name=ModelConstants.DEVICE_PROFILE_PROVISION_DEVICE_KEY) + private String provisionDeviceKey; + public DeviceProfileEntity() { super(); } @@ -93,12 +101,14 @@ public final class DeviceProfileEntity extends BaseSqlEntity impl this.name = deviceProfile.getName(); this.type = deviceProfile.getType(); this.transportType = deviceProfile.getTransportType(); + this.provisionType = deviceProfile.getProvisionType(); this.description = deviceProfile.getDescription(); this.isDefault = deviceProfile.isDefault(); this.profileData = JacksonUtil.convertValue(deviceProfile.getProfileData(), ObjectNode.class); if (deviceProfile.getDefaultRuleChainId() != null) { this.defaultRuleChainId = deviceProfile.getDefaultRuleChainId().getId(); } + this.provisionDeviceKey = deviceProfile.getProvisionDeviceKey(); } @Override @@ -125,12 +135,14 @@ public final class DeviceProfileEntity extends BaseSqlEntity impl deviceProfile.setName(name); deviceProfile.setType(type); deviceProfile.setTransportType(transportType); + deviceProfile.setProvisionType(provisionType); deviceProfile.setDescription(description); deviceProfile.setDefault(isDefault); deviceProfile.setProfileData(JacksonUtil.convertValue(profileData, DeviceProfileData.class)); if (defaultRuleChainId != null) { deviceProfile.setDefaultRuleChainId(new RuleChainId(defaultRuleChainId)); } + deviceProfile.setProvisionDeviceKey(provisionDeviceKey); return deviceProfile; } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceProfileRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceProfileRepository.java index d528650c10..43b6d68efe 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceProfileRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceProfileRepository.java @@ -56,14 +56,10 @@ public interface DeviceProfileRepository extends PagingAndSortingRepository'configuration'->>'provisionDeviceKey' IS NOT NULL " + - "AND d.profile_data->'configuration'->>'provisionDeviceSecret' IS NOT NULL " + - "AND d.profile_data->'configuration'->>'provisionDeviceKey' = :provisionDeviceKey " + - "AND d.profile_data->'configuration'->>'provisionDeviceSecret' = :provisionDeviceSecret", + @Query(value = "SELECT d.* from device_profile d " + + "WHERE d.provision_device_key = :provisionDeviceKey " + + "AND d.profile_data->'provisionConfiguration'->>'provisionDeviceSecret' = :provisionDeviceSecret", nativeQuery = true) - DeviceProfileEntity findProfileByProfileNameAndProfileDataProvisionConfigurationPair(@Param("profileName") String profileName, - @Param("provisionDeviceKey") String provisionDeviceKey, - @Param("provisionDeviceSecret") String provisionDeviceSecret); + DeviceProfileEntity findByProvisionDeviceKeyAndProvisionDeviceSecret(@Param("provisionDeviceKey") String provisionDeviceKey, + @Param("provisionDeviceSecret") String provisionDeviceSecret); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceProfileDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceProfileDao.java index 3a812e78da..2d7c011a87 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceProfileDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceProfileDao.java @@ -81,8 +81,8 @@ public class JpaDeviceProfileDao extends JpaAbstractSearchTextDao Date: Fri, 2 Oct 2020 10:41:55 +0300 Subject: [PATCH 11/48] Refactoring --- .../device/DeviceProvisionServiceImpl.java | 40 ++++++++++--------- .../server/dao/device/DeviceProfileDao.java | 2 +- .../sql/device/DeviceProfileRepository.java | 7 +--- .../dao/sql/device/JpaDeviceProfileDao.java | 4 +- 4 files changed, 26 insertions(+), 27 deletions(-) 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 091e5099ce..2460ce0fde 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 @@ -27,8 +27,9 @@ import org.springframework.util.StringUtils; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; -import org.thingsboard.server.common.data.DeviceProfileProvisionType; import org.thingsboard.server.common.data.audit.ActionType; +import org.thingsboard.server.common.data.device.profile.AllowCreateNewDevicesDeviceProfileProvisionConfiguration; +import org.thingsboard.server.common.data.device.profile.CheckPreProvisionedDevicesDeviceProfileProvisionConfiguration; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.UserId; @@ -115,11 +116,9 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { return Futures.immediateFuture(new ProvisionResponse(null, ProvisionResponseStatus.NOT_FOUND)); } - DeviceProfile targetProfile = deviceProfileDao.findByProvisionDeviceKeyAndProvisionDeviceSecret(provisionRequestKey, provisionRequestSecret); + DeviceProfile targetProfile = deviceProfileDao.findByProvisionDeviceKey(provisionRequestKey); - if (targetProfile == null || - !(targetProfile.getProvisionType() != DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES || - targetProfile.getProvisionType() != DeviceProfileProvisionType.CHECK_PRE_PROVISIONED_DEVICES)) { + if (targetProfile == null) { return Futures.immediateFuture(new ProvisionResponse(null, ProvisionResponseStatus.NOT_FOUND)); } @@ -127,23 +126,28 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { switch(targetProfile.getProvisionType()) { case ALLOW_CREATE_NEW_DEVICES: - if (targetDevice != null) { - 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)); - } else { - return createDevice(provisionRequest, targetProfile); + if (((AllowCreateNewDevicesDeviceProfileProvisionConfiguration) targetProfile.getProfileData().getProvisionConfiguration()).getProvisionDeviceSecret().equals(provisionRequestSecret)){ + if (targetDevice != null) { + 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)); + } else { + return createDevice(provisionRequest, targetProfile); + } } + break; case CHECK_PRE_PROVISIONED_DEVICES: - if (targetDevice != null && targetDevice.getDeviceProfileId().equals(targetProfile.getId())){ - return processProvision(targetDevice, provisionRequest); - } else { - log.warn("[{}] Failed to find pre provisioned device!", provisionRequest.getDeviceName()); - return Futures.immediateFuture(new ProvisionResponse(null, ProvisionResponseStatus.FAILURE)); + if (((CheckPreProvisionedDevicesDeviceProfileProvisionConfiguration) targetProfile.getProfileData().getProvisionConfiguration()).getProvisionDeviceSecret().equals(provisionRequestSecret)) { + if (targetDevice != null && targetDevice.getDeviceProfileId().equals(targetProfile.getId())) { + return processProvision(targetDevice, provisionRequest); + } else { + log.warn("[{}] Failed to find pre provisioned device!", provisionRequest.getDeviceName()); + return Futures.immediateFuture(new ProvisionResponse(null, ProvisionResponseStatus.FAILURE)); + } } - default: - throw new RuntimeException("Strategy is not supported - " + targetProfile.getProvisionType().name()); + break; } + return Futures.immediateFuture(new ProvisionResponse(null, ProvisionResponseStatus.NOT_FOUND)); } private ListenableFuture processProvision(Device device, ProvisionRequest provisionRequest) { diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileDao.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileDao.java index 74a251e3ff..9d11a34b2e 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileDao.java @@ -38,7 +38,7 @@ public interface DeviceProfileDao extends Dao { DeviceProfileInfo findDefaultDeviceProfileInfo(TenantId tenantId); - DeviceProfile findByProvisionDeviceKeyAndProvisionDeviceSecret(String provisionDeviceKey, String provisionDeviceSecret); + DeviceProfile findByProvisionDeviceKey(String provisionDeviceKey); DeviceProfile findByName(TenantId tenantId, String profileName); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceProfileRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceProfileRepository.java index 43b6d68efe..0b0efdc8bd 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceProfileRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceProfileRepository.java @@ -56,10 +56,5 @@ public interface DeviceProfileRepository extends PagingAndSortingRepository'provisionConfiguration'->>'provisionDeviceSecret' = :provisionDeviceSecret", - nativeQuery = true) - DeviceProfileEntity findByProvisionDeviceKeyAndProvisionDeviceSecret(@Param("provisionDeviceKey") String provisionDeviceKey, - @Param("provisionDeviceSecret") String provisionDeviceSecret); + DeviceProfileEntity findByProvisionDeviceKey(@Param("provisionDeviceKey") String provisionDeviceKey); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceProfileDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceProfileDao.java index 2d7c011a87..d47506b90e 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceProfileDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceProfileDao.java @@ -81,8 +81,8 @@ public class JpaDeviceProfileDao extends JpaAbstractSearchTextDao Date: Sun, 4 Oct 2020 00:05:52 +0300 Subject: [PATCH 12/48] UI: Added device provision to device profile --- .../home/components/home-components.module.ts | 7 +- .../add-device-profile-dialog.component.html | 10 +- .../add-device-profile-dialog.component.ts | 20 ++- .../device-profile-data.component.html | 10 ++ .../profile/device-profile-data.component.ts | 4 +- ...ile-provision-configuration.component.html | 46 ++++++ ...ofile-provision-configuration.component.ts | 140 ++++++++++++++++++ .../profile/device-profile.component.ts | 10 +- ui-ngx/src/app/shared/models/device.models.ts | 24 +++ .../assets/locale/locale.constant-en_US.json | 12 +- 10 files changed, 275 insertions(+), 8 deletions(-) create mode 100644 ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.html create mode 100644 ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.ts diff --git a/ui-ngx/src/app/modules/home/components/home-components.module.ts b/ui-ngx/src/app/modules/home/components/home-components.module.ts index d583369016..5209dd3602 100644 --- a/ui-ngx/src/app/modules/home/components/home-components.module.ts +++ b/ui-ngx/src/app/modules/home/components/home-components.module.ts @@ -107,6 +107,7 @@ import { AlarmRuleKeyFiltersDialogComponent } from './profile/alarm/alarm-rule-k import { FilterTextComponent } from './filter/filter-text.component'; import { AddDeviceProfileDialogComponent } from './profile/add-device-profile-dialog.component'; import { RuleChainAutocompleteComponent } from './rule-chain/rule-chain-autocomplete.component'; +import { DeviceProfileProvisionConfigurationComponent } from "./profile/device-profile-provision-configuration.component"; @NgModule({ declarations: @@ -196,7 +197,8 @@ import { RuleChainAutocompleteComponent } from './rule-chain/rule-chain-autocomp DeviceProfileComponent, DeviceProfileDialogComponent, AddDeviceProfileDialogComponent, - RuleChainAutocompleteComponent + RuleChainAutocompleteComponent, + DeviceProfileProvisionConfigurationComponent ], imports: [ CommonModule, @@ -275,7 +277,8 @@ import { RuleChainAutocompleteComponent } from './rule-chain/rule-chain-autocomp DeviceProfileComponent, DeviceProfileDialogComponent, AddDeviceProfileDialogComponent, - RuleChainAutocompleteComponent + RuleChainAutocompleteComponent, + DeviceProfileProvisionConfigurationComponent ], providers: [ WidgetComponentService, diff --git a/ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.html b/ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.html index d7f119a3b7..bda26153d1 100644 --- a/ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.html @@ -93,6 +93,14 @@ + +
+ {{'device-profile.device-provisioning' | translate }} + + +
+
@@ -107,7 +115,7 @@ + (click)="nextStep()">{{ (selectedIndex === 3 ? 'action.add' : 'action.continue') | translate }}
diff --git a/ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.ts b/ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.ts index 476c69488f..42edc3c613 100644 --- a/ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.ts @@ -77,6 +77,8 @@ export class AddDeviceProfileDialogComponent extends alarmRulesFormGroup: FormGroup; + provisionConfigurationFormGroup: FormGroup; + constructor(protected store: Store, protected router: Router, @Inject(MAT_DIALOG_DATA) public data: AddDeviceProfileDialogData, @@ -111,6 +113,12 @@ export class AddDeviceProfileDialogComponent extends alarms: [null] } ); + + this.provisionConfigurationFormGroup = this.fb.group( + { + provisionConfiguration: [null] + } + ) } private deviceProfileTransportTypeChanged() { @@ -131,7 +139,7 @@ export class AddDeviceProfileDialogComponent extends } nextStep() { - if (this.selectedIndex < 2) { + if (this.selectedIndex < 3) { this.addDeviceProfileStepper.next(); } else { this.add(); @@ -146,6 +154,8 @@ export class AddDeviceProfileDialogComponent extends return this.transportConfigFormGroup; case 2: return this.alarmRulesFormGroup; + case 3: + return this.provisionConfigurationFormGroup; } } @@ -154,11 +164,17 @@ export class AddDeviceProfileDialogComponent extends name: this.deviceProfileDetailsFormGroup.get('name').value, type: this.deviceProfileDetailsFormGroup.get('type').value, transportType: this.transportConfigFormGroup.get('transportType').value, + provisionType: this.provisionConfigurationFormGroup.get('provisionConfiguration').value.type, + provisionDeviceKey: this.provisionConfigurationFormGroup.get('provisionConfiguration').value.provisionDeviceKey, description: this.deviceProfileDetailsFormGroup.get('description').value, profileData: { configuration: createDeviceProfileConfiguration(DeviceProfileType.DEFAULT), transportConfiguration: this.transportConfigFormGroup.get('transportConfiguration').value, - alarms: this.alarmRulesFormGroup.get('alarms').value + alarms: this.alarmRulesFormGroup.get('alarms').value, + provisionConfiguration: { + type: this.provisionConfigurationFormGroup.get('provisionConfiguration').value.type, + provisionDeviceSecret: this.provisionConfigurationFormGroup.get('provisionConfiguration').value.provisionDeviceSecret + } } }; if (this.deviceProfileDetailsFormGroup.get('defaultRuleChainId').value) { diff --git a/ui-ngx/src/app/modules/home/components/profile/device-profile-data.component.html b/ui-ngx/src/app/modules/home/components/profile/device-profile-data.component.html index daae838285..eb4aef68eb 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device-profile-data.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/device-profile-data.component.html @@ -51,5 +51,15 @@ formControlName="alarms"> + + + +
device-profile.device-provisioning
+
+
+ + +
diff --git a/ui-ngx/src/app/modules/home/components/profile/device-profile-data.component.ts b/ui-ngx/src/app/modules/home/components/profile/device-profile-data.component.ts index 7d7fc55057..5b7fd9b998 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device-profile-data.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device-profile-data.component.ts @@ -72,7 +72,8 @@ export class DeviceProfileDataComponent implements ControlValueAccessor, OnInit this.deviceProfileDataFormGroup = this.fb.group({ configuration: [null, Validators.required], transportConfiguration: [null, Validators.required], - alarms: [null] + alarms: [null], + provisionConfiguration: [null] }); this.deviceProfileDataFormGroup.valueChanges.subscribe(() => { this.updateModel(); @@ -98,6 +99,7 @@ export class DeviceProfileDataComponent implements ControlValueAccessor, OnInit this.deviceProfileDataFormGroup.patchValue({configuration: value?.configuration}, {emitEvent: false}); this.deviceProfileDataFormGroup.patchValue({transportConfiguration: value?.transportConfiguration}, {emitEvent: false}); this.deviceProfileDataFormGroup.patchValue({alarms: value?.alarms}, {emitEvent: false}); + this.deviceProfileDataFormGroup.patchValue({provisionConfiguration: value?.provisionConfiguration}, {emitEvent: false}); } private updateModel() { diff --git a/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.html b/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.html new file mode 100644 index 0000000000..46d15905d1 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.html @@ -0,0 +1,46 @@ + +
+ + device-profile.provision-strategy + + + {{deviceProvisionTypeTranslateMap.get(type) | translate}} + + + + {{ 'device-profile.provision-strategy-required' | translate }} + + +
+ + device-profile.provision-device-secret + + + {{ 'device-profile.provision-device-secret-required' | translate }} + + + + device-profile.provision-device-key + + + {{ 'device-profile.provision-device-key-required' | translate }} + + +
+
diff --git a/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.ts b/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.ts new file mode 100644 index 0000000000..f37d8bb2c6 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.ts @@ -0,0 +1,140 @@ +/// +/// 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. +/// + +import { Component, forwardRef, Input, OnInit } from "@angular/core"; +import { + ControlValueAccessor, + FormBuilder, + FormControl, + FormGroup, + NG_VALIDATORS, + NG_VALUE_ACCESSOR, + ValidationErrors, + Validator, + Validators +} from "@angular/forms"; +import { coerceBooleanProperty } from "@angular/cdk/coercion"; +import { + DeviceProvisionConfiguration, + DeviceProvisionType, + deviceProvisionTypeTranslationMap +} from "@shared/models/device.models"; +import { isDefinedAndNotNull } from "@core/utils"; + +@Component({ + selector: 'tb-device-profile-provision-configuration', + templateUrl: './device-profile-provision-configuration.component.html', + styleUrls: [], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => DeviceProfileProvisionConfigurationComponent), + multi: true + }, + { + provide: NG_VALIDATORS, + useExisting: forwardRef(() => DeviceProfileProvisionConfigurationComponent), + multi: true, + } + ] +}) +export class DeviceProfileProvisionConfigurationComponent implements ControlValueAccessor, OnInit, Validator { + + provisionConfigurationFormGroup: FormGroup; + + deviceProvisionType = DeviceProvisionType; + deviceProvisionTypes = Object.keys(DeviceProvisionType); + deviceProvisionTypeTranslateMap = deviceProvisionTypeTranslationMap; + + private requiredValue: boolean; + get required(): boolean { + return this.requiredValue; + } + @Input() + set required(value: boolean) { + this.requiredValue = coerceBooleanProperty(value); + } + + @Input() + disabled: boolean; + + private propagateChange = (v: any) => { }; + + constructor(private fb: FormBuilder) { + } + + ngOnInit(): void { + this.provisionConfigurationFormGroup = this.fb.group({ + type: [DeviceProvisionType.DISABLED, Validators.required], + provisionDeviceSecret: [{value: null, disabled: true}, Validators.required], + provisionDeviceKey: [{value: null, disabled: true}, Validators.required] + }); + this.provisionConfigurationFormGroup.get('type').valueChanges.subscribe((type) => { + if(type === DeviceProvisionType.DISABLED) { + this.provisionConfigurationFormGroup.get('provisionDeviceSecret').disable({emitEvent: false}); + this.provisionConfigurationFormGroup.get('provisionDeviceSecret').patchValue(null,{emitEvent: false}); + this.provisionConfigurationFormGroup.get('provisionDeviceKey').disable({emitEvent: false}); + this.provisionConfigurationFormGroup.get('provisionDeviceKey').patchValue(null); + } else { + this.provisionConfigurationFormGroup.get('provisionDeviceSecret').enable({emitEvent: false}); + this.provisionConfigurationFormGroup.get('provisionDeviceKey').enable({emitEvent: false}); + } + }); + this.provisionConfigurationFormGroup.valueChanges.subscribe(() => { + this.updateModel(); + }); + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(fn: any): void { + } + + writeValue(value: DeviceProvisionConfiguration | null): void { + if(isDefinedAndNotNull(value)){ + this.provisionConfigurationFormGroup.patchValue(value, {emitEvent: false}); + } else { + this.provisionConfigurationFormGroup.patchValue({type: DeviceProvisionType.DISABLED}); + } + } + + setDisabledState(isDisabled: boolean){ + this.disabled = isDisabled; + if(this.disabled){ + this.provisionConfigurationFormGroup.disable(); + } else { + this.provisionConfigurationFormGroup.enable({emitEvent: false}); + } + } + + validate(c: FormControl): ValidationErrors | null { + return (this.provisionConfigurationFormGroup.valid) ? null : { + provisionConfiguration: { + valid: false, + }, + }; + } + + private updateModel(): void { + let deviceProvisionConfiguration: DeviceProvisionConfiguration = null; + if (this.provisionConfigurationFormGroup.valid) { + deviceProvisionConfiguration = this.provisionConfigurationFormGroup.getRawValue(); + } + this.propagateChange(deviceProvisionConfiguration); + } +} diff --git a/ui-ngx/src/app/modules/home/components/profile/device-profile.component.ts b/ui-ngx/src/app/modules/home/components/profile/device-profile.component.ts index e3c441c8a2..9c5fc9b5a5 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device-profile.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device-profile.component.ts @@ -126,6 +126,10 @@ export class DeviceProfileComponent extends EntityComponent { } updateForm(entity: DeviceProfile) { + if(entity?.profileData?.provisionConfiguration) { + entity.profileData.provisionConfiguration.provisionDeviceKey = entity?.provisionDeviceKey; + } + this.entityForm.patchValue({name: entity.name}); this.entityForm.patchValue({type: entity.type}, {emitEvent: false}); this.entityForm.patchValue({transportType: entity.transportType}, {emitEvent: false}); @@ -138,7 +142,11 @@ export class DeviceProfileComponent extends EntityComponent { if (formValue.defaultRuleChainId) { formValue.defaultRuleChainId = new RuleChainId(formValue.defaultRuleChainId); } - return formValue; + formValue.provisionType = formValue.profileData.provisionConfiguration.type; + formValue.provisionDeviceKey = formValue.profileData.provisionConfiguration.provisionDeviceKey; + delete formValue.profileData.provisionConfiguration.provisionDeviceKey; + + return super.prepareFormValue(formValue); } onDeviceProfileIdCopied(event) { diff --git a/ui-ngx/src/app/shared/models/device.models.ts b/ui-ngx/src/app/shared/models/device.models.ts index fe92268b80..a0d91c908e 100644 --- a/ui-ngx/src/app/shared/models/device.models.ts +++ b/ui-ngx/src/app/shared/models/device.models.ts @@ -36,6 +36,12 @@ export enum DeviceTransportType { LWM2M = 'LWM2M' } +export enum DeviceProvisionType { + DISABLED = 'DISABLED', + ALLOW_CREATE_NEW_DEVICES = 'ALLOW_CREATE_NEW_DEVICES', + CHECK_PRE_PROVISIONED_DEVICES = 'CHECK_PRE_PROVISIONED_DEVICES' +} + export interface DeviceConfigurationFormInfo { hasProfileConfiguration: boolean; hasDeviceConfiguration: boolean; @@ -67,6 +73,15 @@ export const deviceTransportTypeTranslationMap = new Map( + [ + [DeviceProvisionType.DISABLED, 'device-profile.provision-strategy-disabled'], + [DeviceProvisionType.ALLOW_CREATE_NEW_DEVICES, 'device-profile.provision-strategy-created-new'], + [DeviceProvisionType.CHECK_PRE_PROVISIONED_DEVICES, 'device-profile.provision-strategy-check-pre-provisioned'] + ] +) + export const deviceTransportTypeConfigurationInfoMap = new Map( [ [ @@ -125,6 +140,12 @@ export interface DeviceProfileTransportConfiguration extends DeviceProfileTransp type: DeviceTransportType; } +export interface DeviceProvisionConfiguration { + type: DeviceProvisionType; + provisionDeviceSecret?: string; + provisionDeviceKey?: string; +} + export function createDeviceProfileConfiguration(type: DeviceProfileType): DeviceProfileConfiguration { let configuration: DeviceProfileConfiguration = null; if (type) { @@ -220,6 +241,7 @@ export interface DeviceProfileData { configuration: DeviceProfileConfiguration; transportConfiguration: DeviceProfileTransportConfiguration; alarms?: Array; + provisionConfiguration?: DeviceProvisionConfiguration; } export interface DeviceProfile extends BaseData { @@ -229,6 +251,8 @@ export interface DeviceProfile extends BaseData { default?: boolean; type: DeviceProfileType; transportType: DeviceTransportType; + provisionType: DeviceProvisionType; + provisionDeviceKey?: string; defaultRuleChainId?: RuleChainId; profileData: DeviceProfileData; } diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index c98a2d5c17..23d2fba8df 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -840,7 +840,17 @@ "alarm-details": "Alarm details", "alarm-rule-condition": "Alarm rule condition", "enter-alarm-rule-condition-prompt": "Please add alarm rule condition", - "edit-alarm-rule-condition": "Edit alarm rule condition" + "edit-alarm-rule-condition": "Edit alarm rule condition", + "device-provisioning": "Device provisioning", + "provision-strategy": "Provision strategy", + "provision-strategy-required": "Provision strategy is required.", + "provision-strategy-disabled": "Disabled", + "provision-strategy-created-new": "Allow create new devices", + "provision-strategy-check-pre-provisioned": "Check pre provisioned devices", + "provision-device-key": "Provision device key", + "provision-device-key-required": "Provision device key is required.", + "provision-device-secret": "Provision device secret", + "provision-device-secret-required": "Provision device secret is required." }, "dialog": { "close": "Close dialog" From b5969eacc865b379c3522d80e994a3792d8ab660 Mon Sep 17 00:00:00 2001 From: zbeacon Date: Mon, 5 Oct 2020 10:39:04 +0300 Subject: [PATCH 13/48] Improvements --- .../server/common/data/device/profile/MqttTopics.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttTopics.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttTopics.java index 4abbedbee7..23c173ae97 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttTopics.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttTopics.java @@ -34,8 +34,8 @@ public class MqttTopics { private static final String ATTRIBUTES_RESPONSE = ATTRIBUTES + RESPONSE; private static final String ATTRIBUTES_REQUEST = ATTRIBUTES + REQUEST; - private static final String DEVICE_RPC_RESPONSE = RPC + RESPONSE; - private static final String DEVICE_RPC_REQUEST = RPC + REQUEST; + private static final String DEVICE_RPC_RESPONSE = RPC + RESPONSE + "/"; + private static final String DEVICE_RPC_REQUEST = RPC + REQUEST + "/"; private static final String DEVICE_ATTRIBUTES_RESPONSE = ATTRIBUTES_RESPONSE + "/"; private static final String DEVICE_ATTRIBUTES_REQUEST = ATTRIBUTES_REQUEST + "/"; From 2020da577d54205c91d963996751d83b9384785c Mon Sep 17 00:00:00 2001 From: zbeacon Date: Mon, 5 Oct 2020 10:40:47 +0300 Subject: [PATCH 14/48] Changed provision device key/secret fields position in UI --- ...-profile-provision-configuration.component.html | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.html b/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.html index 46d15905d1..2e9299f0cf 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.html @@ -28,13 +28,6 @@
- - device-profile.provision-device-secret - - - {{ 'device-profile.provision-device-secret-required' | translate }} - - device-profile.provision-device-key @@ -42,5 +35,12 @@ {{ 'device-profile.provision-device-key-required' | translate }} + + device-profile.provision-device-secret + + + {{ 'device-profile.provision-device-secret-required' | translate }} + +
From 0c56a1d8d85a02b786e15c7e762e2a99a7f42cf0 Mon Sep 17 00:00:00 2001 From: zbeacon Date: Mon, 5 Oct 2020 14:44:43 +0300 Subject: [PATCH 15/48] Improvements --- .../server/transport/mqtt/MqttTransportHandler.java | 4 +++- .../server/transport/mqtt/session/DeviceSessionCtx.java | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) 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 621fcf8ea5..b8c2e64790 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 @@ -175,7 +175,9 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement ctx.writeAndFlush(new MqttMessage(new MqttFixedHeader(PINGRESP, false, AT_MOST_ONCE, false, 0))); break; case DISCONNECT: - ctx.close(); + if (checkConnected(ctx, msg)) { + processDisconnect(ctx); + } break; } } diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/DeviceSessionCtx.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/DeviceSessionCtx.java index 1a27b34ab7..c0c5e4cc52 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/DeviceSessionCtx.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/DeviceSessionCtx.java @@ -49,7 +49,7 @@ public class DeviceSessionCtx extends MqttDeviceAwareSessionContext { @Getter @Setter - private boolean provisionOnly; + private boolean provisionOnly = false; private volatile MqttTopicFilter telemetryTopicFilter = MqttTopicFilterFactory.getDefaultTelemetryFilter(); private volatile MqttTopicFilter attributesTopicFilter = MqttTopicFilterFactory.getDefaultAttributesFilter(); From fd80cda30a98a3b6a4bd3ba8a939f6b9d6d43bb4 Mon Sep 17 00:00:00 2001 From: zbeacon Date: Tue, 6 Oct 2020 09:50:47 +0300 Subject: [PATCH 16/48] Added TbCoreComponent annotation --- .../server/service/device/DeviceProvisionServiceImpl.java | 2 ++ 1 file changed, 2 insertions(+) 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 2460ce0fde..2e2f1cbb80 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 @@ -60,6 +60,7 @@ import org.thingsboard.server.queue.TbQueueProducer; import org.thingsboard.server.queue.common.TbProtoQueueMsg; import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.queue.provider.TbQueueProducerProvider; +import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.state.DeviceStateService; import java.util.Collections; @@ -70,6 +71,7 @@ import java.util.concurrent.locks.ReentrantLock; @Service @Slf4j +@TbCoreComponent public class DeviceProvisionServiceImpl implements DeviceProvisionService { protected TbQueueProducer> ruleEngineMsgProducer; From 616f6a8b4f8a7ba1d84012d6c84440abea1c5a03 Mon Sep 17 00:00:00 2001 From: zbeacon Date: Tue, 6 Oct 2020 17:17:50 +0300 Subject: [PATCH 17/48] Changed Provision statuses --- .../service/transport/DefaultTransportApiService.java | 1 + .../dao/device/provision/ProvisionResponseStatus.java | 2 +- common/queue/src/main/proto/queue.proto | 7 ++++--- .../server/transport/mqtt/MqttTransportHandler.java | 1 + .../server/common/transport/adaptor/JsonConverter.java | 4 ++++ 5 files changed, 11 insertions(+), 4 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java b/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java index dc5535f9e1..7b4701754b 100644 --- a/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java +++ b/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java @@ -315,6 +315,7 @@ public class DefaultTransportApiService implements TransportApiService { .setCredentialsValue(deviceCredentials.getCredentialsValue() != null ? deviceCredentials.getCredentialsValue() : "") .build(); } + private ListenableFuture handle(GetTenantRoutingInfoRequestMsg requestMsg) { TenantId tenantId = new TenantId(new UUID(requestMsg.getTenantIdMSB(), requestMsg.getTenantIdLSB())); // TODO: Tenant Profile from cache diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/provision/ProvisionResponseStatus.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/provision/ProvisionResponseStatus.java index 3c993b0e14..46764f1b67 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/provision/ProvisionResponseStatus.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/provision/ProvisionResponseStatus.java @@ -16,7 +16,7 @@ package org.thingsboard.server.dao.device.provision; public enum ProvisionResponseStatus { - + UNKNOWN, SUCCESS, NOT_FOUND, FAILURE diff --git a/common/queue/src/main/proto/queue.proto b/common/queue/src/main/proto/queue.proto index bf308d922e..6a96f8a6d6 100644 --- a/common/queue/src/main/proto/queue.proto +++ b/common/queue/src/main/proto/queue.proto @@ -271,9 +271,10 @@ message ProvisionDeviceResponseMsg { } enum ProvisionResponseStatus { - SUCCESS = 0; - NOT_FOUND = 1; - FAILURE = 2; + UNKNOWN = 0; + SUCCESS = 1; + NOT_FOUND = 2; + FAILURE = 3; } //Used to report session state to tb-Service and persist this state in the cache on the tb-Service level. message SubscriptionInfoProto { 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 b8c2e64790..fcb9b53e2b 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 @@ -68,6 +68,7 @@ import java.io.IOException; import java.net.InetSocketAddress; import java.util.ArrayList; import java.util.List; +import java.util.Optional; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java index 509e691668..cd070e6927 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java @@ -43,6 +43,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.KeyValueType; import org.thingsboard.server.gen.transport.TransportProtos.PostAttributeMsg; import org.thingsboard.server.gen.transport.TransportProtos.PostTelemetryMsg; import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceResponseMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ProvisionResponseStatus; import org.thingsboard.server.gen.transport.TransportProtos.TsKvListProto; import org.thingsboard.server.gen.transport.TransportProtos.TsKvProto; @@ -411,8 +412,10 @@ public class JsonConverter { JsonObject result = new JsonObject(); if (payload.getProvisionResponseStatus() == TransportProtos.ProvisionResponseStatus.NOT_FOUND) { result.addProperty("errorMsg", "Provision data was not found!"); + result.addProperty("provisionDeviceStatus", ProvisionResponseStatus.NOT_FOUND.name()); } else if (payload.getProvisionResponseStatus() == TransportProtos.ProvisionResponseStatus.FAILURE) { result.addProperty("errorMsg", "Failed to provision device!"); + result.addProperty("provisionDeviceStatus", ProvisionResponseStatus.FAILURE.name()); } else { if (toGateway) { result.addProperty("id", requestId); @@ -422,6 +425,7 @@ public class JsonConverter { result.addProperty("credentialsId", payload.getDeviceCredentials().getCredentialsId()); result.addProperty("credentialsValue", StringUtils.isEmpty(payload.getDeviceCredentials().getCredentialsValue()) ? null : payload.getDeviceCredentials().getCredentialsValue()); + result.addProperty("provisionDeviceStatus", ProvisionResponseStatus.SUCCESS.name()); } return result; } From 2ec09e08ff377d84b02e1223009bb76dc0aad594 Mon Sep 17 00:00:00 2001 From: zbeacon Date: Tue, 6 Oct 2020 18:27:58 +0300 Subject: [PATCH 18/48] Added ProvisionTransportType to get protocol --- .../server/transport/mqtt/MqttTransportHandler.java | 8 +++++++- .../server/transport/mqtt/session/DeviceSessionCtx.java | 4 ++++ 2 files changed, 11 insertions(+), 1 deletion(-) 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 fcb9b53e2b..fdbcf01ac7 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 @@ -42,6 +42,7 @@ 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.TransportPayloadType; import org.thingsboard.server.common.data.device.profile.MqttTopics; import org.thingsboard.server.common.msg.EncryptionUtil; import org.thingsboard.server.common.transport.SessionMsgListener; @@ -159,6 +160,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement if (e.getCause().toString().contains("JsonSyntaxException")) { TransportProtos.ProvisionDeviceRequestMsg provisionRequestMsg = deviceSessionCtx.getContext().getProtoMqttAdaptor().convertToProvisionRequestMsg(deviceSessionCtx, mqttMsg); transportService.process(provisionRequestMsg, new DeviceProvisionCallback(ctx, msgId, provisionRequestMsg)); + deviceSessionCtx.setProvisionPayloadType(TransportPayloadType.PROTOBUF); log.trace("[{}][{}] Processing provision publish msg [{}][{}]!", sessionId, deviceSessionCtx.getDeviceId(), topicName, msgId); } else { throw e; @@ -326,7 +328,11 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement ctx.writeAndFlush(createMqttPubAckMsg(msgId)); } try { - deviceSessionCtx.getPayloadAdaptor().convertToPublish(deviceSessionCtx, provisionResponseMsg).ifPresent(deviceSessionCtx.getChannel()::writeAndFlush); + if (deviceSessionCtx.getProvisionPayloadType().equals(TransportPayloadType.JSON)) { + deviceSessionCtx.getContext().getJsonMqttAdaptor().convertToPublish(deviceSessionCtx, provisionResponseMsg).ifPresent(deviceSessionCtx.getChannel()::writeAndFlush); + } else { + deviceSessionCtx.getContext().getProtoMqttAdaptor().convertToPublish(deviceSessionCtx, provisionResponseMsg).ifPresent(deviceSessionCtx.getChannel()::writeAndFlush); + } } catch (Exception e) { log.trace("[{}] Failed to convert device attributes response to MQTT msg", sessionId, e); } diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/DeviceSessionCtx.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/DeviceSessionCtx.java index c0c5e4cc52..20ddfb5a24 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/DeviceSessionCtx.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/DeviceSessionCtx.java @@ -55,6 +55,10 @@ public class DeviceSessionCtx extends MqttDeviceAwareSessionContext { private volatile MqttTopicFilter attributesTopicFilter = MqttTopicFilterFactory.getDefaultAttributesFilter(); private volatile TransportPayloadType payloadType = TransportPayloadType.JSON; + @Getter + @Setter + private TransportPayloadType provisionPayloadType = payloadType; + public DeviceSessionCtx(UUID sessionId, ConcurrentMap mqttQoSMap, MqttTransportContext context) { super(sessionId, mqttQoSMap); this.context = context; From 6d0dc981a0837defaa37c30f2a0edbf4f62fccaf Mon Sep 17 00:00:00 2001 From: zbeacon Date: Tue, 6 Oct 2020 18:28:44 +0300 Subject: [PATCH 19/48] Adding tests for device provisioning feature --- .../mqtt/AbstractMqttIntegrationTest.java | 47 ++++- .../server/mqtt/MqttSqlTestSuite.java | 3 +- .../AbstractMqttProvisionJsonDeviceTest.java | 188 +++++++++++++++++ .../AbstractMqttProvisionProtoDeviceTest.java | 189 ++++++++++++++++++ .../sql/MqttProvisionDeviceJsonSqlTest.java | 23 +++ .../sql/MqttProvisionDeviceProtoSqlTest.java | 23 +++ 6 files changed, 469 insertions(+), 4 deletions(-) create mode 100644 application/src/test/java/org/thingsboard/server/mqtt/provision/AbstractMqttProvisionJsonDeviceTest.java create mode 100644 application/src/test/java/org/thingsboard/server/mqtt/provision/AbstractMqttProvisionProtoDeviceTest.java create mode 100644 application/src/test/java/org/thingsboard/server/mqtt/provision/sql/MqttProvisionDeviceJsonSqlTest.java create mode 100644 application/src/test/java/org/thingsboard/server/mqtt/provision/sql/MqttProvisionDeviceProtoSqlTest.java diff --git a/application/src/test/java/org/thingsboard/server/mqtt/AbstractMqttIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/AbstractMqttIntegrationTest.java index 846343b65e..4e8f3e9fa4 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/AbstractMqttIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/AbstractMqttIntegrationTest.java @@ -25,13 +25,18 @@ import org.junit.Assert; import org.springframework.util.StringUtils; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; +import org.thingsboard.server.common.data.DeviceProfileProvisionType; import org.thingsboard.server.common.data.DeviceProfileType; import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.TransportPayloadType; import org.thingsboard.server.common.data.User; +import org.thingsboard.server.common.data.device.profile.AllowCreateNewDevicesDeviceProfileProvisionConfiguration; +import org.thingsboard.server.common.data.device.profile.CheckPreProvisionedDevicesDeviceProfileProvisionConfiguration; import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileConfiguration; import org.thingsboard.server.common.data.device.profile.DeviceProfileData; +import org.thingsboard.server.common.data.device.profile.DeviceProfileProvisionConfiguration; +import org.thingsboard.server.common.data.device.profile.DisabledDeviceProfileProvisionConfiguration; import org.thingsboard.server.common.data.device.profile.MqttDeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.common.data.security.DeviceCredentials; @@ -62,7 +67,18 @@ public abstract class AbstractMqttIntegrationTest extends AbstractControllerTest protected Device savedGateway; protected String gatewayAccessToken; - protected void processBeforeTest(String deviceName, String gatewayName, TransportPayloadType payloadType, String telemetryTopic, String attributesTopic) throws Exception { + protected void processBeforeTest (String deviceName, String gatewayName, TransportPayloadType payloadType, String telemetryTopic, String attributesTopic) throws Exception { + this.processBeforeTest(deviceName, gatewayName, payloadType, telemetryTopic, attributesTopic, null, null, null); + } + + protected void processBeforeTest(String deviceName, + String gatewayName, + TransportPayloadType payloadType, + String telemetryTopic, + String attributesTopic, + DeviceProfileProvisionType provisionType, + String provisionKey, String provisionSecret + ) throws Exception { loginSysAdmin(); Tenant tenant = new Tenant(); @@ -91,7 +107,7 @@ public abstract class AbstractMqttIntegrationTest extends AbstractControllerTest gateway.setAdditionalInfo(additionalInfo); if (payloadType != null) { - DeviceProfile mqttDeviceProfile = createMqttDeviceProfile(payloadType, telemetryTopic, attributesTopic); + DeviceProfile mqttDeviceProfile = createMqttDeviceProfile(payloadType, telemetryTopic, attributesTopic, provisionType, provisionKey, provisionSecret); DeviceProfile savedDeviceProfile = doPost("/api/deviceProfile", mqttDeviceProfile, DeviceProfile.class); device.setType(savedDeviceProfile.getName()); device.setDeviceProfileId(savedDeviceProfile.getId()); @@ -181,11 +197,23 @@ public abstract class AbstractMqttIntegrationTest extends AbstractControllerTest return keyValueProtoBuilder.build(); } - protected DeviceProfile createMqttDeviceProfile(TransportPayloadType transportPayloadType, String telemetryTopic, String attributesTopic) { + protected DeviceProfile createMqttDeviceProfile(TransportPayloadType transportPayloadType, + String telemetryTopic, String attributesTopic + ) { + return this.createMqttDeviceProfile(transportPayloadType, telemetryTopic, attributesTopic, null, null, null); + } + + protected DeviceProfile createMqttDeviceProfile(TransportPayloadType transportPayloadType, + String telemetryTopic, String attributesTopic, + DeviceProfileProvisionType provisionType, + String provisionKey, String provisionSecret + ) { DeviceProfile deviceProfile = new DeviceProfile(); deviceProfile.setName(transportPayloadType.name()); deviceProfile.setType(DeviceProfileType.DEFAULT); deviceProfile.setTransportType(DeviceTransportType.MQTT); + deviceProfile.setProvisionType(provisionType); + deviceProfile.setProvisionDeviceKey(provisionKey); deviceProfile.setDescription(transportPayloadType.name() + " Test"); DeviceProfileData deviceProfileData = new DeviceProfileData(); DefaultDeviceProfileConfiguration configuration = new DefaultDeviceProfileConfiguration(); @@ -198,6 +226,19 @@ public abstract class AbstractMqttIntegrationTest extends AbstractControllerTest transportConfiguration.setDeviceAttributesTopic(attributesTopic); } deviceProfileData.setTransportConfiguration(transportConfiguration); + DeviceProfileProvisionConfiguration provisionConfiguration; + switch (provisionType) { + case ALLOW_CREATE_NEW_DEVICES: + provisionConfiguration = new AllowCreateNewDevicesDeviceProfileProvisionConfiguration(provisionSecret); + break; + case CHECK_PRE_PROVISIONED_DEVICES: + provisionConfiguration = new CheckPreProvisionedDevicesDeviceProfileProvisionConfiguration(provisionSecret); + break; + case DISABLED: + default: + provisionConfiguration = new DisabledDeviceProfileProvisionConfiguration(provisionSecret); + } + deviceProfileData.setProvisionConfiguration(provisionConfiguration); deviceProfileData.setConfiguration(configuration); deviceProfile.setProfileData(deviceProfileData); deviceProfile.setDefault(false); diff --git a/application/src/test/java/org/thingsboard/server/mqtt/MqttSqlTestSuite.java b/application/src/test/java/org/thingsboard/server/mqtt/MqttSqlTestSuite.java index 095a5c3e7a..f71e8f37a2 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/MqttSqlTestSuite.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/MqttSqlTestSuite.java @@ -31,7 +31,8 @@ import java.util.Arrays; "org.thingsboard.server.mqtt.telemetry.attributes.sql.*Test", "org.thingsboard.server.mqtt.attributes.updates.sql.*Test", "org.thingsboard.server.mqtt.attributes.request.sql.*Test", - "org.thingsboard.server.mqtt.claim.sql.*Test" + "org.thingsboard.server.mqtt.claim.sql.*Test", + "org.thingsboard.server.mqtt.provision.sql.*Test" }) public class MqttSqlTestSuite { diff --git a/application/src/test/java/org/thingsboard/server/mqtt/provision/AbstractMqttProvisionJsonDeviceTest.java b/application/src/test/java/org/thingsboard/server/mqtt/provision/AbstractMqttProvisionJsonDeviceTest.java new file mode 100644 index 0000000000..26d7e2afce --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/mqtt/provision/AbstractMqttProvisionJsonDeviceTest.java @@ -0,0 +1,188 @@ +/** + * 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.mqtt.provision; + +import com.google.gson.JsonObject; +import io.netty.handler.codec.mqtt.MqttQoS; +import lombok.extern.slf4j.Slf4j; +import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken; +import org.eclipse.paho.client.mqttv3.MqttAsyncClient; +import org.eclipse.paho.client.mqttv3.MqttCallback; +import org.eclipse.paho.client.mqttv3.MqttMessage; +import org.junit.After; +import org.junit.Assert; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.DeviceProfileProvisionType; +import org.thingsboard.server.common.data.TransportPayloadType; +import org.thingsboard.server.common.data.device.profile.MqttTopics; +import org.thingsboard.server.common.data.security.DeviceCredentials; +import org.thingsboard.server.common.transport.util.JsonUtils; +import org.thingsboard.server.dao.device.DeviceCredentialsService; +import org.thingsboard.server.dao.device.DeviceService; +import org.thingsboard.server.dao.device.provision.ProvisionResponseStatus; +import org.thingsboard.server.mqtt.AbstractMqttIntegrationTest; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +@Slf4j +public abstract class AbstractMqttProvisionJsonDeviceTest extends AbstractMqttIntegrationTest { + + @Autowired + DeviceCredentialsService deviceCredentialsService; + + @Autowired + DeviceService deviceService; + + @After + public void afterTest() throws Exception { + super.processAfterTest(); + } + + @Test + public void testProvisioningDisabledDevice() throws Exception { + processTestProvisioningDisabledDevice(); + } + + @Test + public void testProvisioningCheckPreProvisionedDevice() throws Exception { + processTestProvisioningCheckPreProvisionedDevice(); + } + + @Test + public void testProvisioningCreateNewDevice() throws Exception { + processTestProvisioningCreateNewDevice(); + } + + @Test + public void testProvisioningWithBadKeyDevice() throws Exception { + processTestProvisioningWithBadKeyDevice(); + } + + + protected void processTestProvisioningDisabledDevice() throws Exception { + super.processBeforeTest("Test Provision device", "Test Provision gateway", TransportPayloadType.JSON, null, null, DeviceProfileProvisionType.DISABLED, null, null); + byte[] result = createMqttClientAndPublish().getPayloadBytes(); + JsonObject response = JsonUtils.parse(new String(result)).getAsJsonObject(); + Assert.assertEquals("Provision data was not found!", response.get("errorMsg").getAsString()); + Assert.assertEquals(ProvisionResponseStatus.NOT_FOUND.name(), response.get("provisionDeviceStatus").getAsString()); + } + + + protected void processTestProvisioningCreateNewDevice() throws Exception { + super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.JSON, null, null, DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES, "testProvisionKey", "testProvisionSecret"); + byte[] result = createMqttClientAndPublish().getPayloadBytes(); + JsonObject response = JsonUtils.parse(new String(result)).getAsJsonObject(); + + Device createdDevice = deviceService.findDeviceByTenantIdAndName(savedTenant.getTenantId(), "Test Provision device"); + + Assert.assertNotNull(createdDevice); + Assert.assertEquals(createdDevice.getId().toString(), response.get("deviceId").getAsString()); + + DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(savedTenant.getTenantId(), createdDevice.getId()); + + Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.get("credentialsType").getAsString()); + Assert.assertEquals(deviceCredentials.getCredentialsId(), response.get("credentialsId").getAsString()); + Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.get("provisionDeviceStatus").getAsString()); + } + + protected void processTestProvisioningCheckPreProvisionedDevice() throws Exception { + super.processBeforeTest("Test Provision device", "Test Provision gateway", TransportPayloadType.JSON, null, null, DeviceProfileProvisionType.CHECK_PRE_PROVISIONED_DEVICES, "testProvisionKey", "testProvisionSecret"); + byte[] result = createMqttClientAndPublish().getPayloadBytes(); + JsonObject response = JsonUtils.parse(new String(result)).getAsJsonObject(); + Assert.assertEquals(savedDevice.getId().toString(), response.get("deviceId").getAsString()); + + DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(savedTenant.getTenantId(), savedDevice.getId()); + + Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.get("credentialsType").getAsString()); + Assert.assertEquals(deviceCredentials.getCredentialsId(), response.get("credentialsId").getAsString()); + Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.get("provisionDeviceStatus").getAsString()); + } + + protected void processTestProvisioningWithBadKeyDevice() throws Exception { + super.processBeforeTest("Test Provision device", "Test Provision gateway", TransportPayloadType.JSON, null, null, DeviceProfileProvisionType.CHECK_PRE_PROVISIONED_DEVICES, "testProvisionKeyOrig", "testProvisionSecret"); + byte[] result = createMqttClientAndPublish().getPayloadBytes(); + JsonObject response = JsonUtils.parse(new String(result)).getAsJsonObject(); + Assert.assertEquals("Provision data was not found!", response.get("errorMsg").getAsString()); + Assert.assertEquals(ProvisionResponseStatus.NOT_FOUND.name(), response.get("provisionDeviceStatus").getAsString()); + } + + protected TestMqttCallback createMqttClientAndPublish() throws Exception{ + String provisionRequestMsg = createTestProvisionMessage(); + MqttAsyncClient client = getMqttAsyncClient("provision"); + TestMqttCallback onProvisionCallback = getTestMqttCallback(); + client.setCallback(onProvisionCallback); + client.subscribe(MqttTopics.DEVICE_PROVISION_RESPONSE_TOPIC, MqttQoS.AT_MOST_ONCE.value()); + Thread.sleep(2000); + client.publish(MqttTopics.DEVICE_PROVISION_REQUEST_TOPIC, new MqttMessage(provisionRequestMsg.getBytes())); + onProvisionCallback.getLatch().await(3, TimeUnit.SECONDS); + return onProvisionCallback; + } + + + protected TestMqttCallback getTestMqttCallback() { + CountDownLatch latch = new CountDownLatch(1); + return new TestMqttCallback(latch); + } + + + protected static class TestMqttCallback implements MqttCallback { + + private final CountDownLatch latch; + private Integer qoS; + private byte[] payloadBytes; + + TestMqttCallback(CountDownLatch latch) { + this.latch = latch; + } + + public int getQoS() { + return qoS; + } + + public byte[] getPayloadBytes() { + return payloadBytes; + } + + public CountDownLatch getLatch() { + return latch; + } + + @Override + public void connectionLost(Throwable throwable) { + } + + @Override + public void messageArrived(String requestTopic, MqttMessage mqttMessage) throws Exception { + qoS = mqttMessage.getQos(); + payloadBytes = mqttMessage.getPayload(); + latch.countDown(); + } + + @Override + public void deliveryComplete(IMqttDeliveryToken iMqttDeliveryToken) { + + } + } + + + protected String createTestProvisionMessage() { + return "{\"deviceName\":\"Test Provision device\",\"provisionDeviceKey\":\"testProvisionKey\", \"provisionDeviceSecret\":\"testProvisionSecret\"}"; + } + +} diff --git a/application/src/test/java/org/thingsboard/server/mqtt/provision/AbstractMqttProvisionProtoDeviceTest.java b/application/src/test/java/org/thingsboard/server/mqtt/provision/AbstractMqttProvisionProtoDeviceTest.java new file mode 100644 index 0000000000..7720ddad4a --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/mqtt/provision/AbstractMqttProvisionProtoDeviceTest.java @@ -0,0 +1,189 @@ +/** + * 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.mqtt.provision; + +import com.google.gson.JsonObject; +import com.sun.mail.iap.ByteArray; +import io.netty.handler.codec.mqtt.MqttQoS; +import lombok.extern.slf4j.Slf4j; +import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken; +import org.eclipse.paho.client.mqttv3.MqttAsyncClient; +import org.eclipse.paho.client.mqttv3.MqttCallback; +import org.eclipse.paho.client.mqttv3.MqttMessage; +import org.junit.After; +import org.junit.Assert; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.DeviceProfileProvisionType; +import org.thingsboard.server.common.data.TransportPayloadType; +import org.thingsboard.server.common.data.device.profile.MqttTopics; +import org.thingsboard.server.common.data.security.DeviceCredentials; +import org.thingsboard.server.common.transport.util.JsonUtils; +import org.thingsboard.server.dao.device.DeviceCredentialsService; +import org.thingsboard.server.dao.device.DeviceService; +import org.thingsboard.server.dao.device.provision.ProvisionResponseStatus; +import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceCredentialsMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceRequestMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceResponseMsg; +import org.thingsboard.server.mqtt.AbstractMqttIntegrationTest; + +import java.util.UUID; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +@Slf4j +public abstract class AbstractMqttProvisionProtoDeviceTest extends AbstractMqttIntegrationTest { + + @Autowired + DeviceCredentialsService deviceCredentialsService; + + @Autowired + DeviceService deviceService; + + @After + public void afterTest() throws Exception { + super.processAfterTest(); + } + + @Test + public void testProvisioningDisabledDevice() throws Exception { + processTestProvisioningDisabledDevice(); + } + + @Test + public void testProvisioningCheckPreProvisionedDevice() throws Exception { + processTestProvisioningCheckPreProvisionedDevice(); + } + + @Test + public void testProvisioningCreateNewDevice() throws Exception { + processTestProvisioningCreateNewDevice(); + } + + @Test + public void testProvisioningWithBadKeyDevice() throws Exception { + processTestProvisioningWithBadKeyDevice(); + } + + + protected void processTestProvisioningDisabledDevice() throws Exception { + super.processBeforeTest("Test Provision device", "Test Provision gateway", TransportPayloadType.PROTOBUF, null, null, DeviceProfileProvisionType.DISABLED, null, null); + ProvisionDeviceResponseMsg result = ProvisionDeviceResponseMsg.parseFrom(createMqttClientAndPublish().getPayloadBytes()); + Assert.assertNotNull(result); + Assert.assertEquals(ProvisionResponseStatus.NOT_FOUND.name(), result.getProvisionResponseStatus().toString()); + } + + + protected void processTestProvisioningCreateNewDevice() throws Exception { + super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.JSON, null, null, DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES, "testProvisionKey", "testProvisionSecret"); + ProvisionDeviceResponseMsg response = ProvisionDeviceResponseMsg.parseFrom(createMqttClientAndPublish().getPayloadBytes()); + + Device createdDevice = deviceService.findDeviceByTenantIdAndName(savedTenant.getTenantId(), "Test Provision device"); + + Assert.assertNotNull(createdDevice); + Assert.assertEquals(createdDevice.getId().getId(), new UUID(response.getDeviceCredentials().getDeviceIdMSB(), response.getDeviceCredentials().getDeviceIdLSB())); + + DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(savedTenant.getTenantId(), createdDevice.getId()); + + Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.getDeviceCredentials().getCredentialsType().toString()); + Assert.assertEquals(deviceCredentials.getCredentialsId(), response.getDeviceCredentials().getCredentialsId()); + Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.getProvisionResponseStatus().toString()); + } + + protected void processTestProvisioningCheckPreProvisionedDevice() throws Exception { + super.processBeforeTest("Test Provision device", "Test Provision gateway", TransportPayloadType.JSON, null, null, DeviceProfileProvisionType.CHECK_PRE_PROVISIONED_DEVICES, "testProvisionKey", "testProvisionSecret"); + ProvisionDeviceResponseMsg response = ProvisionDeviceResponseMsg.parseFrom(createMqttClientAndPublish().getPayloadBytes()); + Assert.assertEquals(savedDevice.getId().getId(), new UUID(response.getDeviceCredentials().getDeviceIdMSB(), response.getDeviceCredentials().getDeviceIdLSB())); + + DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(savedTenant.getTenantId(), savedDevice.getId()); + + Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.getDeviceCredentials().getCredentialsType().toString()); + Assert.assertEquals(deviceCredentials.getCredentialsId(), response.getDeviceCredentials().getCredentialsId()); + Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.getProvisionResponseStatus().toString()); + } + + protected void processTestProvisioningWithBadKeyDevice() throws Exception { + super.processBeforeTest("Test Provision device", "Test Provision gateway", TransportPayloadType.JSON, null, null, DeviceProfileProvisionType.CHECK_PRE_PROVISIONED_DEVICES, "testProvisionKeyOrig", "testProvisionSecret"); + ProvisionDeviceResponseMsg response = ProvisionDeviceResponseMsg.parseFrom(createMqttClientAndPublish().getPayloadBytes()); + Assert.assertEquals(ProvisionResponseStatus.NOT_FOUND.name(), response.getProvisionResponseStatus().toString()); + } + + protected TestMqttCallback createMqttClientAndPublish() throws Exception{ + byte[] provisionRequestMsg = createTestProvisionMessage(); + MqttAsyncClient client = getMqttAsyncClient("provision"); + TestMqttCallback onProvisionCallback = getTestMqttCallback(); + client.setCallback(onProvisionCallback); + client.subscribe(MqttTopics.DEVICE_PROVISION_RESPONSE_TOPIC, MqttQoS.AT_MOST_ONCE.value()); + Thread.sleep(2000); + client.publish(MqttTopics.DEVICE_PROVISION_REQUEST_TOPIC, new MqttMessage(provisionRequestMsg)); + onProvisionCallback.getLatch().await(3, TimeUnit.SECONDS); + return onProvisionCallback; + } + + + protected TestMqttCallback getTestMqttCallback() { + CountDownLatch latch = new CountDownLatch(1); + return new TestMqttCallback(latch); + } + + + protected static class TestMqttCallback implements MqttCallback { + + private final CountDownLatch latch; + private Integer qoS; + private byte[] payloadBytes; + + TestMqttCallback(CountDownLatch latch) { + this.latch = latch; + } + + public int getQoS() { + return qoS; + } + + public byte[] getPayloadBytes() { + return payloadBytes; + } + + public CountDownLatch getLatch() { + return latch; + } + + @Override + public void connectionLost(Throwable throwable) { + } + + @Override + public void messageArrived(String requestTopic, MqttMessage mqttMessage) throws Exception { + qoS = mqttMessage.getQos(); + payloadBytes = mqttMessage.getPayload(); + latch.countDown(); + } + + @Override + public void deliveryComplete(IMqttDeliveryToken iMqttDeliveryToken) { + + } + } + + + protected byte[] createTestProvisionMessage() { + return ProvisionDeviceRequestMsg.newBuilder().setX509CertPubKey("").setDeviceName("Test Provision device").setProvisionDeviceCredentialsMsg(ProvisionDeviceCredentialsMsg.newBuilder().setProvisionDeviceKey("testProvisionKey").setProvisionDeviceSecret("testProvisionSecret")).build().toByteArray(); + } + +} diff --git a/application/src/test/java/org/thingsboard/server/mqtt/provision/sql/MqttProvisionDeviceJsonSqlTest.java b/application/src/test/java/org/thingsboard/server/mqtt/provision/sql/MqttProvisionDeviceJsonSqlTest.java new file mode 100644 index 0000000000..c9e6495fd3 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/mqtt/provision/sql/MqttProvisionDeviceJsonSqlTest.java @@ -0,0 +1,23 @@ +/** + * 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.mqtt.provision.sql; + +import org.thingsboard.server.dao.service.DaoSqlTest; +import org.thingsboard.server.mqtt.provision.AbstractMqttProvisionJsonDeviceTest; + +@DaoSqlTest +public class MqttProvisionDeviceJsonSqlTest extends AbstractMqttProvisionJsonDeviceTest { +} diff --git a/application/src/test/java/org/thingsboard/server/mqtt/provision/sql/MqttProvisionDeviceProtoSqlTest.java b/application/src/test/java/org/thingsboard/server/mqtt/provision/sql/MqttProvisionDeviceProtoSqlTest.java new file mode 100644 index 0000000000..7a8170d01e --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/mqtt/provision/sql/MqttProvisionDeviceProtoSqlTest.java @@ -0,0 +1,23 @@ +/** + * 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.mqtt.provision.sql; + +import org.thingsboard.server.dao.service.DaoSqlTest; +import org.thingsboard.server.mqtt.provision.AbstractMqttProvisionProtoDeviceTest; + +@DaoSqlTest +public class MqttProvisionDeviceProtoSqlTest extends AbstractMqttProvisionProtoDeviceTest { +} From 4494f3dd67504a4e488869868ba3eade8bf087b7 Mon Sep 17 00:00:00 2001 From: zbeacon Date: Wed, 7 Oct 2020 12:10:05 +0300 Subject: [PATCH 20/48] Fix for tests --- .../server/mqtt/AbstractMqttIntegrationTest.java | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/application/src/test/java/org/thingsboard/server/mqtt/AbstractMqttIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/AbstractMqttIntegrationTest.java index 2cf7a0ac62..a11243533f 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/AbstractMqttIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/AbstractMqttIntegrationTest.java @@ -69,7 +69,7 @@ public abstract class AbstractMqttIntegrationTest extends AbstractControllerTest protected String gatewayAccessToken; protected void processBeforeTest (String deviceName, String gatewayName, TransportPayloadType payloadType, String telemetryTopic, String attributesTopic) throws Exception { - this.processBeforeTest(deviceName, gatewayName, payloadType, telemetryTopic, attributesTopic, null, null, null); + this.processBeforeTest(deviceName, gatewayName, payloadType, telemetryTopic, attributesTopic, DeviceProfileProvisionType.DISABLED, null, null); } protected void processBeforeTest(String deviceName, @@ -198,12 +198,6 @@ public abstract class AbstractMqttIntegrationTest extends AbstractControllerTest return keyValueProtoBuilder.build(); } - protected DeviceProfile createMqttDeviceProfile(TransportPayloadType transportPayloadType, - String telemetryTopic, String attributesTopic - ) { - return this.createMqttDeviceProfile(transportPayloadType, telemetryTopic, attributesTopic, null, null, null); - } - protected DeviceProfile createMqttDeviceProfile(TransportPayloadType transportPayloadType, String telemetryTopic, String attributesTopic, DeviceProfileProvisionType provisionType, From ab9199d8b8d323b4f79c7f066203e535f1f4f1ed Mon Sep 17 00:00:00 2001 From: Illia Barkov Date: Wed, 7 Oct 2020 12:56:35 +0300 Subject: [PATCH 21/48] Update locale.constant-en_US.json --- ui-ngx/src/assets/locale/locale.constant-en_US.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index c78d2b83cb..751ae10434 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -855,7 +855,7 @@ "provision-device-key": "Provision device key", "provision-device-key-required": "Provision device key is required.", "provision-device-secret": "Provision device secret", - "provision-device-secret-required": "Provision device secret is required." + "provision-device-secret-required": "Provision device secret is required.", "condition": "Condition", "condition-type": "Condition type", "condition-type-simple": "Simple", From 5dd460b81d616852ef1bd216a20aae0ca0fdc7c0 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Wed, 7 Oct 2020 14:13:25 +0300 Subject: [PATCH 22/48] Some improvements --- .../server/common/data/device/profile/MqttTopics.java | 9 ++++----- .../server/transport/mqtt/MqttTransportHandler.java | 7 +++++-- ui-ngx/src/assets/locale/locale.constant-en_US.json | 2 +- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttTopics.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttTopics.java index 23c173ae97..24086e4194 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttTopics.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttTopics.java @@ -40,7 +40,6 @@ public class MqttTopics { private static final String DEVICE_ATTRIBUTES_RESPONSE = ATTRIBUTES_RESPONSE + "/"; private static final String DEVICE_ATTRIBUTES_REQUEST = ATTRIBUTES_REQUEST + "/"; - private static final String PROVISION_RESPONSE = PROVISION + RESPONSE; // V1_JSON topics public static final String BASE_DEVICE_API_TOPIC = "v1/devices/me"; @@ -55,8 +54,8 @@ public class MqttTopics { public static final String DEVICE_TELEMETRY_TOPIC = BASE_DEVICE_API_TOPIC + TELEMETRY; public static final String DEVICE_CLAIM_TOPIC = BASE_DEVICE_API_TOPIC + CLAIM; public static final String DEVICE_ATTRIBUTES_TOPIC = BASE_DEVICE_API_TOPIC + ATTRIBUTES; - public static final String DEVICE_PROVISION_REQUEST_TOPIC = BASE_DEVICE_API_TOPIC + PROVISION; - public static final String DEVICE_PROVISION_RESPONSE_TOPIC = BASE_DEVICE_API_TOPIC + PROVISION_RESPONSE; + public static final String DEVICE_PROVISION_REQUEST_TOPIC = PROVISION + REQUEST; + public static final String DEVICE_PROVISION_RESPONSE_TOPIC = PROVISION + RESPONSE; // V1_JSON gateway topics @@ -69,8 +68,8 @@ public class MqttTopics { public static final String GATEWAY_RPC_TOPIC = BASE_GATEWAY_API_TOPIC + RPC; public static final String GATEWAY_ATTRIBUTES_REQUEST_TOPIC = BASE_GATEWAY_API_TOPIC + ATTRIBUTES_REQUEST; public static final String GATEWAY_ATTRIBUTES_RESPONSE_TOPIC = BASE_GATEWAY_API_TOPIC + ATTRIBUTES_RESPONSE; - public static final String GATEWAY_PROVISION_REQUEST_TOPIC = BASE_GATEWAY_API_TOPIC + PROVISION; - public static final String GATEWAY_PROVISION_RESPONSE_TOPIC = BASE_GATEWAY_API_TOPIC + PROVISION_RESPONSE; + public static final String GATEWAY_PROVISION_REQUEST_TOPIC = BASE_GATEWAY_API_TOPIC + PROVISION + REQUEST; + public static final String GATEWAY_PROVISION_RESPONSE_TOPIC = BASE_GATEWAY_API_TOPIC + PROVISION + RESPONSE; private MqttTopics() { } 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 cabae348a2..82841d4307 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 @@ -16,6 +16,7 @@ package org.thingsboard.server.transport.mqtt; import com.fasterxml.jackson.databind.JsonNode; +import com.google.gson.JsonParseException; import com.google.gson.JsonSyntaxException; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; @@ -158,7 +159,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement transportService.process(provisionRequestMsg, new DeviceProvisionCallback(ctx, msgId, provisionRequestMsg)); log.trace("[{}][{}] Processing provision publish msg [{}][{}]!", sessionId, deviceSessionCtx.getDeviceId(), topicName, msgId); } catch (Exception e) { - if (e.getCause().toString().contains("JsonSyntaxException")) { + if (e instanceof JsonParseException || (e.getCause() != null && e.getCause() instanceof JsonParseException)) { TransportProtos.ProvisionDeviceRequestMsg provisionRequestMsg = deviceSessionCtx.getContext().getProtoMqttAdaptor().convertToProvisionRequestMsg(deviceSessionCtx, mqttMsg); transportService.process(provisionRequestMsg, new DeviceProvisionCallback(ctx, msgId, provisionRequestMsg)); deviceSessionCtx.setProvisionPayloadType(TransportPayloadType.PROTOBUF); @@ -334,6 +335,9 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement } else { deviceSessionCtx.getContext().getProtoMqttAdaptor().convertToPublish(deviceSessionCtx, provisionResponseMsg).ifPresent(deviceSessionCtx.getChannel()::writeAndFlush); } + + //TODO: close session with some delay. + //transportService.getScheduler().submit task with 60 seconds delay to close the session. } catch (Exception e) { log.trace("[{}] Failed to convert device attributes response to MQTT msg", sessionId, e); } @@ -448,7 +452,6 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement ctx.writeAndFlush(createMqttConnAckMsg(CONNECTION_ACCEPTED)); } else { X509Certificate cert; - if (sslHandler != null && (cert = getX509Certificate()) != null) { processX509CertConnect(ctx, cert); } else { diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index c78d2b83cb..751ae10434 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -855,7 +855,7 @@ "provision-device-key": "Provision device key", "provision-device-key-required": "Provision device key is required.", "provision-device-secret": "Provision device secret", - "provision-device-secret-required": "Provision device secret is required." + "provision-device-secret-required": "Provision device secret is required.", "condition": "Condition", "condition-type": "Condition type", "condition-type-simple": "Simple", From adf5069da1b0f3f8f66c98e01290c70259a3f67d Mon Sep 17 00:00:00 2001 From: zbeacon Date: Thu, 8 Oct 2020 15:08:29 +0300 Subject: [PATCH 23/48] Added ability to provision credentials type from device using provision feature --- .../device/DeviceProvisionServiceImpl.java | 70 ++++++++++++++++--- .../transport/DefaultTransportApiService.java | 8 ++- .../AbstractMqttProvisionProtoDeviceTest.java | 2 +- .../device/provision/ProvisionRequest.java | 5 +- .../server/common/data/DataConstants.java | 6 ++ .../ProvisionDeviceCredentialsData.java | 27 +++++++ .../data/device/profile/MqttTopics.java | 2 - common/queue/src/main/proto/queue.proto | 20 +++++- .../transport/mqtt/MqttTransportHandler.java | 21 +++--- .../mqtt/adaptors/JsonMqttAdaptor.java | 7 -- .../mqtt/adaptors/MqttTransportAdaptor.java | 2 - .../mqtt/adaptors/ProtoMqttAdaptor.java | 9 --- .../common/transport/TransportService.java | 4 ++ .../transport/adaptor/JsonConverter.java | 20 +++++- .../service/DefaultTransportService.java | 10 +-- 15 files changed, 165 insertions(+), 48 deletions(-) create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/ProvisionDeviceCredentialsData.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 2e2f1cbb80..6f88cec33b 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 @@ -28,6 +28,7 @@ import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.audit.ActionType; +import org.thingsboard.server.common.data.device.credentials.BasicMqttCredentials; import org.thingsboard.server.common.data.device.profile.AllowCreateNewDevicesDeviceProfileProvisionConfiguration; import org.thingsboard.server.common.data.device.profile.CheckPreProvisionedDevicesDeviceProfileProvisionConfiguration; import org.thingsboard.server.common.data.id.CustomerId; @@ -118,6 +119,13 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { return Futures.immediateFuture(new ProvisionResponse(null, ProvisionResponseStatus.NOT_FOUND)); } + if (provisionRequest.getCredentialsType() != null) { + ListenableFuture error = validateCredentials(provisionRequest); + if (error != null) { + return error; + } + } + DeviceProfile targetProfile = deviceProfileDao.findByProvisionDeviceKey(provisionRequestKey); if (targetProfile == null) { @@ -152,6 +160,32 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { return Futures.immediateFuture(new ProvisionResponse(null, ProvisionResponseStatus.NOT_FOUND)); } + private ListenableFuture validateCredentials(ProvisionRequest provisionRequest) { + switch (provisionRequest.getCredentialsType()) { + case ACCESS_TOKEN: + if (StringUtils.isEmpty(provisionRequest.getCredentialsData().getToken())) { + log.error("Failed to get token from credentials data!"); + return Futures.immediateFuture(new ProvisionResponse(null, ProvisionResponseStatus.FAILURE)); + } + break; + case MQTT_BASIC: + if (StringUtils.isEmpty(provisionRequest.getCredentialsData().getClientId()) || + StringUtils.isEmpty(provisionRequest.getCredentialsData().getUsername()) || + StringUtils.isEmpty(provisionRequest.getCredentialsData().getPassword())) { + log.error("Failed to get basic mqtt credentials from credentials data!"); + return Futures.immediateFuture(new ProvisionResponse(null, ProvisionResponseStatus.FAILURE)); + } + break; + case X509_CERTIFICATE: + if (StringUtils.isEmpty(provisionRequest.getCredentialsData().getHash())) { + log.error("Failed to get hash from credentials data!"); + return Futures.immediateFuture(new ProvisionResponse(null, ProvisionResponseStatus.FAILURE)); + } + break; + } + return null; + } + private ListenableFuture processProvision(Device device, ProvisionRequest provisionRequest) { ListenableFuture> provisionStateFuture = attributesService.find(device.getTenantId(), device.getId(), DataConstants.SERVER_SCOPE, DEVICE_PROVISION_STATE); @@ -205,7 +239,7 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { return Futures.transform(saveProvisionStateAttribute(savedDevice), input -> new ProvisionResponse( - getDeviceCredentials(savedDevice, provisionRequest.getX509CertPubKey()), + getDeviceCredentials(savedDevice), ProvisionResponseStatus.SUCCESS), MoreExecutors.directExecutor()); } log.warn("[{}] The device is already provisioned!", device.getName()); @@ -224,17 +258,33 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { device.setName(provisionRequest.getDeviceName()); device.setType(profile.getName()); device.setTenantId(profile.getTenantId()); - return deviceService.saveDevice(device); + Device savedDevice = deviceService.saveDevice(device); + if (provisionRequest.getCredentialsType() != null) { + DeviceCredentials deviceCredentials = new DeviceCredentials(); + deviceCredentials.setCredentialsType(provisionRequest.getCredentialsType()); + switch (provisionRequest.getCredentialsType()) { + case ACCESS_TOKEN: + deviceCredentials.setDeviceId(savedDevice.getId()); + deviceCredentials.setCredentialsId(provisionRequest.getCredentialsData().getToken()); + break; + case MQTT_BASIC: + BasicMqttCredentials mqttCredentials = new BasicMqttCredentials(); + mqttCredentials.setClientId(provisionRequest.getCredentialsData().getClientId()); + mqttCredentials.setUserName(provisionRequest.getCredentialsData().getUsername()); + mqttCredentials.setPassword(provisionRequest.getCredentialsData().getPassword()); + deviceCredentials.setCredentialsValue(JacksonUtil.toString(mqttCredentials)); + break; + case X509_CERTIFICATE: + deviceCredentials.setCredentialsValue(provisionRequest.getCredentialsData().getHash()); + break; + } + deviceCredentialsService.updateDeviceCredentials(savedDevice.getTenantId(), deviceCredentials); + } + return savedDevice; } - private DeviceCredentials getDeviceCredentials(Device device, String x509CertPubKey) { - DeviceCredentials credentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(device.getTenantId(), device.getId()); - if (!StringUtils.isEmpty(x509CertPubKey)) { - credentials.setCredentialsType(DeviceCredentialsType.X509_CERTIFICATE); - credentials.setCredentialsValue(x509CertPubKey); - return deviceCredentialsService.updateDeviceCredentials(device.getTenantId(), credentials); - } - return credentials; + private DeviceCredentials getDeviceCredentials(Device device) { + return deviceCredentialsService.findDeviceCredentialsByDeviceId(device.getTenantId(), device.getId()); } private void pushProvisionEventToRuleEngine(ProvisionRequest request, Device device, String type) { diff --git a/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java b/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java index 7b4701754b..b2c93a0ecb 100644 --- a/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java +++ b/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java @@ -30,6 +30,7 @@ import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.device.credentials.BasicMqttCredentials; +import org.thingsboard.server.common.data.device.credentials.ProvisionDeviceCredentialsData; import org.thingsboard.server.common.data.device.profile.ProvisionDeviceProfileCredentials; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.DeviceId; @@ -281,7 +282,12 @@ public class DefaultTransportApiService implements TransportApiService { provisionResponseFuture = deviceProvisionService.provisionDevice( new ProvisionRequest( requestMsg.getDeviceName(), - requestMsg.getX509CertPubKey(), + requestMsg.getCredentialsType() != null ? DeviceCredentialsType.valueOf(requestMsg.getCredentialsType().name()) : null, + new ProvisionDeviceCredentialsData(requestMsg.getCredentialsDataProto().getValidateDeviceTokenRequestMsg().getToken(), + requestMsg.getCredentialsDataProto().getValidateBasicMqttCredRequestMsg().getClientId(), + requestMsg.getCredentialsDataProto().getValidateBasicMqttCredRequestMsg().getUserName(), + requestMsg.getCredentialsDataProto().getValidateBasicMqttCredRequestMsg().getPassword(), + requestMsg.getCredentialsDataProto().getValidateDeviceX509CertRequestMsg().getHash()), new ProvisionDeviceProfileCredentials( requestMsg.getProvisionDeviceCredentialsMsg().getProvisionDeviceKey(), requestMsg.getProvisionDeviceCredentialsMsg().getProvisionDeviceSecret()))); diff --git a/application/src/test/java/org/thingsboard/server/mqtt/provision/AbstractMqttProvisionProtoDeviceTest.java b/application/src/test/java/org/thingsboard/server/mqtt/provision/AbstractMqttProvisionProtoDeviceTest.java index 7720ddad4a..54e99077ea 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/provision/AbstractMqttProvisionProtoDeviceTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/provision/AbstractMqttProvisionProtoDeviceTest.java @@ -183,7 +183,7 @@ public abstract class AbstractMqttProvisionProtoDeviceTest extends AbstractMqttI protected byte[] createTestProvisionMessage() { - return ProvisionDeviceRequestMsg.newBuilder().setX509CertPubKey("").setDeviceName("Test Provision device").setProvisionDeviceCredentialsMsg(ProvisionDeviceCredentialsMsg.newBuilder().setProvisionDeviceKey("testProvisionKey").setProvisionDeviceSecret("testProvisionSecret")).build().toByteArray(); + return ProvisionDeviceRequestMsg.newBuilder().setDeviceName("Test Provision device").setProvisionDeviceCredentialsMsg(ProvisionDeviceCredentialsMsg.newBuilder().setProvisionDeviceKey("testProvisionKey").setProvisionDeviceSecret("testProvisionSecret")).build().toByteArray(); } } 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 1c743fd855..f45b541074 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,12 +17,15 @@ package org.thingsboard.server.dao.device.provision; import lombok.AllArgsConstructor; import lombok.Data; +import org.thingsboard.server.common.data.device.credentials.ProvisionDeviceCredentialsData; import org.thingsboard.server.common.data.device.profile.ProvisionDeviceProfileCredentials; +import org.thingsboard.server.common.data.security.DeviceCredentialsType; @Data @AllArgsConstructor public class ProvisionRequest { private String deviceName; - private String x509CertPubKey; + private DeviceCredentialsType credentialsType; + private ProvisionDeviceCredentialsData credentialsData; private ProvisionDeviceProfileCredentials 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 d2c4aaf60b..5aadca44ec 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 @@ -79,5 +79,11 @@ public class DataConstants { 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 CREDENTIALS_TYPE = "credentialsType"; + public static final String TOKEN = "token"; + public static final String HASH = "hash"; + public static final String CLIENT_ID = "clientId"; + public static final String USERNAME = "username"; + public static final String PASSWORD = "password"; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/ProvisionDeviceCredentialsData.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/ProvisionDeviceCredentialsData.java new file mode 100644 index 0000000000..a3e70afd83 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/ProvisionDeviceCredentialsData.java @@ -0,0 +1,27 @@ +/** + * 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.credentials; + +import lombok.Data; + +@Data +public class ProvisionDeviceCredentialsData { + private final String token; + private final String clientId; + private final String username; + private final String password; + private final String hash; +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttTopics.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttTopics.java index 24086e4194..b226b87999 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttTopics.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttTopics.java @@ -68,8 +68,6 @@ public class MqttTopics { public static final String GATEWAY_RPC_TOPIC = BASE_GATEWAY_API_TOPIC + RPC; public static final String GATEWAY_ATTRIBUTES_REQUEST_TOPIC = BASE_GATEWAY_API_TOPIC + ATTRIBUTES_REQUEST; public static final String GATEWAY_ATTRIBUTES_RESPONSE_TOPIC = BASE_GATEWAY_API_TOPIC + ATTRIBUTES_RESPONSE; - public static final String GATEWAY_PROVISION_REQUEST_TOPIC = BASE_GATEWAY_API_TOPIC + PROVISION + REQUEST; - public static final String GATEWAY_PROVISION_RESPONSE_TOPIC = BASE_GATEWAY_API_TOPIC + PROVISION + RESPONSE; private MqttTopics() { } diff --git a/common/queue/src/main/proto/queue.proto b/common/queue/src/main/proto/queue.proto index 6a96f8a6d6..e6dc171550 100644 --- a/common/queue/src/main/proto/queue.proto +++ b/common/queue/src/main/proto/queue.proto @@ -76,6 +76,7 @@ enum KeyValueType { enum CredentialsType { ACCESS_TOKEN = 0; X509_CERTIFICATE = 1; + BASIC_MQTT = 2; } message KeyValueProto { @@ -254,10 +255,22 @@ message DeviceCredentialsProto { string credentialsValue = 5; } +message CredentialsDataProto { + ValidateDeviceTokenRequestMsg validateDeviceTokenRequestMsg = 1; + ValidateDeviceX509CertRequestMsg validateDeviceX509CertRequestMsg = 2; + ValidateBasicMqttCredRequestMsg validateBasicMqttCredRequestMsg = 3; +} + message ProvisionDeviceRequestMsg { string deviceName = 1; - string x509CertPubKey = 2; + CredentialsType credentialsType = 2; ProvisionDeviceCredentialsMsg provisionDeviceCredentialsMsg = 3; + CredentialsDataProto credentialsDataProto = 4; +} + +message GatewayProvisionRequestMsg { + int32 requestId = 1; + ProvisionDeviceRequestMsg provisionDeviceRequestMsg = 2; } message ProvisionDeviceCredentialsMsg { @@ -270,6 +283,11 @@ message ProvisionDeviceResponseMsg { ProvisionResponseStatus provisionResponseStatus = 2; } +message GatewayProvisionResponseMsg { + int32 requestId = 1; + ProvisionDeviceResponseMsg provisionDeviceResponseMsg = 2; +} + enum ProvisionResponseStatus { UNKNOWN = 0; SUCCESS = 1; 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 82841d4307..2ef4085ac0 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 @@ -17,7 +17,6 @@ package org.thingsboard.server.transport.mqtt; import com.fasterxml.jackson.databind.JsonNode; import com.google.gson.JsonParseException; -import com.google.gson.JsonSyntaxException; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; import io.netty.handler.codec.mqtt.MqttConnAckMessage; @@ -70,11 +69,10 @@ import java.io.IOException; import java.net.InetSocketAddress; import java.util.ArrayList; import java.util.List; -import java.util.Optional; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; -import java.util.Date; +import java.util.concurrent.TimeUnit; import static io.netty.handler.codec.mqtt.MqttConnectReturnCode.CONNECTION_ACCEPTED; import static io.netty.handler.codec.mqtt.MqttConnectReturnCode.CONNECTION_REFUSED_NOT_AUTHORIZED; @@ -156,11 +154,13 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement if (topicName.equals(MqttTopics.DEVICE_PROVISION_REQUEST_TOPIC)) { try { TransportProtos.ProvisionDeviceRequestMsg provisionRequestMsg = deviceSessionCtx.getContext().getJsonMqttAdaptor().convertToProvisionRequestMsg(deviceSessionCtx, mqttMsg); + validateProvisionMessage(provisionRequestMsg); transportService.process(provisionRequestMsg, new DeviceProvisionCallback(ctx, msgId, provisionRequestMsg)); log.trace("[{}][{}] Processing provision publish msg [{}][{}]!", sessionId, deviceSessionCtx.getDeviceId(), topicName, msgId); } catch (Exception e) { if (e instanceof JsonParseException || (e.getCause() != null && e.getCause() instanceof JsonParseException)) { TransportProtos.ProvisionDeviceRequestMsg provisionRequestMsg = deviceSessionCtx.getContext().getProtoMqttAdaptor().convertToProvisionRequestMsg(deviceSessionCtx, mqttMsg); + validateProvisionMessage(provisionRequestMsg); transportService.process(provisionRequestMsg, new DeviceProvisionCallback(ctx, msgId, provisionRequestMsg)); deviceSessionCtx.setProvisionPayloadType(TransportPayloadType.PROTOBUF); log.trace("[{}][{}] Processing provision publish msg [{}][{}]!", sessionId, deviceSessionCtx.getDeviceId(), topicName, msgId); @@ -187,6 +187,13 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement } } + private void validateProvisionMessage(TransportProtos.ProvisionDeviceRequestMsg provisionRequestMsg) { + if (provisionRequestMsg.getProvisionDeviceCredentialsMsg().getProvisionDeviceKey() != null && + provisionRequestMsg.getProvisionDeviceCredentialsMsg().getProvisionDeviceSecret() != null && + provisionRequestMsg.getDeviceName() != null) + throw new RuntimeException("Wrong credentials!"); + } + private void processRegularSessionMsg(ChannelHandlerContext ctx, MqttMessage msg) { switch (msg.fixedHeader().messageType()) { case PUBLISH: @@ -335,9 +342,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement } else { deviceSessionCtx.getContext().getProtoMqttAdaptor().convertToPublish(deviceSessionCtx, provisionResponseMsg).ifPresent(deviceSessionCtx.getChannel()::writeAndFlush); } - - //TODO: close session with some delay. - //transportService.getScheduler().submit task with 60 seconds delay to close the session. + transportService.getSchedulerExecutor().schedule(() -> processDisconnect(ctx), 60, TimeUnit.SECONDS); } catch (Exception e) { log.trace("[{}] Failed to convert device attributes response to MQTT msg", sessionId, e); } @@ -379,7 +384,6 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement case MqttTopics.GATEWAY_ATTRIBUTES_TOPIC: case MqttTopics.GATEWAY_RPC_TOPIC: case MqttTopics.GATEWAY_ATTRIBUTES_RESPONSE_TOPIC: - case MqttTopics.GATEWAY_PROVISION_RESPONSE_TOPIC: case MqttTopics.DEVICE_PROVISION_RESPONSE_TOPIC: registerSubQoS(topic, grantedQoSList, reqQoS); break; @@ -447,7 +451,8 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement private void processConnect(ChannelHandlerContext ctx, MqttConnectMessage msg) { log.info("[{}] Processing connect msg for client: {}!", sessionId, msg.payload().clientIdentifier()); String userName = msg.payload().userName(); - if (DataConstants.PROVISION.equals(userName)) { + String clientId = msg.payload().clientIdentifier(); + if (DataConstants.PROVISION.equals(userName) || DataConstants.PROVISION.equals(clientId)) { deviceSessionCtx.setProvisionOnly(true); ctx.writeAndFlush(createMqttConnAckMsg(CONNECTION_ACCEPTED)); } else { diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/JsonMqttAdaptor.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/JsonMqttAdaptor.java index 54f8075c24..0799d29c57 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/JsonMqttAdaptor.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/JsonMqttAdaptor.java @@ -153,13 +153,6 @@ public class JsonMqttAdaptor implements MqttTransportAdaptor { return Optional.of(createMqttPublishMsg(ctx, MqttTopics.DEVICE_PROVISION_RESPONSE_TOPIC, JsonConverter.toJson(provisionResponse))); } - @Override - public Optional convertToGatewayPublish(MqttDeviceAwareSessionContext ctx, String deviceName, TransportProtos.ProvisionDeviceResponseMsg responseMsg) { - return Optional.of(createMqttPublishMsg(ctx, - MqttTopics.GATEWAY_PROVISION_RESPONSE_TOPIC, - JsonConverter.toGatewayJson(deviceName, responseMsg))); - } - public static JsonElement validateJsonPayload(UUID sessionId, ByteBuf payloadData) throws AdaptorException { String payload = validatePayload(sessionId, payloadData, false); try { diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/MqttTransportAdaptor.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/MqttTransportAdaptor.java index c0f1dd2369..bcb2401ff7 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/MqttTransportAdaptor.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/MqttTransportAdaptor.java @@ -69,6 +69,4 @@ public interface MqttTransportAdaptor { Optional convertToPublish(MqttDeviceAwareSessionContext ctx, ProvisionDeviceResponseMsg provisionResponse) throws AdaptorException; - Optional convertToGatewayPublish(MqttDeviceAwareSessionContext ctx, String deviceName, ProvisionDeviceResponseMsg provisionResponse) throws AdaptorException; - } diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/ProtoMqttAdaptor.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/ProtoMqttAdaptor.java index d5e9599812..1a0d33cde4 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/ProtoMqttAdaptor.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/ProtoMqttAdaptor.java @@ -187,15 +187,6 @@ public class ProtoMqttAdaptor implements MqttTransportAdaptor { return Optional.of(createMqttPublishMsg(ctx, MqttTopics.GATEWAY_RPC_TOPIC, payloadBytes)); } - @Override - public Optional convertToGatewayPublish(MqttDeviceAwareSessionContext ctx, String deviceName, TransportProtos.ProvisionDeviceResponseMsg responseMsg) throws AdaptorException { - TransportApiProtos.GatewayProvisionResponseMsg.Builder builder = TransportApiProtos.GatewayProvisionResponseMsg.newBuilder(); - builder.setDeviceName(deviceName); - builder.setProvisionDeviceResponseMsg(responseMsg); - byte[] payloadBytes = builder.build().toByteArray(); - return Optional.of(createMqttPublishMsg(ctx, MqttTopics.GATEWAY_PROVISION_RESPONSE_TOPIC, payloadBytes)); - } - public static byte[] toBytes(ByteBuf inbound) { byte[] bytes = new byte[inbound.readableBytes()]; int readerIndex = inbound.readerIndex(); diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java index 995b9eca91..775a91f720 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java @@ -40,6 +40,8 @@ import org.thingsboard.server.gen.transport.TransportProtos.ValidateBasicMqttCre import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceTokenRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceX509CertRequestMsg; +import java.util.concurrent.ScheduledExecutorService; + /** * Created by ashvayka on 04.10.18. */ @@ -88,6 +90,8 @@ public interface TransportService { void process(SessionInfoProto sessionInfo, ClaimDeviceMsg msg, TransportServiceCallback callback); + ScheduledExecutorService getSchedulerExecutor(); + void registerAsyncSession(SessionInfoProto sessionInfo, SessionMsgListener listener); void registerSyncSession(SessionInfoProto sessionInfo, SessionMsgListener listener, long timeout); diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java index cd070e6927..a2cba83360 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java @@ -37,6 +37,8 @@ import org.thingsboard.server.common.data.kv.StringDataEntry; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos.AttributeUpdateNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.ClaimDeviceMsg; +import org.thingsboard.server.gen.transport.TransportProtos.CredentialsDataProto.Builder; +import org.thingsboard.server.gen.transport.TransportProtos.CredentialsType; import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeResponseMsg; import org.thingsboard.server.gen.transport.TransportProtos.KeyValueProto; import org.thingsboard.server.gen.transport.TransportProtos.KeyValueType; @@ -46,7 +48,11 @@ import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceRespo import org.thingsboard.server.gen.transport.TransportProtos.ProvisionResponseStatus; import org.thingsboard.server.gen.transport.TransportProtos.TsKvListProto; import org.thingsboard.server.gen.transport.TransportProtos.TsKvProto; +import org.thingsboard.server.gen.transport.TransportProtos.ValidateBasicMqttCredRequestMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceTokenRequestMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceX509CertRequestMsg; +import javax.xml.crypto.Data; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; @@ -554,7 +560,17 @@ public class JsonConverter { private static TransportProtos.ProvisionDeviceRequestMsg buildProvisionRequestMsg(JsonObject jo) { return TransportProtos.ProvisionDeviceRequestMsg.newBuilder() .setDeviceName(getStrValue(jo, DataConstants.DEVICE_NAME, true)) - .setX509CertPubKey(getStrValue(jo, DataConstants.CERT_PUB_KEY, false)) + .setCredentialsType(TransportProtos.CredentialsType.valueOf(getStrValue(jo, DataConstants.CREDENTIALS_TYPE, false))) + .setCredentialsDataProto(TransportProtos.CredentialsDataProto.newBuilder() + .setValidateDeviceTokenRequestMsg(ValidateDeviceTokenRequestMsg.newBuilder().setToken(getStrValue(jo, DataConstants.TOKEN, false)).build()) + .setValidateBasicMqttCredRequestMsg(ValidateBasicMqttCredRequestMsg.newBuilder() + .setClientId(getStrValue(jo, DataConstants.CLIENT_ID, false)) + .setUserName(getStrValue(jo, DataConstants.USERNAME, false)) + .setPassword(getStrValue(jo, DataConstants.PASSWORD, false)) + .build()) + .setValidateDeviceX509CertRequestMsg(ValidateDeviceX509CertRequestMsg.newBuilder() + .setHash(getStrValue(jo, DataConstants.PASSWORD, false)).build()) + .build()) .setProvisionDeviceCredentialsMsg(buildProvisionDeviceCredentialsMsg( getStrValue(jo, DataConstants.PROVISION_KEY, true), getStrValue(jo, DataConstants.PROVISION_SECRET, true))) @@ -567,6 +583,8 @@ public class JsonConverter { .setProvisionDeviceSecret(provisionSecret) .build(); } + + private static String getStrValue(JsonObject jo, String field, boolean requiredField) { if (jo.has(field)) { return jo.get(field).getAsString(); diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java index 58b06e696e..47abc07286 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java @@ -34,7 +34,6 @@ import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.msg.TbMsg; -import org.thingsboard.server.common.msg.TbMsgDataType; import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.common.msg.queue.ServiceQueue; import org.thingsboard.server.common.msg.queue.ServiceType; @@ -49,7 +48,6 @@ import org.thingsboard.server.common.transport.TransportServiceCallback; import org.thingsboard.server.common.transport.auth.GetOrCreateDeviceFromGatewayResponse; import org.thingsboard.server.common.transport.auth.TransportDeviceInfo; import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; -import org.thingsboard.server.common.transport.util.DataDecodingEncodingService; import org.thingsboard.server.common.transport.util.JsonUtils; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceRequestMsg; @@ -77,11 +75,9 @@ import org.thingsboard.server.common.stats.StatsType; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; -import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Map; -import java.util.Optional; import java.util.Random; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; @@ -93,7 +89,6 @@ import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; -import java.util.function.Function; /** * Created by ashvayka on 17.10.18. @@ -236,6 +231,11 @@ public class DefaultTransportService implements TransportService { } } + @Override + public ScheduledExecutorService getSchedulerExecutor(){ + return this.schedulerExecutor; + } + @Override public void registerAsyncSession(TransportProtos.SessionInfoProto sessionInfo, SessionMsgListener listener) { sessions.putIfAbsent(toSessionId(sessionInfo), new SessionMetaData(sessionInfo, TransportProtos.SessionType.ASYNC, listener)); From 6df92197900df6cb750920100eebc0b1d2265354 Mon Sep 17 00:00:00 2001 From: zbeacon Date: Thu, 8 Oct 2020 16:13:45 +0300 Subject: [PATCH 24/48] Improvements for provisioning --- .../device/DeviceProvisionServiceImpl.java | 15 +++++++++------ .../transport/mqtt/MqttTransportHandler.java | 11 +---------- .../common/transport/adaptor/JsonConverter.java | 6 ++---- .../common/transport/adaptor/ProtoConverter.java | 3 --- 4 files changed, 12 insertions(+), 23 deletions(-) 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 6f88cec33b..e03f5e9493 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 @@ -38,7 +38,6 @@ import org.thingsboard.server.common.data.kv.AttributeKvEntry; import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry; import org.thingsboard.server.common.data.kv.StringDataEntry; import org.thingsboard.server.common.data.security.DeviceCredentials; -import org.thingsboard.server.common.data.security.DeviceCredentialsType; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.common.msg.queue.ServiceType; @@ -134,9 +133,9 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { Device targetDevice = deviceDao.findDeviceByTenantIdAndName(targetProfile.getTenantId().getId(), provisionRequest.getDeviceName()).orElse(null); - switch(targetProfile.getProvisionType()) { + switch (targetProfile.getProvisionType()) { case ALLOW_CREATE_NEW_DEVICES: - if (((AllowCreateNewDevicesDeviceProfileProvisionConfiguration) targetProfile.getProfileData().getProvisionConfiguration()).getProvisionDeviceSecret().equals(provisionRequestSecret)){ + if (((AllowCreateNewDevicesDeviceProfileProvisionConfiguration) targetProfile.getProfileData().getProvisionConfiguration()).getProvisionDeviceSecret().equals(provisionRequestSecret)) { if (targetDevice != null) { log.warn("[{}] The device is present and could not be provisioned once more!", targetDevice.getName()); notify(targetDevice, provisionRequest, DataConstants.PROVISION_FAILURE, false); @@ -170,8 +169,8 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { break; case MQTT_BASIC: if (StringUtils.isEmpty(provisionRequest.getCredentialsData().getClientId()) || - StringUtils.isEmpty(provisionRequest.getCredentialsData().getUsername()) || - StringUtils.isEmpty(provisionRequest.getCredentialsData().getPassword())) { + StringUtils.isEmpty(provisionRequest.getCredentialsData().getUsername()) || + StringUtils.isEmpty(provisionRequest.getCredentialsData().getPassword())) { log.error("Failed to get basic mqtt credentials from credentials data!"); return Futures.immediateFuture(new ProvisionResponse(null, ProvisionResponseStatus.FAILURE)); } @@ -259,7 +258,11 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { device.setType(profile.getName()); device.setTenantId(profile.getTenantId()); Device savedDevice = deviceService.saveDevice(device); - if (provisionRequest.getCredentialsType() != null) { + if (!StringUtils.isEmpty(provisionRequest.getCredentialsData().getToken()) || + !StringUtils.isEmpty(provisionRequest.getCredentialsData().getHash()) || + !StringUtils.isEmpty(provisionRequest.getCredentialsData().getUsername()) || + !StringUtils.isEmpty(provisionRequest.getCredentialsData().getPassword()) || + !StringUtils.isEmpty(provisionRequest.getCredentialsData().getClientId())) { DeviceCredentials deviceCredentials = new DeviceCredentials(); deviceCredentials.setCredentialsType(provisionRequest.getCredentialsType()); switch (provisionRequest.getCredentialsType()) { 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 2ef4085ac0..fab0579bb2 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 @@ -154,13 +154,11 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement if (topicName.equals(MqttTopics.DEVICE_PROVISION_REQUEST_TOPIC)) { try { TransportProtos.ProvisionDeviceRequestMsg provisionRequestMsg = deviceSessionCtx.getContext().getJsonMqttAdaptor().convertToProvisionRequestMsg(deviceSessionCtx, mqttMsg); - validateProvisionMessage(provisionRequestMsg); transportService.process(provisionRequestMsg, new DeviceProvisionCallback(ctx, msgId, provisionRequestMsg)); log.trace("[{}][{}] Processing provision publish msg [{}][{}]!", sessionId, deviceSessionCtx.getDeviceId(), topicName, msgId); } catch (Exception e) { if (e instanceof JsonParseException || (e.getCause() != null && e.getCause() instanceof JsonParseException)) { TransportProtos.ProvisionDeviceRequestMsg provisionRequestMsg = deviceSessionCtx.getContext().getProtoMqttAdaptor().convertToProvisionRequestMsg(deviceSessionCtx, mqttMsg); - validateProvisionMessage(provisionRequestMsg); transportService.process(provisionRequestMsg, new DeviceProvisionCallback(ctx, msgId, provisionRequestMsg)); deviceSessionCtx.setProvisionPayloadType(TransportPayloadType.PROTOBUF); log.trace("[{}][{}] Processing provision publish msg [{}][{}]!", sessionId, deviceSessionCtx.getDeviceId(), topicName, msgId); @@ -187,13 +185,6 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement } } - private void validateProvisionMessage(TransportProtos.ProvisionDeviceRequestMsg provisionRequestMsg) { - if (provisionRequestMsg.getProvisionDeviceCredentialsMsg().getProvisionDeviceKey() != null && - provisionRequestMsg.getProvisionDeviceCredentialsMsg().getProvisionDeviceSecret() != null && - provisionRequestMsg.getDeviceName() != null) - throw new RuntimeException("Wrong credentials!"); - } - private void processRegularSessionMsg(ChannelHandlerContext ctx, MqttMessage msg) { switch (msg.fixedHeader().messageType()) { case PUBLISH: @@ -493,7 +484,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement private void processX509CertConnect(ChannelHandlerContext ctx, X509Certificate cert) { try { - if(!context.isSkipValidityCheckForClientCert()){ + if (!context.isSkipValidityCheckForClientCert()) { cert.checkValidity(); } String strCert = SslUtil.getX509CertificateString(cert); diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java index a2cba83360..dd6938e17f 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java @@ -37,7 +37,6 @@ import org.thingsboard.server.common.data.kv.StringDataEntry; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos.AttributeUpdateNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.ClaimDeviceMsg; -import org.thingsboard.server.gen.transport.TransportProtos.CredentialsDataProto.Builder; import org.thingsboard.server.gen.transport.TransportProtos.CredentialsType; import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeResponseMsg; import org.thingsboard.server.gen.transport.TransportProtos.KeyValueProto; @@ -52,7 +51,6 @@ import org.thingsboard.server.gen.transport.TransportProtos.ValidateBasicMqttCre import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceTokenRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceX509CertRequestMsg; -import javax.xml.crypto.Data; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; @@ -560,7 +558,7 @@ public class JsonConverter { private static TransportProtos.ProvisionDeviceRequestMsg buildProvisionRequestMsg(JsonObject jo) { return TransportProtos.ProvisionDeviceRequestMsg.newBuilder() .setDeviceName(getStrValue(jo, DataConstants.DEVICE_NAME, true)) - .setCredentialsType(TransportProtos.CredentialsType.valueOf(getStrValue(jo, DataConstants.CREDENTIALS_TYPE, false))) + .setCredentialsType(jo.get(DataConstants.CREDENTIALS_TYPE) != null ? TransportProtos.CredentialsType.valueOf(getStrValue(jo, DataConstants.CREDENTIALS_TYPE, false)) : CredentialsType.ACCESS_TOKEN) .setCredentialsDataProto(TransportProtos.CredentialsDataProto.newBuilder() .setValidateDeviceTokenRequestMsg(ValidateDeviceTokenRequestMsg.newBuilder().setToken(getStrValue(jo, DataConstants.TOKEN, false)).build()) .setValidateBasicMqttCredRequestMsg(ValidateBasicMqttCredRequestMsg.newBuilder() @@ -570,7 +568,7 @@ public class JsonConverter { .build()) .setValidateDeviceX509CertRequestMsg(ValidateDeviceX509CertRequestMsg.newBuilder() .setHash(getStrValue(jo, DataConstants.PASSWORD, false)).build()) - .build()) + .build()) .setProvisionDeviceCredentialsMsg(buildProvisionDeviceCredentialsMsg( getStrValue(jo, DataConstants.PROVISION_KEY, true), getStrValue(jo, DataConstants.PROVISION_SECRET, true))) diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/ProtoConverter.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/ProtoConverter.java index 26e7a9cf9e..2c8ca7e3b5 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/ProtoConverter.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/ProtoConverter.java @@ -15,10 +15,7 @@ */ package org.thingsboard.server.common.transport.adaptor; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; import com.google.gson.JsonParser; -import com.google.gson.JsonSyntaxException; import com.google.protobuf.InvalidProtocolBufferException; import lombok.extern.slf4j.Slf4j; import org.springframework.util.CollectionUtils; From 17ca3cfab4e5f9cf47949318d4e3fe6626eed73c Mon Sep 17 00:00:00 2001 From: zbeacon Date: Thu, 8 Oct 2020 16:51:28 +0300 Subject: [PATCH 25/48] Refactoring --- .../server/service/device/DeviceProvisionServiceImpl.java | 3 ++- .../server/service/transport/DefaultTransportApiService.java | 3 +-- common/queue/src/main/proto/queue.proto | 2 +- .../server/common/transport/adaptor/JsonConverter.java | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) 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 e03f5e9493..b6a94bf9b3 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 @@ -263,7 +263,7 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { !StringUtils.isEmpty(provisionRequest.getCredentialsData().getUsername()) || !StringUtils.isEmpty(provisionRequest.getCredentialsData().getPassword()) || !StringUtils.isEmpty(provisionRequest.getCredentialsData().getClientId())) { - DeviceCredentials deviceCredentials = new DeviceCredentials(); + DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(savedDevice.getTenantId(), savedDevice.getId()); deviceCredentials.setCredentialsType(provisionRequest.getCredentialsType()); switch (provisionRequest.getCredentialsType()) { case ACCESS_TOKEN: @@ -281,6 +281,7 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { deviceCredentials.setCredentialsValue(provisionRequest.getCredentialsData().getHash()); break; } + deviceCredentials.setCredentialsType(provisionRequest.getCredentialsType()); deviceCredentialsService.updateDeviceCredentials(savedDevice.getTenantId(), deviceCredentials); } return savedDevice; diff --git a/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java b/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java index b2c93a0ecb..eb2fdbc65e 100644 --- a/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java +++ b/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java @@ -315,8 +315,7 @@ public class DefaultTransportApiService implements TransportApiService { return TransportProtos.DeviceCredentialsProto.newBuilder() .setDeviceIdMSB(deviceCredentials.getDeviceId().getId().getMostSignificantBits()) .setDeviceIdLSB(deviceCredentials.getDeviceId().getId().getLeastSignificantBits()) - .setCredentialsType(deviceCredentials.getCredentialsType() == DeviceCredentialsType.ACCESS_TOKEN ? - CredentialsType.ACCESS_TOKEN : CredentialsType.X509_CERTIFICATE) + .setCredentialsType(TransportProtos.CredentialsType.valueOf(deviceCredentials.getCredentialsType().name())) .setCredentialsId(deviceCredentials.getCredentialsId()) .setCredentialsValue(deviceCredentials.getCredentialsValue() != null ? deviceCredentials.getCredentialsValue() : "") .build(); diff --git a/common/queue/src/main/proto/queue.proto b/common/queue/src/main/proto/queue.proto index e6dc171550..7c2ae80f9b 100644 --- a/common/queue/src/main/proto/queue.proto +++ b/common/queue/src/main/proto/queue.proto @@ -76,7 +76,7 @@ enum KeyValueType { enum CredentialsType { ACCESS_TOKEN = 0; X509_CERTIFICATE = 1; - BASIC_MQTT = 2; + MQTT_BASIC = 2; } message KeyValueProto { diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java index dd6938e17f..321ed22baa 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java @@ -567,7 +567,7 @@ public class JsonConverter { .setPassword(getStrValue(jo, DataConstants.PASSWORD, false)) .build()) .setValidateDeviceX509CertRequestMsg(ValidateDeviceX509CertRequestMsg.newBuilder() - .setHash(getStrValue(jo, DataConstants.PASSWORD, false)).build()) + .setHash(getStrValue(jo, DataConstants.HASH, false)).build()) .build()) .setProvisionDeviceCredentialsMsg(buildProvisionDeviceCredentialsMsg( getStrValue(jo, DataConstants.PROVISION_KEY, true), From 6b72e82104a2cb0f20efc3e036bd72d804e41f4e Mon Sep 17 00:00:00 2001 From: zbeacon Date: Thu, 8 Oct 2020 17:30:35 +0300 Subject: [PATCH 26/48] Improvement --- .../server/service/device/DeviceProvisionServiceImpl.java | 6 ------ 1 file changed, 6 deletions(-) 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 b6a94bf9b3..5a007d27ca 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 @@ -161,12 +161,6 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { private ListenableFuture validateCredentials(ProvisionRequest provisionRequest) { switch (provisionRequest.getCredentialsType()) { - case ACCESS_TOKEN: - if (StringUtils.isEmpty(provisionRequest.getCredentialsData().getToken())) { - log.error("Failed to get token from credentials data!"); - return Futures.immediateFuture(new ProvisionResponse(null, ProvisionResponseStatus.FAILURE)); - } - break; case MQTT_BASIC: if (StringUtils.isEmpty(provisionRequest.getCredentialsData().getClientId()) || StringUtils.isEmpty(provisionRequest.getCredentialsData().getUsername()) || From d032b0a3f7a5394cfe3bbefb54a5e1ff96ce7f3e Mon Sep 17 00:00:00 2001 From: zbeacon Date: Fri, 9 Oct 2020 09:28:04 +0300 Subject: [PATCH 27/48] Adding tests --- .../AbstractMqttProvisionJsonDeviceTest.java | 115 +++++++++++++-- .../AbstractMqttProvisionProtoDeviceTest.java | 138 ++++++++++++++++-- 2 files changed, 235 insertions(+), 18 deletions(-) diff --git a/application/src/test/java/org/thingsboard/server/mqtt/provision/AbstractMqttProvisionJsonDeviceTest.java b/application/src/test/java/org/thingsboard/server/mqtt/provision/AbstractMqttProvisionJsonDeviceTest.java index 26d7e2afce..7c341029b6 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/provision/AbstractMqttProvisionJsonDeviceTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/provision/AbstractMqttProvisionJsonDeviceTest.java @@ -29,12 +29,15 @@ import org.springframework.beans.factory.annotation.Autowired; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfileProvisionType; import org.thingsboard.server.common.data.TransportPayloadType; +import org.thingsboard.server.common.data.device.credentials.BasicMqttCredentials; import org.thingsboard.server.common.data.device.profile.MqttTopics; import org.thingsboard.server.common.data.security.DeviceCredentials; +import org.thingsboard.server.common.msg.EncryptionUtil; import org.thingsboard.server.common.transport.util.JsonUtils; import org.thingsboard.server.dao.device.DeviceCredentialsService; import org.thingsboard.server.dao.device.DeviceService; import org.thingsboard.server.dao.device.provision.ProvisionResponseStatus; +import org.thingsboard.server.dao.util.mapping.JacksonUtil; import org.thingsboard.server.mqtt.AbstractMqttIntegrationTest; import java.util.concurrent.CountDownLatch; @@ -65,8 +68,23 @@ public abstract class AbstractMqttProvisionJsonDeviceTest extends AbstractMqttIn } @Test - public void testProvisioningCreateNewDevice() throws Exception { - processTestProvisioningCreateNewDevice(); + public void testProvisioningCreateNewDeviceWithoutCredentials() throws Exception { + processTestProvisioningCreateNewDeviceWithoutCredentials(); + } + + @Test + public void testProvisioningCreateNewDeviceWithAccessToken() throws Exception { + processTestProvisioningCreateNewDeviceWithAccessToken(); + } + + @Test + public void testProvisioningCreateNewDeviceWithCert() throws Exception { + processTestProvisioningCreateNewDeviceWithCert(); + } + + @Test + public void testProvisioningCreateNewDeviceWithMqttBasic() throws Exception { + processTestProvisioningCreateNewDeviceWithMqttBasic(); } @Test @@ -84,7 +102,7 @@ public abstract class AbstractMqttProvisionJsonDeviceTest extends AbstractMqttIn } - protected void processTestProvisioningCreateNewDevice() throws Exception { + protected void processTestProvisioningCreateNewDeviceWithoutCredentials() throws Exception { super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.JSON, null, null, DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES, "testProvisionKey", "testProvisionSecret"); byte[] result = createMqttClientAndPublish().getPayloadBytes(); JsonObject response = JsonUtils.parse(new String(result)).getAsJsonObject(); @@ -101,6 +119,83 @@ public abstract class AbstractMqttProvisionJsonDeviceTest extends AbstractMqttIn Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.get("provisionDeviceStatus").getAsString()); } + + protected void processTestProvisioningCreateNewDeviceWithAccessToken() throws Exception { + super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.JSON, null, null, DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES, "testProvisionKey", "testProvisionSecret"); + String requestCredentials = ",\"credentialsType\": \"ACCESS_TOKEN\",\"token\": \"test_token\""; + byte[] result = createMqttClientAndPublish(requestCredentials).getPayloadBytes(); + JsonObject response = JsonUtils.parse(new String(result)).getAsJsonObject(); + + Device createdDevice = deviceService.findDeviceByTenantIdAndName(savedTenant.getTenantId(), "Test Provision device"); + + Assert.assertNotNull(createdDevice); + Assert.assertEquals(createdDevice.getId().toString(), response.get("deviceId").getAsString()); + + DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(savedTenant.getTenantId(), createdDevice.getId()); + + Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.get("credentialsType").getAsString()); + Assert.assertEquals(deviceCredentials.getCredentialsId(), response.get("credentialsId").getAsString()); + Assert.assertEquals(deviceCredentials.getCredentialsType().name(), "ACCESS_TOKEN"); + Assert.assertEquals(deviceCredentials.getCredentialsId(), "test_token"); + Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.get("provisionDeviceStatus").getAsString()); + } + + + protected void processTestProvisioningCreateNewDeviceWithCert() throws Exception { + super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.JSON, null, null, DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES, "testProvisionKey", "testProvisionSecret"); + String requestCredentials = ",\"credentialsType\": \"X509_CERTIFICATE\",\"hash\": \"testHash\""; + byte[] result = createMqttClientAndPublish(requestCredentials).getPayloadBytes(); + JsonObject response = JsonUtils.parse(new String(result)).getAsJsonObject(); + + Device createdDevice = deviceService.findDeviceByTenantIdAndName(savedTenant.getTenantId(), "Test Provision device"); + + Assert.assertNotNull(createdDevice); + Assert.assertEquals(createdDevice.getId().toString(), response.get("deviceId").getAsString()); + + DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(savedTenant.getTenantId(), createdDevice.getId()); + + Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.get("credentialsType").getAsString()); + Assert.assertEquals(deviceCredentials.getCredentialsId(), response.get("credentialsId").getAsString()); + Assert.assertEquals(deviceCredentials.getCredentialsType().name(), "X509_CERTIFICATE"); + + String cert = EncryptionUtil.trimNewLines(deviceCredentials.getCredentialsValue()); + String sha3Hash = EncryptionUtil.getSha3Hash(cert); + + Assert.assertEquals(deviceCredentials.getCredentialsId(), sha3Hash); + + Assert.assertEquals(deviceCredentials.getCredentialsValue(), "testHash"); + Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.get("provisionDeviceStatus").getAsString()); + } + + + protected void processTestProvisioningCreateNewDeviceWithMqttBasic() throws Exception { + super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.JSON, null, null, DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES, "testProvisionKey", "testProvisionSecret"); + String requestCredentials = ",\"credentialsType\": \"MQTT_BASIC\",\"clientId\": \"test_clientId\",\"username\": \"test_username\",\"password\": \"test_password\""; + byte[] result = createMqttClientAndPublish(requestCredentials).getPayloadBytes(); + JsonObject response = JsonUtils.parse(new String(result)).getAsJsonObject(); + + Device createdDevice = deviceService.findDeviceByTenantIdAndName(savedTenant.getTenantId(), "Test Provision device"); + + Assert.assertNotNull(createdDevice); + Assert.assertEquals(createdDevice.getId().toString(), response.get("deviceId").getAsString()); + + DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(savedTenant.getTenantId(), createdDevice.getId()); + + Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.get("credentialsType").getAsString()); + Assert.assertEquals(deviceCredentials.getCredentialsId(), response.get("credentialsId").getAsString()); + Assert.assertEquals(deviceCredentials.getCredentialsType().name(), "MQTT_BASIC"); + Assert.assertEquals(deviceCredentials.getCredentialsId(), EncryptionUtil.getSha3Hash("|", "test_clientId", "test_username")); + + BasicMqttCredentials mqttCredentials = new BasicMqttCredentials(); + mqttCredentials.setClientId("test_clientId"); + mqttCredentials.setUserName("test_username"); + mqttCredentials.setPassword("test_password"); + + Assert.assertEquals(deviceCredentials.getCredentialsValue(), JacksonUtil.toString(mqttCredentials)); + Assert.assertEquals(deviceCredentials.getCredentialsId(), response.get("credentialsId").getAsString()); + Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.get("provisionDeviceStatus").getAsString()); + } + protected void processTestProvisioningCheckPreProvisionedDevice() throws Exception { super.processBeforeTest("Test Provision device", "Test Provision gateway", TransportPayloadType.JSON, null, null, DeviceProfileProvisionType.CHECK_PRE_PROVISIONED_DEVICES, "testProvisionKey", "testProvisionSecret"); byte[] result = createMqttClientAndPublish().getPayloadBytes(); @@ -122,8 +217,12 @@ public abstract class AbstractMqttProvisionJsonDeviceTest extends AbstractMqttIn Assert.assertEquals(ProvisionResponseStatus.NOT_FOUND.name(), response.get("provisionDeviceStatus").getAsString()); } - protected TestMqttCallback createMqttClientAndPublish() throws Exception{ - String provisionRequestMsg = createTestProvisionMessage(); + protected TestMqttCallback createMqttClientAndPublish() throws Exception { + return createMqttClientAndPublish(""); + } + + protected TestMqttCallback createMqttClientAndPublish(String deviceCredentials) throws Exception { + String provisionRequestMsg = createTestProvisionMessage(deviceCredentials); MqttAsyncClient client = getMqttAsyncClient("provision"); TestMqttCallback onProvisionCallback = getTestMqttCallback(); client.setCallback(onProvisionCallback); @@ -180,9 +279,7 @@ public abstract class AbstractMqttProvisionJsonDeviceTest extends AbstractMqttIn } } - - protected String createTestProvisionMessage() { - return "{\"deviceName\":\"Test Provision device\",\"provisionDeviceKey\":\"testProvisionKey\", \"provisionDeviceSecret\":\"testProvisionSecret\"}"; + protected String createTestProvisionMessage(String deviceCredentials) { + return "{\"deviceName\":\"Test Provision device\",\"provisionDeviceKey\":\"testProvisionKey\", \"provisionDeviceSecret\":\"testProvisionSecret\"" + deviceCredentials + "}"; } - } diff --git a/application/src/test/java/org/thingsboard/server/mqtt/provision/AbstractMqttProvisionProtoDeviceTest.java b/application/src/test/java/org/thingsboard/server/mqtt/provision/AbstractMqttProvisionProtoDeviceTest.java index 54e99077ea..c5becedbbd 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/provision/AbstractMqttProvisionProtoDeviceTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/provision/AbstractMqttProvisionProtoDeviceTest.java @@ -30,16 +30,25 @@ import org.springframework.beans.factory.annotation.Autowired; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfileProvisionType; import org.thingsboard.server.common.data.TransportPayloadType; +import org.thingsboard.server.common.data.device.credentials.BasicMqttCredentials; import org.thingsboard.server.common.data.device.profile.MqttTopics; import org.thingsboard.server.common.data.security.DeviceCredentials; +import org.thingsboard.server.common.data.security.DeviceCredentialsType; +import org.thingsboard.server.common.msg.EncryptionUtil; import org.thingsboard.server.common.transport.util.JsonUtils; import org.thingsboard.server.dao.device.DeviceCredentialsService; import org.thingsboard.server.dao.device.DeviceService; import org.thingsboard.server.dao.device.provision.ProvisionResponseStatus; +import org.thingsboard.server.dao.util.mapping.JacksonUtil; import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.gen.transport.TransportProtos.CredentialsDataProto; +import org.thingsboard.server.gen.transport.TransportProtos.CredentialsType; import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceCredentialsMsg; import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceResponseMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ValidateBasicMqttCredRequestMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceTokenRequestMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceX509CertRequestMsg; import org.thingsboard.server.mqtt.AbstractMqttIntegrationTest; import java.util.UUID; @@ -71,8 +80,23 @@ public abstract class AbstractMqttProvisionProtoDeviceTest extends AbstractMqttI } @Test - public void testProvisioningCreateNewDevice() throws Exception { - processTestProvisioningCreateNewDevice(); + public void testProvisioningCreateNewDeviceWithoutCredentials() throws Exception { + processTestProvisioningCreateNewDeviceWithoutCredentials(); + } + + @Test + public void testProvisioningCreateNewDeviceWithAccessToken() throws Exception { + processTestProvisioningCreateNewDeviceWithAccessToken(); + } + + @Test + public void testProvisioningCreateNewDeviceWithCert() throws Exception { + processTestProvisioningCreateNewDeviceWithCert(); + } + + @Test + public void testProvisioningCreateNewDeviceWithMqttBasic() throws Exception { + processTestProvisioningCreateNewDeviceWithMqttBasic(); } @Test @@ -88,10 +112,79 @@ public abstract class AbstractMqttProvisionProtoDeviceTest extends AbstractMqttI Assert.assertEquals(ProvisionResponseStatus.NOT_FOUND.name(), result.getProvisionResponseStatus().toString()); } + protected void processTestProvisioningCreateNewDeviceWithoutCredentials() throws Exception { + super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.PROTOBUF, null, null, DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES, "testProvisionKey", "testProvisionSecret"); + ProvisionDeviceResponseMsg response = ProvisionDeviceResponseMsg.parseFrom(createMqttClientAndPublish().getPayloadBytes()); + + Device createdDevice = deviceService.findDeviceByTenantIdAndName(savedTenant.getTenantId(), "Test Provision device"); + + Assert.assertNotNull(createdDevice); + Assert.assertEquals(createdDevice.getId().getId(), new UUID(response.getDeviceCredentials().getDeviceIdMSB(), response.getDeviceCredentials().getDeviceIdLSB())); + + DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(savedTenant.getTenantId(), createdDevice.getId()); + + Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.getDeviceCredentials().getCredentialsType().toString()); + Assert.assertEquals(deviceCredentials.getCredentialsId(), response.getDeviceCredentials().getCredentialsId()); + Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.getProvisionResponseStatus().toString()); + } - protected void processTestProvisioningCreateNewDevice() throws Exception { + protected void processTestProvisioningCreateNewDeviceWithAccessToken() throws Exception { super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.JSON, null, null, DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES, "testProvisionKey", "testProvisionSecret"); - ProvisionDeviceResponseMsg response = ProvisionDeviceResponseMsg.parseFrom(createMqttClientAndPublish().getPayloadBytes()); + CredentialsDataProto requestCredentials = CredentialsDataProto.newBuilder().setValidateDeviceTokenRequestMsg(ValidateDeviceTokenRequestMsg.newBuilder().setToken("test_token").build()).build(); + + ProvisionDeviceResponseMsg response = ProvisionDeviceResponseMsg.parseFrom(createMqttClientAndPublish(createTestsProvisionMessage(CredentialsType.ACCESS_TOKEN, requestCredentials)).getPayloadBytes()); + + Device createdDevice = deviceService.findDeviceByTenantIdAndName(savedTenant.getTenantId(), "Test Provision device"); + + Assert.assertNotNull(createdDevice); + Assert.assertEquals(createdDevice.getId().getId(), new UUID(response.getDeviceCredentials().getDeviceIdMSB(), response.getDeviceCredentials().getDeviceIdLSB())); + + DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(savedTenant.getTenantId(), createdDevice.getId()); + + Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.getDeviceCredentials().getCredentialsType().toString()); + Assert.assertEquals(deviceCredentials.getCredentialsId(), response.getDeviceCredentials().getCredentialsId()); + Assert.assertEquals(deviceCredentials.getCredentialsType(), DeviceCredentialsType.ACCESS_TOKEN); + Assert.assertEquals(deviceCredentials.getCredentialsId(), "test_token"); + Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.getProvisionResponseStatus().toString()); + } + + protected void processTestProvisioningCreateNewDeviceWithCert() throws Exception { + super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.JSON, null, null, DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES, "testProvisionKey", "testProvisionSecret"); + CredentialsDataProto requestCredentials = CredentialsDataProto.newBuilder().setValidateDeviceX509CertRequestMsg(ValidateDeviceX509CertRequestMsg.newBuilder().setHash("testHash").build()).build(); + + ProvisionDeviceResponseMsg response = ProvisionDeviceResponseMsg.parseFrom(createMqttClientAndPublish(createTestsProvisionMessage(CredentialsType.X509_CERTIFICATE, requestCredentials)).getPayloadBytes()); + + Device createdDevice = deviceService.findDeviceByTenantIdAndName(savedTenant.getTenantId(), "Test Provision device"); + + Assert.assertNotNull(createdDevice); + Assert.assertEquals(createdDevice.getId().getId(), new UUID(response.getDeviceCredentials().getDeviceIdMSB(), response.getDeviceCredentials().getDeviceIdLSB())); + + DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(savedTenant.getTenantId(), createdDevice.getId()); + + Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.getDeviceCredentials().getCredentialsType().toString()); + Assert.assertEquals(deviceCredentials.getCredentialsId(), response.getDeviceCredentials().getCredentialsId()); + Assert.assertEquals(deviceCredentials.getCredentialsType(), DeviceCredentialsType.X509_CERTIFICATE); + + String cert = EncryptionUtil.trimNewLines(deviceCredentials.getCredentialsValue()); + String sha3Hash = EncryptionUtil.getSha3Hash(cert); + + Assert.assertEquals(deviceCredentials.getCredentialsId(), sha3Hash); + + Assert.assertEquals(deviceCredentials.getCredentialsValue(), "testHash"); + Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.getProvisionResponseStatus().toString()); + } + + protected void processTestProvisioningCreateNewDeviceWithMqttBasic() throws Exception { + super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.JSON, null, null, DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES, "testProvisionKey", "testProvisionSecret"); + CredentialsDataProto requestCredentials = CredentialsDataProto.newBuilder().setValidateBasicMqttCredRequestMsg( + ValidateBasicMqttCredRequestMsg.newBuilder() + .setClientId("test_clientId") + .setUserName("test_username") + .setPassword("test_password") + .build() + ).build(); + + ProvisionDeviceResponseMsg response = ProvisionDeviceResponseMsg.parseFrom(createMqttClientAndPublish(createTestsProvisionMessage(CredentialsType.MQTT_BASIC, requestCredentials)).getPayloadBytes()); Device createdDevice = deviceService.findDeviceByTenantIdAndName(savedTenant.getTenantId(), "Test Provision device"); @@ -102,11 +195,21 @@ public abstract class AbstractMqttProvisionProtoDeviceTest extends AbstractMqttI Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.getDeviceCredentials().getCredentialsType().toString()); Assert.assertEquals(deviceCredentials.getCredentialsId(), response.getDeviceCredentials().getCredentialsId()); + Assert.assertEquals(deviceCredentials.getCredentialsType(), DeviceCredentialsType.MQTT_BASIC); + Assert.assertEquals(deviceCredentials.getCredentialsId(), EncryptionUtil.getSha3Hash("|", "test_clientId", "test_username")); + + BasicMqttCredentials mqttCredentials = new BasicMqttCredentials(); + mqttCredentials.setClientId("test_clientId"); + mqttCredentials.setUserName("test_username"); + mqttCredentials.setPassword("test_password"); + + Assert.assertEquals(deviceCredentials.getCredentialsValue(), JacksonUtil.toString(mqttCredentials)); + Assert.assertEquals(deviceCredentials.getCredentialsId(), response.getDeviceCredentials().getCredentialsId()); Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.getProvisionResponseStatus().toString()); } protected void processTestProvisioningCheckPreProvisionedDevice() throws Exception { - super.processBeforeTest("Test Provision device", "Test Provision gateway", TransportPayloadType.JSON, null, null, DeviceProfileProvisionType.CHECK_PRE_PROVISIONED_DEVICES, "testProvisionKey", "testProvisionSecret"); + super.processBeforeTest("Test Provision device", "Test Provision gateway", TransportPayloadType.PROTOBUF, null, null, DeviceProfileProvisionType.CHECK_PRE_PROVISIONED_DEVICES, "testProvisionKey", "testProvisionSecret"); ProvisionDeviceResponseMsg response = ProvisionDeviceResponseMsg.parseFrom(createMqttClientAndPublish().getPayloadBytes()); Assert.assertEquals(savedDevice.getId().getId(), new UUID(response.getDeviceCredentials().getDeviceIdMSB(), response.getDeviceCredentials().getDeviceIdLSB())); @@ -118,13 +221,17 @@ public abstract class AbstractMqttProvisionProtoDeviceTest extends AbstractMqttI } protected void processTestProvisioningWithBadKeyDevice() throws Exception { - super.processBeforeTest("Test Provision device", "Test Provision gateway", TransportPayloadType.JSON, null, null, DeviceProfileProvisionType.CHECK_PRE_PROVISIONED_DEVICES, "testProvisionKeyOrig", "testProvisionSecret"); + super.processBeforeTest("Test Provision device", "Test Provision gateway", TransportPayloadType.PROTOBUF, null, null, DeviceProfileProvisionType.CHECK_PRE_PROVISIONED_DEVICES, "testProvisionKeyOrig", "testProvisionSecret"); ProvisionDeviceResponseMsg response = ProvisionDeviceResponseMsg.parseFrom(createMqttClientAndPublish().getPayloadBytes()); Assert.assertEquals(ProvisionResponseStatus.NOT_FOUND.name(), response.getProvisionResponseStatus().toString()); } - protected TestMqttCallback createMqttClientAndPublish() throws Exception{ + protected TestMqttCallback createMqttClientAndPublish() throws Exception { byte[] provisionRequestMsg = createTestProvisionMessage(); + return createMqttClientAndPublish(provisionRequestMsg); + } + + protected TestMqttCallback createMqttClientAndPublish(byte[] provisionRequestMsg) throws Exception { MqttAsyncClient client = getMqttAsyncClient("provision"); TestMqttCallback onProvisionCallback = getTestMqttCallback(); client.setCallback(onProvisionCallback); @@ -181,9 +288,22 @@ public abstract class AbstractMqttProvisionProtoDeviceTest extends AbstractMqttI } } + protected byte[] createTestsProvisionMessage(CredentialsType credentialsType, CredentialsDataProto credentialsData) throws Exception { + return ProvisionDeviceRequestMsg.newBuilder() + .setDeviceName("Test Provision device") + .setCredentialsType(credentialsType != null ? credentialsType : CredentialsType.ACCESS_TOKEN) + .setCredentialsDataProto(credentialsData != null ? credentialsData: CredentialsDataProto.newBuilder().build()) + .setProvisionDeviceCredentialsMsg( + ProvisionDeviceCredentialsMsg.newBuilder() + .setProvisionDeviceKey("testProvisionKey") + .setProvisionDeviceSecret("testProvisionSecret") + ).build() + .toByteArray(); + } + - protected byte[] createTestProvisionMessage() { - return ProvisionDeviceRequestMsg.newBuilder().setDeviceName("Test Provision device").setProvisionDeviceCredentialsMsg(ProvisionDeviceCredentialsMsg.newBuilder().setProvisionDeviceKey("testProvisionKey").setProvisionDeviceSecret("testProvisionSecret")).build().toByteArray(); + protected byte[] createTestProvisionMessage() throws Exception { + return createTestsProvisionMessage(null, null); } } From 93278f7236555b26f52af3a8e4fdebc80037cee1 Mon Sep 17 00:00:00 2001 From: zbeacon Date: Fri, 9 Oct 2020 17:32:32 +0300 Subject: [PATCH 28/48] Refactoring according to comments --- .../device/DeviceProvisionServiceImpl.java | 105 ++++-------------- .../transport/DefaultTransportApiService.java | 46 ++++---- .../dao/device/DeviceProvisionService.java | 6 +- .../server/dao/device/DeviceService.java | 4 + .../provision/ProvisionFailedException.java | 22 ++++ .../provision/ProvisionResponseStatus.java | 1 - .../ProvisionDeviceCredentialsData.java | 2 +- .../DeviceProfileProvisionConfiguration.java | 4 +- common/queue/src/main/proto/queue.proto | 10 -- .../src/main/proto/transport.proto | 5 - .../server/dao/device/DeviceServiceImpl.java | 46 ++++++++ 11 files changed, 125 insertions(+), 126 deletions(-) create mode 100644 common/dao-api/src/main/java/org/thingsboard/server/dao/device/provision/ProvisionFailedException.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 5a007d27ca..ab333442c4 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 @@ -16,6 +16,7 @@ package org.thingsboard.server.service.device; import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ObjectNode; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; @@ -28,9 +29,6 @@ import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.audit.ActionType; -import org.thingsboard.server.common.data.device.credentials.BasicMqttCredentials; -import org.thingsboard.server.common.data.device.profile.AllowCreateNewDevicesDeviceProfileProvisionConfiguration; -import org.thingsboard.server.common.data.device.profile.CheckPreProvisionedDevicesDeviceProfileProvisionConfiguration; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.UserId; @@ -49,6 +47,7 @@ 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; +import org.thingsboard.server.dao.device.provision.ProvisionFailedException; import org.thingsboard.server.dao.device.provision.ProvisionRequest; import org.thingsboard.server.dao.device.provision.ProvisionResponse; import org.thingsboard.server.dao.device.provision.ProvisionResponseStatus; @@ -115,81 +114,55 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { String provisionRequestSecret = provisionRequest.getCredentials().getProvisionDeviceSecret(); if (StringUtils.isEmpty(provisionRequestKey) || StringUtils.isEmpty(provisionRequestSecret)) { - return Futures.immediateFuture(new ProvisionResponse(null, ProvisionResponseStatus.NOT_FOUND)); - } - - if (provisionRequest.getCredentialsType() != null) { - ListenableFuture error = validateCredentials(provisionRequest); - if (error != null) { - return error; - } + throw new ProvisionFailedException(ProvisionResponseStatus.NOT_FOUND.name()); } DeviceProfile targetProfile = deviceProfileDao.findByProvisionDeviceKey(provisionRequestKey); - if (targetProfile == null) { - return Futures.immediateFuture(new ProvisionResponse(null, ProvisionResponseStatus.NOT_FOUND)); + if (targetProfile == null || targetProfile.getProfileData().getProvisionConfiguration() == null || + targetProfile.getProfileData().getProvisionConfiguration().getProvisionDeviceSecret() == null) { + throw new ProvisionFailedException(ProvisionResponseStatus.NOT_FOUND.name()); } Device targetDevice = deviceDao.findDeviceByTenantIdAndName(targetProfile.getTenantId().getId(), provisionRequest.getDeviceName()).orElse(null); switch (targetProfile.getProvisionType()) { case ALLOW_CREATE_NEW_DEVICES: - if (((AllowCreateNewDevicesDeviceProfileProvisionConfiguration) targetProfile.getProfileData().getProvisionConfiguration()).getProvisionDeviceSecret().equals(provisionRequestSecret)) { + if (targetProfile.getProfileData().getProvisionConfiguration().getProvisionDeviceSecret().equals(provisionRequestSecret)) { if (targetDevice != null) { 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)); + throw new ProvisionFailedException(ProvisionResponseStatus.FAILURE.name()); } else { return createDevice(provisionRequest, targetProfile); } } break; case CHECK_PRE_PROVISIONED_DEVICES: - if (((CheckPreProvisionedDevicesDeviceProfileProvisionConfiguration) targetProfile.getProfileData().getProvisionConfiguration()).getProvisionDeviceSecret().equals(provisionRequestSecret)) { + if (targetProfile.getProfileData().getProvisionConfiguration().getProvisionDeviceSecret().equals(provisionRequestSecret)) { if (targetDevice != null && targetDevice.getDeviceProfileId().equals(targetProfile.getId())) { return processProvision(targetDevice, provisionRequest); } else { log.warn("[{}] Failed to find pre provisioned device!", provisionRequest.getDeviceName()); - return Futures.immediateFuture(new ProvisionResponse(null, ProvisionResponseStatus.FAILURE)); + throw new ProvisionFailedException(ProvisionResponseStatus.FAILURE.name()); } } break; } - return Futures.immediateFuture(new ProvisionResponse(null, ProvisionResponseStatus.NOT_FOUND)); - } - - private ListenableFuture validateCredentials(ProvisionRequest provisionRequest) { - switch (provisionRequest.getCredentialsType()) { - case MQTT_BASIC: - if (StringUtils.isEmpty(provisionRequest.getCredentialsData().getClientId()) || - StringUtils.isEmpty(provisionRequest.getCredentialsData().getUsername()) || - StringUtils.isEmpty(provisionRequest.getCredentialsData().getPassword())) { - log.error("Failed to get basic mqtt credentials from credentials data!"); - return Futures.immediateFuture(new ProvisionResponse(null, ProvisionResponseStatus.FAILURE)); - } - break; - case X509_CERTIFICATE: - if (StringUtils.isEmpty(provisionRequest.getCredentialsData().getHash())) { - log.error("Failed to get hash from credentials data!"); - return Futures.immediateFuture(new ProvisionResponse(null, ProvisionResponseStatus.FAILURE)); - } - break; - } - return null; + throw new ProvisionFailedException(ProvisionResponseStatus.NOT_FOUND.name()); } private ListenableFuture processProvision(Device device, ProvisionRequest provisionRequest) { ListenableFuture> provisionStateFuture = attributesService.find(device.getTenantId(), device.getId(), DataConstants.SERVER_SCOPE, DEVICE_PROVISION_STATE); ListenableFuture provisionedFuture = Futures.transformAsync(provisionStateFuture, optionalAtr -> { - if (optionalAtr.isPresent()) { + if (optionalAtr != null && optionalAtr.isPresent()) { String state = optionalAtr.get().getValueAsString(); if (state.equals(PROVISIONED_STATE)) { return Futures.immediateFuture(true); } else { log.error("[{}][{}] Unknown provision state: {}!", device.getName(), DEVICE_PROVISION_STATE, state); - return Futures.immediateCancelledFuture(); + throw new ProvisionFailedException(ProvisionResponseStatus.FAILURE.name()); } } return Futures.transform(saveProvisionStateAttribute(device), input -> false, MoreExecutors.directExecutor()); @@ -200,7 +173,7 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { return Futures.transform(provisionedFuture, provisioned -> { if (provisioned) { notify(device, provisionRequest, DataConstants.PROVISION_FAILURE, false); - return new ProvisionResponse(null, ProvisionResponseStatus.FAILURE); + throw new ProvisionFailedException(ProvisionResponseStatus.FAILURE.name()); } notify(device, provisionRequest, DataConstants.PROVISION_SUCCESS, true); return new ProvisionResponse(deviceCredentialsService.findDeviceCredentialsByDeviceId(device.getTenantId(), device.getId()), ProvisionResponseStatus.SUCCESS); @@ -224,7 +197,7 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { private ListenableFuture processCreateDevice(ProvisionRequest provisionRequest, DeviceProfile profile) { Device device = deviceService.findDeviceByTenantIdAndName(profile.getTenantId(), provisionRequest.getDeviceName()); if (device == null) { - Device savedDevice = saveDevice(provisionRequest, profile); + Device savedDevice = deviceService.saveDevice(provisionRequest, profile); deviceStateService.onDeviceAdded(savedDevice); pushDeviceCreatedEventToRuleEngine(savedDevice); @@ -234,10 +207,11 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { new ProvisionResponse( getDeviceCredentials(savedDevice), ProvisionResponseStatus.SUCCESS), MoreExecutors.directExecutor()); + } else { + log.warn("[{}] The device is already provisioned!", device.getName()); + notify(device, provisionRequest, DataConstants.PROVISION_FAILURE, false); + throw new ProvisionFailedException(ProvisionResponseStatus.FAILURE.name()); } - log.warn("[{}] The device is already provisioned!", device.getName()); - notify(device, provisionRequest, DataConstants.PROVISION_FAILURE, false); - return Futures.immediateFuture(new ProvisionResponse(null, ProvisionResponseStatus.FAILURE)); } private ListenableFuture> saveProvisionStateAttribute(Device device) { @@ -246,51 +220,16 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { System.currentTimeMillis()))); } - private Device saveDevice(ProvisionRequest provisionRequest, DeviceProfile profile) { - Device device = new Device(); - device.setName(provisionRequest.getDeviceName()); - device.setType(profile.getName()); - device.setTenantId(profile.getTenantId()); - Device savedDevice = deviceService.saveDevice(device); - if (!StringUtils.isEmpty(provisionRequest.getCredentialsData().getToken()) || - !StringUtils.isEmpty(provisionRequest.getCredentialsData().getHash()) || - !StringUtils.isEmpty(provisionRequest.getCredentialsData().getUsername()) || - !StringUtils.isEmpty(provisionRequest.getCredentialsData().getPassword()) || - !StringUtils.isEmpty(provisionRequest.getCredentialsData().getClientId())) { - DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(savedDevice.getTenantId(), savedDevice.getId()); - deviceCredentials.setCredentialsType(provisionRequest.getCredentialsType()); - switch (provisionRequest.getCredentialsType()) { - case ACCESS_TOKEN: - deviceCredentials.setDeviceId(savedDevice.getId()); - deviceCredentials.setCredentialsId(provisionRequest.getCredentialsData().getToken()); - break; - case MQTT_BASIC: - BasicMqttCredentials mqttCredentials = new BasicMqttCredentials(); - mqttCredentials.setClientId(provisionRequest.getCredentialsData().getClientId()); - mqttCredentials.setUserName(provisionRequest.getCredentialsData().getUsername()); - mqttCredentials.setPassword(provisionRequest.getCredentialsData().getPassword()); - deviceCredentials.setCredentialsValue(JacksonUtil.toString(mqttCredentials)); - break; - case X509_CERTIFICATE: - deviceCredentials.setCredentialsValue(provisionRequest.getCredentialsData().getHash()); - break; - } - deviceCredentials.setCredentialsType(provisionRequest.getCredentialsType()); - deviceCredentialsService.updateDeviceCredentials(savedDevice.getTenantId(), deviceCredentials); - } - return savedDevice; - } - private DeviceCredentials getDeviceCredentials(Device device) { return deviceCredentialsService.findDeviceCredentialsByDeviceId(device.getTenantId(), device.getId()); } private void pushProvisionEventToRuleEngine(ProvisionRequest request, Device device, String type) { try { - ObjectNode entityNode = JacksonUtil.OBJECT_MAPPER.valueToTree(request); - TbMsg msg = TbMsg.newMsg(type, device.getId(), createTbMsgMetaData(device), JacksonUtil.OBJECT_MAPPER.writeValueAsString(entityNode)); + JsonNode entityNode = JacksonUtil.valueToTree(request); + TbMsg msg = TbMsg.newMsg(type, device.getId(), createTbMsgMetaData(device), JacksonUtil.toString(entityNode)); sendToRuleEngine(device.getTenantId(), msg, null); - } catch (JsonProcessingException | IllegalArgumentException e) { + } catch (IllegalArgumentException e) { log.warn("[{}] Failed to push device action to rule engine: {}", device.getId(), type, e); } } diff --git a/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java b/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java index eb2fdbc65e..7791ec67a7 100644 --- a/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java +++ b/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java @@ -50,13 +50,11 @@ import org.thingsboard.server.dao.device.DeviceProvisionService; import org.thingsboard.server.dao.device.DeviceService; import org.thingsboard.server.dao.device.provision.ProvisionRequest; import org.thingsboard.server.dao.device.provision.ProvisionResponse; -import org.thingsboard.server.dao.device.provision.ProvisionResponseStatus; import org.thingsboard.server.dao.relation.RelationService; import org.thingsboard.server.dao.tenant.TenantProfileService; import org.thingsboard.server.dao.tenant.TenantService; import org.thingsboard.server.dao.util.mapping.JacksonUtil; import org.thingsboard.server.gen.transport.TransportProtos; -import org.thingsboard.server.gen.transport.TransportProtos.CredentialsType; import org.thingsboard.server.gen.transport.TransportProtos.DeviceInfoProto; import org.thingsboard.server.gen.transport.TransportProtos.GetOrCreateDeviceFromGatewayRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.GetOrCreateDeviceFromGatewayResponseMsg; @@ -70,6 +68,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceTokenR import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceX509CertRequestMsg; import org.thingsboard.server.queue.common.TbProtoQueueMsg; import org.thingsboard.server.queue.util.TbCoreComponent; +import org.thingsboard.server.dao.device.provision.ProvisionFailedException; import org.thingsboard.server.service.executors.DbCallbackExecutorService; import org.thingsboard.server.service.queue.TbClusterService; import org.thingsboard.server.service.state.DeviceStateService; @@ -276,30 +275,29 @@ public class DefaultTransportApiService implements TransportApiService { }, dbCallbackExecutorService); } - private ListenableFuture handle(ProvisionDeviceRequestMsg requestMsg) { ListenableFuture provisionResponseFuture = null; - provisionResponseFuture = deviceProvisionService.provisionDevice( - new ProvisionRequest( - requestMsg.getDeviceName(), - requestMsg.getCredentialsType() != null ? DeviceCredentialsType.valueOf(requestMsg.getCredentialsType().name()) : null, - new ProvisionDeviceCredentialsData(requestMsg.getCredentialsDataProto().getValidateDeviceTokenRequestMsg().getToken(), - requestMsg.getCredentialsDataProto().getValidateBasicMqttCredRequestMsg().getClientId(), - requestMsg.getCredentialsDataProto().getValidateBasicMqttCredRequestMsg().getUserName(), - requestMsg.getCredentialsDataProto().getValidateBasicMqttCredRequestMsg().getPassword(), - requestMsg.getCredentialsDataProto().getValidateDeviceX509CertRequestMsg().getHash()), - new ProvisionDeviceProfileCredentials( - requestMsg.getProvisionDeviceCredentialsMsg().getProvisionDeviceKey(), - requestMsg.getProvisionDeviceCredentialsMsg().getProvisionDeviceSecret()))); - return Futures.transform(provisionResponseFuture, provisionResponse -> { - if (provisionResponse.getResponseStatus() == ProvisionResponseStatus.NOT_FOUND) { - return getTransportApiResponseMsg(TransportProtos.DeviceCredentialsProto.getDefaultInstance(), TransportProtos.ProvisionResponseStatus.NOT_FOUND); - } else if (provisionResponse.getResponseStatus() == ProvisionResponseStatus.FAILURE) { - return getTransportApiResponseMsg(TransportProtos.DeviceCredentialsProto.getDefaultInstance(), TransportProtos.ProvisionResponseStatus.FAILURE); - } else { - return getTransportApiResponseMsg(getDeviceCredentials(provisionResponse.getDeviceCredentials()), TransportProtos.ProvisionResponseStatus.SUCCESS); - } - }, dbCallbackExecutorService); + try { + provisionResponseFuture = deviceProvisionService.provisionDevice( + new ProvisionRequest( + requestMsg.getDeviceName(), + requestMsg.getCredentialsType() != null ? DeviceCredentialsType.valueOf(requestMsg.getCredentialsType().name()) : null, + new ProvisionDeviceCredentialsData(requestMsg.getCredentialsDataProto().getValidateDeviceTokenRequestMsg().getToken(), + requestMsg.getCredentialsDataProto().getValidateBasicMqttCredRequestMsg().getClientId(), + requestMsg.getCredentialsDataProto().getValidateBasicMqttCredRequestMsg().getUserName(), + requestMsg.getCredentialsDataProto().getValidateBasicMqttCredRequestMsg().getPassword(), + requestMsg.getCredentialsDataProto().getValidateDeviceX509CertRequestMsg().getHash()), + new ProvisionDeviceProfileCredentials( + requestMsg.getProvisionDeviceCredentialsMsg().getProvisionDeviceKey(), + requestMsg.getProvisionDeviceCredentialsMsg().getProvisionDeviceSecret()))); + } catch (ProvisionFailedException e) { + return Futures.immediateFuture(getTransportApiResponseMsg( + TransportProtos.DeviceCredentialsProto.getDefaultInstance(), + TransportProtos.ProvisionResponseStatus.valueOf(e.getMessage()))); + } + return Futures.transform(provisionResponseFuture, provisionResponse -> getTransportApiResponseMsg( + getDeviceCredentials(provisionResponse.getDeviceCredentials()), TransportProtos.ProvisionResponseStatus.SUCCESS), + dbCallbackExecutorService); } private TransportApiResponseMsg getTransportApiResponseMsg(TransportProtos.DeviceCredentialsProto deviceCredentials, TransportProtos.ProvisionResponseStatus status) { diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceProvisionService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceProvisionService.java index 28456fb4fe..2a0d651801 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceProvisionService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceProvisionService.java @@ -16,9 +16,13 @@ package org.thingsboard.server.dao.device; import com.google.common.util.concurrent.ListenableFuture; +import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.DeviceProfile; +import org.thingsboard.server.dao.device.provision.ProvisionFailedException; import org.thingsboard.server.dao.device.provision.ProvisionRequest; import org.thingsboard.server.dao.device.provision.ProvisionResponse; public interface DeviceProvisionService { - ListenableFuture provisionDevice(ProvisionRequest provisionRequest); + + ListenableFuture provisionDevice(ProvisionRequest provisionRequest) throws ProvisionFailedException; } diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceService.java index ba2f09ae73..715313538d 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceService.java @@ -18,6 +18,7 @@ package org.thingsboard.server.dao.device; import com.google.common.util.concurrent.ListenableFuture; 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.EntitySubtype; import org.thingsboard.server.common.data.device.DeviceSearchQuery; import org.thingsboard.server.common.data.id.CustomerId; @@ -26,6 +27,7 @@ import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; +import org.thingsboard.server.dao.device.provision.ProvisionRequest; import java.util.List; @@ -83,4 +85,6 @@ public interface DeviceService { Device assignDeviceToTenant(TenantId tenantId, Device device); + Device saveDevice(ProvisionRequest provisionRequest, DeviceProfile profile); + } diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/provision/ProvisionFailedException.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/provision/ProvisionFailedException.java new file mode 100644 index 0000000000..04e8e00ce5 --- /dev/null +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/provision/ProvisionFailedException.java @@ -0,0 +1,22 @@ +/** + * 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.dao.device.provision; + +public class ProvisionFailedException extends RuntimeException { + public ProvisionFailedException(String errorMsg) { + super(errorMsg); + } +} diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/provision/ProvisionResponseStatus.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/provision/ProvisionResponseStatus.java index 46764f1b67..d4fbe65a97 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/provision/ProvisionResponseStatus.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/provision/ProvisionResponseStatus.java @@ -20,5 +20,4 @@ public enum ProvisionResponseStatus { SUCCESS, NOT_FOUND, FAILURE - } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/ProvisionDeviceCredentialsData.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/ProvisionDeviceCredentialsData.java index a3e70afd83..fe6cc2621a 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/ProvisionDeviceCredentialsData.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/ProvisionDeviceCredentialsData.java @@ -23,5 +23,5 @@ public class ProvisionDeviceCredentialsData { private final String clientId; private final String username; private final String password; - private final String hash; + private final String x509CertHash; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileProvisionConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileProvisionConfiguration.java index 140c7334ec..f892aabf76 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileProvisionConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileProvisionConfiguration.java @@ -33,7 +33,9 @@ import org.thingsboard.server.common.data.DeviceProfileProvisionType; @JsonSubTypes.Type(value = CheckPreProvisionedDevicesDeviceProfileProvisionConfiguration.class, name = "CHECK_PRE_PROVISIONED_DEVICES")}) public interface DeviceProfileProvisionConfiguration { + String getProvisionDeviceSecret(); + @JsonIgnore DeviceProfileProvisionType getType(); -} \ No newline at end of file +} diff --git a/common/queue/src/main/proto/queue.proto b/common/queue/src/main/proto/queue.proto index 7c2ae80f9b..166f94b864 100644 --- a/common/queue/src/main/proto/queue.proto +++ b/common/queue/src/main/proto/queue.proto @@ -268,11 +268,6 @@ message ProvisionDeviceRequestMsg { CredentialsDataProto credentialsDataProto = 4; } -message GatewayProvisionRequestMsg { - int32 requestId = 1; - ProvisionDeviceRequestMsg provisionDeviceRequestMsg = 2; -} - message ProvisionDeviceCredentialsMsg { string provisionDeviceKey = 1; string provisionDeviceSecret = 2; @@ -283,11 +278,6 @@ message ProvisionDeviceResponseMsg { ProvisionResponseStatus provisionResponseStatus = 2; } -message GatewayProvisionResponseMsg { - int32 requestId = 1; - ProvisionDeviceResponseMsg provisionDeviceResponseMsg = 2; -} - enum ProvisionResponseStatus { UNKNOWN = 0; SUCCESS = 1; diff --git a/common/transport/transport-api/src/main/proto/transport.proto b/common/transport/transport-api/src/main/proto/transport.proto index 5eff9313fa..b536c22198 100644 --- a/common/transport/transport-api/src/main/proto/transport.proto +++ b/common/transport/transport-api/src/main/proto/transport.proto @@ -72,11 +72,6 @@ message GatewayAttributesMsg { repeated AttributesMsg msg = 1; } -message GatewayProvisionResponseMsg { - string deviceName = 1; - transport.ProvisionDeviceResponseMsg provisionDeviceResponseMsg = 2; -} - message GatewayRpcResponseMsg { string deviceName = 1; int32 id = 2; diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java index ad66ed47fd..1df488ff63 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java @@ -40,6 +40,7 @@ import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityView; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.device.DeviceSearchQuery; +import org.thingsboard.server.common.data.device.credentials.BasicMqttCredentials; import org.thingsboard.server.common.data.device.data.DefaultDeviceConfiguration; import org.thingsboard.server.common.data.device.data.DefaultDeviceTransportConfiguration; import org.thingsboard.server.common.data.device.data.DeviceData; @@ -57,6 +58,9 @@ import org.thingsboard.server.common.data.relation.EntitySearchDirection; import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.common.data.security.DeviceCredentialsType; import org.thingsboard.server.dao.customer.CustomerDao; +import org.thingsboard.server.dao.device.provision.ProvisionFailedException; +import org.thingsboard.server.dao.device.provision.ProvisionRequest; +import org.thingsboard.server.dao.device.provision.ProvisionResponseStatus; import org.thingsboard.server.dao.entity.AbstractEntityService; import org.thingsboard.server.dao.entityview.EntityViewService; import org.thingsboard.server.dao.event.EventService; @@ -64,6 +68,7 @@ import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.PaginatedRemover; import org.thingsboard.server.dao.tenant.TenantDao; +import org.thingsboard.server.dao.util.mapping.JacksonUtil; import javax.annotation.Nullable; import java.util.ArrayList; @@ -466,6 +471,47 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe return doSaveDevice(device, null); } + @Override + @Transactional + public Device saveDevice(ProvisionRequest provisionRequest, DeviceProfile profile) { + Device device = new Device(); + device.setName(provisionRequest.getDeviceName()); + device.setType(profile.getName()); + device.setTenantId(profile.getTenantId()); + Device savedDevice = saveDevice(device); + if (!StringUtils.isEmpty(provisionRequest.getCredentialsData().getToken()) || + !StringUtils.isEmpty(provisionRequest.getCredentialsData().getX509CertHash()) || + !StringUtils.isEmpty(provisionRequest.getCredentialsData().getUsername()) || + !StringUtils.isEmpty(provisionRequest.getCredentialsData().getPassword()) || + !StringUtils.isEmpty(provisionRequest.getCredentialsData().getClientId())) { + DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(savedDevice.getTenantId(), savedDevice.getId()); + deviceCredentials.setCredentialsType(provisionRequest.getCredentialsType()); + switch (provisionRequest.getCredentialsType()) { + case ACCESS_TOKEN: + deviceCredentials.setDeviceId(savedDevice.getId()); + deviceCredentials.setCredentialsId(provisionRequest.getCredentialsData().getToken()); + break; + case MQTT_BASIC: + BasicMqttCredentials mqttCredentials = new BasicMqttCredentials(); + mqttCredentials.setClientId(provisionRequest.getCredentialsData().getClientId()); + mqttCredentials.setUserName(provisionRequest.getCredentialsData().getUsername()); + mqttCredentials.setPassword(provisionRequest.getCredentialsData().getPassword()); + deviceCredentials.setCredentialsValue(JacksonUtil.toString(mqttCredentials)); + break; + case X509_CERTIFICATE: + deviceCredentials.setCredentialsValue(provisionRequest.getCredentialsData().getX509CertHash()); + break; + } + deviceCredentials.setCredentialsType(provisionRequest.getCredentialsType()); + try { + deviceCredentialsService.updateDeviceCredentials(savedDevice.getTenantId(), deviceCredentials); + } catch (Exception e) { + throw new ProvisionFailedException(ProvisionResponseStatus.FAILURE.name()); + } + } + return savedDevice; + } + private DataValidator deviceValidator = new DataValidator() { From ca4e9238c88bb69e2abec04ec8be4a2740d1ead0 Mon Sep 17 00:00:00 2001 From: zbeacon Date: Mon, 12 Oct 2020 13:30:23 +0300 Subject: [PATCH 29/48] Fix for device provision tests --- .../AbstractMqttProvisionProtoDeviceTest.java | 10 +++------- .../server/dao/device/DeviceServiceImpl.java | 7 +++++-- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/application/src/test/java/org/thingsboard/server/mqtt/provision/AbstractMqttProvisionProtoDeviceTest.java b/application/src/test/java/org/thingsboard/server/mqtt/provision/AbstractMqttProvisionProtoDeviceTest.java index c5becedbbd..12d8f91eb6 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/provision/AbstractMqttProvisionProtoDeviceTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/provision/AbstractMqttProvisionProtoDeviceTest.java @@ -15,8 +15,6 @@ */ package org.thingsboard.server.mqtt.provision; -import com.google.gson.JsonObject; -import com.sun.mail.iap.ByteArray; import io.netty.handler.codec.mqtt.MqttQoS; import lombok.extern.slf4j.Slf4j; import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken; @@ -35,12 +33,10 @@ import org.thingsboard.server.common.data.device.profile.MqttTopics; import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.common.data.security.DeviceCredentialsType; import org.thingsboard.server.common.msg.EncryptionUtil; -import org.thingsboard.server.common.transport.util.JsonUtils; import org.thingsboard.server.dao.device.DeviceCredentialsService; import org.thingsboard.server.dao.device.DeviceService; import org.thingsboard.server.dao.device.provision.ProvisionResponseStatus; import org.thingsboard.server.dao.util.mapping.JacksonUtil; -import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos.CredentialsDataProto; import org.thingsboard.server.gen.transport.TransportProtos.CredentialsType; import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceCredentialsMsg; @@ -129,7 +125,7 @@ public abstract class AbstractMqttProvisionProtoDeviceTest extends AbstractMqttI } protected void processTestProvisioningCreateNewDeviceWithAccessToken() throws Exception { - super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.JSON, null, null, DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES, "testProvisionKey", "testProvisionSecret"); + super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.PROTOBUF, null, null, DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES, "testProvisionKey", "testProvisionSecret"); CredentialsDataProto requestCredentials = CredentialsDataProto.newBuilder().setValidateDeviceTokenRequestMsg(ValidateDeviceTokenRequestMsg.newBuilder().setToken("test_token").build()).build(); ProvisionDeviceResponseMsg response = ProvisionDeviceResponseMsg.parseFrom(createMqttClientAndPublish(createTestsProvisionMessage(CredentialsType.ACCESS_TOKEN, requestCredentials)).getPayloadBytes()); @@ -149,7 +145,7 @@ public abstract class AbstractMqttProvisionProtoDeviceTest extends AbstractMqttI } protected void processTestProvisioningCreateNewDeviceWithCert() throws Exception { - super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.JSON, null, null, DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES, "testProvisionKey", "testProvisionSecret"); + super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.PROTOBUF, null, null, DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES, "testProvisionKey", "testProvisionSecret"); CredentialsDataProto requestCredentials = CredentialsDataProto.newBuilder().setValidateDeviceX509CertRequestMsg(ValidateDeviceX509CertRequestMsg.newBuilder().setHash("testHash").build()).build(); ProvisionDeviceResponseMsg response = ProvisionDeviceResponseMsg.parseFrom(createMqttClientAndPublish(createTestsProvisionMessage(CredentialsType.X509_CERTIFICATE, requestCredentials)).getPayloadBytes()); @@ -175,7 +171,7 @@ public abstract class AbstractMqttProvisionProtoDeviceTest extends AbstractMqttI } protected void processTestProvisioningCreateNewDeviceWithMqttBasic() throws Exception { - super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.JSON, null, null, DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES, "testProvisionKey", "testProvisionSecret"); + super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.PROTOBUF, null, null, DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES, "testProvisionKey", "testProvisionSecret"); CredentialsDataProto requestCredentials = CredentialsDataProto.newBuilder().setValidateBasicMqttCredRequestMsg( ValidateBasicMqttCredRequestMsg.newBuilder() .setClientId("test_clientId") diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java index 1df488ff63..b0290e8eb1 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java @@ -472,6 +472,7 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe } @Override + @CacheEvict(cacheNames = DEVICE_CACHE, key = "{#profile.tenantId, #provisionRequest.deviceName}") @Transactional public Device saveDevice(ProvisionRequest provisionRequest, DeviceProfile profile) { Device device = new Device(); @@ -485,10 +486,13 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe !StringUtils.isEmpty(provisionRequest.getCredentialsData().getPassword()) || !StringUtils.isEmpty(provisionRequest.getCredentialsData().getClientId())) { DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(savedDevice.getTenantId(), savedDevice.getId()); + if (deviceCredentials == null) { + deviceCredentials = new DeviceCredentials(); + } + deviceCredentials.setDeviceId(savedDevice.getId()); deviceCredentials.setCredentialsType(provisionRequest.getCredentialsType()); switch (provisionRequest.getCredentialsType()) { case ACCESS_TOKEN: - deviceCredentials.setDeviceId(savedDevice.getId()); deviceCredentials.setCredentialsId(provisionRequest.getCredentialsData().getToken()); break; case MQTT_BASIC: @@ -502,7 +506,6 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe deviceCredentials.setCredentialsValue(provisionRequest.getCredentialsData().getX509CertHash()); break; } - deviceCredentials.setCredentialsType(provisionRequest.getCredentialsType()); try { deviceCredentialsService.updateDeviceCredentials(savedDevice.getTenantId(), deviceCredentials); } catch (Exception e) { From 6d4245d6039f812f4cdecc94ba4ae308f6e978f6 Mon Sep 17 00:00:00 2001 From: Chantsova Ekaterina Date: Mon, 12 Oct 2020 15:34:02 +0300 Subject: [PATCH 30/48] Chart tooltips: fix wrong number of decimals after floating point --- .../main/data/json/system/widget_bundles/charts.json | 10 +++++----- .../modules/home/components/widget/lib/flot-widget.ts | 6 +++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/application/src/main/data/json/system/widget_bundles/charts.json b/application/src/main/data/json/system/widget_bundles/charts.json index 22c9d20a23..1010013689 100644 --- a/application/src/main/data/json/system/widget_bundles/charts.json +++ b/application/src/main/data/json/system/widget_bundles/charts.json @@ -19,7 +19,7 @@ ], "templateHtml": "\n", "templateCss": "", - "controllerScript": "self.onInit = function() {\n var barData = {\n labels: [],\n datasets: []\n };\n \n for (var i = 0; i < self.ctx.datasources.length; i++) {\n var datasource = self.ctx.datasources[i];\n for (var d = 0; d < datasource.dataKeys.length; d++) {\n var dataset = {\n label: datasource.dataKeys[d].label,\n data: [0],\n backgroundColor: [datasource.dataKeys[d].color],\n borderColor: [datasource.dataKeys[d].color],\n borderWidth: 1\n }\n barData.datasets.push(dataset);\n }\n }\n\n var ctx = $('#barChart', self.ctx.$container);\n self.ctx.chart = new Chart(ctx, {\n type: 'bar',\n data: barData,\n options: {\n responsive: false,\n maintainAspectRatio: false,\n scales: {\n yAxes: [{\n ticks: {\n beginAtZero:true\n }\n }]\n }\n }\n });\n \n self.onResize();\n}\n\nself.onDataUpdated = function() {\n var c = 0;\n for (var i = 0; i < self.ctx.chart.data.datasets.length; i++) {\n var dataset = self.ctx.chart.data.datasets[i];\n var cellData = self.ctx.data[i]; \n if (cellData.data.length > 0) {\n var tvPair = cellData.data[cellData.data.length - 1];\n var value = tvPair[1];\n dataset.data[0] = parseFloat(value);\n }\n }\n self.ctx.chart.update();\n}\n\nself.onResize = function() {\n self.ctx.chart.resize();\n}\n\n", + "controllerScript": "self.onInit = function() {\n var barData = {\n labels: [],\n datasets: []\n };\n \n for (var i = 0; i < self.ctx.datasources.length; i++) {\n var datasource = self.ctx.datasources[i];\n for (var d = 0; d < datasource.dataKeys.length; d++) {\n var dataKey = datasource.dataKeys[d];\n var units = dataKey.units && dataKey.units.length ? dataKey.units : self.ctx.units;\n units = units ? (' (' + units + ')') : '';\n var dataset = {\n label: dataKey.label + units,\n data: [0],\n backgroundColor: [dataKey.color],\n borderColor: [dataKey.color],\n borderWidth: 1\n }\n barData.datasets.push(dataset);\n }\n }\n\n var ctx = $('#barChart', self.ctx.$container);\n self.ctx.chart = new Chart(ctx, {\n type: 'bar',\n data: barData,\n options: {\n responsive: false,\n maintainAspectRatio: false,\n scales: {\n yAxes: [{\n ticks: {\n beginAtZero:true\n }\n }]\n }\n }\n });\n \n self.onResize();\n}\n\nself.onDataUpdated = function() {\n var c = 0;\n for (var i = 0; i < self.ctx.chart.data.datasets.length; i++) {\n var dataset = self.ctx.chart.data.datasets[i];\n var cellData = self.ctx.data[i]; \n if (cellData.data.length > 0) {\n var decimals;\n if (typeof cellData.dataKey.decimals !== 'undefined' \n && cellData.dataKey.decimals !== null ) {\n decimals = cellData.dataKey.decimals; \n } else {\n decimals = self.ctx.decimals;\n }\n var tvPair = cellData.data[cellData.data.length - 1];\n var value = self.ctx.utils.formatValue(tvPair[1], decimals);\n dataset.data[0] = parseFloat(value);\n }\n }\n self.ctx.chart.update();\n}\n\nself.onResize = function() {\n self.ctx.chart.resize();\n}\n\n", "settingsSchema": "{}", "dataKeySettingsSchema": "{}\n", "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"First\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = (prevValue-50) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+50;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Second\",\"color\":\"#4caf50\",\"settings\":{},\"_hash\":0.545701115289893,\"funcBody\":\"var value = (prevValue-20) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+20;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Third\",\"color\":\"#f44336\",\"settings\":{},\"_hash\":0.2592906835158064,\"funcBody\":\"var value = (prevValue-40) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+40;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Fourth\",\"color\":\"#ffc107\",\"settings\":{},\"_hash\":0.12880275585455747,\"funcBody\":\"var value = (prevValue-50) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+50;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{},\"title\":\"Bars - Chart.js\"}" @@ -55,7 +55,7 @@ ], "templateHtml": "\n", "templateCss": "", - "controllerScript": "self.onInit = function() {\n var pieData = {\n labels: [],\n datasets: []\n };\n\n var dataset = {\n data: [],\n backgroundColor: [],\n borderColor: [],\n borderWidth: [],\n hoverBackgroundColor: []\n }\n \n var borderColor = self.ctx.settings.borderColor || '#fff';\n var borderWidth = typeof self.ctx.settings.borderWidth !== 'undefined' ? self.ctx.settings.borderWidth : 5;\n \n pieData.datasets.push(dataset);\n \n for (var i=0; i < self.ctx.data.length; i++) {\n var dataKey = self.ctx.data[i].dataKey;\n pieData.labels.push(dataKey.label);\n dataset.data.push(0);\n var hoverBackgroundColor = tinycolor(dataKey.color).lighten(15);\n dataset.backgroundColor.push(dataKey.color);\n dataset.borderColor.push(borderColor);\n dataset.borderWidth.push(borderWidth);\n dataset.hoverBackgroundColor.push(hoverBackgroundColor.toRgbString());\n }\n\n var options = {\n responsive: false,\n maintainAspectRatio: false,\n legend: {\n display: true,\n labels: {\n fontColor: '#666'\n }\n },\n tooltips: {\n callbacks: {\n label: function(tooltipItem, data) {\n var label = data.labels[tooltipItem.index];\n var value = data.datasets[tooltipItem.datasetIndex].data[tooltipItem.index];\n var content = label + ': ' + value;\n var units = self.ctx.settings.units ? self.ctx.settings.units : self.ctx.units;\n if (units) {\n content += ' ' + units;\n } \n return content;\n }\n }\n }\n };\n\n if (self.ctx.settings.legend) {\n options.legend.display = self.ctx.settings.legend.display !== false;\n options.legend.labels.fontColor = self.ctx.settings.legend.labelsFontColor || '#666';\n }\n\n var ctx = $('#pieChart', self.ctx.$container);\n self.ctx.chart = new Chart(ctx, {\n type: 'doughnut',\n data: pieData,\n options: options\n });\n \n self.onResize();\n}\n\nself.onDataUpdated = function() {\n for (var i = 0; i < self.ctx.data.length; i++) {\n var cellData = self.ctx.data[i];\n if (cellData.data.length > 0) {\n var tvPair = cellData.data[cellData.data.length - 1];\n var value = tvPair[1];\n self.ctx.chart.data.datasets[0].data[i] = parseFloat(value);\n }\n }\n self.ctx.chart.update();\n}\n\nself.onResize = function() {\n self.ctx.chart.resize();\n}\n\n", + "controllerScript": "self.onInit = function() {\n var pieData = {\n labels: [],\n datasets: []\n };\n\n var dataset = {\n data: [],\n backgroundColor: [],\n borderColor: [],\n borderWidth: [],\n hoverBackgroundColor: []\n }\n \n var borderColor = self.ctx.settings.borderColor || '#fff';\n var borderWidth = typeof self.ctx.settings.borderWidth !== 'undefined' ? self.ctx.settings.borderWidth : 5;\n \n pieData.datasets.push(dataset);\n \n for (var i=0; i < self.ctx.data.length; i++) {\n var dataKey = self.ctx.data[i].dataKey;\n var units = dataKey.units && dataKey.units.length ? dataKey.units : self.ctx.units;\n units = units ? (' (' + units + ')') : '';\n pieData.labels.push(dataKey.label + units);\n dataset.data.push(0);\n var hoverBackgroundColor = tinycolor(dataKey.color).lighten(15);\n dataset.backgroundColor.push(dataKey.color);\n dataset.borderColor.push(borderColor);\n dataset.borderWidth.push(borderWidth);\n dataset.hoverBackgroundColor.push(hoverBackgroundColor.toRgbString());\n }\n\n var options = {\n responsive: false,\n maintainAspectRatio: false,\n legend: {\n display: true,\n labels: {\n fontColor: '#666'\n }\n },\n tooltips: {\n callbacks: {\n label: function(tooltipItem, data) {\n var label = data.labels[tooltipItem.index];\n var value = data.datasets[tooltipItem.datasetIndex].data[tooltipItem.index];\n var content = label + ': ' + value;\n var units = self.ctx.settings.units ? self.ctx.settings.units : self.ctx.units;\n if (units) {\n content += ' ' + units;\n } \n return content;\n }\n }\n }\n };\n\n if (self.ctx.settings.legend) {\n options.legend.display = self.ctx.settings.legend.display !== false;\n options.legend.labels.fontColor = self.ctx.settings.legend.labelsFontColor || '#666';\n }\n\n var ctx = $('#pieChart', self.ctx.$container);\n self.ctx.chart = new Chart(ctx, {\n type: 'doughnut',\n data: pieData,\n options: options\n });\n \n self.onResize();\n}\n\nself.onDataUpdated = function() {\n for (var i = 0; i < self.ctx.data.length; i++) {\n var cellData = self.ctx.data[i];\n if (cellData.data.length > 0) {\n var decimals;\n if (typeof cellData.dataKey.decimals !== 'undefined' \n && cellData.dataKey.decimals !== null ) {\n decimals = cellData.dataKey.decimals; \n } else {\n decimals = self.ctx.decimals;\n }\n var tvPair = cellData.data[cellData.data.length - 1];\n var value = self.ctx.utils.formatValue(tvPair[1], decimals);\n self.ctx.chart.data.datasets[0].data[i] = parseFloat(value);\n }\n }\n self.ctx.chart.update();\n}\n\nself.onResize = function() {\n self.ctx.chart.resize();\n}\n\n", "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"borderWidth\": {\n \"title\": \"Border width\",\n \"type\": \"number\",\n \"default\": 5\n },\n \"borderColor\": {\n \"title\": \"Border color\",\n \"type\": \"string\",\n \"default\": \"#fff\"\n },\n \"legend\": {\n \"title\": \"Legend settings\",\n \"type\": \"object\",\n \"properties\": {\n \"display\": {\n \"title\": \"Display legend\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"labelsFontColor\": {\n \"title\": \"Labels font color\",\n \"type\": \"string\",\n \"default\": \"#666\"\n }\n }\n }\n },\n \"required\": []\n },\n \"form\": [\n \"borderWidth\", \n {\n \"key\": \"borderColor\",\n \"type\": \"color\"\n }, \n {\n \"key\": \"legend\",\n \"items\": [\n \"legend.display\",\n {\n \"key\": \"legend.labelsFontColor\",\n \"type\": \"color\"\n }\n ]\n }\n ]\n}", "dataKeySettingsSchema": "{}\n", "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"First\",\"color\":\"#26a69a\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = (prevValue-50) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+50;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Second\",\"color\":\"#f57c00\",\"settings\":{},\"_hash\":0.545701115289893,\"funcBody\":\"var value = (prevValue-20) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+20;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Third\",\"color\":\"#afb42b\",\"settings\":{},\"_hash\":0.2592906835158064,\"funcBody\":\"var value = (prevValue-40) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+40;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Fourth\",\"color\":\"#673ab7\",\"settings\":{},\"_hash\":0.12880275585455747,\"funcBody\":\"var value = (prevValue-50) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+50;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"borderWidth\":5,\"borderColor\":\"#fff\",\"legend\":{\"display\":true,\"labelsFontColor\":\"#666666\"}},\"title\":\"Doughnut - Chart.js\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400}}" @@ -91,7 +91,7 @@ ], "templateHtml": "\n", "templateCss": "", - "controllerScript": "self.onInit = function() {\n \n var pieData = {\n labels: [],\n datasets: []\n };\n\n var dataset = {\n data: [],\n backgroundColor: [],\n borderColor: [],\n borderWidth: [],\n hoverBackgroundColor: []\n }\n \n pieData.datasets.push(dataset);\n \n for (var i=0; i < self.ctx.data.length; i++) {\n var dataKey = self.ctx.data[i].dataKey;\n pieData.labels.push(dataKey.label);\n dataset.data.push(0);\n var hoverBackgroundColor = tinycolor(dataKey.color).lighten(15);\n var borderColor = tinycolor(dataKey.color).darken();\n dataset.backgroundColor.push(dataKey.color);\n dataset.borderColor.push('#fff');\n dataset.borderWidth.push(5);\n dataset.hoverBackgroundColor.push(hoverBackgroundColor.toRgbString());\n }\n\n var ctx = $('#pieChart', self.ctx.$container);\n self.ctx.chart = new Chart(ctx, {\n type: 'pie',\n data: pieData,\n options: {\n responsive: false,\n maintainAspectRatio: false\n }\n }); \n \n self.onResize();\n}\n\nself.onDataUpdated = function() {\n for (var i = 0; i < self.ctx.data.length; i++) {\n var cellData = self.ctx.data[i];\n if (cellData.data.length > 0) {\n var tvPair = cellData.data[cellData.data.length - 1];\n var value = tvPair[1];\n self.ctx.chart.data.datasets[0].data[i] = parseFloat(value);\n }\n }\n self.ctx.chart.update();\n}\n\nself.onResize = function() {\n self.ctx.chart.resize();\n}\n", + "controllerScript": "self.onInit = function() {\n \n var pieData = {\n labels: [],\n datasets: []\n };\n\n var dataset = {\n data: [],\n backgroundColor: [],\n borderColor: [],\n borderWidth: [],\n hoverBackgroundColor: []\n }\n \n pieData.datasets.push(dataset);\n \n for (var i=0; i < self.ctx.data.length; i++) {\n var dataKey = self.ctx.data[i].dataKey;\n var units = dataKey.units && dataKey.units.length ? dataKey.units : self.ctx.units;\n units = units ? (' (' + units + ')') : '';\n pieData.labels.push(dataKey.label + units);\n dataset.data.push(0);\n var hoverBackgroundColor = tinycolor(dataKey.color).lighten(15);\n var borderColor = tinycolor(dataKey.color).darken();\n dataset.backgroundColor.push(dataKey.color);\n dataset.borderColor.push('#fff');\n dataset.borderWidth.push(5);\n dataset.hoverBackgroundColor.push(hoverBackgroundColor.toRgbString());\n }\n\n var ctx = $('#pieChart', self.ctx.$container);\n self.ctx.chart = new Chart(ctx, {\n type: 'pie',\n data: pieData,\n options: {\n responsive: false,\n maintainAspectRatio: false\n }\n }); \n \n self.onResize();\n}\n\nself.onDataUpdated = function() {\n for (var i = 0; i < self.ctx.data.length; i++) {\n var cellData = self.ctx.data[i];\n if (cellData.data.length > 0) {\n var decimals;\n if (typeof cellData.dataKey.decimals !== 'undefined' \n && cellData.dataKey.decimals !== null ) {\n decimals = cellData.dataKey.decimals; \n } else {\n decimals = self.ctx.decimals;\n }\n var tvPair = cellData.data[cellData.data.length - 1];\n var value = self.ctx.utils.formatValue(tvPair[1], decimals);\n self.ctx.chart.data.datasets[0].data[i] = parseFloat(value);\n }\n }\n self.ctx.chart.update();\n}\n\nself.onResize = function() {\n self.ctx.chart.resize();\n}\n", "settingsSchema": "{}", "dataKeySettingsSchema": "{}\n", "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"First\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = (prevValue-50) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+50;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Second\",\"color\":\"#4caf50\",\"settings\":{},\"_hash\":0.545701115289893,\"funcBody\":\"var value = (prevValue-20) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+20;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Third\",\"color\":\"#f44336\",\"settings\":{},\"_hash\":0.2592906835158064,\"funcBody\":\"var value = (prevValue-40) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+40;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Fourth\",\"color\":\"#ffc107\",\"settings\":{},\"_hash\":0.12880275585455747,\"funcBody\":\"var value = (prevValue-50) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+50;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{},\"title\":\"Pie - Chart.js\"}" @@ -111,7 +111,7 @@ ], "templateHtml": "\n", "templateCss": "", - "controllerScript": "self.onInit = function() {\n var pieData = {\n labels: [],\n datasets: []\n };\n\n var dataset = {\n data: [],\n backgroundColor: [],\n borderColor: [],\n borderWidth: [],\n hoverBackgroundColor: []\n }\n \n pieData.datasets.push(dataset);\n \n for (var i = 0; i < self.ctx.data.length; i++) {\n var dataKey = self.ctx.data[i].dataKey;\n pieData.labels.push(dataKey.label);\n dataset.data.push(0);\n var hoverBackgroundColor = tinycolor(dataKey.color).lighten(15);\n var borderColor = tinycolor(dataKey.color).darken();\n dataset.backgroundColor.push(dataKey.color);\n dataset.borderColor.push('#fff');\n dataset.borderWidth.push(5);\n dataset.hoverBackgroundColor.push(hoverBackgroundColor.toRgbString());\n }\n\n var ctx = $('#pieChart', self.ctx.$container);\n self.ctx.chart = new Chart(ctx, {\n type: 'polarArea',\n data: pieData,\n options: {\n responsive: false,\n maintainAspectRatio: false\n }\n });\n \n self.onResize();\n}\n\nself.onDataUpdated = function() {\n for (var i = 0; i < self.ctx.data.length; i++) {\n var cellData = self.ctx.data[i];\n if (cellData.data.length > 0) {\n var tvPair = cellData.data[cellData.data.length - 1];\n var value = tvPair[1];\n self.ctx.chart.data.datasets[0].data[i] = parseFloat(value);\n }\n }\n \n self.ctx.chart.update();\n}\n\nself.onResize = function() {\n if (self.ctx.height >= 70) {\n try {\n self.ctx.chart.resize();\n } catch (e) {}\n }\n}\n", + "controllerScript": "self.onInit = function() {\n var pieData = {\n labels: [],\n datasets: []\n };\n\n var dataset = {\n data: [],\n backgroundColor: [],\n borderColor: [],\n borderWidth: [],\n hoverBackgroundColor: []\n }\n \n pieData.datasets.push(dataset);\n \n for (var i = 0; i < self.ctx.data.length; i++) {\n var dataKey = self.ctx.data[i].dataKey;\n var units = dataKey.units && dataKey.units.length ? dataKey.units : self.ctx.units;\n units = units ? (' (' + units + ')') : '';\n pieData.labels.push(dataKey.label + units);\n dataset.data.push(0);\n var hoverBackgroundColor = tinycolor(dataKey.color).lighten(15);\n var borderColor = tinycolor(dataKey.color).darken();\n dataset.backgroundColor.push(dataKey.color);\n dataset.borderColor.push('#fff');\n dataset.borderWidth.push(5);\n dataset.hoverBackgroundColor.push(hoverBackgroundColor.toRgbString());\n }\n\n var ctx = $('#pieChart', self.ctx.$container);\n self.ctx.chart = new Chart(ctx, {\n type: 'polarArea',\n data: pieData,\n options: {\n responsive: false,\n maintainAspectRatio: false\n }\n });\n \n self.onResize();\n}\n\nself.onDataUpdated = function() {\n for (var i = 0; i < self.ctx.data.length; i++) {\n var cellData = self.ctx.data[i];\n if (cellData.data.length > 0) {\n var decimals;\n if (typeof cellData.dataKey.decimals !== 'undefined' \n && cellData.dataKey.decimals !== null ) {\n decimals = cellData.dataKey.decimals; \n } else {\n decimals = self.ctx.decimals;\n }\n var tvPair = cellData.data[cellData.data.length - 1];\n var value = self.ctx.utils.formatValue(tvPair[1], decimals);\n self.ctx.chart.data.datasets[0].data[i] = parseFloat(value);\n }\n }\n \n self.ctx.chart.update();\n}\n\nself.onResize = function() {\n if (self.ctx.height >= 70) {\n try {\n self.ctx.chart.resize();\n } catch (e) {}\n }\n}\n", "settingsSchema": "{}", "dataKeySettingsSchema": "{}\n", "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"First\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = (prevValue-50) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+50;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Second\",\"color\":\"#4caf50\",\"settings\":{},\"_hash\":0.545701115289893,\"funcBody\":\"var value = (prevValue-20) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+20;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Third\",\"color\":\"#f44336\",\"settings\":{},\"_hash\":0.2592906835158064,\"funcBody\":\"var value = (prevValue-40) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+40;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Fourth\",\"color\":\"#ffc107\",\"settings\":{},\"_hash\":0.12880275585455747,\"funcBody\":\"var value = (prevValue-50) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+50;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Fifth\",\"color\":\"#607d8b\",\"settings\":{},\"_hash\":0.2074391823443591,\"funcBody\":\"var value = (prevValue-50) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+50;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{},\"title\":\"Polar Area - Chart.js\"}" @@ -131,7 +131,7 @@ ], "templateHtml": "\n", "templateCss": "", - "controllerScript": "self.onInit = function() {\n var barData = {\n labels: [],\n datasets: []\n };\n\n var backgroundColor = tinycolor(self.ctx.data[0].dataKey.color);\n backgroundColor.setAlpha(0.2);\n var borderColor = tinycolor(self.ctx.data[0].dataKey.color);\n borderColor.setAlpha(1);\n var dataset = {\n label: self.ctx.datasources[0].name,\n data: [],\n backgroundColor: backgroundColor.toRgbString(),\n borderColor: borderColor.toRgbString(),\n pointBackgroundColor: borderColor.toRgbString(),\n pointBorderColor: borderColor.darken().toRgbString(),\n borderWidth: 1\n }\n \n barData.datasets.push(dataset);\n \n for (var i = 0; i < self.ctx.data.length; i++) {\n var dataKey = self.ctx.data[i].dataKey;\n barData.labels.push(dataKey.label);\n dataset.data.push(0);\n }\n\n var ctx = $('#radarChart', self.ctx.$container);\n self.ctx.chart = new Chart(ctx, {\n type: 'radar',\n data: barData,\n options: {\n responsive: false,\n maintainAspectRatio: false\n }\n });\n \n self.onResize();\n}\n\nself.onDataUpdated = function() {\n for (var i = 0; i < self.ctx.data.length; i++) {\n var cellData = self.ctx.data[i];\n if (cellData.data.length > 0) {\n var tvPair = cellData.data[cellData.data.length - 1];\n var value = tvPair[1];\n self.ctx.chart.data.datasets[0].data[i] = parseFloat(value);\n }\n } \n self.ctx.chart.update();\n}\n\nself.onResize = function() {\n if (self.ctx.height >= 70) {\n self.ctx.chart.resize();\n }\n}\n", + "controllerScript": "self.onInit = function() {\n var barData = {\n labels: [],\n datasets: []\n };\n\n var backgroundColor = tinycolor(self.ctx.data[0].dataKey.color);\n backgroundColor.setAlpha(0.2);\n var borderColor = tinycolor(self.ctx.data[0].dataKey.color);\n borderColor.setAlpha(1);\n var dataset = {\n label: self.ctx.datasources[0].name,\n data: [],\n backgroundColor: backgroundColor.toRgbString(),\n borderColor: borderColor.toRgbString(),\n pointBackgroundColor: borderColor.toRgbString(),\n pointBorderColor: borderColor.darken().toRgbString(),\n borderWidth: 1\n }\n \n barData.datasets.push(dataset);\n \n for (var i = 0; i < self.ctx.data.length; i++) {\n var dataKey = self.ctx.data[i].dataKey;\n var units = dataKey.units && dataKey.units.length ? dataKey.units : self.ctx.units;\n units = units ? (' (' + units + ')') : '';\n barData.labels.push(dataKey.label + units);\n dataset.data.push(0);\n }\n\n var ctx = $('#radarChart', self.ctx.$container);\n self.ctx.chart = new Chart(ctx, {\n type: 'radar',\n data: barData,\n options: {\n responsive: false,\n maintainAspectRatio: false\n }\n });\n \n self.onResize();\n}\n\nself.onDataUpdated = function() {\n for (var i = 0; i < self.ctx.data.length; i++) {\n var cellData = self.ctx.data[i];\n if (cellData.data.length > 0) {\n var decimals;\n if (typeof cellData.dataKey.decimals !== 'undefined' \n && cellData.dataKey.decimals !== null ) {\n decimals = cellData.dataKey.decimals; \n } else {\n decimals = self.ctx.decimals;\n }\n var tvPair = cellData.data[cellData.data.length - 1];\n var value = self.ctx.utils.formatValue(tvPair[1], decimals);\n self.ctx.chart.data.datasets[0].data[i] = parseFloat(value);\n }\n } \n self.ctx.chart.update();\n}\n\nself.onResize = function() {\n if (self.ctx.height >= 70) {\n self.ctx.chart.resize();\n }\n}\n", "settingsSchema": "{}", "dataKeySettingsSchema": "{}\n", "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"First\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = (prevValue-50) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+50;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Second\",\"color\":\"#4caf50\",\"settings\":{},\"_hash\":0.545701115289893,\"funcBody\":\"var value = (prevValue-20) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+20;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Third\",\"color\":\"#f44336\",\"settings\":{},\"_hash\":0.2592906835158064,\"funcBody\":\"var value = (prevValue-40) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+40;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Fourth\",\"color\":\"#ffc107\",\"settings\":{},\"_hash\":0.12880275585455747,\"funcBody\":\"var value = (prevValue-50) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+50;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{},\"title\":\"Radar - Chart.js\"}" diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/flot-widget.ts b/ui-ngx/src/app/modules/home/components/widget/lib/flot-widget.ts index 03f6d1b975..7b9ab5117a 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/flot-widget.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/flot-widget.ts @@ -20,7 +20,7 @@ import { createLabelFromDatasource, deepClone, insertVariable, - isDefined, + isDefined, isDefinedAndNotNull, isEqual, isNumber, isUndefined @@ -993,7 +993,7 @@ export class TbFlot { private seriesInfoDivFromInfo(seriesHoverInfo: TbFlotSeriesHoverInfo, seriesIndex: number): string { const units = seriesHoverInfo.units && seriesHoverInfo.units.length ? seriesHoverInfo.units : this.trackUnits; - const decimals = isDefined(seriesHoverInfo.decimals) ? seriesHoverInfo.decimals : this.trackDecimals; + const decimals = isDefinedAndNotNull(seriesHoverInfo.decimals) ? seriesHoverInfo.decimals : this.trackDecimals; const divElement = this.seriesInfoDiv(seriesHoverInfo.label, seriesHoverInfo.color, seriesHoverInfo.value, units, decimals, seriesHoverInfo.index === seriesIndex, null, seriesHoverInfo.tooltipValueFormatFunction); return divElement.prop('outerHTML'); @@ -1020,7 +1020,7 @@ export class TbFlot { private formatPieTooltip(item: TbFlotPlotItem): string { const units = item.series.dataKey.units && item.series.dataKey.units.length ? item.series.dataKey.units : this.trackUnits; - const decimals = isDefined(item.series.dataKey.decimals) ? item.series.dataKey.decimals : this.trackDecimals; + const decimals = isDefinedAndNotNull(item.series.dataKey.decimals) ? item.series.dataKey.decimals : this.trackDecimals; const divElement = this.seriesInfoDiv(item.series.dataKey.label, item.series.dataKey.color, item.datapoint[1][0][1], units, decimals, true, item.series.percent, item.series.dataKey.tooltipValueFormatFunction); return divElement.prop('outerHTML'); From 1a718593ecd6919f164b40830d3783b4415aab27 Mon Sep 17 00:00:00 2001 From: zbeacon Date: Mon, 12 Oct 2020 16:19:36 +0300 Subject: [PATCH 31/48] Refactoring --- .../device/DeviceProvisionServiceImpl.java | 72 +++++++++---------- .../transport/DefaultTransportApiService.java | 4 +- .../dao/device/DeviceProvisionService.java | 2 +- 3 files changed, 35 insertions(+), 43 deletions(-) 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 ab333442c4..d372b878f7 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 @@ -65,6 +65,7 @@ import org.thingsboard.server.service.state.DeviceStateService; import java.util.Collections; import java.util.List; import java.util.Optional; +import java.util.concurrent.ExecutionException; import java.util.concurrent.locks.ReentrantLock; @@ -109,7 +110,7 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { } @Override - public ListenableFuture provisionDevice(ProvisionRequest provisionRequest) { + public ProvisionResponse provisionDevice(ProvisionRequest provisionRequest) { String provisionRequestKey = provisionRequest.getCredentials().getProvisionDeviceKey(); String provisionRequestSecret = provisionRequest.getCredentials().getProvisionDeviceSecret(); @@ -152,35 +153,24 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { throw new ProvisionFailedException(ProvisionResponseStatus.NOT_FOUND.name()); } - private ListenableFuture processProvision(Device device, ProvisionRequest provisionRequest) { - ListenableFuture> provisionStateFuture = attributesService.find(device.getTenantId(), device.getId(), - DataConstants.SERVER_SCOPE, DEVICE_PROVISION_STATE); - ListenableFuture provisionedFuture = Futures.transformAsync(provisionStateFuture, optionalAtr -> { - if (optionalAtr != null && optionalAtr.isPresent()) { - String state = optionalAtr.get().getValueAsString(); - if (state.equals(PROVISIONED_STATE)) { - return Futures.immediateFuture(true); - } else { - log.error("[{}][{}] Unknown provision state: {}!", device.getName(), DEVICE_PROVISION_STATE, state); - throw new ProvisionFailedException(ProvisionResponseStatus.FAILURE.name()); - } - } - return Futures.transform(saveProvisionStateAttribute(device), input -> false, MoreExecutors.directExecutor()); - }, MoreExecutors.directExecutor()); - if (provisionedFuture.isCancelled()) { - throw new RuntimeException("Unknown provision state!"); - } - return Futures.transform(provisionedFuture, provisioned -> { - if (provisioned) { + private ProvisionResponse processProvision(Device device, ProvisionRequest provisionRequest) { + try { + Optional provisionState = attributesService.find(device.getTenantId(), device.getId(), + DataConstants.SERVER_SCOPE, DEVICE_PROVISION_STATE).get(); + if (provisionState != null && provisionState.isPresent() && !provisionState.get().getValueAsString().equals(PROVISIONED_STATE)) { notify(device, provisionRequest, DataConstants.PROVISION_FAILURE, false); throw new ProvisionFailedException(ProvisionResponseStatus.FAILURE.name()); + } else { + saveProvisionStateAttribute(device).get(); + notify(device, provisionRequest, DataConstants.PROVISION_SUCCESS, true); } - notify(device, provisionRequest, DataConstants.PROVISION_SUCCESS, true); - return new ProvisionResponse(deviceCredentialsService.findDeviceCredentialsByDeviceId(device.getTenantId(), device.getId()), ProvisionResponseStatus.SUCCESS); - }, MoreExecutors.directExecutor()); + } catch (InterruptedException | ExecutionException e) { + throw new ProvisionFailedException(ProvisionResponseStatus.FAILURE.name()); + } + return new ProvisionResponse(deviceCredentialsService.findDeviceCredentialsByDeviceId(device.getTenantId(), device.getId()), ProvisionResponseStatus.SUCCESS); } - private ListenableFuture createDevice(ProvisionRequest provisionRequest, DeviceProfile profile) { + private ProvisionResponse createDevice(ProvisionRequest provisionRequest, DeviceProfile profile) { deviceCreationLock.lock(); try { return processCreateDevice(provisionRequest, profile); @@ -194,22 +184,24 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { logAction(device.getTenantId(), device.getCustomerId(), device, success, provisionRequest); } - private ListenableFuture processCreateDevice(ProvisionRequest provisionRequest, DeviceProfile profile) { + private ProvisionResponse processCreateDevice(ProvisionRequest provisionRequest, DeviceProfile profile) { Device device = deviceService.findDeviceByTenantIdAndName(profile.getTenantId(), provisionRequest.getDeviceName()); - if (device == null) { - Device savedDevice = deviceService.saveDevice(provisionRequest, profile); - - deviceStateService.onDeviceAdded(savedDevice); - pushDeviceCreatedEventToRuleEngine(savedDevice); - notify(savedDevice, provisionRequest, DataConstants.PROVISION_SUCCESS, true); - - return Futures.transform(saveProvisionStateAttribute(savedDevice), input -> - new ProvisionResponse( - getDeviceCredentials(savedDevice), - ProvisionResponseStatus.SUCCESS), MoreExecutors.directExecutor()); - } else { - log.warn("[{}] The device is already provisioned!", device.getName()); - notify(device, provisionRequest, DataConstants.PROVISION_FAILURE, false); + try { + if (device == null) { + Device savedDevice = deviceService.saveDevice(provisionRequest, profile); + + deviceStateService.onDeviceAdded(savedDevice); + saveProvisionStateAttribute(savedDevice).get(); + pushDeviceCreatedEventToRuleEngine(savedDevice); + notify(savedDevice, provisionRequest, DataConstants.PROVISION_SUCCESS, true); + + return new ProvisionResponse(getDeviceCredentials(savedDevice), ProvisionResponseStatus.SUCCESS); + } else { + log.warn("[{}] The device is already provisioned!", device.getName()); + notify(device, provisionRequest, DataConstants.PROVISION_FAILURE, false); + throw new ProvisionFailedException(ProvisionResponseStatus.FAILURE.name()); + } + } catch (InterruptedException | ExecutionException e) { throw new ProvisionFailedException(ProvisionResponseStatus.FAILURE.name()); } } diff --git a/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java b/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java index 7791ec67a7..7fa4630359 100644 --- a/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java +++ b/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java @@ -278,7 +278,7 @@ public class DefaultTransportApiService implements TransportApiService { private ListenableFuture handle(ProvisionDeviceRequestMsg requestMsg) { ListenableFuture provisionResponseFuture = null; try { - provisionResponseFuture = deviceProvisionService.provisionDevice( + provisionResponseFuture = Futures.immediateFuture(deviceProvisionService.provisionDevice( new ProvisionRequest( requestMsg.getDeviceName(), requestMsg.getCredentialsType() != null ? DeviceCredentialsType.valueOf(requestMsg.getCredentialsType().name()) : null, @@ -289,7 +289,7 @@ public class DefaultTransportApiService implements TransportApiService { requestMsg.getCredentialsDataProto().getValidateDeviceX509CertRequestMsg().getHash()), new ProvisionDeviceProfileCredentials( requestMsg.getProvisionDeviceCredentialsMsg().getProvisionDeviceKey(), - requestMsg.getProvisionDeviceCredentialsMsg().getProvisionDeviceSecret()))); + requestMsg.getProvisionDeviceCredentialsMsg().getProvisionDeviceSecret())))); } catch (ProvisionFailedException e) { return Futures.immediateFuture(getTransportApiResponseMsg( TransportProtos.DeviceCredentialsProto.getDefaultInstance(), diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceProvisionService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceProvisionService.java index 2a0d651801..5d038b2d31 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceProvisionService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceProvisionService.java @@ -24,5 +24,5 @@ import org.thingsboard.server.dao.device.provision.ProvisionResponse; public interface DeviceProvisionService { - ListenableFuture provisionDevice(ProvisionRequest provisionRequest) throws ProvisionFailedException; + ProvisionResponse provisionDevice(ProvisionRequest provisionRequest) throws ProvisionFailedException; } From 155b967b478f0d3462d06ba95a745255de2bf4be Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Mon, 12 Oct 2020 17:29:27 +0300 Subject: [PATCH 32/48] Improve device profile alarm rules UI --- .../home/components/home-components.module.ts | 19 +- ...alarm-rule-condition-dialog.component.html | 124 ++++++++++++ ...alarm-rule-condition-dialog.component.scss | 20 ++ .../alarm-rule-condition-dialog.component.ts | 150 ++++++++++++++ .../alarm/alarm-rule-condition.component.html | 8 +- .../alarm/alarm-rule-condition.component.scss | 11 +- .../alarm/alarm-rule-condition.component.ts | 85 ++++++-- .../profile/alarm/alarm-rule.component.html | 118 +++-------- .../profile/alarm/alarm-rule.component.scss | 24 +++ .../profile/alarm/alarm-rule.component.ts | 93 +++------ ...l => alarm-schedule-dialog.component.html} | 15 +- ....ts => alarm-schedule-dialog.component.ts} | 34 ++-- .../alarm/alarm-schedule-info.component.html | 33 +++ .../alarm/alarm-schedule-info.component.scss | 34 ++++ .../alarm/alarm-schedule-info.component.ts | 137 +++++++++++++ .../alarm/alarm-schedule.component.html | 191 ++---------------- .../profile/alarm/alarm-schedule.component.ts | 83 ++++---- .../alarm/create-alarm-rules.component.scss | 1 + .../alarm/device-profile-alarm.component.html | 2 +- .../alarm/device-profile-alarm.component.ts | 1 + .../device-profile-alarms.component.html | 3 +- .../edit-alarm-details-dialog.component.html | 53 +++++ .../edit-alarm-details-dialog.component.ts | 80 ++++++++ .../device-profile-tabs.component.html | 28 +-- ui-ngx/src/app/shared/models/device.models.ts | 61 ++++++ .../app/shared/models/query/query.models.ts | 32 +-- .../assets/locale/locale.constant-en_US.json | 10 +- 27 files changed, 992 insertions(+), 458 deletions(-) create mode 100644 ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule-condition-dialog.component.html create mode 100644 ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule-condition-dialog.component.scss create mode 100644 ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule-condition-dialog.component.ts rename ui-ngx/src/app/modules/home/components/profile/alarm/{alarm-rule-key-filters-dialog.component.html => alarm-schedule-dialog.component.html} (74%) rename ui-ngx/src/app/modules/home/components/profile/alarm/{alarm-rule-key-filters-dialog.component.ts => alarm-schedule-dialog.component.ts} (65%) create mode 100644 ui-ngx/src/app/modules/home/components/profile/alarm/alarm-schedule-info.component.html create mode 100644 ui-ngx/src/app/modules/home/components/profile/alarm/alarm-schedule-info.component.scss create mode 100644 ui-ngx/src/app/modules/home/components/profile/alarm/alarm-schedule-info.component.ts create mode 100644 ui-ngx/src/app/modules/home/components/profile/alarm/edit-alarm-details-dialog.component.html create mode 100644 ui-ngx/src/app/modules/home/components/profile/alarm/edit-alarm-details-dialog.component.ts diff --git a/ui-ngx/src/app/modules/home/components/home-components.module.ts b/ui-ngx/src/app/modules/home/components/home-components.module.ts index 9bbac4154d..15d33f709c 100644 --- a/ui-ngx/src/app/modules/home/components/home-components.module.ts +++ b/ui-ngx/src/app/modules/home/components/home-components.module.ts @@ -102,13 +102,16 @@ import { DeviceProfileAlarmComponent } from './profile/alarm/device-profile-alar import { CreateAlarmRulesComponent } from './profile/alarm/create-alarm-rules.component'; import { AlarmRuleComponent } from './profile/alarm/alarm-rule.component'; import { AlarmRuleConditionComponent } from './profile/alarm/alarm-rule-condition.component'; -import { AlarmRuleKeyFiltersDialogComponent } from './profile/alarm/alarm-rule-key-filters-dialog.component'; import { FilterTextComponent } from './filter/filter-text.component'; import { AddDeviceProfileDialogComponent } from './profile/add-device-profile-dialog.component'; import { RuleChainAutocompleteComponent } from './rule-chain/rule-chain-autocomplete.component'; import { AlarmScheduleComponent } from './profile/alarm/alarm-schedule.component'; import { DeviceWizardDialogComponent } from './wizard/device-wizard-dialog.component'; import { DeviceCredentialsComponent } from './device/device-credentials.component'; +import { AlarmScheduleInfoComponent } from './profile/alarm/alarm-schedule-info.component'; +import { AlarmScheduleDialogComponent } from '@home/components/profile/alarm/alarm-schedule-dialog.component'; +import { EditAlarmDetailsDialogComponent } from './profile/alarm/edit-alarm-details-dialog.component'; +import { AlarmRuleConditionDialogComponent } from '@home/components/profile/alarm/alarm-rule-condition-dialog.component'; @NgModule({ declarations: @@ -190,7 +193,7 @@ import { DeviceCredentialsComponent } from './device/device-credentials.componen DeviceProfileTransportConfigurationComponent, CreateAlarmRulesComponent, AlarmRuleComponent, - AlarmRuleKeyFiltersDialogComponent, + AlarmRuleConditionDialogComponent, AlarmRuleConditionComponent, DeviceProfileAlarmComponent, DeviceProfileAlarmsComponent, @@ -198,9 +201,12 @@ import { DeviceCredentialsComponent } from './device/device-credentials.componen DeviceProfileDialogComponent, AddDeviceProfileDialogComponent, RuleChainAutocompleteComponent, + AlarmScheduleInfoComponent, AlarmScheduleComponent, DeviceWizardDialogComponent, - DeviceCredentialsComponent + DeviceCredentialsComponent, + AlarmScheduleDialogComponent, + EditAlarmDetailsDialogComponent ], imports: [ CommonModule, @@ -271,7 +277,7 @@ import { DeviceCredentialsComponent } from './device/device-credentials.componen DeviceProfileTransportConfigurationComponent, CreateAlarmRulesComponent, AlarmRuleComponent, - AlarmRuleKeyFiltersDialogComponent, + AlarmRuleConditionDialogComponent, AlarmRuleConditionComponent, DeviceProfileAlarmComponent, DeviceProfileAlarmsComponent, @@ -281,7 +287,10 @@ import { DeviceCredentialsComponent } from './device/device-credentials.componen RuleChainAutocompleteComponent, DeviceWizardDialogComponent, DeviceCredentialsComponent, - AlarmScheduleComponent + AlarmScheduleInfoComponent, + AlarmScheduleComponent, + AlarmScheduleDialogComponent, + EditAlarmDetailsDialogComponent ], providers: [ WidgetComponentService, diff --git a/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule-condition-dialog.component.html b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule-condition-dialog.component.html new file mode 100644 index 0000000000..7be952c68d --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule-condition-dialog.component.html @@ -0,0 +1,124 @@ + +
+ +

{{ (readonly ? 'device-profile.alarm-rule-condition' : 'device-profile.edit-alarm-rule-condition') | translate }}

+ + +
+ + +
+
+
+ + +
+ + device-profile.condition-type + + + {{ alarmConditionTypeTranslation.get(alarmConditionType) | translate }} + + + + {{ 'device-profile.condition-type-required' | translate }} + + +
+ + + + + {{ 'device-profile.condition-duration-value-required' | translate }} + + + {{ 'device-profile.condition-duration-value-range' | translate }} + + + {{ 'device-profile.condition-duration-value-range' | translate }} + + + {{ 'device-profile.condition-duration-value-pattern' | translate }} + + + + + + + {{ timeUnitTranslations.get(timeUnit) | translate }} + + + + {{ 'device-profile.condition-duration-time-unit-required' | translate }} + + +
+
+ + + + + {{ 'device-profile.condition-repeating-value-required' | translate }} + + + {{ 'device-profile.condition-repeating-value-range' | translate }} + + + {{ 'device-profile.condition-repeating-value-range' | translate }} + + + {{ 'device-profile.condition-repeating-value-pattern' | translate }} + + +
+
+
+
+
+
+ + +
+
diff --git a/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule-condition-dialog.component.scss b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule-condition-dialog.component.scss new file mode 100644 index 0000000000..4af573d021 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule-condition-dialog.component.scss @@ -0,0 +1,20 @@ +/** + * 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. + */ +:host { + .row { + margin-top: 1em; + } +} diff --git a/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule-condition-dialog.component.ts b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule-condition-dialog.component.ts new file mode 100644 index 0000000000..4055560b38 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule-condition-dialog.component.ts @@ -0,0 +1,150 @@ +/// +/// 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. +/// + +import { Component, Inject, OnInit, SkipSelf } from '@angular/core'; +import { ErrorStateMatcher } from '@angular/material/core'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { FormBuilder, FormControl, FormGroup, FormGroupDirective, NgForm, Validators } from '@angular/forms'; +import { Router } from '@angular/router'; +import { DialogComponent } from '@app/shared/components/dialog.component'; +import { UtilsService } from '@core/services/utils.service'; +import { TranslateService } from '@ngx-translate/core'; +import { KeyFilter, keyFilterInfosToKeyFilters, keyFiltersToKeyFilterInfos } from '@shared/models/query/query.models'; +import { AlarmCondition, AlarmConditionType, AlarmConditionTypeTranslationMap } from '@shared/models/device.models'; +import { TimeUnit, timeUnitTranslationMap } from '@shared/models/time/time.models'; + +export interface AlarmRuleConditionDialogData { + readonly: boolean; + condition: AlarmCondition; +} + +@Component({ + selector: 'tb-alarm-rule-condition-dialog', + templateUrl: './alarm-rule-condition-dialog.component.html', + providers: [{provide: ErrorStateMatcher, useExisting: AlarmRuleConditionDialogComponent}], + styleUrls: ['/alarm-rule-condition-dialog.component.scss'] +}) +export class AlarmRuleConditionDialogComponent extends DialogComponent + implements OnInit, ErrorStateMatcher { + + timeUnits = Object.keys(TimeUnit); + timeUnitTranslations = timeUnitTranslationMap; + alarmConditionTypes = Object.keys(AlarmConditionType); + AlarmConditionType = AlarmConditionType; + alarmConditionTypeTranslation = AlarmConditionTypeTranslationMap; + + readonly = this.data.readonly; + condition = this.data.condition; + + conditionFormGroup: FormGroup; + + submitted = false; + + constructor(protected store: Store, + protected router: Router, + @Inject(MAT_DIALOG_DATA) public data: AlarmRuleConditionDialogData, + @SkipSelf() private errorStateMatcher: ErrorStateMatcher, + public dialogRef: MatDialogRef, + private fb: FormBuilder, + private utils: UtilsService, + public translate: TranslateService) { + super(store, router, dialogRef); + + this.conditionFormGroup = this.fb.group({ + keyFilters: [keyFiltersToKeyFilterInfos(this.condition?.condition), Validators.required], + spec: this.fb.group({ + type: [AlarmConditionType.SIMPLE, Validators.required], + unit: [{value: null, disable: true}, Validators.required], + value: [{value: null, disable: true}, [Validators.required, Validators.min(1), Validators.max(2147483647), Validators.pattern('[0-9]*')]], + count: [{value: null, disable: true}, [Validators.required, Validators.min(1), Validators.max(2147483647), Validators.pattern('[0-9]*')]] + }) + }); + this.conditionFormGroup.patchValue({spec: this.condition?.spec}); + this.conditionFormGroup.get('spec.type').valueChanges.subscribe((type) => { + this.updateValidators(type, true, true); + }); + if (this.readonly) { + this.conditionFormGroup.disable({emitEvent: false}); + } else { + this.updateValidators(this.condition?.spec?.type); + } + } + + ngOnInit(): void { + } + + isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean { + const originalErrorState = this.errorStateMatcher.isErrorState(control, form); + const customErrorState = !!(control && control.invalid && this.submitted); + return originalErrorState || customErrorState; + } + + private updateValidators(type: AlarmConditionType, resetDuration = false, emitEvent = false) { + switch (type) { + case AlarmConditionType.DURATION: + this.conditionFormGroup.get('spec.value').enable(); + this.conditionFormGroup.get('spec.unit').enable(); + this.conditionFormGroup.get('spec.count').disable(); + if (resetDuration) { + this.conditionFormGroup.get('spec').patchValue({ + count: null + }); + } + break; + case AlarmConditionType.REPEATING: + this.conditionFormGroup.get('spec.count').enable(); + this.conditionFormGroup.get('spec.value').disable(); + this.conditionFormGroup.get('spec.unit').disable(); + if (resetDuration) { + this.conditionFormGroup.get('spec').patchValue({ + value: null, + unit: null + }); + } + break; + case AlarmConditionType.SIMPLE: + this.conditionFormGroup.get('spec.value').disable(); + this.conditionFormGroup.get('spec.unit').disable(); + this.conditionFormGroup.get('spec.count').disable(); + if (resetDuration) { + this.conditionFormGroup.get('spec').patchValue({ + value: null, + unit: null, + count: null + }); + } + break; + } + this.conditionFormGroup.get('spec.value').updateValueAndValidity({emitEvent}); + this.conditionFormGroup.get('spec.unit').updateValueAndValidity({emitEvent}); + this.conditionFormGroup.get('spec.count').updateValueAndValidity({emitEvent}); + } + + cancel(): void { + this.dialogRef.close(null); + } + + save(): void { + this.submitted = true; + this.condition = { + condition: keyFilterInfosToKeyFilters(this.conditionFormGroup.get('keyFilters').value), + spec: this.conditionFormGroup.get('spec').value + }; + this.dialogRef.close(this.condition); + } +} diff --git a/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule-condition.component.html b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule-condition.component.html index 170182ded3..15d6173e0e 100644 --- a/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule-condition.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule-condition.component.html @@ -15,8 +15,9 @@ limitations under the License. --> -
+
+
- + +
+
diff --git a/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule-condition.component.scss b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule-condition.component.scss index 1d654bc0f4..0b1ccabd65 100644 --- a/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule-condition.component.scss +++ b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule-condition.component.scss @@ -21,10 +21,15 @@ } } .tb-alarm-rule-condition { - padding: 8px; - border: 1px groove rgba(0, 0, 0, .25); - border-radius: 4px; cursor: pointer; + .tb-alarm-rule-condition-spec { + margin-top: 1em; + line-height: 1.8em; + padding: 4px; + &.disabled { + opacity: 0.7; + } + } } } diff --git a/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule-condition.component.ts b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule-condition.component.ts index 7416b944eb..23e52cf8cd 100644 --- a/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule-condition.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule-condition.component.ts @@ -18,20 +18,21 @@ import { Component, forwardRef, Input, OnInit } from '@angular/core'; import { ControlValueAccessor, FormBuilder, - FormControl, + FormControl, FormGroup, NG_VALIDATORS, NG_VALUE_ACCESSOR, - Validator + Validator, Validators } from '@angular/forms'; import { MatDialog } from '@angular/material/dialog'; -import { KeyFilter } from '@shared/models/query/query.models'; -import { deepClone } from '@core/utils'; -import { - AlarmRuleKeyFiltersDialogComponent, - AlarmRuleKeyFiltersDialogData -} from './alarm-rule-key-filters-dialog.component'; +import { deepClone, isUndefined } from '@core/utils'; import { TranslateService } from '@ngx-translate/core'; import { DatePipe } from '@angular/common'; +import { AlarmCondition, AlarmConditionSpec, AlarmConditionType } from '@shared/models/device.models'; +import { + AlarmRuleConditionDialogComponent, + AlarmRuleConditionDialogData +} from '@home/components/profile/alarm/alarm-rule-condition-dialog.component'; +import { TimeUnit } from '@shared/models/time/time.models'; @Component({ selector: 'tb-alarm-rule-condition', @@ -55,9 +56,11 @@ export class AlarmRuleConditionComponent implements ControlValueAccessor, OnInit @Input() disabled: boolean; - alarmRuleConditionControl: FormControl; + alarmRuleConditionFormGroup: FormGroup; + + specText = ''; - private modelValue: Array; + private modelValue: AlarmCondition; private propagateChange = (v: any) => { }; @@ -75,25 +78,31 @@ export class AlarmRuleConditionComponent implements ControlValueAccessor, OnInit } ngOnInit() { - this.alarmRuleConditionControl = this.fb.control(null); + this.alarmRuleConditionFormGroup = this.fb.group({ + condition: [null, Validators.required], + spec: [null, Validators.required] + }); } setDisabledState(isDisabled: boolean): void { this.disabled = isDisabled; if (this.disabled) { - this.alarmRuleConditionControl.disable({emitEvent: false}); + this.alarmRuleConditionFormGroup.disable({emitEvent: false}); } else { - this.alarmRuleConditionControl.enable({emitEvent: false}); + this.alarmRuleConditionFormGroup.enable({emitEvent: false}); } } - writeValue(value: Array): void { + writeValue(value: AlarmCondition): void { this.modelValue = value; + if (this.modelValue !== null && isUndefined(this.modelValue?.spec)) { + this.modelValue = Object.assign(this.modelValue, {spec: {type: AlarmConditionType.SIMPLE}}); + } this.updateConditionInfo(); } public conditionSet() { - return this.modelValue && this.modelValue.length; + return this.modelValue && this.modelValue.condition.length; } public validate(c: FormControl) { @@ -108,13 +117,13 @@ export class AlarmRuleConditionComponent implements ControlValueAccessor, OnInit if ($event) { $event.stopPropagation(); } - this.dialog.open>(AlarmRuleKeyFiltersDialogComponent, { + this.dialog.open(AlarmRuleConditionDialogComponent, { disableClose: true, panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], data: { readonly: this.disabled, - keyFilters: this.disabled ? this.modelValue : deepClone(this.modelValue) + condition: this.disabled ? this.modelValue : deepClone(this.modelValue) } }).afterClosed().subscribe((result) => { if (result) { @@ -125,7 +134,45 @@ export class AlarmRuleConditionComponent implements ControlValueAccessor, OnInit } private updateConditionInfo() { - this.alarmRuleConditionControl.patchValue(this.modelValue); + this.alarmRuleConditionFormGroup.patchValue( + { + condition: this.modelValue?.condition, + spec: this.modelValue?.spec + } + ); + this.updateSpecText(); + } + + private updateSpecText() { + this.specText = ''; + if (this.modelValue && this.modelValue.spec) { + const spec = this.modelValue.spec; + switch (spec.type) { + case AlarmConditionType.SIMPLE: + break; + case AlarmConditionType.DURATION: + let duringText = ''; + switch (spec.unit) { + case TimeUnit.SECONDS: + duringText = this.translate.instant('timewindow.seconds', {seconds: spec.value}); + break; + case TimeUnit.MINUTES: + duringText = this.translate.instant('timewindow.minutes', {minutes: spec.value}); + break; + case TimeUnit.HOURS: + duringText = this.translate.instant('timewindow.hours', {hours: spec.value}); + break; + case TimeUnit.DAYS: + duringText = this.translate.instant('timewindow.days', {days: spec.value}); + break; + } + this.specText = this.translate.instant('device-profile.condition-during', {during: duringText}); + break; + case AlarmConditionType.REPEATING: + this.specText = this.translate.instant('device-profile.condition-repeat-times', {count: spec.count}); + break; + } + } } private updateModel() { diff --git a/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule.component.html b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule.component.html index 66eb99f27e..3c49f61cf0 100644 --- a/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule.component.html @@ -16,90 +16,36 @@ -->
- - - - -
- - device-profile.condition-type - - - {{ alarmConditionTypeTranslation.get(alarmConditionType) | translate }} - - - - {{ 'device-profile.condition-type-required' | translate }} - - -
- - - - - {{ 'device-profile.condition-duration-value-required' | translate }} - - - {{ 'device-profile.condition-duration-value-range' | translate }} - - - {{ 'device-profile.condition-duration-value-range' | translate }} - - - {{ 'device-profile.condition-duration-value-pattern' | translate }} - - - - - - - {{ timeUnitTranslations.get(timeUnit) | translate }} - - - - {{ 'device-profile.condition-duration-time-unit-required' | translate }} - - -
-
- - - - - {{ 'device-profile.condition-repeating-value-required' | translate }} - - - {{ 'device-profile.condition-repeating-value-range' | translate }} - - - {{ 'device-profile.condition-repeating-value-range' | translate }} - - - {{ 'device-profile.condition-repeating-value-pattern' | translate }} - - -
-
-
- - - - - - - device-profile.alarm-details - - - -
+ + + + + + +
diff --git a/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule.component.scss b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule.component.scss index 8af986af69..a52e6f312e 100644 --- a/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule.component.scss +++ b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule.component.scss @@ -17,5 +17,29 @@ .row { margin-top: 1em; } + .tb-alarm-rule-details { + a.mat-button { + &:hover, &:focus { + border-bottom: none; + } + } + .tb-alarm-rule-details-content { + min-height: 33px; + overflow: hidden; + white-space: pre; + line-height: 1.8em; + padding: 4px; + cursor: pointer; + &.collapsed { + max-height: 33px; + white-space: nowrap; + text-overflow: ellipsis; + } + &.disabled { + opacity: 0.7; + cursor: auto; + } + } + } } diff --git a/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule.component.ts b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule.component.ts index fe2916b854..4c86f7dfbd 100644 --- a/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule.component.ts @@ -25,11 +25,14 @@ import { Validator, Validators } from '@angular/forms'; -import { AlarmConditionType, AlarmConditionTypeTranslationMap, AlarmRule } from '@shared/models/device.models'; +import { AlarmRule } from '@shared/models/device.models'; import { MatDialog } from '@angular/material/dialog'; -import { TimeUnit, timeUnitTranslationMap } from '@shared/models/time/time.models'; import { coerceBooleanProperty } from '@angular/cdk/coercion'; -import { isUndefined } from '@core/utils'; +import { isDefinedAndNotNull } from '@core/utils'; +import { + EditAlarmDetailsDialogComponent, + EditAlarmDetailsDialogData +} from '@home/components/profile/alarm/edit-alarm-details-dialog.component'; @Component({ selector: 'tb-alarm-rule', @@ -50,12 +53,6 @@ import { isUndefined } from '@core/utils'; }) export class AlarmRuleComponent implements ControlValueAccessor, OnInit, Validator { - timeUnits = Object.keys(TimeUnit); - timeUnitTranslations = timeUnitTranslationMap; - alarmConditionTypes = Object.keys(AlarmConditionType); - AlarmConditionType = AlarmConditionType; - alarmConditionTypeTranslation = AlarmConditionTypeTranslationMap; - @Input() disabled: boolean; @@ -72,6 +69,8 @@ export class AlarmRuleComponent implements ControlValueAccessor, OnInit, Validat alarmRuleFormGroup: FormGroup; + expandAlarmDetails = false; + private propagateChange = (v: any) => { }; constructor(private dialog: MatDialog, @@ -87,21 +86,10 @@ export class AlarmRuleComponent implements ControlValueAccessor, OnInit, Validat ngOnInit() { this.alarmRuleFormGroup = this.fb.group({ - condition: this.fb.group({ - condition: [null, Validators.required], - spec: this.fb.group({ - type: [AlarmConditionType.SIMPLE, Validators.required], - unit: [{value: null, disable: true}, Validators.required], - value: [{value: null, disable: true}, [Validators.required, Validators.min(1), Validators.max(2147483647), Validators.pattern('[0-9]*')]], - count: [{value: null, disable: true}, [Validators.required, Validators.min(1), Validators.max(2147483647), Validators.pattern('[0-9]*')]] - }) - }, Validators.required), + condition: [null, [Validators.required]], schedule: [null], alarmDetails: [null] }); - this.alarmRuleFormGroup.get('condition.spec.type').valueChanges.subscribe((type) => { - this.updateValidators(type, true, true); - }); this.alarmRuleFormGroup.valueChanges.subscribe(() => { this.updateModel(); }); @@ -118,11 +106,25 @@ export class AlarmRuleComponent implements ControlValueAccessor, OnInit, Validat writeValue(value: AlarmRule): void { this.modelValue = value; - if (this.modelValue !== null && isUndefined(this.modelValue?.condition?.spec)) { - this.modelValue = Object.assign(this.modelValue, {condition: {spec: {type: AlarmConditionType.SIMPLE}}}); - } this.alarmRuleFormGroup.reset(this.modelValue || undefined, {emitEvent: false}); - this.updateValidators(this.modelValue?.condition?.spec?.type); + } + + public openEditDetailsDialog($event: Event) { + if ($event) { + $event.stopPropagation(); + } + this.dialog.open(EditAlarmDetailsDialogComponent, { + disableClose: true, + panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], + data: { + alarmDetails: this.alarmRuleFormGroup.get('alarmDetails').value + } + }).afterClosed().subscribe((alarmDetails) => { + if (isDefinedAndNotNull(alarmDetails)) { + this.alarmRuleFormGroup.patchValue({alarmDetails}); + } + }); } public validate(c: FormControl) { @@ -133,47 +135,6 @@ export class AlarmRuleComponent implements ControlValueAccessor, OnInit, Validat }; } - private updateValidators(type: AlarmConditionType, resetDuration = false, emitEvent = false) { - switch (type) { - case AlarmConditionType.DURATION: - this.alarmRuleFormGroup.get('condition.spec.value').enable(); - this.alarmRuleFormGroup.get('condition.spec.unit').enable(); - this.alarmRuleFormGroup.get('condition.spec.count').disable(); - if (resetDuration) { - this.alarmRuleFormGroup.get('condition.spec').patchValue({ - count: null - }); - } - break; - case AlarmConditionType.REPEATING: - this.alarmRuleFormGroup.get('condition.spec.count').enable(); - this.alarmRuleFormGroup.get('condition.spec.value').disable(); - this.alarmRuleFormGroup.get('condition.spec.unit').disable(); - if (resetDuration) { - this.alarmRuleFormGroup.get('condition.spec').patchValue({ - value: null, - unit: null - }); - } - break; - case AlarmConditionType.SIMPLE: - this.alarmRuleFormGroup.get('condition.spec.value').disable(); - this.alarmRuleFormGroup.get('condition.spec.unit').disable(); - this.alarmRuleFormGroup.get('condition.spec.count').disable(); - if (resetDuration) { - this.alarmRuleFormGroup.get('condition.spec').patchValue({ - value: null, - unit: null, - count: null - }); - } - break; - } - this.alarmRuleFormGroup.get('condition.spec.value').updateValueAndValidity({emitEvent}); - this.alarmRuleFormGroup.get('condition.spec.unit').updateValueAndValidity({emitEvent}); - this.alarmRuleFormGroup.get('condition.spec.count').updateValueAndValidity({emitEvent}); - } - private updateModel() { const value = this.alarmRuleFormGroup.value; if (this.modelValue) { diff --git a/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule-key-filters-dialog.component.html b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-schedule-dialog.component.html similarity index 74% rename from ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule-key-filters-dialog.component.html rename to ui-ngx/src/app/modules/home/components/profile/alarm/alarm-schedule-dialog.component.html index 26defe62ff..abe511fc3f 100644 --- a/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule-key-filters-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-schedule-dialog.component.html @@ -15,9 +15,9 @@ limitations under the License. --> -
+ -

{{ (readonly ? 'device-profile.alarm-rule-condition' : 'device-profile.edit-alarm-rule-condition') | translate }}

+

{{ (readonly ? 'device-profile.schedule' : 'device-profile.edit-schedule') | translate }}

@@ -43,7 +40,7 @@ + + + +
+
+
+ + device-profile.alarm-details + + +
+
+
+
+ + +
+ diff --git a/ui-ngx/src/app/modules/home/components/profile/alarm/edit-alarm-details-dialog.component.ts b/ui-ngx/src/app/modules/home/components/profile/alarm/edit-alarm-details-dialog.component.ts new file mode 100644 index 0000000000..bc46bee51d --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/profile/alarm/edit-alarm-details-dialog.component.ts @@ -0,0 +1,80 @@ +/// +/// 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. +/// + +import { Component, Inject, OnInit, SkipSelf } from '@angular/core'; +import { ErrorStateMatcher } from '@angular/material/core'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { FormBuilder, FormControl, FormGroup, FormGroupDirective, NgForm } from '@angular/forms'; +import { Router } from '@angular/router'; +import { DialogComponent } from '@app/shared/components/dialog.component'; +import { UtilsService } from '@core/services/utils.service'; +import { TranslateService } from '@ngx-translate/core'; + +export interface EditAlarmDetailsDialogData { + alarmDetails: string; +} + +@Component({ + selector: 'tb-edit-alarm-details-dialog', + templateUrl: './edit-alarm-details-dialog.component.html', + providers: [{provide: ErrorStateMatcher, useExisting: EditAlarmDetailsDialogComponent}], + styleUrls: [] +}) +export class EditAlarmDetailsDialogComponent extends DialogComponent + implements OnInit, ErrorStateMatcher { + + alarmDetails = this.data.alarmDetails; + + editDetailsFormGroup: FormGroup; + + submitted = false; + + constructor(protected store: Store, + protected router: Router, + @Inject(MAT_DIALOG_DATA) public data: EditAlarmDetailsDialogData, + @SkipSelf() private errorStateMatcher: ErrorStateMatcher, + public dialogRef: MatDialogRef, + private fb: FormBuilder, + private utils: UtilsService, + public translate: TranslateService) { + super(store, router, dialogRef); + + this.editDetailsFormGroup = this.fb.group({ + alarmDetails: [this.alarmDetails] + }); + } + + ngOnInit(): void { + } + + isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean { + const originalErrorState = this.errorStateMatcher.isErrorState(control, form); + const customErrorState = !!(control && control.invalid && this.submitted); + return originalErrorState || customErrorState; + } + + cancel(): void { + this.dialogRef.close(null); + } + + save(): void { + this.submitted = true; + this.alarmDetails = this.editDetailsFormGroup.get('alarmDetails').value; + this.dialogRef.close(this.alarmDetails); + } +} diff --git a/ui-ngx/src/app/modules/home/pages/device-profile/device-profile-tabs.component.html b/ui-ngx/src/app/modules/home/pages/device-profile/device-profile-tabs.component.html index 437fc21c86..76dfe27511 100644 --- a/ui-ngx/src/app/modules/home/pages/device-profile/device-profile-tabs.component.html +++ b/ui-ngx/src/app/modules/home/pages/device-profile/device-profile-tabs.component.html @@ -61,29 +61,7 @@ - - - - - - - - - - - - - + + diff --git a/ui-ngx/src/app/shared/models/device.models.ts b/ui-ngx/src/app/shared/models/device.models.ts index d1fb40a9d5..4354521330 100644 --- a/ui-ngx/src/app/shared/models/device.models.ts +++ b/ui-ngx/src/app/shared/models/device.models.ts @@ -25,6 +25,8 @@ import { RuleChainId } from '@shared/models/id/rule-chain-id'; import { EntityInfoData } from '@shared/models/entity.models'; import { KeyFilter } from '@shared/models/query/query.models'; import { TimeUnit } from '@shared/models/time/time.models'; +import * as _moment from 'moment-timezone'; +import { AbstractControl, FormGroup } from '@angular/forms'; export enum DeviceProfileType { DEFAULT = 'DEFAULT' @@ -408,3 +410,62 @@ export interface ClaimResult { device: Device; response: ClaimResponse; } + +export const dayOfWeekTranslations = new Array( + 'device-profile.schedule-day.monday', + 'device-profile.schedule-day.tuesday', + 'device-profile.schedule-day.wednesday', + 'device-profile.schedule-day.thursday', + 'device-profile.schedule-day.friday', + 'device-profile.schedule-day.saturday', + 'device-profile.schedule-day.sunday' +); + +export function getDayString(day: number): string { + switch (day) { + case 0: + return 'device-profile.schedule-day.monday'; + case 1: + return this.translate.instant('device-profile.schedule-day.tuesday'); + case 2: + return this.translate.instant('device-profile.schedule-day.wednesday'); + case 3: + return this.translate.instant('device-profile.schedule-day.thursday'); + case 4: + return this.translate.instant('device-profile.schedule-day.friday'); + case 5: + return this.translate.instant('device-profile.schedule-day.saturday'); + case 6: + return this.translate.instant('device-profile.schedule-day.sunday'); + } +} + +export function timeOfDayToUTCTimestamp(date: Date | number): number { + if (typeof date === 'number' || date === null) { + return 0; + } + return _moment.utc([1970, 0, 1, date.getHours(), date.getMinutes(), date.getSeconds(), 0]).valueOf(); +} + +export function utcTimestampToTimeOfDay(time = 0): Date { + return new Date(time + new Date().getTimezoneOffset() * 60 * 1000); +} + +function timeOfDayToMoment(date: Date | number): _moment.Moment { + if (typeof date === 'number' || date === null) { + return _moment([1970, 0, 1, 0, 0, 0, 0]); + } + return _moment([1970, 0, 1, date.getHours(), date.getMinutes(), 0, 0]); +} + +export function getAlarmScheduleRangeText(startsOn: Date | number, endsOn: Date | number): string { + const start = timeOfDayToMoment(startsOn); + const end = timeOfDayToMoment(endsOn); + if (start < end) { + return `${start.format('hh:mm A')} – ${end.format('hh:mm A')}`; + } else if (start.valueOf() === 0 && end.valueOf() === 0 || start.isSame(_moment([1970, 0])) && end.isSame(_moment([1970, 0]))) { + return '12:00 AM – 12:00 PM'; + } + return `12:00 AM – ${end.format('hh:mm A')}` + + ` and ${start.format('hh:mm A')} – 12:00 PM`; +} diff --git a/ui-ngx/src/app/shared/models/query/query.models.ts b/ui-ngx/src/app/shared/models/query/query.models.ts index 3f4b6896db..ba86bce85b 100644 --- a/ui-ngx/src/app/shared/models/query/query.models.ts +++ b/ui-ngx/src/app/shared/models/query/query.models.ts @@ -476,21 +476,23 @@ export function keyFilterInfosToKeyFilters(keyFilterInfos: Array) export function keyFiltersToKeyFilterInfos(keyFilters: Array): Array { const keyFilterInfos: Array = []; const keyFilterInfoMap: {[infoKey: string]: KeyFilterInfo} = {}; - for (const keyFilter of keyFilters) { - const key = keyFilter.key; - const infoKey = key.key + key.type + keyFilter.valueType; - let keyFilterInfo = keyFilterInfoMap[infoKey]; - if (!keyFilterInfo) { - keyFilterInfo = { - key, - valueType: keyFilter.valueType, - predicates: [] - }; - keyFilterInfoMap[infoKey] = keyFilterInfo; - keyFilterInfos.push(keyFilterInfo); - } - if (keyFilter.predicate) { - keyFilterInfo.predicates.push(keyFilterPredicateToKeyFilterPredicateInfo(keyFilter.predicate)); + if (keyFilters) { + for (const keyFilter of keyFilters) { + const key = keyFilter.key; + const infoKey = key.key + key.type + keyFilter.valueType; + let keyFilterInfo = keyFilterInfoMap[infoKey]; + if (!keyFilterInfo) { + keyFilterInfo = { + key, + valueType: keyFilter.valueType, + predicates: [] + }; + keyFilterInfoMap[infoKey] = keyFilterInfo; + keyFilterInfos.push(keyFilterInfo); + } + if (keyFilter.predicate) { + keyFilterInfo.predicates.push(keyFilterPredicateToKeyFilterPredicateInfo(keyFilter.predicate)); + } } } return keyFilterInfos; diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 8f89fa8f39..9a5d71a2e6 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -55,7 +55,9 @@ "continue": "Continue", "discard-changes": "Discard Changes", "download": "Download", - "next-with-label": "Next: {{label}}" + "next-with-label": "Next: {{label}}", + "read-more": "Read more", + "hide": "Hide" }, "aggregation": { "aggregation": "Aggregation", @@ -932,15 +934,18 @@ "condition-type": "Condition type", "condition-type-simple": "Simple", "condition-type-duration": "Duration", + "condition-during": "During {{during}}", "condition-type-repeating": "Repeating", "condition-type-required": "Condition type is required.", "condition-repeating-value": "Count of events", "condition-repeating-value-range": "Count of events should be in a range from 1 to 2147483647.", "condition-repeating-value-pattern": "Count of events should be integers.", "condition-repeating-value-required": "Count of events is required.", + "condition-repeat-times": "Repeats { count, plural, 1 {1 time} other {# times} }", "schedule-type": "Scheduler type", "schedule-type-required": "Scheduler type is required.", "schedule": "Schedule", + "edit-schedule": "Edit alarm schedule", "schedule-any-time": "Active all the time", "schedule-specific-time": "Active at a specific time", "schedule-custom": "Custom", @@ -956,7 +961,8 @@ "schedule-days": "Days", "schedule-time": "Time", "schedule-time-from": "From", - "schedule-time-to": "To" + "schedule-time-to": "To", + "schedule-days-of-week-required": "At least one day of week should be selected." }, "dialog": { "close": "Close dialog" From 4952c310a466bbf675e263a32a26def1b57b4cdd Mon Sep 17 00:00:00 2001 From: vzikratyi Date: Mon, 12 Oct 2020 15:13:40 +0300 Subject: [PATCH 33/48] Added mapperConfig to OAuth2Template --- .../facebook_config.json | 13 +++-- .../github_config.json | 7 ++- .../google_config.json | 13 +++-- .../OAuth2ClientRegistrationTemplate.java | 6 +-- ...Auth2ClientRegistrationTemplateEntity.java | 50 +++++++++++-------- .../OAuth2ConfigTemplateServiceImpl.java | 8 ++- .../BaseOAuth2ConfigTemplateServiceTest.java | 10 ++-- 7 files changed, 64 insertions(+), 43 deletions(-) diff --git a/application/src/main/data/json/system/oauth2_config_templates/facebook_config.json b/application/src/main/data/json/system/oauth2_config_templates/facebook_config.json index 9b059e81c1..3961bf3688 100644 --- a/application/src/main/data/json/system/oauth2_config_templates/facebook_config.json +++ b/application/src/main/data/json/system/oauth2_config_templates/facebook_config.json @@ -7,11 +7,14 @@ "userInfoUri": "https://graph.facebook.com/me?fields=id,name,first_name,last_name,email", "clientAuthenticationMethod": "BASIC", "userNameAttributeName": "email", - "basic": { - "emailAttributeKey": "email", - "firstNameAttributeKey": "first_name", - "lastNameAttributeKey": "last_name", - "tenantNameStrategy": "DOMAIN" + "mapperConfig": { + "type": "BASIC", + "basic": { + "emailAttributeKey": "email", + "firstNameAttributeKey": "first_name", + "lastNameAttributeKey": "last_name", + "tenantNameStrategy": "DOMAIN" + } }, "comment": null, "loginButtonIcon": "mdi:facebook", diff --git a/application/src/main/data/json/system/oauth2_config_templates/github_config.json b/application/src/main/data/json/system/oauth2_config_templates/github_config.json index 6c3a51723f..6569162ead 100644 --- a/application/src/main/data/json/system/oauth2_config_templates/github_config.json +++ b/application/src/main/data/json/system/oauth2_config_templates/github_config.json @@ -7,8 +7,11 @@ "userInfoUri": "https://api.github.com/user", "clientAuthenticationMethod": "BASIC", "userNameAttributeName": "login", - "basic": { - "tenantNameStrategy": "DOMAIN" + "mapperConfig": { + "type": "GITHUB", + "basic": { + "tenantNameStrategy": "DOMAIN" + } }, "comment": "In order to log into ThingsBoard you need to have user's email. You may configure and use Custom OAuth2 Mapper to get email information. Please refer to Github Documentation", "loginButtonIcon": "mdi:github", diff --git a/application/src/main/data/json/system/oauth2_config_templates/google_config.json b/application/src/main/data/json/system/oauth2_config_templates/google_config.json index 2756bcc957..03e3046125 100644 --- a/application/src/main/data/json/system/oauth2_config_templates/google_config.json +++ b/application/src/main/data/json/system/oauth2_config_templates/google_config.json @@ -8,11 +8,14 @@ "userInfoUri": "https://openidconnect.googleapis.com/v1/userinfo", "clientAuthenticationMethod": "BASIC", "userNameAttributeName": "email", - "basic": { - "emailAttributeKey": "email", - "firstNameAttributeKey": "given_name", - "lastNameAttributeKey": "family_name", - "tenantNameStrategy": "DOMAIN" + "mapperConfig": { + "type": "BASIC", + "basic": { + "emailAttributeKey": "email", + "firstNameAttributeKey": "given_name", + "lastNameAttributeKey": "family_name", + "tenantNameStrategy": "DOMAIN" + } }, "comment": null, "loginButtonIcon": "mdi:google", diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientRegistrationTemplate.java b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientRegistrationTemplate.java index 897472f154..30c6e1c4a9 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientRegistrationTemplate.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientRegistrationTemplate.java @@ -34,8 +34,7 @@ import java.util.List; public class OAuth2ClientRegistrationTemplate extends SearchTextBasedWithAdditionalInfo implements HasName { private String providerId; - private MapperType mapperType; - private OAuth2BasicMapperConfig basic; + private OAuth2MapperConfig mapperConfig; private String authorizationUri; private String accessTokenUri; private List scope; @@ -51,8 +50,7 @@ public class OAuth2ClientRegistrationTemplate extends SearchTextBasedWithAdditio public OAuth2ClientRegistrationTemplate(OAuth2ClientRegistrationTemplate clientRegistrationTemplate) { super(clientRegistrationTemplate); this.providerId = clientRegistrationTemplate.providerId; - this.mapperType = clientRegistrationTemplate.mapperType; - this.basic = clientRegistrationTemplate.basic; + this.mapperConfig = clientRegistrationTemplate.mapperConfig; this.authorizationUri = clientRegistrationTemplate.authorizationUri; this.accessTokenUri = clientRegistrationTemplate.accessTokenUri; this.scope = clientRegistrationTemplate.scope; diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2ClientRegistrationTemplateEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2ClientRegistrationTemplateEntity.java index e7973dcd5d..d4fb6bfebe 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2ClientRegistrationTemplateEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2ClientRegistrationTemplateEntity.java @@ -109,17 +109,20 @@ public class OAuth2ClientRegistrationTemplateEntity extends BaseSqlEntity Date: Mon, 12 Oct 2020 18:03:05 +0300 Subject: [PATCH 34/48] Improved scheduler logic --- .../rule/engine/profile/AlarmRuleState.java | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmRuleState.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmRuleState.java index 95356239f5..d9d08efbdc 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmRuleState.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmRuleState.java @@ -165,9 +165,7 @@ class AlarmRuleState { return false; } } - long startOfDay = zdt.toLocalDate().atStartOfDay(zoneId).toInstant().toEpochMilli(); - long msFromStartOfDay = eventTs - startOfDay; - return schedule.getStartsOn() <= msFromStartOfDay && schedule.getEndsOn() > msFromStartOfDay; + return isActive(eventTs, zoneId, zdt, schedule.getStartsOn(), schedule.getEndsOn()); } private boolean isActiveCustom(CustomTimeSchedule schedule, long eventTs) { @@ -177,9 +175,7 @@ class AlarmRuleState { for (CustomTimeScheduleItem item : schedule.getItems()) { if (item.getDayOfWeek() == dayOfWeek) { if (item.isEnabled()) { - long startOfDay = zdt.toLocalDate().atStartOfDay(zoneId).toInstant().toEpochMilli(); - long msFromStartOfDay = eventTs - startOfDay; - return item.getStartsOn() <= msFromStartOfDay && item.getEndsOn() > msFromStartOfDay; + return isActive(eventTs, zoneId, zdt, item.getStartsOn(), item.getEndsOn()); } else { return false; } @@ -188,6 +184,16 @@ class AlarmRuleState { return false; } + private boolean isActive(long eventTs, ZoneId zoneId, ZonedDateTime zdt, long startsOn, long endsOn) { + long startOfDay = zdt.toLocalDate().atStartOfDay(zoneId).toInstant().toEpochMilli(); + long msFromStartOfDay = eventTs - startOfDay; + if (startsOn <= endsOn) { + return startsOn <= msFromStartOfDay && endsOn > msFromStartOfDay; + } else { + return startsOn < msFromStartOfDay || (0 < msFromStartOfDay && msFromStartOfDay < endsOn); + } + } + public void clear() { if (state.getEventCount() > 0 || state.getLastEventTs() > 0 || state.getDuration() > 0) { state.setEventCount(0L); From 40be6e588c5460f5223aa67343415b7dde449b67 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Mon, 12 Oct 2020 18:05:26 +0300 Subject: [PATCH 35/48] Fix device wizard dialog. Fix install script for oauth2 client registration templates. --- .../server/service/install/InstallScripts.java | 6 ++++++ .../server/dao/oauth2/OAuth2ConfigTemplateService.java | 3 +++ .../dao/oauth2/OAuth2ClientRegistrationTemplateDao.java | 4 ++++ .../dao/oauth2/OAuth2ConfigTemplateServiceImpl.java | 9 +++++++++ .../oauth2/JpaOAuth2ClientRegistrationTemplateDao.java | 7 +++++++ .../OAuth2ClientRegistrationTemplateRepository.java | 3 +++ .../components/wizard/device-wizard-dialog.component.ts | 4 ++-- 7 files changed, 34 insertions(+), 2 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java b/application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java index f432851ffc..ac047060ae 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java +++ b/application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java @@ -42,6 +42,7 @@ import java.nio.file.DirectoryStream; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.Optional; import static org.thingsboard.server.service.install.DatabaseHelper.objectMapper; @@ -243,6 +244,11 @@ public class InstallScripts { try { JsonNode oauth2ConfigTemplateJson = objectMapper.readTree(path.toFile()); OAuth2ClientRegistrationTemplate clientRegistrationTemplate = objectMapper.treeToValue(oauth2ConfigTemplateJson, OAuth2ClientRegistrationTemplate.class); + Optional existingClientRegistrationTemplate = + oAuth2TemplateService.findClientRegistrationTemplateByProviderId(clientRegistrationTemplate.getProviderId()); + if (existingClientRegistrationTemplate.isPresent()) { + clientRegistrationTemplate.setId(existingClientRegistrationTemplate.get().getId()); + } oAuth2TemplateService.saveClientRegistrationTemplate(clientRegistrationTemplate); } catch (Exception e) { log.error("Unable to load oauth2 config templates from json: [{}]", path.toString()); diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ConfigTemplateService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ConfigTemplateService.java index b3718a8d92..54a47d9fdb 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ConfigTemplateService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ConfigTemplateService.java @@ -19,10 +19,13 @@ import org.thingsboard.server.common.data.id.OAuth2ClientRegistrationTemplateId; import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationTemplate; import java.util.List; +import java.util.Optional; public interface OAuth2ConfigTemplateService { OAuth2ClientRegistrationTemplate saveClientRegistrationTemplate(OAuth2ClientRegistrationTemplate clientRegistrationTemplate); + Optional findClientRegistrationTemplateByProviderId(String providerId); + OAuth2ClientRegistrationTemplate findClientRegistrationTemplateById(OAuth2ClientRegistrationTemplateId templateId); List findAllClientRegistrationTemplates(); diff --git a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientRegistrationTemplateDao.java b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientRegistrationTemplateDao.java index 75fcbf1b10..6c377bc067 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientRegistrationTemplateDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientRegistrationTemplateDao.java @@ -19,7 +19,11 @@ import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationTemplat import org.thingsboard.server.dao.Dao; import java.util.List; +import java.util.Optional; public interface OAuth2ClientRegistrationTemplateDao extends Dao { + + Optional findByProviderId(String providerId); + List findAll(); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ConfigTemplateServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ConfigTemplateServiceImpl.java index 4eaeb914c9..e95ea6c39b 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ConfigTemplateServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ConfigTemplateServiceImpl.java @@ -29,6 +29,7 @@ import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.service.DataValidator; import java.util.List; +import java.util.Optional; import static org.thingsboard.server.dao.service.Validator.validateId; import static org.thingsboard.server.dao.service.Validator.validateString; @@ -37,6 +38,7 @@ import static org.thingsboard.server.dao.service.Validator.validateString; @Service public class OAuth2ConfigTemplateServiceImpl extends AbstractEntityService implements OAuth2ConfigTemplateService { public static final String INCORRECT_CLIENT_REGISTRATION_TEMPLATE_ID = "Incorrect clientRegistrationTemplateId "; + public static final String INCORRECT_CLIENT_REGISTRATION_PROVIDER_ID = "Incorrect clientRegistrationProviderId "; @Autowired private OAuth2ClientRegistrationTemplateDao clientRegistrationTemplateDao; @@ -59,6 +61,13 @@ public class OAuth2ConfigTemplateServiceImpl extends AbstractEntityService imple return savedClientRegistrationTemplate; } + @Override + public Optional findClientRegistrationTemplateByProviderId(String providerId) { + log.trace("Executing findClientRegistrationTemplateByProviderId [{}]", providerId); + validateString(providerId, INCORRECT_CLIENT_REGISTRATION_PROVIDER_ID + providerId); + return clientRegistrationTemplateDao.findByProviderId(providerId); + } + @Override public OAuth2ClientRegistrationTemplate findClientRegistrationTemplateById(OAuth2ClientRegistrationTemplateId templateId) { log.trace("Executing findClientRegistrationTemplateById [{}]", templateId); diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/JpaOAuth2ClientRegistrationTemplateDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/JpaOAuth2ClientRegistrationTemplateDao.java index 70c3c03479..4540a9b3d1 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/JpaOAuth2ClientRegistrationTemplateDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/JpaOAuth2ClientRegistrationTemplateDao.java @@ -26,6 +26,7 @@ import org.thingsboard.server.dao.sql.JpaAbstractDao; import java.util.ArrayList; import java.util.List; +import java.util.Optional; import java.util.UUID; @Component @@ -43,6 +44,12 @@ public class JpaOAuth2ClientRegistrationTemplateDao extends JpaAbstractDao findByProviderId(String providerId) { + OAuth2ClientRegistrationTemplate oAuth2ClientRegistrationTemplate = DaoUtil.getData(repository.findByProviderId(providerId)); + return Optional.ofNullable(oAuth2ClientRegistrationTemplate); + } + @Override public List findAll() { Iterable entities = repository.findAll(); diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/OAuth2ClientRegistrationTemplateRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/OAuth2ClientRegistrationTemplateRepository.java index 8ffd380496..cba85280d7 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/OAuth2ClientRegistrationTemplateRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/OAuth2ClientRegistrationTemplateRepository.java @@ -21,4 +21,7 @@ import org.thingsboard.server.dao.model.sql.OAuth2ClientRegistrationTemplateEnti import java.util.UUID; public interface OAuth2ClientRegistrationTemplateRepository extends CrudRepository { + + OAuth2ClientRegistrationTemplateEntity findByProviderId(String providerId); + } diff --git a/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.ts b/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.ts index 9e861c1359..bbbde588b7 100644 --- a/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.ts @@ -266,11 +266,11 @@ export class DeviceWizardDialogComponent extends }) ); } else { - return of(null); + return of(this.deviceWizardFormGroup.get('deviceProfileId').value); } } - private createDevice(profileId: EntityId = this.deviceWizardFormGroup.get('deviceProfileId').value): Observable> { + private createDevice(profileId): Observable> { const device = { name: this.deviceWizardFormGroup.get('name').value, label: this.deviceWizardFormGroup.get('label').value, From 1f7623352a52741700cfdc9c2df63bba0d5ef150 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Mon, 12 Oct 2020 18:11:22 +0300 Subject: [PATCH 36/48] UI: refactoring OAuth2/Login Page and added oauth mapperType GITHUB --- ui-ngx/src/app/core/auth/auth.service.ts | 9 +- ui-ngx/src/app/core/http/admin.service.ts | 22 +-- ui-ngx/src/app/core/http/oauth2.service.ts | 44 +++++ ui-ngx/src/app/core/http/public-api.ts | 1 + .../admin/oauth2-settings.component.html | 35 ++-- .../admin/oauth2-settings.component.scss | 11 +- .../pages/admin/oauth2-settings.component.ts | 158 +++++++++--------- .../login/pages/login/login.component.html | 28 ++-- .../login/pages/login/login.component.scss | 13 +- .../login/pages/login/login.component.ts | 4 +- ui-ngx/src/app/shared/models/login.models.ts | 6 - ui-ngx/src/app/shared/models/oauth2.models.ts | 119 +++++++++++++ ui-ngx/src/app/shared/models/public-api.ts | 1 + .../src/app/shared/models/settings.models.ts | 99 ----------- 14 files changed, 298 insertions(+), 252 deletions(-) create mode 100644 ui-ngx/src/app/core/http/oauth2.service.ts create mode 100644 ui-ngx/src/app/shared/models/oauth2.models.ts diff --git a/ui-ngx/src/app/core/auth/auth.service.ts b/ui-ngx/src/app/core/auth/auth.service.ts index 32733ac139..da41c7c1b1 100644 --- a/ui-ngx/src/app/core/auth/auth.service.ts +++ b/ui-ngx/src/app/core/auth/auth.service.ts @@ -21,7 +21,7 @@ import { HttpClient } from '@angular/common/http'; import { forkJoin, Observable, of, ReplaySubject, throwError } from 'rxjs'; import { catchError, map, mergeMap, tap } from 'rxjs/operators'; -import { LoginRequest, LoginResponse, OAuth2Client, PublicLoginRequest } from '@shared/models/login.models'; +import { LoginRequest, LoginResponse, PublicLoginRequest } from '@shared/models/login.models'; import { ActivatedRoute, Router, UrlTree } from '@angular/router'; import { defaultHttpOptions } from '../http/http-utils'; import { UserService } from '../http/user.service'; @@ -44,6 +44,7 @@ import { AdminService } from '@core/http/admin.service'; import { ActionNotificationShow } from '@core/notification/notification.actions'; import { MatDialog, MatDialogConfig } from '@angular/material/dialog'; import { AlertDialogComponent } from '@shared/components/dialog/alert-dialog.component'; +import { OAuth2ClientInfo } from '@shared/models/oauth2.models'; @Injectable({ providedIn: 'root' @@ -67,7 +68,7 @@ export class AuthService { } redirectUrl: string; - oauth2Clients: Array = null; + oauth2Clients: Array = null; private refreshTokenSubject: ReplaySubject = null; private jwtHelper = new JwtHelperService(); @@ -197,8 +198,8 @@ export class AuthService { }); } - public loadOAuth2Clients(): Observable> { - return this.http.post>(`/api/noauth/oauth2Clients`, + public loadOAuth2Clients(): Observable> { + return this.http.post>(`/api/noauth/oauth2Clients`, null, defaultHttpOptions()).pipe( tap((OAuth2Clients) => { this.oauth2Clients = OAuth2Clients; diff --git a/ui-ngx/src/app/core/http/admin.service.ts b/ui-ngx/src/app/core/http/admin.service.ts index 9423392773..f575f56906 100644 --- a/ui-ngx/src/app/core/http/admin.service.ts +++ b/ui-ngx/src/app/core/http/admin.service.ts @@ -18,14 +18,7 @@ import { Injectable } from '@angular/core'; import { defaultHttpOptionsFromConfig, RequestConfig } from './http-utils'; import { Observable } from 'rxjs'; import { HttpClient } from '@angular/common/http'; -import { - AdminSettings, - ClientProviderTemplated, - MailServerSettings, - OAuth2Settings, - SecuritySettings, - UpdateMessage -} from '@shared/models/settings.models'; +import { AdminSettings, MailServerSettings, SecuritySettings, UpdateMessage } from '@shared/models/settings.models'; @Injectable({ providedIn: 'root' @@ -60,19 +53,6 @@ export class AdminService { defaultHttpOptionsFromConfig(config)); } - public getOAuth2Settings(config?: RequestConfig): Observable { - return this.http.get(`/api/oauth2/config`, defaultHttpOptionsFromConfig(config)); - } - - public getOAuth2Template(config?: RequestConfig): Observable> { - return this.http.get>(`/api/oauth2/config/template`, defaultHttpOptionsFromConfig(config)); - } - - public saveOAuth2Settings(OAuth2Setting: OAuth2Settings, config?: RequestConfig): Observable { - return this.http.post('/api/oauth2/config', OAuth2Setting, - defaultHttpOptionsFromConfig(config)); - } - public checkUpdates(config?: RequestConfig): Observable { return this.http.get(`/api/admin/updates`, defaultHttpOptionsFromConfig(config)); } diff --git a/ui-ngx/src/app/core/http/oauth2.service.ts b/ui-ngx/src/app/core/http/oauth2.service.ts new file mode 100644 index 0000000000..a8f890223d --- /dev/null +++ b/ui-ngx/src/app/core/http/oauth2.service.ts @@ -0,0 +1,44 @@ +/// +/// 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. +/// + +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { defaultHttpOptionsFromConfig, RequestConfig } from '@core/http/http-utils'; +import { Observable } from 'rxjs'; +import { OAuth2ClientRegistrationTemplate, OAuth2ClientsParams } from '@shared/models/oauth2.models'; + +@Injectable({ + providedIn: 'root' +}) +export class OAuth2Service { + + constructor( + private http: HttpClient + ) { } + + public getOAuth2Settings(config?: RequestConfig): Observable { + return this.http.get(`/api/oauth2/config`, defaultHttpOptionsFromConfig(config)); + } + + public getOAuth2Template(config?: RequestConfig): Observable> { + return this.http.get>(`/api/oauth2/config/template`, defaultHttpOptionsFromConfig(config)); + } + + public saveOAuth2Settings(OAuth2Setting: OAuth2ClientsParams, config?: RequestConfig): Observable { + return this.http.post('/api/oauth2/config', OAuth2Setting, + defaultHttpOptionsFromConfig(config)); + } +} diff --git a/ui-ngx/src/app/core/http/public-api.ts b/ui-ngx/src/app/core/http/public-api.ts index db477c3a8c..10c6b5429e 100644 --- a/ui-ngx/src/app/core/http/public-api.ts +++ b/ui-ngx/src/app/core/http/public-api.ts @@ -28,6 +28,7 @@ export * from './entity-relation.service'; export * from './entity-view.service'; export * from './event.service'; export * from './http-utils'; +export * from './oauth2.service'; export * from './queue.service'; export * from './rule-chain.service'; export * from './tenant.service'; diff --git a/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.html b/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.html index 30564e9306..9274b5cbfc 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.html +++ b/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.html @@ -33,7 +33,7 @@ {{ 'admin.oauth2.enable' | translate }} -
+
@@ -59,7 +59,7 @@
-
+
@@ -160,7 +160,7 @@ -
+
@@ -182,9 +182,9 @@
- + [disabled]="isCustomProvider(registration)" + [expanded]="isCustomProvider(registration)"> + {{ 'admin.oauth2.custom-setting' | translate }} @@ -199,7 +199,7 @@ @@ -216,7 +216,7 @@ @@ -235,7 +235,7 @@ @@ -249,7 +249,7 @@ @@ -271,7 +271,7 @@ -
+
admin.oauth2.login-button-label admin.oauth2.type - - {{ converterTypeExternalUser }} + + {{ mapperConfigType }}
- + *ngIf="registration.get('mapperConfig.type').value !== mapperConfigType.CUSTOM"> + admin.oauth2.email-attribute-key
+ *ngIf="registration.get('mapperConfig.type').value === mapperConfigType.CUSTOM"> admin.oauth2.url diff --git a/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.scss b/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.scss index 0ad644f61b..0358badb1c 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.scss +++ b/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.scss @@ -41,6 +41,14 @@ padding-bottom: 0; } } + + .mat-expansion-panel { + .mat-expansion-panel-header { + &.mat-expanded { + height: 48px; + } + } + } } :host ::ng-deep{ @@ -49,9 +57,6 @@ .mat-expansion-panel-body{ padding: 0 2px 1em; } - .mat-tab-label{ - text-transform: none; - } .mat-form-field-suffix .mat-icon-button .mat-icon{ font-size: 18px; } diff --git a/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.ts b/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.ts index 11b39468e9..75a25a5eff 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.ts +++ b/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.ts @@ -18,22 +18,21 @@ import { Component, Inject, OnDestroy, OnInit } from '@angular/core'; import { AbstractControl, FormArray, FormBuilder, FormGroup, Validators } from '@angular/forms'; import { ClientAuthenticationMethod, - ClientProviderTemplated, ClientRegistration, DomainInfo, DomainSchema, domainSchemaTranslations, - DomainsParam, MapperConfig, MapperConfigBasic, MapperConfigCustom, MapperConfigType, - OAuth2Settings, + OAuth2ClientRegistrationTemplate, + OAuth2ClientsDomainParams, + OAuth2ClientsParams, TenantNameStrategy -} from '@shared/models/settings.models'; +} from '@shared/models/oauth2.models'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; -import { AdminService } from '@core/http/admin.service'; import { PageComponent } from '@shared/components/page.component'; import { HasConfirmForm } from '@core/guards/confirm-on-exit.guard'; import { COMMA, ENTER } from '@angular/cdk/keycodes'; @@ -42,7 +41,8 @@ import { WINDOW } from '@core/services/window.service'; import { forkJoin, Subscription } from 'rxjs'; import { DialogService } from '@core/services/dialog.service'; import { TranslateService } from '@ngx-translate/core'; -import { isDefined } from '@core/utils'; +import { isDefined, isDefinedAndNotNull } from '@core/utils'; +import { OAuth2Service } from '@core/http/oauth2.service'; @Component({ selector: 'tb-oauth2-settings', @@ -53,7 +53,7 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha private URL_REGEXP = /^[A-Za-z][A-Za-z\d.+-]*:\/*(?:\w+(?::\w+)?@)?[^\s/]+(?::\d+)?(?:\/[\w#!:.,?+=&%@\-/]*)?$/; private subscriptions: Subscription[] = []; - private templates = new Map(); + private templates = new Map(); private defaultProvider = { additionalInfo: { providerName: 'Custom' @@ -75,10 +75,11 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha readonly separatorKeysCodes: number[] = [ENTER, COMMA]; oauth2SettingsForm: FormGroup; - oauth2Settings: OAuth2Settings; + auth2ClientsParams: OAuth2ClientsParams; clientAuthenticationMethods = Object.keys(ClientAuthenticationMethod); - converterTypesExternalUser = Object.keys(MapperConfigType); + mapperConfigType = MapperConfigType; + mapperConfigTypes = Object.keys(MapperConfigType); tenantNameStrategies = Object.keys(TenantNameStrategy); protocols = Object.keys(DomainSchema); domainSchemaTranslations = domainSchemaTranslations; @@ -86,7 +87,7 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha templateProvider = ['Custom']; constructor(protected store: Store, - private adminService: AdminService, + private oauth2Service: OAuth2Service, private fb: FormBuilder, private dialogService: DialogService, private translate: TranslateService, @@ -97,13 +98,13 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha ngOnInit(): void { this.buildOAuth2SettingsForm(); forkJoin([ - this.adminService.getOAuth2Template(), - this.adminService.getOAuth2Settings() + this.oauth2Service.getOAuth2Template(), + this.oauth2Service.getOAuth2Settings() ]).subscribe( - ([templates, oauth2Settings]) => { + ([templates, auth2ClientsParams]) => { this.initTemplates(templates); - this.oauth2Settings = oauth2Settings; - this.initOAuth2Settings(this.oauth2Settings); + this.auth2ClientsParams = auth2ClientsParams; + this.initOAuth2Settings(this.auth2ClientsParams); } ); } @@ -115,8 +116,11 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha }); } - private initTemplates(templates: ClientProviderTemplated[]): void { - templates.map(provider => this.templates.set(provider.name, provider)); + private initTemplates(templates: OAuth2ClientRegistrationTemplate[]): void { + templates.map(provider => { + delete provider.additionalInfo; + this.templates.set(provider.name, provider); + }); this.templateProvider.push(...Array.from(this.templates.keys())); this.templateProvider.sort(); } @@ -169,10 +173,10 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha }); } - private initOAuth2Settings(oauth2Settings: OAuth2Settings): void { - if (oauth2Settings) { - this.oauth2SettingsForm.patchValue({enabled: oauth2Settings.enabled}, {emitEvent: false}); - oauth2Settings.domainsParams.forEach((domain) => { + private initOAuth2Settings(auth2ClientsParams: OAuth2ClientsParams): void { + if (auth2ClientsParams) { + this.oauth2SettingsForm.patchValue({enabled: auth2ClientsParams.enabled}, {emitEvent: false}); + auth2ClientsParams.domainsParams.forEach((domain) => { this.domainsParams.push(this.buildDomainsForm(domain)); }); } @@ -204,17 +208,17 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha return this.translate.instant('admin.oauth2.new-domain'); } - private buildDomainsForm(domainParams?: DomainsParam): FormGroup { + private buildDomainsForm(auth2ClientsDomainParams?: OAuth2ClientsDomainParams): FormGroup { const formDomain = this.fb.group({ domainInfos: this.fb.array([], Validators.required), clientRegistrations: this.fb.array([], Validators.required) }); - if (domainParams) { - domainParams.domainInfos.forEach((domain) => { + if (auth2ClientsDomainParams) { + auth2ClientsDomainParams.domainInfos.forEach((domain) => { this.clientDomainInfos(formDomain).push(this.buildDomainForm(domain)); }); - domainParams.clientRegistrations.forEach((registration) => { + auth2ClientsDomainParams.clientRegistrations.forEach((registration) => { this.clientDomainProviders(formDomain).push(this.buildProviderForm(registration)); }); } else { @@ -235,10 +239,10 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha return domain; } - private buildProviderForm(registrationData?: ClientRegistration): FormGroup { + private buildProviderForm(clientRegistration?: ClientRegistration): FormGroup { let additionalInfo = null; - if (registrationData?.additionalInfo) { - additionalInfo = JSON.parse(registrationData.additionalInfo); + if (isDefinedAndNotNull(clientRegistration?.additionalInfo)) { + additionalInfo = clientRegistration.additionalInfo; if (this.templateProvider.indexOf(additionalInfo.providerName) === -1) { additionalInfo.providerName = 'Custom'; } @@ -248,73 +252,70 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha defaultProviderName = 'Google'; } - const clientRegistration = this.fb.group({ - id: this.fb.group({ - id: [registrationData?.id?.id ? registrationData.id.id : null], - entityType: [registrationData?.id?.entityType ? registrationData.id.entityType : null] - }), + const clientRegistrationFormGroup = this.fb.group({ additionalInfo: this.fb.group({ providerName: [additionalInfo?.providerName ? additionalInfo?.providerName : defaultProviderName, Validators.required] }), - loginButtonLabel: [registrationData?.loginButtonLabel ? registrationData.loginButtonLabel : null, Validators.required], - loginButtonIcon: [registrationData?.loginButtonIcon ? registrationData.loginButtonIcon : null], - clientId: [registrationData?.clientId ? registrationData.clientId : '', Validators.required], - clientSecret: [registrationData?.clientSecret ? registrationData.clientSecret : '', Validators.required], - accessTokenUri: [registrationData?.accessTokenUri ? registrationData.accessTokenUri : '', + loginButtonLabel: [clientRegistration?.loginButtonLabel ? clientRegistration.loginButtonLabel : null, Validators.required], + loginButtonIcon: [clientRegistration?.loginButtonIcon ? clientRegistration.loginButtonIcon : null], + clientId: [clientRegistration?.clientId ? clientRegistration.clientId : '', Validators.required], + clientSecret: [clientRegistration?.clientSecret ? clientRegistration.clientSecret : '', Validators.required], + accessTokenUri: [clientRegistration?.accessTokenUri ? clientRegistration.accessTokenUri : '', [Validators.required, Validators.pattern(this.URL_REGEXP)]], - authorizationUri: [registrationData?.authorizationUri ? registrationData.authorizationUri : '', + authorizationUri: [clientRegistration?.authorizationUri ? clientRegistration.authorizationUri : '', [Validators.required, Validators.pattern(this.URL_REGEXP)]], - scope: this.fb.array(registrationData?.scope ? registrationData.scope : [], Validators.required), - jwkSetUri: [registrationData?.jwkSetUri ? registrationData.jwkSetUri : '', Validators.pattern(this.URL_REGEXP)], - userInfoUri: [registrationData?.userInfoUri ? registrationData.userInfoUri : '', + scope: this.fb.array(clientRegistration?.scope ? clientRegistration.scope : [], Validators.required), + jwkSetUri: [clientRegistration?.jwkSetUri ? clientRegistration.jwkSetUri : '', Validators.pattern(this.URL_REGEXP)], + userInfoUri: [clientRegistration?.userInfoUri ? clientRegistration.userInfoUri : '', [Validators.required, Validators.pattern(this.URL_REGEXP)]], clientAuthenticationMethod: [ - registrationData?.clientAuthenticationMethod ? registrationData.clientAuthenticationMethod : ClientAuthenticationMethod.POST, + clientRegistration?.clientAuthenticationMethod ? clientRegistration.clientAuthenticationMethod : ClientAuthenticationMethod.POST, Validators.required], userNameAttributeName: [ - registrationData?.userNameAttributeName ? registrationData.userNameAttributeName : 'email', Validators.required], + clientRegistration?.userNameAttributeName ? clientRegistration.userNameAttributeName : 'email', Validators.required], mapperConfig: this.fb.group({ - allowUserCreation: [registrationData?.mapperConfig?.allowUserCreation ? registrationData.mapperConfig.allowUserCreation : true], - activateUser: [registrationData?.mapperConfig?.activateUser ? registrationData.mapperConfig.activateUser : false], - type: [registrationData?.mapperConfig?.type ? registrationData.mapperConfig.type : MapperConfigType.BASIC, Validators.required] + allowUserCreation: [ + clientRegistration?.mapperConfig?.allowUserCreation ? clientRegistration.mapperConfig.allowUserCreation : true + ], + activateUser: [clientRegistration?.mapperConfig?.activateUser ? clientRegistration.mapperConfig.activateUser : false], + type: [ + clientRegistration?.mapperConfig?.type ? clientRegistration.mapperConfig.type : MapperConfigType.BASIC, Validators.required + ] } ) }); - if (registrationData) { - this.changeMapperConfigType(clientRegistration, registrationData.mapperConfig.type, registrationData.mapperConfig); + if (clientRegistration) { + this.changeMapperConfigType(clientRegistrationFormGroup, clientRegistration.mapperConfig.type, clientRegistration.mapperConfig); } else { - this.changeMapperConfigType(clientRegistration, MapperConfigType.BASIC); - this.setProviderDefaultValue(defaultProviderName, clientRegistration); + this.changeMapperConfigType(clientRegistrationFormGroup, MapperConfigType.BASIC); + this.setProviderDefaultValue(defaultProviderName, clientRegistrationFormGroup); } - this.subscriptions.push(clientRegistration.get('mapperConfig.type').valueChanges.subscribe((value) => { - this.changeMapperConfigType(clientRegistration, value); + this.subscriptions.push(clientRegistrationFormGroup.get('mapperConfig.type').valueChanges.subscribe((value) => { + this.changeMapperConfigType(clientRegistrationFormGroup, value); })); - this.subscriptions.push(clientRegistration.get('additionalInfo.providerName').valueChanges.subscribe((provider) => { - (clientRegistration.get('scope') as FormArray).clear(); - this.setProviderDefaultValue(provider, clientRegistration); + this.subscriptions.push(clientRegistrationFormGroup.get('additionalInfo.providerName').valueChanges.subscribe((provider) => { + (clientRegistrationFormGroup.get('scope') as FormArray).clear(); + this.setProviderDefaultValue(provider, clientRegistrationFormGroup); })); - return clientRegistration; + return clientRegistrationFormGroup; } private setProviderDefaultValue(provider: string, clientRegistration: FormGroup) { if (provider === 'Custom') { - const defaultSettings = {...this.defaultProvider, ...{id: clientRegistration.get('id').value}}; - clientRegistration.reset(defaultSettings, {emitEvent: false}); + clientRegistration.reset(this.defaultProvider, {emitEvent: false}); clientRegistration.get('accessTokenUri').enable(); clientRegistration.get('authorizationUri').enable(); clientRegistration.get('jwkSetUri').enable(); clientRegistration.get('userInfoUri').enable(); } else { const template = this.templates.get(provider); - delete template.id; - delete template.additionalInfo; template.clientId = ''; template.clientSecret = ''; template.scope.forEach(() => { @@ -324,44 +325,33 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha clientRegistration.get('authorizationUri').disable(); clientRegistration.get('jwkSetUri').disable(); clientRegistration.get('userInfoUri').disable(); - clientRegistration.patchValue(template, {emitEvent: false}); + clientRegistration.patchValue(template); } } private changeMapperConfigType(control: AbstractControl, type: MapperConfigType, predefinedValue?: MapperConfig) { const mapperConfig = control.get('mapperConfig') as FormGroup; - if (type === MapperConfigType.BASIC) { - mapperConfig.removeControl('custom'); - mapperConfig.addControl('basic', this.formBasicGroup(predefinedValue?.basic)); - } else { + if (type === MapperConfigType.CUSTOM) { mapperConfig.removeControl('basic'); mapperConfig.addControl('custom', this.formCustomGroup(predefinedValue?.custom)); + } else { + mapperConfig.removeControl('custom'); + mapperConfig.addControl('basic', this.formBasicGroup(predefinedValue?.basic)); } } save(): void { - const setting = this.prepareFormValue(this.oauth2SettingsForm.getRawValue()); - this.adminService.saveOAuth2Settings(setting).subscribe( + const setting = this.oauth2SettingsForm.getRawValue(); + this.oauth2Service.saveOAuth2Settings(setting).subscribe( (oauth2Settings) => { - this.oauth2Settings = oauth2Settings; - this.oauth2SettingsForm.markAsPristine(); + this.auth2ClientsParams = oauth2Settings; + this.oauth2SettingsForm.patchValue(this.oauth2SettingsForm, {emitEvent: false}); this.oauth2SettingsForm.markAsUntouched(); + this.oauth2SettingsForm.markAsPristine(); } ); } - private prepareFormValue(formValue: OAuth2Settings): OAuth2Settings{ - formValue.domainsParams.forEach((setting, index) => { - setting.clientRegistrations.forEach((registration) => { - registration.additionalInfo = JSON.stringify(registration.additionalInfo); - if (registration.id.id === null) { - delete registration.id; - } - }); - }); - return formValue; - } - confirmForm(): FormGroup { return this.oauth2SettingsForm; } @@ -451,6 +441,10 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha return controller.get('additionalInfo.providerName').value; } + isCustomProvider(controller: AbstractControl): boolean { + return this.getProviderName(controller) === 'Custom'; + } + getHelpLink(controller: AbstractControl): string { const provider = controller.get('additionalInfo.providerName').value; if (provider === null || provider === 'Custom') { diff --git a/ui-ngx/src/app/modules/login/pages/login/login.component.html b/ui-ngx/src/app/modules/login/pages/login/login.component.html index 3f5438dcd3..35dbb0e401 100644 --- a/ui-ngx/src/app/modules/login/pages/login/login.component.html +++ b/ui-ngx/src/app/modules/login/pages/login/login.component.html @@ -28,6 +28,19 @@
+
+ + + +
+
+
{{ "login.or" | translate | uppercase }}
+
+
+
login.username @@ -41,7 +54,7 @@ lock -
+
@@ -49,19 +62,6 @@
-
-
-
-
{{ "login.or" | translate | uppercase }}
-
-
- - - - {{ 'login.login-with' | translate: {name: oauth2Client.name} }} - - -
diff --git a/ui-ngx/src/app/modules/login/pages/login/login.component.scss b/ui-ngx/src/app/modules/login/pages/login/login.component.scss index e8e9fc29f2..6a1c75aaaf 100644 --- a/ui-ngx/src/app/modules/login/pages/login/login.component.scss +++ b/ui-ngx/src/app/modules/login/pages/login/login.component.scss @@ -27,8 +27,11 @@ width: 550px !important; } - .tb-reset-password{ - padding: 0 6px; + .forgot-password { + padding: 0 0.5em 1em; + .tb-reset-password { + padding: 0 6px; + } } .tb-action-button{ @@ -65,9 +68,11 @@ min-width: 20px; } - a.centered { + a.login-with-button { + color: rgba(black, 0.87); + &:hover { - border-bottom: 0px; + border-bottom: 0; } } diff --git a/ui-ngx/src/app/modules/login/pages/login/login.component.ts b/ui-ngx/src/app/modules/login/pages/login/login.component.ts index a9717179d6..30ecafe0b2 100644 --- a/ui-ngx/src/app/modules/login/pages/login/login.component.ts +++ b/ui-ngx/src/app/modules/login/pages/login/login.component.ts @@ -23,7 +23,7 @@ import { FormBuilder } from '@angular/forms'; import { HttpErrorResponse } from '@angular/common/http'; import { Constants } from '@shared/models/constants'; import { Router } from '@angular/router'; -import { OAuth2Client } from '@shared/models/login.models'; +import { OAuth2ClientInfo } from '@shared/models/oauth2.models'; @Component({ selector: 'tb-login', @@ -36,7 +36,7 @@ export class LoginComponent extends PageComponent implements OnInit { username: '', password: '' }); - oauth2Clients: Array = null; + oauth2Clients: Array = null; constructor(protected store: Store, private authService: AuthService, diff --git a/ui-ngx/src/app/shared/models/login.models.ts b/ui-ngx/src/app/shared/models/login.models.ts index 7c22bd3952..19b40fd50e 100644 --- a/ui-ngx/src/app/shared/models/login.models.ts +++ b/ui-ngx/src/app/shared/models/login.models.ts @@ -27,9 +27,3 @@ export interface LoginResponse { token: string; refreshToken: string; } - -export interface OAuth2Client { - name: string; - icon?: string; - url: string; -} diff --git a/ui-ngx/src/app/shared/models/oauth2.models.ts b/ui-ngx/src/app/shared/models/oauth2.models.ts new file mode 100644 index 0000000000..54d4d3c8d7 --- /dev/null +++ b/ui-ngx/src/app/shared/models/oauth2.models.ts @@ -0,0 +1,119 @@ +/// +/// 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. +/// + +import { HasUUID } from '@shared/models/id/has-uuid'; + +export interface OAuth2ClientsParams { + enabled: boolean; + domainsParams: OAuth2ClientsDomainParams[]; +} + +export interface OAuth2ClientsDomainParams { + clientRegistrations: ClientRegistration[]; + domainInfos: DomainInfo[]; +} + +export interface DomainInfo { + name: string; + scheme: DomainSchema; +} + +export enum DomainSchema{ + HTTP = 'HTTP', + HTTPS = 'HTTPS', + MIXED = 'MIXED' +} + +export const domainSchemaTranslations = new Map( + [ + [DomainSchema.HTTP, 'admin.oauth2.domain-schema-http'], + [DomainSchema.HTTPS, 'admin.oauth2.domain-schema-https'], + [DomainSchema.MIXED, 'admin.oauth2.domain-schema-mixed'] + ] +); + +export enum MapperConfigType{ + BASIC = 'BASIC', + CUSTOM = 'CUSTOM', + GITHUB = 'GITHUB' +} + +export enum TenantNameStrategy{ + DOMAIN = 'DOMAIN', + EMAIL = 'EMAIL', + CUSTOM = 'CUSTOM' +} + +export interface OAuth2ClientRegistrationTemplate extends ClientRegistration{ + comment: string; + createdTime: number; + helpLink: string; + name: string; + providerId: string; + id: HasUUID; +} + +export interface ClientRegistration { + loginButtonLabel: string; + loginButtonIcon: string; + clientId: string; + clientSecret: string; + accessTokenUri: string; + authorizationUri: string; + scope: string[]; + jwkSetUri?: string; + userInfoUri: string; + clientAuthenticationMethod: ClientAuthenticationMethod; + userNameAttributeName: string; + mapperConfig: MapperConfig; + additionalInfo: string; +} + +export enum ClientAuthenticationMethod { + BASIC = 'BASIC', + POST = 'POST' +} + +export interface MapperConfig { + allowUserCreation: boolean; + activateUser: boolean; + type: MapperConfigType; + basic?: MapperConfigBasic; + custom?: MapperConfigCustom; +} + +export interface MapperConfigBasic { + emailAttributeKey: string; + firstNameAttributeKey?: string; + lastNameAttributeKey?: string; + tenantNameStrategy: TenantNameStrategy; + tenantNamePattern?: string; + customerNamePattern?: string; + defaultDashboardName?: string; + alwaysFullScreen?: boolean; +} + +export interface MapperConfigCustom { + url: string; + username?: string; + password?: string; +} + +export interface OAuth2ClientInfo { + name: string; + icon?: string; + url: string; +} diff --git a/ui-ngx/src/app/shared/models/public-api.ts b/ui-ngx/src/app/shared/models/public-api.ts index cbc030a4fa..c2130236c1 100644 --- a/ui-ngx/src/app/shared/models/public-api.ts +++ b/ui-ngx/src/app/shared/models/public-api.ts @@ -36,6 +36,7 @@ export * from './error.models'; export * from './event.models'; export * from './login.models'; export * from './material.models'; +export * from './oauth2.models'; export * from './queue.models'; export * from './relation.models'; export * from './rule-chain.models'; diff --git a/ui-ngx/src/app/shared/models/settings.models.ts b/ui-ngx/src/app/shared/models/settings.models.ts index f31dc5ebe8..f71ac1bb2e 100644 --- a/ui-ngx/src/app/shared/models/settings.models.ts +++ b/ui-ngx/src/app/shared/models/settings.models.ts @@ -14,9 +14,6 @@ /// limitations under the License. /// -import { EntityId } from '@shared/models/id/entity-id'; -import { TenantId } from '@shared/models/id/tenant-id'; - export const smtpPortPattern: RegExp = /^([0-9]{1,4}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])$/; export interface AdminSettings { @@ -63,99 +60,3 @@ export interface UpdateMessage { message: string; updateAvailable: boolean; } - -export interface OAuth2Settings { - enabled: boolean; - domainsParams: DomainsParam[]; -} - -export interface DomainsParam { - clientRegistrations: ClientRegistration[]; - domainInfos: DomainInfo[]; -} - -export interface DomainInfo { - name: string; - scheme: DomainSchema; -} - -export enum DomainSchema{ - HTTP = 'HTTP', - HTTPS = 'HTTPS', - MIXED = 'MIXED' -} - -export const domainSchemaTranslations = new Map( - [ - [DomainSchema.HTTP, 'admin.oauth2.domain-schema-http'], - [DomainSchema.HTTPS, 'admin.oauth2.domain-schema-https'], - [DomainSchema.MIXED, 'admin.oauth2.domain-schema-mixed'] - ] -); - -export enum MapperConfigType{ - BASIC = 'BASIC', - CUSTOM = 'CUSTOM' -} - -export enum TenantNameStrategy{ - DOMAIN = 'DOMAIN', - EMAIL = 'EMAIL', - CUSTOM = 'CUSTOM' -} - -export interface ClientProviderTemplated extends ClientRegistration{ - comment: string; - createdTime: number; - helpLink: string; - name: string; - providerId: string; - tenantId: TenantId; -} - -export interface ClientRegistration { - loginButtonLabel: string; - loginButtonIcon: string; - clientId: string; - clientSecret: string; - accessTokenUri: string; - authorizationUri: string; - scope: string[]; - jwkSetUri?: string; - userInfoUri: string; - clientAuthenticationMethod: ClientAuthenticationMethod; - userNameAttributeName: string; - mapperConfig: MapperConfig; - id?: EntityId; - additionalInfo: string; -} - -export enum ClientAuthenticationMethod { - BASIC = 'BASIC', - POST = 'POST' -} - -export interface MapperConfig { - allowUserCreation: boolean; - activateUser: boolean; - type: MapperConfigType; - basic?: MapperConfigBasic; - custom?: MapperConfigCustom; -} - -export interface MapperConfigBasic { - emailAttributeKey: string; - firstNameAttributeKey?: string; - lastNameAttributeKey?: string; - tenantNameStrategy: TenantNameStrategy; - tenantNamePattern?: string; - customerNamePattern?: string; - defaultDashboardName?: string; - alwaysFullScreen?: boolean; -} - -export interface MapperConfigCustom { - url: string; - username?: string; - password?: string; -} From df784db8a1481b318757eff457870af136554417 Mon Sep 17 00:00:00 2001 From: Chantsova Ekaterina Date: Mon, 12 Oct 2020 19:31:42 +0300 Subject: [PATCH 37/48] Refactoring --- .../src/app/modules/home/components/widget/lib/flot-widget.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/flot-widget.ts b/ui-ngx/src/app/modules/home/components/widget/lib/flot-widget.ts index 7b9ab5117a..b7962a098d 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/flot-widget.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/flot-widget.ts @@ -20,7 +20,8 @@ import { createLabelFromDatasource, deepClone, insertVariable, - isDefined, isDefinedAndNotNull, + isDefined, + isDefinedAndNotNull, isEqual, isNumber, isUndefined From 054fcba46f58f6b12a9ed347fa4a9b8afecf3222 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Mon, 12 Oct 2020 19:33:52 +0300 Subject: [PATCH 38/48] UI: Change OAuth2 templated icon --- ui-ngx/src/app/app.component.ts | 21 +++++++++++++++++++ .../admin/oauth2-settings.component.html | 2 +- .../login/pages/login/login.component.html | 2 +- .../login/pages/login/login.component.scss | 6 ++++++ .../assets/locale/locale.constant-en_US.json | 2 +- 5 files changed, 30 insertions(+), 3 deletions(-) diff --git a/ui-ngx/src/app/app.component.ts b/ui-ngx/src/app/app.component.ts index b46adc095d..f7d290c5ca 100644 --- a/ui-ngx/src/app/app.component.ts +++ b/ui-ngx/src/app/app.component.ts @@ -68,6 +68,27 @@ export class AppComponent implements OnInit { ) ); + this.matIconRegistry.addSvgIconLiteral( + 'google-logo', + this.domSanitizer.bypassSecurityTrustHtml( + '' + ) + ); + + this.matIconRegistry.addSvgIconLiteral( + 'github-logo', + this.domSanitizer.bypassSecurityTrustHtml( + '' + ) + ); + + this.matIconRegistry.addSvgIconLiteral( + 'facebook-logo', + this.domSanitizer.bypassSecurityTrustHtml( + '' + ) + ); + this.storageService.testLocalStorage(); this.setupTranslate(); diff --git a/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.html b/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.html index 9274b5cbfc..ee1c5b725c 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.html +++ b/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.html @@ -275,7 +275,7 @@ admin.oauth2.login-button-label {{ 'admin.oauth2.login-button-label-required' | translate }} diff --git a/ui-ngx/src/app/modules/login/pages/login/login.component.html b/ui-ngx/src/app/modules/login/pages/login/login.component.html index 35dbb0e401..c0d4d5ed23 100644 --- a/ui-ngx/src/app/modules/login/pages/login/login.component.html +++ b/ui-ngx/src/app/modules/login/pages/login/login.component.html @@ -31,7 +31,7 @@
diff --git a/ui-ngx/src/app/modules/login/pages/login/login.component.scss b/ui-ngx/src/app/modules/login/pages/login/login.component.scss index 6a1c75aaaf..b8fa052e75 100644 --- a/ui-ngx/src/app/modules/login/pages/login/login.component.scss +++ b/ui-ngx/src/app/modules/login/pages/login/login.component.scss @@ -74,6 +74,12 @@ &:hover { border-bottom: 0; } + + .icon{ + height: 20px; + width: 20px; + vertical-align: sub; + } } .centered ::ng-deep .mat-button-wrapper { diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 9a5d71a2e6..6c2cb84b80 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -158,7 +158,7 @@ "last-name-attribute-key": "Last name attribute key", "login-button-icon": "Login button icon", "login-button-label": "Provider label", - "login-button-label-1": "Login with $(Provider label)", + "login-button-label-placeholder": "Login with $(Provider label)", "login-button-label-required": "Label is required.", "login-provider": "Login provider", "mapper": "Mapper", From 220c5f627510cdd9c00e5a47233f6f821a3e6d6b Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Mon, 12 Oct 2020 19:35:11 +0300 Subject: [PATCH 39/48] UI: Change OAuth2 templated json icon --- .../json/system/oauth2_config_templates/facebook_config.json | 2 +- .../data/json/system/oauth2_config_templates/github_config.json | 2 +- .../data/json/system/oauth2_config_templates/google_config.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/application/src/main/data/json/system/oauth2_config_templates/facebook_config.json b/application/src/main/data/json/system/oauth2_config_templates/facebook_config.json index 3961bf3688..04847a9da6 100644 --- a/application/src/main/data/json/system/oauth2_config_templates/facebook_config.json +++ b/application/src/main/data/json/system/oauth2_config_templates/facebook_config.json @@ -17,7 +17,7 @@ } }, "comment": null, - "loginButtonIcon": "mdi:facebook", + "loginButtonIcon": "facebook-logo", "loginButtonLabel": "Facebook", "helpLink": "https://developers.facebook.com/docs/facebook-login/web#logindialog" } diff --git a/application/src/main/data/json/system/oauth2_config_templates/github_config.json b/application/src/main/data/json/system/oauth2_config_templates/github_config.json index 6569162ead..0a1ae5779b 100644 --- a/application/src/main/data/json/system/oauth2_config_templates/github_config.json +++ b/application/src/main/data/json/system/oauth2_config_templates/github_config.json @@ -14,7 +14,7 @@ } }, "comment": "In order to log into ThingsBoard you need to have user's email. You may configure and use Custom OAuth2 Mapper to get email information. Please refer to Github Documentation", - "loginButtonIcon": "mdi:github", + "loginButtonIcon": "github-logo", "loginButtonLabel": "Github", "helpLink": "https://docs.github.com/en/developers/apps/creating-an-oauth-app" } diff --git a/application/src/main/data/json/system/oauth2_config_templates/google_config.json b/application/src/main/data/json/system/oauth2_config_templates/google_config.json index 03e3046125..f8626439ba 100644 --- a/application/src/main/data/json/system/oauth2_config_templates/google_config.json +++ b/application/src/main/data/json/system/oauth2_config_templates/google_config.json @@ -18,7 +18,7 @@ } }, "comment": null, - "loginButtonIcon": "mdi:google", + "loginButtonIcon": "google-logo", "loginButtonLabel": "Google", "helpLink": "https://developers.google.com/adwords/api/docs/guides/authentication" } From e113daa085dd760a0c034537dab797d4e4023884 Mon Sep 17 00:00:00 2001 From: Vladyslav Prykhodko Date: Tue, 13 Oct 2020 00:59:56 +0300 Subject: [PATCH 40/48] UI: Added validation scope --- .../pages/admin/oauth2-settings.component.html | 4 ++++ .../home/pages/admin/oauth2-settings.component.ts | 14 ++++++++++++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.html b/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.html index ee1c5b725c..e9d3030019 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.html +++ b/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.html @@ -316,6 +316,10 @@ {{ 'admin.oauth2.scope-required' | translate }} + + diff --git a/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.ts b/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.ts index 75a25a5eff..400b2d3fc2 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.ts +++ b/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.ts @@ -15,7 +15,7 @@ /// import { Component, Inject, OnDestroy, OnInit } from '@angular/core'; -import { AbstractControl, FormArray, FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { AbstractControl, FormArray, FormBuilder, FormGroup, ValidationErrors, Validators } from '@angular/forms'; import { ClientAuthenticationMethod, ClientRegistration, @@ -266,7 +266,7 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha authorizationUri: [clientRegistration?.authorizationUri ? clientRegistration.authorizationUri : '', [Validators.required, Validators.pattern(this.URL_REGEXP)]], - scope: this.fb.array(clientRegistration?.scope ? clientRegistration.scope : [], Validators.required), + scope: this.fb.array(clientRegistration?.scope ? clientRegistration.scope : [], this.validateScope), jwkSetUri: [clientRegistration?.jwkSetUri ? clientRegistration.jwkSetUri : '', Validators.pattern(this.URL_REGEXP)], userInfoUri: [clientRegistration?.userInfoUri ? clientRegistration.userInfoUri : '', [Validators.required, @@ -307,6 +307,16 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha return clientRegistrationFormGroup; } + private validateScope (control: AbstractControl): ValidationErrors | null { + const scope: string[] = control.value; + if (!scope || !scope.length) { + return { + required: true + }; + } + return null; + } + private setProviderDefaultValue(provider: string, clientRegistration: FormGroup) { if (provider === 'Custom') { clientRegistration.reset(this.defaultProvider, {emitEvent: false}); From 29b9682ff1b107fc4cce29553d3894a569d7fdc8 Mon Sep 17 00:00:00 2001 From: Vladyslav Prykhodko Date: Tue, 13 Oct 2020 01:41:13 +0300 Subject: [PATCH 41/48] UI: Added device profile show settings in edit mode --- .../profile/device-profile.component.html | 59 +++++++++++++++++++ .../profile/device-profile.component.ts | 18 ++++++ 2 files changed, 77 insertions(+) diff --git a/ui-ngx/src/app/modules/home/components/profile/device-profile.component.html b/ui-ngx/src/app/modules/home/components/profile/device-profile.component.html index 780d98adfe..4fbc413d98 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device-profile.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/device-profile.component.html @@ -68,6 +68,65 @@ device-profile.description + + device-profile.transport-type + + + {{deviceTransportTypeTranslations.get(type) | translate}} + + + + {{ 'device-profile.transport-type-required' | translate }} + + +
+ + + + +
device-profile.profile-configuration
+
+
+ + +
+ + + +
device-profile.transport-configuration
+
+
+ + +
+ + + +
{{'device-profile.alarm-rules-with-count' | translate: + {count: entityForm.get('profileData.alarms').value ? + entityForm.get('profileData.alarms').value.length : 0} }}
+
+
+ + +
+ + + +
device-profile.device-provisioning
+
+
+ + +
+
+
diff --git a/ui-ngx/src/app/modules/home/components/profile/device-profile.component.ts b/ui-ngx/src/app/modules/home/components/profile/device-profile.component.ts index 38aed4d77a..09223491e1 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device-profile.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device-profile.component.ts @@ -28,10 +28,12 @@ import { DeviceProfile, DeviceProfileData, DeviceProfileType, + deviceProfileTypeConfigurationInfoMap, deviceProfileTypeTranslationMap, DeviceProvisionConfiguration, DeviceProvisionType, DeviceTransportType, + deviceTransportTypeConfigurationInfoMap, deviceTransportTypeTranslationMap } from '@shared/models/device.models'; import { EntityType } from '@shared/models/entity-type.models'; @@ -57,6 +59,10 @@ export class DeviceProfileComponent extends EntityComponent { deviceTransportTypeTranslations = deviceTransportTypeTranslationMap; + displayProfileConfiguration: boolean; + + displayTransportConfiguration: boolean; + constructor(protected store: Store, protected translate: TranslateService, @Optional() @Inject('entity') protected entityValue: DeviceProfile, @@ -74,6 +80,10 @@ export class DeviceProfileComponent extends EntityComponent { } buildForm(entity: DeviceProfile): FormGroup { + this.displayProfileConfiguration = entity && entity.type && + deviceProfileTypeConfigurationInfoMap.get(entity.type).hasProfileConfiguration; + this.displayTransportConfiguration = entity && entity.transportType && + deviceTransportTypeConfigurationInfoMap.get(entity.transportType).hasProfileConfiguration; const deviceProvisionConfiguration: DeviceProvisionConfiguration = { type: entity?.provisionType ? entity?.provisionType : DeviceProvisionType.DISABLED, provisionDeviceKey: entity?.provisionDeviceKey, @@ -116,6 +126,8 @@ export class DeviceProfileComponent extends EntityComponent { private deviceProfileTypeChanged(form: FormGroup) { const deviceProfileType: DeviceProfileType = form.get('type').value; + this.displayProfileConfiguration = deviceProfileType && + deviceProfileTypeConfigurationInfoMap.get(deviceProfileType).hasProfileConfiguration; let profileData: DeviceProfileData = form.getRawValue().profileData; if (!profileData) { profileData = { @@ -129,6 +141,8 @@ export class DeviceProfileComponent extends EntityComponent { private deviceProfileTransportTypeChanged(form: FormGroup) { const deviceTransportType: DeviceTransportType = form.get('transportType').value; + this.displayTransportConfiguration = deviceTransportType && + deviceTransportTypeConfigurationInfoMap.get(deviceTransportType).hasProfileConfiguration; let profileData: DeviceProfileData = form.getRawValue().profileData; if (!profileData) { profileData = { @@ -141,6 +155,10 @@ export class DeviceProfileComponent extends EntityComponent { } updateForm(entity: DeviceProfile) { + this.displayProfileConfiguration = entity.type && + deviceProfileTypeConfigurationInfoMap.get(entity.type).hasProfileConfiguration; + this.displayTransportConfiguration = entity.transportType && + deviceTransportTypeConfigurationInfoMap.get(entity.transportType).hasProfileConfiguration; const deviceProvisionConfiguration: DeviceProvisionConfiguration = { type: entity?.provisionType ? entity?.provisionType : DeviceProvisionType.DISABLED, provisionDeviceKey: entity?.provisionDeviceKey, From 31676d2110ba43eed338a66783463e122b524798 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Tue, 13 Oct 2020 09:09:12 +0300 Subject: [PATCH 42/48] UI: Refactoring show error in device-credentials component --- .../components/device/device-credentials.component.html | 6 +++--- .../home/components/device/device-credentials.component.ts | 7 ++----- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/device/device-credentials.component.html b/ui-ngx/src/app/modules/home/components/device/device-credentials.component.html index fa0c7e14a2..a8b48e30d1 100644 --- a/ui-ngx/src/app/modules/home/components/device/device-credentials.component.html +++ b/ui-ngx/src/app/modules/home/components/device/device-credentials.component.html @@ -70,8 +70,8 @@ {{hidePassword ? 'visibility_off' : 'visibility'}}
- - {{ 'device.client-id-or-user-name-necessary' | translate }} - +
diff --git a/ui-ngx/src/app/modules/home/components/device/device-credentials.component.ts b/ui-ngx/src/app/modules/home/components/device/device-credentials.component.ts index 298270d153..fd6680fe17 100644 --- a/ui-ngx/src/app/modules/home/components/device/device-credentials.component.ts +++ b/ui-ngx/src/app/modules/home/components/device/device-credentials.component.ts @@ -174,8 +174,8 @@ export class DeviceCredentialsComponent implements ControlValueAccessor, OnInit, updateValidators(): void { this.hidePassword = true; - const crendetialsType = this.deviceCredentialsFormGroup.get('credentialsType').value as DeviceCredentialsType; - switch (crendetialsType) { + const credentialsType = this.deviceCredentialsFormGroup.get('credentialsType').value as DeviceCredentialsType; + switch (credentialsType) { case DeviceCredentialsType.ACCESS_TOKEN: this.deviceCredentialsFormGroup.get('credentialsId').setValidators([Validators.required, Validators.pattern(/^.{1,20}$/)]); this.deviceCredentialsFormGroup.get('credentialsId').updateValueAndValidity({emitEvent: false}); @@ -215,9 +215,6 @@ export class DeviceCredentialsComponent implements ControlValueAccessor, OnInit, const value = this.deviceCredentialsFormGroup.get('credentialsBasic.password').value; if (value !== '') { this.deviceCredentialsFormGroup.get('credentialsBasic.userName').setValidators([Validators.required]); - if (this.deviceCredentialsFormGroup.get('credentialsBasic.userName').untouched) { - this.deviceCredentialsFormGroup.get('credentialsBasic.userName').markAsTouched({onlySelf: true}); - } } else { this.deviceCredentialsFormGroup.get('credentialsBasic.userName').setValidators([]); } From 6ef7e813fe99f22aa4e350040f93d8ffdfe999ae Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Tue, 13 Oct 2020 10:02:30 +0300 Subject: [PATCH 43/48] UI: Added catch error get OAuth2 --- ui-ngx/src/app/core/auth/auth.service.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/ui-ngx/src/app/core/auth/auth.service.ts b/ui-ngx/src/app/core/auth/auth.service.ts index da41c7c1b1..1f8788d821 100644 --- a/ui-ngx/src/app/core/auth/auth.service.ts +++ b/ui-ngx/src/app/core/auth/auth.service.ts @@ -201,10 +201,11 @@ export class AuthService { public loadOAuth2Clients(): Observable> { return this.http.post>(`/api/noauth/oauth2Clients`, null, defaultHttpOptions()).pipe( - tap((OAuth2Clients) => { - this.oauth2Clients = OAuth2Clients; - }) - ); + catchError(err => of([])), + tap((OAuth2Clients) => { + this.oauth2Clients = OAuth2Clients; + }) + ); } private forceDefaultPlace(authState?: AuthState, path?: string, params?: any): boolean { From e0ac59b94b53c0d3c887e8dc0f7f1317a6ff40a6 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Tue, 13 Oct 2020 11:08:13 +0300 Subject: [PATCH 44/48] UI: Device provisioning autogenerate device key and secret --- ui-ngx/src/app/core/utils.ts | 12 ++++++++++++ ...vice-profile-provision-configuration.component.ts | 12 ++++++++++-- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/ui-ngx/src/app/core/utils.ts b/ui-ngx/src/app/core/utils.ts index c0a01f3bec..4650caca77 100644 --- a/ui-ngx/src/app/core/utils.ts +++ b/ui-ngx/src/app/core/utils.ts @@ -403,3 +403,15 @@ export function deepTrim(obj: T): T { return acc; }, (Array.isArray(obj) ? [] : {}) as T); } + +export function generateSecret(length?: number): string { + if (isUndefined(length) || length == null) { + length = 1; + } + const l = length > 10 ? 10 : length; + const str = Math.random().toString(36).substr(2, l); + if (str.length >= length) { + return str; + } + return str.concat(generateSecret(length - str.length)); +} diff --git a/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.ts b/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.ts index aaaf81bc04..9793af2748 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.ts @@ -32,7 +32,7 @@ import { DeviceProvisionType, deviceProvisionTypeTranslationMap } from "@shared/models/device.models"; -import { isDefinedAndNotNull } from "@core/utils"; +import { generateSecret, isDefinedAndNotNull } from '@core/utils'; @Component({ selector: 'tb-device-profile-provision-configuration', @@ -85,10 +85,18 @@ export class DeviceProfileProvisionConfigurationComponent implements ControlValu this.provisionConfigurationFormGroup.get('type').valueChanges.subscribe((type) => { if (type === DeviceProvisionType.DISABLED) { this.provisionConfigurationFormGroup.get('provisionDeviceSecret').disable({emitEvent: false}); - this.provisionConfigurationFormGroup.get('provisionDeviceSecret').patchValue(null,{emitEvent: false}); + this.provisionConfigurationFormGroup.get('provisionDeviceSecret').patchValue(null, {emitEvent: false}); this.provisionConfigurationFormGroup.get('provisionDeviceKey').disable({emitEvent: false}); this.provisionConfigurationFormGroup.get('provisionDeviceKey').patchValue(null); } else { + const provisionDeviceSecret: string = this.provisionConfigurationFormGroup.get('provisionDeviceSecret').value; + if (!provisionDeviceSecret || !provisionDeviceSecret.length) { + this.provisionConfigurationFormGroup.get('provisionDeviceSecret').patchValue(generateSecret(20), {emitEvent: false}); + } + const provisionDeviceKey: string = this.provisionConfigurationFormGroup.get('provisionDeviceKey').value; + if (!provisionDeviceKey || !provisionDeviceKey.length) { + this.provisionConfigurationFormGroup.get('provisionDeviceKey').patchValue(generateSecret(20), {emitEvent: false}); + } this.provisionConfigurationFormGroup.get('provisionDeviceSecret').enable({emitEvent: false}); this.provisionConfigurationFormGroup.get('provisionDeviceKey').enable({emitEvent: false}); } From ff4ca5a760c8cde794788e8284a6746537c94f1a Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Tue, 13 Oct 2020 11:12:12 +0300 Subject: [PATCH 45/48] UI: Update transport type hints --- ui-ngx/src/assets/locale/locale.constant-en_US.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index d4bae7064e..bc7c84981b 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -862,9 +862,9 @@ "transport-type": "Transport type", "transport-type-required": "Transport type is required.", "transport-type-default": "Default", - "transport-type-default-hint": "Refers to already existing transport configuration or basic MQTT, HTTP, CoAP settings", + "transport-type-default-hint": "Supports basic MQTT, HTTP and CoAP transport", "transport-type-mqtt": "MQTT", - "transport-type-mqtt-hint": "Advanced MQTT topic filters and Device profile settings", + "transport-type-mqtt-hint": "Enables advanced MQTT transport settings", "transport-type-lwm2m": "LWM2M", "transport-type-lwm2m-hint": "LWM2M transport type", "description": "Description", From 6f80cf95e2699c0d36bc8e378158c6ac7f8ce622 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Tue, 13 Oct 2020 11:53:22 +0300 Subject: [PATCH 46/48] Tenant Profile Cache --- .../server/actors/ActorSystemContext.java | 5 + .../server/actors/app/AppActor.java | 33 ++++-- .../server/actors/tenant/TenantActor.java | 4 +- .../server/controller/BaseController.java | 4 + .../server/controller/TenantController.java | 2 + .../controller/TenantProfileController.java | 7 +- .../profile/DefaultTbTenantProfileCache.java | 106 ++++++++++++++++++ .../service/profile/TbTenantProfileCache.java | 38 +++++++ .../DefaultTenantRoutingInfoService.java | 10 +- .../transport/DefaultTransportApiService.java | 13 +-- 10 files changed, 195 insertions(+), 27 deletions(-) create mode 100644 application/src/main/java/org/thingsboard/server/service/profile/DefaultTbTenantProfileCache.java create mode 100644 application/src/main/java/org/thingsboard/server/service/profile/TbTenantProfileCache.java diff --git a/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java b/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java index 8f6a290af4..d35dadd16f 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java +++ b/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java @@ -72,6 +72,7 @@ import org.thingsboard.server.service.executors.ExternalCallExecutorService; import org.thingsboard.server.service.executors.SharedEventLoopGroupService; import org.thingsboard.server.service.mail.MailExecutorService; import org.thingsboard.server.service.profile.TbDeviceProfileCache; +import org.thingsboard.server.service.profile.TbTenantProfileCache; import org.thingsboard.server.service.queue.TbClusterService; import org.thingsboard.server.service.rpc.TbCoreDeviceRpcService; import org.thingsboard.server.service.rpc.TbRuleEngineDeviceRpcService; @@ -127,6 +128,10 @@ public class ActorSystemContext { @Getter private DeviceService deviceService; + @Autowired + @Getter + private TbTenantProfileCache tenantProfileCache; + @Autowired @Getter private TbDeviceProfileCache deviceProfileCache; diff --git a/application/src/main/java/org/thingsboard/server/actors/app/AppActor.java b/application/src/main/java/org/thingsboard/server/actors/app/AppActor.java index 16beb3a045..826596db56 100644 --- a/application/src/main/java/org/thingsboard/server/actors/app/AppActor.java +++ b/application/src/main/java/org/thingsboard/server/actors/app/AppActor.java @@ -30,6 +30,7 @@ import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.id.TenantProfileId; import org.thingsboard.server.common.data.page.PageDataIterable; import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; import org.thingsboard.server.common.msg.MsgType; @@ -40,7 +41,9 @@ import org.thingsboard.server.common.msg.queue.QueueToRuleEngineMsg; import org.thingsboard.server.common.msg.queue.RuleEngineException; import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.dao.model.ModelConstants; +import org.thingsboard.server.dao.tenant.TenantProfileService; import org.thingsboard.server.dao.tenant.TenantService; +import org.thingsboard.server.service.profile.TbTenantProfileCache; import org.thingsboard.server.service.transport.msg.TransportToDeviceActorMsgWrapper; import java.util.HashSet; @@ -50,13 +53,14 @@ import java.util.Set; @Slf4j public class AppActor extends ContextAwareActor { - private static final TenantId SYSTEM_TENANT = new TenantId(ModelConstants.NULL_UUID); + private final TbTenantProfileCache tenantProfileCache; private final TenantService tenantService; private final Set deletedTenants; private boolean ruleChainsInitialized; private AppActor(ActorSystemContext systemContext) { super(systemContext); + this.tenantProfileCache = systemContext.getTenantProfileCache(); this.tenantService = systemContext.getTenantService(); this.deletedTenants = new HashSet<>(); } @@ -117,8 +121,7 @@ public class AppActor extends ContextAwareActor { boolean isRuleEngine = systemContext.getServiceInfoProvider().isService(ServiceType.TB_RULE_ENGINE); boolean isCore = systemContext.getServiceInfoProvider().isService(ServiceType.TB_CORE); for (Tenant tenant : tenantIterator) { - // TODO: Tenant Profile from cache - TenantProfile tenantProfile = systemContext.getTenantProfileService().findTenantProfileById(TenantId.SYS_TENANT_ID, tenant.getTenantProfileId()); + TenantProfile tenantProfile = tenantProfileCache.get(tenant.getTenantProfileId()); if (isCore || (isRuleEngine && !tenantProfile.isIsolatedTbRuleEngine())) { log.debug("[{}] Creating tenant actor", tenant.getId()); getOrCreateTenantActor(tenant.getId()); @@ -133,7 +136,7 @@ public class AppActor extends ContextAwareActor { } private void onQueueToRuleEngineMsg(QueueToRuleEngineMsg msg) { - if (SYSTEM_TENANT.equals(msg.getTenantId())) { + if (TenantId.SYS_TENANT_ID.equals(msg.getTenantId())) { msg.getTbMsg().getCallback().onFailure(new RuleEngineException("Message has system tenant id!")); } else { if (!deletedTenants.contains(msg.getTenantId())) { @@ -146,15 +149,23 @@ public class AppActor extends ContextAwareActor { private void onComponentLifecycleMsg(ComponentLifecycleMsg msg) { TbActorRef target = null; - if (SYSTEM_TENANT.equals(msg.getTenantId())) { - log.warn("Message has system tenant id: {}", msg); + if (TenantId.SYS_TENANT_ID.equals(msg.getTenantId())) { + if (msg.getEntityId().getEntityType() == EntityType.TENANT_PROFILE) { + tenantProfileCache.evict(new TenantProfileId(msg.getEntityId().getId())); + } else { + log.warn("Message has system tenant id: {}", msg); + } } else { - if (msg.getEntityId().getEntityType() == EntityType.TENANT - && msg.getEvent() == ComponentLifecycleEvent.DELETED) { - log.info("[{}] Handling tenant deleted notification: {}", msg.getTenantId(), msg); + if (msg.getEntityId().getEntityType() == EntityType.TENANT) { TenantId tenantId = new TenantId(msg.getEntityId().getId()); - deletedTenants.add(tenantId); - ctx.stop(new TbEntityActorId(tenantId)); + tenantProfileCache.evict(tenantId); + if (msg.getEvent() == ComponentLifecycleEvent.DELETED) { + log.info("[{}] Handling tenant deleted notification: {}", msg.getTenantId(), msg); + deletedTenants.add(tenantId); + ctx.stop(new TbEntityActorId(tenantId)); + } else { + target = getOrCreateTenantActor(msg.getTenantId()); + } } else { target = getOrCreateTenantActor(msg.getTenantId()); } diff --git a/application/src/main/java/org/thingsboard/server/actors/tenant/TenantActor.java b/application/src/main/java/org/thingsboard/server/actors/tenant/TenantActor.java index 128280e9bc..161239be7e 100644 --- a/application/src/main/java/org/thingsboard/server/actors/tenant/TenantActor.java +++ b/application/src/main/java/org/thingsboard/server/actors/tenant/TenantActor.java @@ -77,9 +77,7 @@ public class TenantActor extends RuleChainManagerActor { // This Service may be started for specific tenant only. Optional isolatedTenantId = systemContext.getServiceInfoProvider().getIsolatedTenant(); - // TODO: Tenant Profile from cache - - TenantProfile tenantProfile = systemContext.getTenantProfileService().findTenantProfileById(tenantId, tenant.getTenantProfileId()); + TenantProfile tenantProfile = systemContext.getTenantProfileCache().get(tenant.getTenantProfileId()); isRuleEngineForCurrentTenant = systemContext.getServiceInfoProvider().isService(ServiceType.TB_RULE_ENGINE); isCore = systemContext.getServiceInfoProvider().isService(ServiceType.TB_CORE); diff --git a/application/src/main/java/org/thingsboard/server/controller/BaseController.java b/application/src/main/java/org/thingsboard/server/controller/BaseController.java index 703bb39eb0..b8fe956217 100644 --- a/application/src/main/java/org/thingsboard/server/controller/BaseController.java +++ b/application/src/main/java/org/thingsboard/server/controller/BaseController.java @@ -94,6 +94,7 @@ import org.thingsboard.server.queue.provider.TbQueueProducerProvider; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.component.ComponentDiscoveryService; import org.thingsboard.server.service.profile.TbDeviceProfileCache; +import org.thingsboard.server.service.profile.TbTenantProfileCache; import org.thingsboard.server.service.queue.TbClusterService; import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.permission.AccessControlService; @@ -205,6 +206,9 @@ public abstract class BaseController { @Autowired protected TbQueueProducerProvider producerProvider; + @Autowired + protected TbTenantProfileCache tenantProfileCache; + @Autowired protected TbDeviceProfileCache deviceProfileCache; diff --git a/application/src/main/java/org/thingsboard/server/controller/TenantController.java b/application/src/main/java/org/thingsboard/server/controller/TenantController.java index ebb46778c9..7c23545374 100644 --- a/application/src/main/java/org/thingsboard/server/controller/TenantController.java +++ b/application/src/main/java/org/thingsboard/server/controller/TenantController.java @@ -91,6 +91,7 @@ public class TenantController extends BaseController { if (newTenant) { installScripts.createDefaultRuleChains(tenant.getId()); } + tenantProfileCache.evict(tenant.getId()); return tenant; } catch (Exception e) { throw handleException(e); @@ -106,6 +107,7 @@ public class TenantController extends BaseController { TenantId tenantId = new TenantId(toUUID(strTenantId)); checkTenantId(tenantId, Operation.DELETE); tenantService.deleteTenant(tenantId); + tenantProfileCache.evict(tenantId); tbClusterService.onEntityStateChange(tenantId, tenantId, ComponentLifecycleEvent.DELETED); } catch (Exception e) { throw handleException(e); diff --git a/application/src/main/java/org/thingsboard/server/controller/TenantProfileController.java b/application/src/main/java/org/thingsboard/server/controller/TenantProfileController.java index dfe0b19a42..4c9d541ea6 100644 --- a/application/src/main/java/org/thingsboard/server/controller/TenantProfileController.java +++ b/application/src/main/java/org/thingsboard/server/controller/TenantProfileController.java @@ -29,9 +29,11 @@ import org.springframework.web.bind.annotation.RestController; import org.thingsboard.server.common.data.EntityInfo; import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.exception.ThingsboardException; +import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantProfileId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; +import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.security.permission.Operation; import org.thingsboard.server.service.security.permission.Resource; @@ -93,8 +95,11 @@ public class TenantProfileController extends BaseController { } tenantProfile = checkNotNull(tenantProfileService.saveTenantProfile(getTenantId(), tenantProfile)); + tenantProfileCache.put(tenantProfile); + tbClusterService.onEntityStateChange(TenantId.SYS_TENANT_ID, tenantProfile.getId(), + newTenantProfile ? ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED); return tenantProfile; - } catch (Exception e) { + } catch (Exception e) { throw handleException(e); } } diff --git a/application/src/main/java/org/thingsboard/server/service/profile/DefaultTbTenantProfileCache.java b/application/src/main/java/org/thingsboard/server/service/profile/DefaultTbTenantProfileCache.java new file mode 100644 index 0000000000..4260e36400 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/profile/DefaultTbTenantProfileCache.java @@ -0,0 +1,106 @@ +/** + * 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.service.profile; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.DeviceProfile; +import org.thingsboard.server.common.data.Tenant; +import org.thingsboard.server.common.data.TenantProfile; +import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.id.DeviceProfileId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.id.TenantProfileId; +import org.thingsboard.server.dao.device.DeviceProfileService; +import org.thingsboard.server.dao.device.DeviceService; +import org.thingsboard.server.dao.tenant.TenantProfileService; +import org.thingsboard.server.dao.tenant.TenantService; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +@Service +@Slf4j +public class DefaultTbTenantProfileCache implements TbTenantProfileCache { + + private final Lock tenantProfileFetchLock = new ReentrantLock(); + private final TenantProfileService tenantProfileService; + private final TenantService tenantService; + + private final ConcurrentMap tenantProfilesMap = new ConcurrentHashMap<>(); + private final ConcurrentMap tenantsMap = new ConcurrentHashMap<>(); + + public DefaultTbTenantProfileCache(TenantProfileService tenantProfileService, TenantService tenantService) { + this.tenantProfileService = tenantProfileService; + this.tenantService = tenantService; + } + + @Override + public TenantProfile get(TenantProfileId tenantProfileId) { + TenantProfile profile = tenantProfilesMap.get(tenantProfileId); + if (profile == null) { + profile = tenantProfilesMap.get(tenantProfileId); + if (profile == null) { + tenantProfileFetchLock.lock(); + try { + profile = tenantProfileService.findTenantProfileById(TenantId.SYS_TENANT_ID, tenantProfileId); + if (profile != null) { + tenantProfilesMap.put(tenantProfileId, profile); + } + } finally { + tenantProfileFetchLock.unlock(); + } + } + } + return profile; + } + + @Override + public TenantProfile get(TenantId tenantId) { + TenantProfileId profileId = tenantsMap.get(tenantId); + if (profileId == null) { + Tenant tenant = tenantService.findTenantById(tenantId); + if (tenant != null) { + profileId = tenant.getTenantProfileId(); + tenantsMap.put(tenantId, profileId); + } else { + return null; + } + } + return get(profileId); + } + + @Override + public void put(TenantProfile profile) { + if (profile.getId() != null) { + tenantProfilesMap.put(profile.getId(), profile); + } + } + + @Override + public void evict(TenantProfileId profileId) { + tenantProfilesMap.remove(profileId); + } + + @Override + public void evict(TenantId tenantId) { + tenantsMap.remove(tenantId); + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/profile/TbTenantProfileCache.java b/application/src/main/java/org/thingsboard/server/service/profile/TbTenantProfileCache.java new file mode 100644 index 0000000000..d9f944012f --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/profile/TbTenantProfileCache.java @@ -0,0 +1,38 @@ +/** + * 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.service.profile; + +import org.thingsboard.rule.engine.api.RuleEngineDeviceProfileCache; +import org.thingsboard.server.common.data.DeviceProfile; +import org.thingsboard.server.common.data.TenantProfile; +import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.id.DeviceProfileId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.id.TenantProfileId; + +public interface TbTenantProfileCache { + + TenantProfile get(TenantId tenantId); + + TenantProfile get(TenantProfileId tenantProfileId); + + void put(TenantProfile profile); + + void evict(TenantProfileId id); + + void evict(TenantId id); + +} diff --git a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTenantRoutingInfoService.java b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTenantRoutingInfoService.java index f89e05a57b..4d5219fbfe 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTenantRoutingInfoService.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTenantRoutingInfoService.java @@ -25,6 +25,7 @@ import org.thingsboard.server.dao.tenant.TenantProfileService; import org.thingsboard.server.dao.tenant.TenantService; import org.thingsboard.server.queue.discovery.TenantRoutingInfo; import org.thingsboard.server.queue.discovery.TenantRoutingInfoService; +import org.thingsboard.server.service.profile.TbTenantProfileCache; @Slf4j @Service @@ -33,19 +34,18 @@ public class DefaultTenantRoutingInfoService implements TenantRoutingInfoService private final TenantService tenantService; - private final TenantProfileService tenantProfileService; + private final TbTenantProfileCache tenantProfileCache; - public DefaultTenantRoutingInfoService(TenantService tenantService, TenantProfileService tenantProfileService) { + public DefaultTenantRoutingInfoService(TenantService tenantService, TbTenantProfileCache tenantProfileCache) { this.tenantService = tenantService; - this.tenantProfileService = tenantProfileService; + this.tenantProfileCache = tenantProfileCache; } @Override public TenantRoutingInfo getRoutingInfo(TenantId tenantId) { Tenant tenant = tenantService.findTenantById(tenantId); if (tenant != null) { - // TODO: Tenant Profile from cache - TenantProfile tenantProfile = tenantProfileService.findTenantProfileById(tenantId, tenant.getTenantProfileId()); + TenantProfile tenantProfile = tenantProfileCache.get(tenant.getTenantProfileId()); return new TenantRoutingInfo(tenantId, tenantProfile.isIsolatedTbCore(), tenantProfile.isIsolatedTbRuleEngine()); } else { throw new RuntimeException("Tenant not found!"); diff --git a/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java b/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java index 7fa4630359..c806757ddb 100644 --- a/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java +++ b/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java @@ -70,6 +70,7 @@ import org.thingsboard.server.queue.common.TbProtoQueueMsg; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.dao.device.provision.ProvisionFailedException; import org.thingsboard.server.service.executors.DbCallbackExecutorService; +import org.thingsboard.server.service.profile.TbTenantProfileCache; import org.thingsboard.server.service.queue.TbClusterService; import org.thingsboard.server.service.state.DeviceStateService; @@ -92,7 +93,7 @@ public class DefaultTransportApiService implements TransportApiService { //TODO: Constructor dependencies; private final DeviceProfileService deviceProfileService; private final TenantService tenantService; - private final TenantProfileService tenantProfileService; + private final TbTenantProfileCache tenantProfileCache; private final DeviceService deviceService; private final RelationService relationService; private final DeviceCredentialsService deviceCredentialsService; @@ -106,14 +107,14 @@ public class DefaultTransportApiService implements TransportApiService { private final ConcurrentMap deviceCreationLocks = new ConcurrentHashMap<>(); public DefaultTransportApiService(DeviceProfileService deviceProfileService, TenantService tenantService, - TenantProfileService tenantProfileService, DeviceService deviceService, + TbTenantProfileCache tenantProfileCache, DeviceService deviceService, RelationService relationService, DeviceCredentialsService deviceCredentialsService, DeviceStateService deviceStateService, DbCallbackExecutorService dbCallbackExecutorService, TbClusterService tbClusterService, DataDecodingEncodingService dataDecodingEncodingService, DeviceProvisionService deviceProvisionService) { this.deviceProfileService = deviceProfileService; this.tenantService = tenantService; - this.tenantProfileService = tenantProfileService; + this.tenantProfileCache = tenantProfileCache; this.deviceService = deviceService; this.relationService = relationService; this.deviceCredentialsService = deviceCredentialsService; @@ -321,10 +322,8 @@ public class DefaultTransportApiService implements TransportApiService { private ListenableFuture handle(GetTenantRoutingInfoRequestMsg requestMsg) { TenantId tenantId = new TenantId(new UUID(requestMsg.getTenantIdMSB(), requestMsg.getTenantIdLSB())); - // TODO: Tenant Profile from cache - ListenableFuture tenantProfileFuture = - Futures.transform(tenantService.findTenantByIdAsync(TenantId.SYS_TENANT_ID, tenantId), tenant -> - tenantProfileService.findTenantProfileById(TenantId.SYS_TENANT_ID, tenant.getTenantProfileId()), dbCallbackExecutorService); + + ListenableFuture tenantProfileFuture = Futures.immediateFuture(tenantProfileCache.get(tenantId)); return Futures.transform(tenantProfileFuture, tenantProfile -> TransportApiResponseMsg.newBuilder() .setGetTenantRoutingInfoResponseMsg(GetTenantRoutingInfoResponseMsg.newBuilder().setIsolatedTbCore(tenantProfile.isIsolatedTbCore()) .setIsolatedTbRuleEngine(tenantProfile.isIsolatedTbRuleEngine()).build()).build(), dbCallbackExecutorService); From 390bbfd67f91b7792aa3d5ed16a06d3ba60bd321 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Tue, 13 Oct 2020 12:01:47 +0300 Subject: [PATCH 47/48] UI: Fixed disabled form in edit device-profile --- ...ce-profile-provision-configuration.component.ts | 14 +++++++++----- .../components/profile/device-profile.component.ts | 2 -- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.ts b/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.ts index 9793af2748..e25c07e005 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.ts @@ -14,7 +14,7 @@ /// limitations under the License. /// -import { Component, forwardRef, Input, OnInit } from "@angular/core"; +import { Component, forwardRef, Input, OnInit } from '@angular/core'; import { ControlValueAccessor, FormBuilder, @@ -25,13 +25,13 @@ import { ValidationErrors, Validator, Validators -} from "@angular/forms"; -import { coerceBooleanProperty } from "@angular/cdk/coercion"; +} from '@angular/forms'; +import { coerceBooleanProperty } from '@angular/cdk/coercion'; import { DeviceProvisionConfiguration, DeviceProvisionType, deviceProvisionTypeTranslationMap -} from "@shared/models/device.models"; +} from '@shared/models/device.models'; import { generateSecret, isDefinedAndNotNull } from '@core/utils'; @Component({ @@ -126,7 +126,11 @@ export class DeviceProfileProvisionConfigurationComponent implements ControlValu if (this.disabled){ this.provisionConfigurationFormGroup.disable(); } else { - this.provisionConfigurationFormGroup.enable({emitEvent: false}); + if (this.provisionConfigurationFormGroup.get('type').value !== DeviceProvisionType.DISABLED) { + this.provisionConfigurationFormGroup.enable({emitEvent: false}); + } else { + this.provisionConfigurationFormGroup.get('type').enable({emitEvent: false}); + } } } diff --git a/ui-ngx/src/app/modules/home/components/profile/device-profile.component.ts b/ui-ngx/src/app/modules/home/components/profile/device-profile.component.ts index 09223491e1..e837caf660 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device-profile.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device-profile.component.ts @@ -94,8 +94,6 @@ export class DeviceProfileComponent extends EntityComponent { name: [entity ? entity.name : '', [Validators.required]], type: [entity ? entity.type : null, [Validators.required]], transportType: [entity ? entity.transportType : null, [Validators.required]], - provisionType: [deviceProvisionConfiguration.type, [Validators.required]], - provisionDeviceKey: [deviceProvisionConfiguration.provisionDeviceKey], profileData: this.fb.group({ configuration: [entity && !this.isAdd ? entity.profileData?.configuration : {}, Validators.required], transportConfiguration: [entity && !this.isAdd ? entity.profileData?.transportConfiguration : {}, Validators.required], From 8c29339f1364a066553db06444933cd4c0975f51 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Tue, 13 Oct 2020 13:20:54 +0300 Subject: [PATCH 48/48] Add support for port in oauth2 domain name. Update proxy.conf to support oauth2 urls --- .../server/controller/OAuth2Controller.java | 2 +- .../org/thingsboard/server/utils/MiscUtils.java | 16 ++++++++++++++++ ui-ngx/proxy.conf.js | 8 ++++++++ .../pages/admin/oauth2-settings.component.ts | 3 ++- 4 files changed, 27 insertions(+), 2 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java b/application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java index ecd74bef39..323eb19bcd 100644 --- a/application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java +++ b/application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java @@ -40,7 +40,7 @@ public class OAuth2Controller extends BaseController { @ResponseBody public List getOAuth2Clients(HttpServletRequest request) throws ThingsboardException { try { - return oAuth2Service.getOAuth2Clients(MiscUtils.getScheme(request), MiscUtils.getDomainName(request)); + return oAuth2Service.getOAuth2Clients(MiscUtils.getScheme(request), MiscUtils.getDomainNameAndPort(request)); } catch (Exception e) { throw handleException(e); } diff --git a/application/src/main/java/org/thingsboard/server/utils/MiscUtils.java b/application/src/main/java/org/thingsboard/server/utils/MiscUtils.java index ad242c96e5..e2d9302d79 100644 --- a/application/src/main/java/org/thingsboard/server/utils/MiscUtils.java +++ b/application/src/main/java/org/thingsboard/server/utils/MiscUtils.java @@ -68,6 +68,22 @@ public class MiscUtils { return request.getServerName(); } + public static String getDomainNameAndPort(HttpServletRequest request){ + String domainName = getDomainName(request); + String scheme = getScheme(request); + int port = MiscUtils.getPort(request); + if (needsPort(scheme, port)) { + domainName += ":" + port; + } + return domainName; + } + + private static boolean needsPort(String scheme, int port) { + boolean isHttpDefault = "http".equals(scheme.toLowerCase()) && port == 80; + boolean isHttpsDefault = "https".equals(scheme.toLowerCase()) && port == 443; + return !isHttpDefault && !isHttpsDefault; + } + public static int getPort(HttpServletRequest request){ String forwardedProto = request.getHeader("x-forwarded-proto"); diff --git a/ui-ngx/proxy.conf.js b/ui-ngx/proxy.conf.js index af558a7a7a..5be6bde1d1 100644 --- a/ui-ngx/proxy.conf.js +++ b/ui-ngx/proxy.conf.js @@ -27,6 +27,14 @@ const PROXY_CONFIG = { "target": ruleNodeUiforwardUrl, "secure": false, }, + "/oauth2": { + "target": forwardUrl, + "secure": false, + }, + "/login/oauth2": { + "target": forwardUrl, + "secure": false, + }, "/static": { "target": forwardUrl, "secure": false, diff --git a/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.ts b/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.ts index 400b2d3fc2..58cbe536d7 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.ts +++ b/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.ts @@ -52,6 +52,7 @@ import { OAuth2Service } from '@core/http/oauth2.service'; export class OAuth2SettingsComponent extends PageComponent implements OnInit, HasConfirmForm, OnDestroy { private URL_REGEXP = /^[A-Za-z][A-Za-z\d.+-]*:\/*(?:\w+(?::\w+)?@)?[^\s/]+(?::\d+)?(?:\/[\w#!:.,?+=&%@\-/]*)?$/; + private DOMAIN_AND_PORT_REGEXP = /^(?:\w+(?::\w+)?@)?[^\s/]+(?::\d+)?$/; private subscriptions: Subscription[] = []; private templates = new Map(); private defaultProvider = { @@ -233,7 +234,7 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha const domain = this.fb.group({ name: [domainInfo ? domainInfo.name : this.window.location.hostname, [ Validators.required, - Validators.pattern('((?![:/]).)*$')]], + Validators.pattern(this.DOMAIN_AND_PORT_REGEXP)]], scheme: [domainInfo?.scheme ? domainInfo.scheme : DomainSchema.HTTPS, Validators.required] }, {validators: this.uniqueDomainValidator}); return domain;