136 changed files with 3997 additions and 856 deletions
@ -0,0 +1,257 @@ |
|||
/** |
|||
* 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.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; |
|||
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.audit.ActionType; |
|||
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; |
|||
import org.thingsboard.server.common.data.security.DeviceCredentials; |
|||
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.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; |
|||
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.queue.provider.TbQueueProducerProvider; |
|||
import org.thingsboard.server.queue.util.TbCoreComponent; |
|||
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; |
|||
|
|||
|
|||
@Service |
|||
@Slf4j |
|||
@TbCoreComponent |
|||
public class DeviceProvisionServiceImpl implements DeviceProvisionService { |
|||
|
|||
protected TbQueueProducer<TbProtoQueueMsg<ToRuleEngineMsg>> ruleEngineMsgProducer; |
|||
|
|||
private static final String DEVICE_PROVISION_STATE = "provisionState"; |
|||
private static final String PROVISIONED_STATE = "provisioned"; |
|||
|
|||
private final ReentrantLock deviceCreationLock = new ReentrantLock(); |
|||
|
|||
@Autowired |
|||
DeviceDao deviceDao; |
|||
|
|||
@Autowired |
|||
DeviceProfileDao deviceProfileDao; |
|||
|
|||
@Autowired |
|||
DeviceService deviceService; |
|||
|
|||
@Autowired |
|||
DeviceCredentialsService deviceCredentialsService; |
|||
|
|||
@Autowired |
|||
AttributesService attributesService; |
|||
|
|||
@Autowired |
|||
DeviceStateService deviceStateService; |
|||
|
|||
@Autowired |
|||
AuditLogService auditLogService; |
|||
|
|||
@Autowired |
|||
PartitionService partitionService; |
|||
|
|||
public DeviceProvisionServiceImpl(TbQueueProducerProvider producerProvider) { |
|||
ruleEngineMsgProducer = producerProvider.getRuleEngineMsgProducer(); |
|||
} |
|||
|
|||
@Override |
|||
public ProvisionResponse provisionDevice(ProvisionRequest provisionRequest) { |
|||
String provisionRequestKey = provisionRequest.getCredentials().getProvisionDeviceKey(); |
|||
String provisionRequestSecret = provisionRequest.getCredentials().getProvisionDeviceSecret(); |
|||
|
|||
if (StringUtils.isEmpty(provisionRequestKey) || StringUtils.isEmpty(provisionRequestSecret)) { |
|||
throw new ProvisionFailedException(ProvisionResponseStatus.NOT_FOUND.name()); |
|||
} |
|||
|
|||
DeviceProfile targetProfile = deviceProfileDao.findByProvisionDeviceKey(provisionRequestKey); |
|||
|
|||
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 (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); |
|||
throw new ProvisionFailedException(ProvisionResponseStatus.FAILURE.name()); |
|||
} else { |
|||
return createDevice(provisionRequest, targetProfile); |
|||
} |
|||
} |
|||
break; |
|||
case CHECK_PRE_PROVISIONED_DEVICES: |
|||
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()); |
|||
throw new ProvisionFailedException(ProvisionResponseStatus.FAILURE.name()); |
|||
} |
|||
} |
|||
break; |
|||
} |
|||
throw new ProvisionFailedException(ProvisionResponseStatus.NOT_FOUND.name()); |
|||
} |
|||
|
|||
private ProvisionResponse processProvision(Device device, ProvisionRequest provisionRequest) { |
|||
try { |
|||
Optional<AttributeKvEntry> 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); |
|||
} |
|||
} catch (InterruptedException | ExecutionException e) { |
|||
throw new ProvisionFailedException(ProvisionResponseStatus.FAILURE.name()); |
|||
} |
|||
return new ProvisionResponse(deviceCredentialsService.findDeviceCredentialsByDeviceId(device.getTenantId(), device.getId()), ProvisionResponseStatus.SUCCESS); |
|||
} |
|||
|
|||
private ProvisionResponse createDevice(ProvisionRequest provisionRequest, DeviceProfile profile) { |
|||
deviceCreationLock.lock(); |
|||
try { |
|||
return processCreateDevice(provisionRequest, profile); |
|||
} finally { |
|||
deviceCreationLock.unlock(); |
|||
} |
|||
} |
|||
|
|||
private void notify(Device device, ProvisionRequest provisionRequest, String type, boolean success) { |
|||
pushProvisionEventToRuleEngine(provisionRequest, device, type); |
|||
logAction(device.getTenantId(), device.getCustomerId(), device, success, provisionRequest); |
|||
} |
|||
|
|||
private ProvisionResponse processCreateDevice(ProvisionRequest provisionRequest, DeviceProfile profile) { |
|||
Device device = deviceService.findDeviceByTenantIdAndName(profile.getTenantId(), provisionRequest.getDeviceName()); |
|||
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()); |
|||
} |
|||
} |
|||
|
|||
private ListenableFuture<List<Void>> 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 DeviceCredentials getDeviceCredentials(Device device) { |
|||
return deviceCredentialsService.findDeviceCredentialsByDeviceId(device.getTenantId(), device.getId()); |
|||
} |
|||
|
|||
private void pushProvisionEventToRuleEngine(ProvisionRequest request, Device device, String type) { |
|||
try { |
|||
JsonNode entityNode = JacksonUtil.valueToTree(request); |
|||
TbMsg msg = TbMsg.newMsg(type, device.getId(), createTbMsgMetaData(device), JacksonUtil.toString(entityNode)); |
|||
sendToRuleEngine(device.getTenantId(), msg, null); |
|||
} catch (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 = 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); |
|||
} |
|||
} |
|||
|
|||
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, CustomerId customerId, Device device, boolean success, ProvisionRequest provisionRequest) { |
|||
ActionType actionType = success ? ActionType.PROVISION_SUCCESS : ActionType.PROVISION_FAILURE; |
|||
auditLogService.logEntityAction(tenantId, customerId, new UserId(UserId.NULL_UUID), device.getName(), device.getId(), device, actionType, null, provisionRequest); |
|||
} |
|||
} |
|||
@ -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<TenantProfileId, TenantProfile> tenantProfilesMap = new ConcurrentHashMap<>(); |
|||
private final ConcurrentMap<TenantId, TenantProfileId> 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); |
|||
} |
|||
|
|||
} |
|||
@ -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); |
|||
|
|||
} |
|||
@ -0,0 +1,285 @@ |
|||
/** |
|||
* 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.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; |
|||
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 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 |
|||
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 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(); |
|||
|
|||
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 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(); |
|||
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 { |
|||
return createMqttClientAndPublish(""); |
|||
} |
|||
|
|||
protected TestMqttCallback createMqttClientAndPublish(String deviceCredentials) throws Exception { |
|||
String provisionRequestMsg = createTestProvisionMessage(deviceCredentials); |
|||
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(String deviceCredentials) { |
|||
return "{\"deviceName\":\"Test Provision device\",\"provisionDeviceKey\":\"testProvisionKey\", \"provisionDeviceSecret\":\"testProvisionSecret\"" + deviceCredentials + "}"; |
|||
} |
|||
} |
|||
@ -0,0 +1,305 @@ |
|||
/** |
|||
* 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 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.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.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.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; |
|||
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 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 |
|||
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 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 processTestProvisioningCreateNewDeviceWithAccessToken() throws Exception { |
|||
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()); |
|||
|
|||
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.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()); |
|||
|
|||
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.PROTOBUF, 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"); |
|||
|
|||
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.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.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())); |
|||
|
|||
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.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 { |
|||
byte[] provisionRequestMsg = createTestProvisionMessage(); |
|||
return createMqttClientAndPublish(provisionRequestMsg); |
|||
} |
|||
|
|||
protected TestMqttCallback createMqttClientAndPublish(byte[] provisionRequestMsg) throws Exception { |
|||
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[] 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() throws Exception { |
|||
return createTestsProvisionMessage(null, null); |
|||
} |
|||
|
|||
} |
|||
@ -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 { |
|||
} |
|||
@ -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 { |
|||
} |
|||
@ -0,0 +1,28 @@ |
|||
/** |
|||
* 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.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 { |
|||
|
|||
ProvisionResponse provisionDevice(ProvisionRequest provisionRequest) throws ProvisionFailedException; |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
@ -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.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 DeviceCredentialsType credentialsType; |
|||
private ProvisionDeviceCredentialsData credentialsData; |
|||
private ProvisionDeviceProfileCredentials credentials; |
|||
} |
|||
@ -0,0 +1,25 @@ |
|||
/** |
|||
* 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.Data; |
|||
import org.thingsboard.server.common.data.security.DeviceCredentials; |
|||
|
|||
@Data |
|||
public class ProvisionResponse { |
|||
private final DeviceCredentials deviceCredentials; |
|||
private final ProvisionResponseStatus responseStatus; |
|||
} |
|||
@ -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.dao.device.provision; |
|||
|
|||
public enum ProvisionResponseStatus { |
|||
UNKNOWN, |
|||
SUCCESS, |
|||
NOT_FOUND, |
|||
FAILURE |
|||
} |
|||
@ -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.common.data; |
|||
|
|||
public enum DeviceProfileProvisionType { |
|||
DISABLED, |
|||
ALLOW_CREATE_NEW_DEVICES, |
|||
CHECK_PRE_PROVISIONED_DEVICES |
|||
} |
|||
@ -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 x509CertHash; |
|||
} |
|||
@ -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; |
|||
} |
|||
|
|||
} |
|||
@ -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; |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,41 @@ |
|||
/** |
|||
* 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 { |
|||
|
|||
String getProvisionDeviceSecret(); |
|||
|
|||
@JsonIgnore |
|||
DeviceProfileProvisionType getType(); |
|||
|
|||
} |
|||
@ -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; |
|||
} |
|||
|
|||
} |
|||
@ -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.common.data.device.profile; |
|||
|
|||
import lombok.Data; |
|||
|
|||
@Data |
|||
public class ProvisionDeviceProfileCredentials { |
|||
private final String provisionDeviceKey; |
|||
private final String provisionDeviceSecret; |
|||
} |
|||
@ -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<OAuth2ClientsParams> { |
|||
return this.http.get<OAuth2ClientsParams>(`/api/oauth2/config`, defaultHttpOptionsFromConfig(config)); |
|||
} |
|||
|
|||
public getOAuth2Template(config?: RequestConfig): Observable<Array<OAuth2ClientRegistrationTemplate>> { |
|||
return this.http.get<Array<OAuth2ClientRegistrationTemplate>>(`/api/oauth2/config/template`, defaultHttpOptionsFromConfig(config)); |
|||
} |
|||
|
|||
public saveOAuth2Settings(OAuth2Setting: OAuth2ClientsParams, config?: RequestConfig): Observable<OAuth2ClientsParams> { |
|||
return this.http.post<OAuth2ClientsParams>('/api/oauth2/config', OAuth2Setting, |
|||
defaultHttpOptionsFromConfig(config)); |
|||
} |
|||
} |
|||
@ -0,0 +1,124 @@ |
|||
<!-- |
|||
|
|||
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. |
|||
|
|||
--> |
|||
<form [formGroup]="conditionFormGroup" (ngSubmit)="save()" style="width: 700px;"> |
|||
<mat-toolbar color="primary"> |
|||
<h2>{{ (readonly ? 'device-profile.alarm-rule-condition' : 'device-profile.edit-alarm-rule-condition') | translate }}</h2> |
|||
<span fxFlex></span> |
|||
<button mat-icon-button |
|||
(click)="cancel()" |
|||
type="button"> |
|||
<mat-icon class="material-icons">close</mat-icon> |
|||
</button> |
|||
</mat-toolbar> |
|||
<mat-progress-bar color="warn" mode="indeterminate" *ngIf="isLoading$ | async"> |
|||
</mat-progress-bar> |
|||
<div mat-dialog-content> |
|||
<fieldset [disabled]="isLoading$ | async"> |
|||
<div fxFlex fxLayout="column"> |
|||
<tb-key-filter-list |
|||
[displayUserParameters]="false" |
|||
[allowUserDynamicSource]="false" |
|||
[telemetryKeysOnly]="true" |
|||
formControlName="keyFilters"> |
|||
</tb-key-filter-list> |
|||
<section formGroupName="spec" class="row"> |
|||
<mat-form-field class="mat-block" hideRequiredMarker> |
|||
<mat-label translate>device-profile.condition-type</mat-label> |
|||
<mat-select formControlName="type" required> |
|||
<mat-option *ngFor="let alarmConditionType of alarmConditionTypes" [value]="alarmConditionType"> |
|||
{{ alarmConditionTypeTranslation.get(alarmConditionType) | translate }} |
|||
</mat-option> |
|||
</mat-select> |
|||
<mat-error *ngIf="conditionFormGroup.get('spec.type').hasError('required')"> |
|||
{{ 'device-profile.condition-type-required' | translate }} |
|||
</mat-error> |
|||
</mat-form-field> |
|||
<div fxLayout="row" fxLayoutGap="8px" *ngIf="conditionFormGroup.get('spec.type').value == AlarmConditionType.DURATION"> |
|||
<mat-form-field class="mat-block" hideRequiredMarker fxFlex floatLabel="always"> |
|||
<mat-label></mat-label> |
|||
<input type="number" required |
|||
step="1" min="1" max="2147483647" matInput |
|||
placeholder="{{ 'device-profile.condition-duration-value' | translate }}" |
|||
formControlName="value"> |
|||
<mat-error *ngIf="conditionFormGroup.get('spec.value').hasError('required')"> |
|||
{{ 'device-profile.condition-duration-value-required' | translate }} |
|||
</mat-error> |
|||
<mat-error *ngIf="conditionFormGroup.get('spec.value').hasError('min')"> |
|||
{{ 'device-profile.condition-duration-value-range' | translate }} |
|||
</mat-error> |
|||
<mat-error *ngIf="conditionFormGroup.get('spec.value').hasError('max')"> |
|||
{{ 'device-profile.condition-duration-value-range' | translate }} |
|||
</mat-error> |
|||
<mat-error *ngIf="conditionFormGroup.get('spec.value').hasError('pattern')"> |
|||
{{ 'device-profile.condition-duration-value-pattern' | translate }} |
|||
</mat-error> |
|||
</mat-form-field> |
|||
<mat-form-field class="mat-block" hideRequiredMarker fxFlex floatLabel="always"> |
|||
<mat-label></mat-label> |
|||
<mat-select formControlName="unit" |
|||
required |
|||
placeholder="{{ 'device-profile.condition-duration-time-unit' | translate }}"> |
|||
<mat-option *ngFor="let timeUnit of timeUnits" [value]="timeUnit"> |
|||
{{ timeUnitTranslations.get(timeUnit) | translate }} |
|||
</mat-option> |
|||
</mat-select> |
|||
<mat-error *ngIf="conditionFormGroup.get('spec.unit').hasError('required')"> |
|||
{{ 'device-profile.condition-duration-time-unit-required' | translate }} |
|||
</mat-error> |
|||
</mat-form-field> |
|||
</div> |
|||
<div fxLayout="row" fxLayoutGap="8px" *ngIf="conditionFormGroup.get('spec.type').value == AlarmConditionType.REPEATING"> |
|||
<mat-form-field class="mat-block" hideRequiredMarker fxFlex floatLabel="always"> |
|||
<mat-label></mat-label> |
|||
<input type="number" required |
|||
step="1" min="1" max="2147483647" matInput |
|||
placeholder="{{ 'device-profile.condition-repeating-value' | translate }}" |
|||
formControlName="count"> |
|||
<mat-error *ngIf="conditionFormGroup.get('spec.count').hasError('required')"> |
|||
{{ 'device-profile.condition-repeating-value-required' | translate }} |
|||
</mat-error> |
|||
<mat-error *ngIf="conditionFormGroup.get('spec.count').hasError('min')"> |
|||
{{ 'device-profile.condition-repeating-value-range' | translate }} |
|||
</mat-error> |
|||
<mat-error *ngIf="conditionFormGroup.get('spec.count').hasError('max')"> |
|||
{{ 'device-profile.condition-repeating-value-range' | translate }} |
|||
</mat-error> |
|||
<mat-error *ngIf="conditionFormGroup.get('spec.count').hasError('pattern')"> |
|||
{{ 'device-profile.condition-repeating-value-pattern' | translate }} |
|||
</mat-error> |
|||
</mat-form-field> |
|||
</div> |
|||
</section> |
|||
</div> |
|||
</fieldset> |
|||
</div> |
|||
<div mat-dialog-actions fxLayoutAlign="end center"> |
|||
<button mat-raised-button color="primary" |
|||
*ngIf="!readonly" |
|||
type="submit" |
|||
[disabled]="(isLoading$ | async) || conditionFormGroup.invalid || !conditionFormGroup.dirty"> |
|||
{{ 'action.save' | translate }} |
|||
</button> |
|||
<button mat-button color="primary" |
|||
type="button" |
|||
[disabled]="(isLoading$ | async)" |
|||
(click)="cancel()" cdkFocusInitial> |
|||
{{ (readonly ? 'action.close' : 'action.cancel') | translate }} |
|||
</button> |
|||
</div> |
|||
</form> |
|||
@ -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; |
|||
} |
|||
} |
|||
@ -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<AlarmRuleConditionDialogComponent, AlarmCondition> |
|||
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<AppState>, |
|||
protected router: Router, |
|||
@Inject(MAT_DIALOG_DATA) public data: AlarmRuleConditionDialogData, |
|||
@SkipSelf() private errorStateMatcher: ErrorStateMatcher, |
|||
public dialogRef: MatDialogRef<AlarmRuleConditionDialogComponent, AlarmCondition>, |
|||
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); |
|||
} |
|||
} |
|||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue