Refactor after review and add tests for extracting by regex
This commit is contained in:
		
							parent
							
								
									5d84945ccd
								
							
						
					
					
						commit
						fbeb56cf70
					
				@ -100,7 +100,7 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public ProvisionResponse provisionDeviceViaX509Chain(DeviceProfile targetProfile, ProvisionRequest provisionRequest) {
 | 
			
		||||
    public ProvisionResponse provisionDeviceViaX509Chain(DeviceProfile targetProfile, ProvisionRequest provisionRequest) throws ProvisionFailedException {
 | 
			
		||||
        if (targetProfile == null) {
 | 
			
		||||
            throw new ProvisionFailedException("Device profile is not specified!");
 | 
			
		||||
        }
 | 
			
		||||
@ -110,9 +110,10 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService {
 | 
			
		||||
        X509CertificateChainProvisionConfiguration configuration = (X509CertificateChainProvisionConfiguration) targetProfile.getProfileData().getProvisionConfiguration();
 | 
			
		||||
        String certificateValue = provisionRequest.getCredentialsData().getX509CertHash();
 | 
			
		||||
        String certificateRegEx = configuration.getCertificateRegExPattern();
 | 
			
		||||
        String deviceName = extractDeviceNameFromCertificateCNByRegEx(targetProfile, certificateValue, certificateRegEx);
 | 
			
		||||
        String commonName = getCNFromX509Certificate(certificateValue);
 | 
			
		||||
        String deviceName = extractDeviceNameFromCNByRegEx(targetProfile, commonName, certificateRegEx);
 | 
			
		||||
        if (StringUtils.isBlank(deviceName)) {
 | 
			
		||||
            log.warn("Device name cannot be extracted using regex [{}] for certificate [{}]", certificateRegEx, certificateValue);
 | 
			
		||||
            log.warn("[{}][{}] Failed to extract device name using [{}] and certificate: [{}]", targetProfile.getTenantId(), targetProfile.getId(), certificateRegEx, certificateValue);
 | 
			
		||||
            throw new ProvisionFailedException(ProvisionResponseStatus.FAILURE.name());
 | 
			
		||||
        }
 | 
			
		||||
        provisionRequest.setDeviceName(deviceName);
 | 
			
		||||
@ -120,7 +121,7 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService {
 | 
			
		||||
        X509CertificateChainProvisionConfiguration x509Configuration = (X509CertificateChainProvisionConfiguration) targetProfile.getProfileData().getProvisionConfiguration();
 | 
			
		||||
        if (targetDevice != null && targetDevice.getDeviceProfileId().equals(targetProfile.getId())) {
 | 
			
		||||
            DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(targetDevice.getTenantId(), targetDevice.getId());
 | 
			
		||||
            if (deviceCredentials.getCredentialsType() == DeviceCredentialsType.X509_CERTIFICATE) {
 | 
			
		||||
            if (DeviceCredentialsType.X509_CERTIFICATE.equals(deviceCredentials.getCredentialsType())) {
 | 
			
		||||
                String updatedDeviceCertificateValue = provisionRequest.getCredentialsData().getX509CertHash();
 | 
			
		||||
                deviceCredentials = updateDeviceCredentials(targetDevice.getTenantId(), deviceCredentials,
 | 
			
		||||
                        updatedDeviceCertificateValue, DeviceCredentialsType.X509_CERTIFICATE);
 | 
			
		||||
@ -295,21 +296,25 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService {
 | 
			
		||||
        auditLogService.logEntityAction(tenantId, customerId, new UserId(UserId.NULL_UUID), device.getName(), device.getId(), device, actionType, null, provisionRequest);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private String extractDeviceNameFromCertificateCNByRegEx(DeviceProfile profile, String x509Value, String regex) {
 | 
			
		||||
    private String getCNFromX509Certificate(String x509Value) {
 | 
			
		||||
        try {
 | 
			
		||||
            String commonName = SslUtil.parseCommonName(SslUtil.readCertFile(x509Value));
 | 
			
		||||
            log.trace("Extract CN [{}] by regex pattern [{}]", commonName, regex);
 | 
			
		||||
            Pattern pattern = Pattern.compile(regex);
 | 
			
		||||
            Matcher matcher = pattern.matcher(commonName);
 | 
			
		||||
            if (matcher.find()) {
 | 
			
		||||
                return matcher.group(1);
 | 
			
		||||
            } else {
 | 
			
		||||
                return null;
 | 
			
		||||
            }
 | 
			
		||||
        } catch (Exception ignored) {
 | 
			
		||||
            log.trace("[{}][{}] Failed to extract device name using [{}] and certificate: [{}]", profile.getTenantId(), profile.getId(), regex, x509Value);
 | 
			
		||||
            return SslUtil.parseCommonName(SslUtil.readCertFile(x509Value));
 | 
			
		||||
        } catch (Exception e) {
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public String extractDeviceNameFromCNByRegEx(DeviceProfile profile, String commonName, String regex) {
 | 
			
		||||
        try {
 | 
			
		||||
            log.trace("Extract device name from CN [{}] by regex pattern [{}]", commonName, regex);
 | 
			
		||||
            Pattern pattern = Pattern.compile(regex);
 | 
			
		||||
            Matcher matcher = pattern.matcher(commonName);
 | 
			
		||||
            if (matcher.find()) {
 | 
			
		||||
                return matcher.group(1);
 | 
			
		||||
            }
 | 
			
		||||
        } catch (Exception ignored) {}
 | 
			
		||||
        log.trace("[{}][{}] Failed to match device name using [{}] from CN: [{}]", profile.getTenantId(), profile.getId(), regex, commonName);
 | 
			
		||||
        return null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -33,6 +33,7 @@ import org.thingsboard.server.common.data.ApiUsageState;
 | 
			
		||||
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.DeviceTransportType;
 | 
			
		||||
import org.thingsboard.server.common.data.EntityType;
 | 
			
		||||
import org.thingsboard.server.common.data.OtaPackage;
 | 
			
		||||
@ -113,7 +114,6 @@ import java.util.concurrent.locks.ReentrantLock;
 | 
			
		||||
import java.util.regex.Pattern;
 | 
			
		||||
import java.util.stream.Collectors;
 | 
			
		||||
 | 
			
		||||
import static org.thingsboard.server.common.data.DeviceProfileProvisionType.X509_CERTIFICATE_CHAIN;
 | 
			
		||||
import static org.thingsboard.server.service.transport.BasicCredentialsValidationResult.PASSWORD_MISMATCH;
 | 
			
		||||
import static org.thingsboard.server.service.transport.BasicCredentialsValidationResult.VALID;
 | 
			
		||||
 | 
			
		||||
@ -246,7 +246,7 @@ public class DefaultTransportApiService implements TransportApiService {
 | 
			
		||||
                return getDeviceInfo(credentials);
 | 
			
		||||
            }
 | 
			
		||||
            DeviceProfile deviceProfile = deviceProfileService.findDeviceProfileByProvisionDeviceKey(certificateHash);
 | 
			
		||||
            if (deviceProfile != null && X509_CERTIFICATE_CHAIN.equals(deviceProfile.getProvisionType())) {
 | 
			
		||||
            if (deviceProfile != null && DeviceProfileProvisionType.X509_CERTIFICATE_CHAIN.equals(deviceProfile.getProvisionType())) {
 | 
			
		||||
                String updatedDeviceProvisionSecret = chain.get(0);
 | 
			
		||||
                ProvisionRequest provisionRequest = createProvisionRequest(updatedDeviceProvisionSecret);
 | 
			
		||||
                try {
 | 
			
		||||
@ -259,7 +259,7 @@ public class DefaultTransportApiService implements TransportApiService {
 | 
			
		||||
                    return getEmptyTransportApiResponseFuture();
 | 
			
		||||
                }
 | 
			
		||||
            } else if (deviceProfile != null) {
 | 
			
		||||
                log.warn("[{}] Device Profile provision configuration mismatched: expected {}, actual {}", deviceProfile.getId(), X509_CERTIFICATE_CHAIN, deviceProfile.getProvisionType());
 | 
			
		||||
                log.warn("[{}][{}] Device Profile provision configuration mismatched: expected {}, actual {}", deviceProfile.getTenantId(), deviceProfile.getId(), DeviceProfileProvisionType.X509_CERTIFICATE_CHAIN, deviceProfile.getProvisionType());
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return getEmptyTransportApiResponseFuture();
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,267 @@
 | 
			
		||||
/**
 | 
			
		||||
 * Copyright © 2016-2023 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.provision;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
import lombok.extern.slf4j.Slf4j;
 | 
			
		||||
import org.assertj.core.api.Assertions;
 | 
			
		||||
import org.junit.Before;
 | 
			
		||||
import org.junit.Test;
 | 
			
		||||
import org.junit.runner.RunWith;
 | 
			
		||||
import org.springframework.boot.test.mock.mockito.MockBean;
 | 
			
		||||
import org.springframework.boot.test.mock.mockito.SpyBean;
 | 
			
		||||
import org.springframework.test.context.ContextConfiguration;
 | 
			
		||||
import org.springframework.test.context.junit4.SpringRunner;
 | 
			
		||||
import org.thingsboard.server.cluster.TbClusterService;
 | 
			
		||||
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.Tenant;
 | 
			
		||||
import org.thingsboard.server.common.data.device.credentials.ProvisionDeviceCredentialsData;
 | 
			
		||||
import org.thingsboard.server.common.data.device.profile.DeviceProfileData;
 | 
			
		||||
import org.thingsboard.server.common.data.device.profile.X509CertificateChainProvisionConfiguration;
 | 
			
		||||
import org.thingsboard.server.common.data.id.CustomerId;
 | 
			
		||||
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.security.DeviceCredentials;
 | 
			
		||||
import org.thingsboard.server.common.data.security.DeviceCredentialsType;
 | 
			
		||||
import org.thingsboard.server.common.msg.EncryptionUtil;
 | 
			
		||||
import org.thingsboard.server.common.transport.util.SslUtil;
 | 
			
		||||
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.DeviceProfileService;
 | 
			
		||||
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.gen.transport.TransportProtos;
 | 
			
		||||
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.device.DeviceProvisionServiceImpl;;import java.io.IOException;
 | 
			
		||||
import java.nio.file.Files;
 | 
			
		||||
import java.nio.file.Paths;
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.UUID;
 | 
			
		||||
import java.util.regex.Matcher;
 | 
			
		||||
import java.util.regex.Pattern;
 | 
			
		||||
 | 
			
		||||
import static org.mockito.ArgumentMatchers.any;
 | 
			
		||||
import static org.mockito.Mockito.times;
 | 
			
		||||
import static org.mockito.Mockito.verify;
 | 
			
		||||
import static org.mockito.Mockito.when;
 | 
			
		||||
 | 
			
		||||
@Slf4j
 | 
			
		||||
@RunWith(SpringRunner.class)
 | 
			
		||||
@ContextConfiguration(classes = DeviceProvisionServiceImpl.class)
 | 
			
		||||
public class DeviceProvisionServiceTest {
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    @MockBean
 | 
			
		||||
    protected TbQueueProducerProvider producerProvider;
 | 
			
		||||
    @MockBean
 | 
			
		||||
    protected TbQueueProducer<TbProtoQueueMsg<TransportProtos.ToRuleEngineMsg>> ruleEngineMsgProducer;
 | 
			
		||||
    @MockBean
 | 
			
		||||
    protected TbClusterService clusterService;
 | 
			
		||||
    @MockBean
 | 
			
		||||
    protected DeviceProfileService deviceProfileService;
 | 
			
		||||
    @MockBean
 | 
			
		||||
    protected DeviceService deviceService;
 | 
			
		||||
    @MockBean
 | 
			
		||||
    protected DeviceCredentialsService deviceCredentialsService;
 | 
			
		||||
    @MockBean
 | 
			
		||||
    protected AttributesService attributesService;
 | 
			
		||||
    @MockBean
 | 
			
		||||
    protected AuditLogService auditLogService;
 | 
			
		||||
    @MockBean
 | 
			
		||||
    protected PartitionService partitionService;
 | 
			
		||||
    @SpyBean
 | 
			
		||||
    DeviceProvisionServiceImpl service;
 | 
			
		||||
 | 
			
		||||
    private String[] chain;
 | 
			
		||||
 | 
			
		||||
    @Before
 | 
			
		||||
    public void setUp() {
 | 
			
		||||
        String filePath = "src/test/resources/provision/x509ChainProvisionTest.pem";
 | 
			
		||||
        try {
 | 
			
		||||
            String certificateChain = Files.readString(Paths.get(filePath));
 | 
			
		||||
            certificateChain = certTrimNewLinesForChainInDeviceProfile(certificateChain);
 | 
			
		||||
            chain = fetchLeafCertificateFromChain(certificateChain);
 | 
			
		||||
        } catch (IOException e) {
 | 
			
		||||
            throw new RuntimeException(e);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void provisionDeviceViaX509Certificate() {
 | 
			
		||||
        var tenant = createTenant();
 | 
			
		||||
        var deviceProfile = createDeviceProfile(tenant.getId(), chain[1], true);
 | 
			
		||||
 | 
			
		||||
        var device = createDevice(tenant.getId(), deviceProfile.getId());
 | 
			
		||||
        when(deviceService.findDeviceByTenantIdAndName(any(), any())).thenReturn(device);
 | 
			
		||||
 | 
			
		||||
        var deviceCredentials = createDeviceCredentials(chain[0], device.getId());
 | 
			
		||||
        when(deviceCredentialsService.findDeviceCredentialsByDeviceId(any(), any())).thenReturn(deviceCredentials);
 | 
			
		||||
        when(deviceCredentialsService.updateDeviceCredentials(any(), any())).thenReturn(deviceCredentials);
 | 
			
		||||
 | 
			
		||||
        ProvisionResponse response = service.provisionDeviceViaX509Chain(deviceProfile, createProvisionRequest(chain[0]));
 | 
			
		||||
 | 
			
		||||
        verify(deviceService, times(1)).findDeviceByTenantIdAndName(any(), any());
 | 
			
		||||
        verify(deviceCredentialsService, times(1)).findDeviceCredentialsByDeviceId(any(), any());
 | 
			
		||||
        verify(deviceCredentialsService, times(1)).updateDeviceCredentials(any(), any());
 | 
			
		||||
 | 
			
		||||
        Assertions.assertThat(response.getResponseStatus()).isEqualTo(ProvisionResponseStatus.SUCCESS);
 | 
			
		||||
        Assertions.assertThat(response.getDeviceCredentials()).isEqualTo(deviceCredentials);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void provisionDeviceWithIncorrectConfiguration() {
 | 
			
		||||
        var tenant = createTenant();
 | 
			
		||||
        var deviceProfile = createDeviceProfile(tenant.getId(), chain[1], false);
 | 
			
		||||
 | 
			
		||||
        Assertions.assertThatThrownBy(() ->
 | 
			
		||||
                        service.provisionDeviceViaX509Chain(deviceProfile, createProvisionRequest(chain[0])))
 | 
			
		||||
                .isInstanceOf(ProvisionFailedException.class);
 | 
			
		||||
 | 
			
		||||
        verify(deviceService, times(1)).findDeviceByTenantIdAndName(any(), any());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void matchDeviceNameFromX509CNCertificateByRegex() {
 | 
			
		||||
        var tenant = createTenant();
 | 
			
		||||
        var deviceProfile = createDeviceProfile(tenant.getId(), chain[1], true);
 | 
			
		||||
        X509CertificateChainProvisionConfiguration configuration = (X509CertificateChainProvisionConfiguration) deviceProfile.getProfileData().getProvisionConfiguration();
 | 
			
		||||
        String CN = getCNFromX509Certificate(chain[0]);
 | 
			
		||||
        String deviceName = service.extractDeviceNameFromCNByRegEx(deviceProfile, CN, configuration.getCertificateRegExPattern());
 | 
			
		||||
 | 
			
		||||
        Assertions.assertThat(deviceName).isNotBlank();
 | 
			
		||||
        Assertions.assertThat(deviceName).isEqualTo("deviceCertificate");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void matchDeviceNameFromCNByRegex() {
 | 
			
		||||
        var CN = "DeviceA.company.com";
 | 
			
		||||
        var regex = "(.*)\\.company.com";
 | 
			
		||||
        var result = service.extractDeviceNameFromCNByRegEx(null, CN, regex);
 | 
			
		||||
        Assertions.assertThat(result).isNotBlank();
 | 
			
		||||
        Assertions.assertThat(result).isEqualTo("DeviceA");
 | 
			
		||||
 | 
			
		||||
        CN = "DeviceA@company.com";
 | 
			
		||||
        regex = "(.*)@company.com";
 | 
			
		||||
        result = service.extractDeviceNameFromCNByRegEx(null, CN, regex);
 | 
			
		||||
        Assertions.assertThat(result).isNotBlank();
 | 
			
		||||
        Assertions.assertThat(result).isEqualTo("DeviceA");
 | 
			
		||||
 | 
			
		||||
        CN = "prefixDeviceAsuffix@company.com";
 | 
			
		||||
        regex = "prefix(.*)suffix@company.com";
 | 
			
		||||
        result = service.extractDeviceNameFromCNByRegEx(null, CN, regex);
 | 
			
		||||
        Assertions.assertThat(result).isNotBlank();
 | 
			
		||||
        Assertions.assertThat(result).isEqualTo("DeviceA");
 | 
			
		||||
 | 
			
		||||
        CN = "prefixDeviceAsufix@company.com";
 | 
			
		||||
        regex = "prefix(.*)sufix@company.com";
 | 
			
		||||
        result = service.extractDeviceNameFromCNByRegEx(null, CN, regex);
 | 
			
		||||
        Assertions.assertThat(result).isNotBlank();
 | 
			
		||||
        Assertions.assertThat(result).isEqualTo("DeviceA");
 | 
			
		||||
 | 
			
		||||
        CN = "region.DeviceA.220423@company.com";
 | 
			
		||||
        regex = "\\D+\\.(.*)\\.\\d+@company.com";
 | 
			
		||||
        result = service.extractDeviceNameFromCNByRegEx(null, CN, regex);
 | 
			
		||||
        Assertions.assertThat(result).isNotBlank();
 | 
			
		||||
        Assertions.assertThat(result).isEqualTo("DeviceA");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private DeviceProfile createDeviceProfile(TenantId tenantId, String certificateValue, boolean isAllowToCreateNewDevices) {
 | 
			
		||||
        X509CertificateChainProvisionConfiguration provision = new X509CertificateChainProvisionConfiguration();
 | 
			
		||||
        provision.setProvisionDeviceSecret(certificateValue);
 | 
			
		||||
        provision.setCertificateRegExPattern("([^@]+)");
 | 
			
		||||
        provision.setAllowCreateNewDevicesByX509Certificate(isAllowToCreateNewDevices);
 | 
			
		||||
 | 
			
		||||
        DeviceProfileData deviceProfileData = new DeviceProfileData();
 | 
			
		||||
        deviceProfileData.setProvisionConfiguration(provision);
 | 
			
		||||
 | 
			
		||||
        DeviceProfile deviceProfile = new DeviceProfile();
 | 
			
		||||
        deviceProfile.setId(new DeviceProfileId(UUID.randomUUID()));
 | 
			
		||||
        deviceProfile.setProfileData(deviceProfileData);
 | 
			
		||||
        deviceProfile.setProvisionDeviceKey(EncryptionUtil.getSha3Hash(certificateValue));
 | 
			
		||||
        deviceProfile.setProvisionType(DeviceProfileProvisionType.X509_CERTIFICATE_CHAIN);
 | 
			
		||||
        deviceProfile.setTenantId(tenantId);
 | 
			
		||||
        return deviceProfile;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private Device createDevice(TenantId tenantId, DeviceProfileId deviceProfileId) {
 | 
			
		||||
        Device device = new Device();
 | 
			
		||||
        device.setTenantId(tenantId);
 | 
			
		||||
        device.setId(new DeviceId(UUID.randomUUID()));
 | 
			
		||||
        device.setDeviceProfileId(deviceProfileId);
 | 
			
		||||
        device.setCustomerId(new CustomerId(UUID.randomUUID()));
 | 
			
		||||
        return device;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private Tenant createTenant() {
 | 
			
		||||
        Tenant tenant = new Tenant();
 | 
			
		||||
        tenant.setId(new TenantId(UUID.randomUUID()));
 | 
			
		||||
        return tenant;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private DeviceCredentials createDeviceCredentials(String certificateValue, DeviceId deviceId) {
 | 
			
		||||
        DeviceCredentials deviceCredentials = new DeviceCredentials();
 | 
			
		||||
        deviceCredentials.setDeviceId(deviceId);
 | 
			
		||||
        deviceCredentials.setCredentialsValue(certificateValue);
 | 
			
		||||
        deviceCredentials.setCredentialsId(EncryptionUtil.getSha3Hash(certificateValue));
 | 
			
		||||
        deviceCredentials.setCredentialsType(DeviceCredentialsType.X509_CERTIFICATE);
 | 
			
		||||
        return deviceCredentials;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private ProvisionRequest createProvisionRequest(String certificateValue) {
 | 
			
		||||
        return new ProvisionRequest(null, DeviceCredentialsType.X509_CERTIFICATE,
 | 
			
		||||
                new ProvisionDeviceCredentialsData(null, null, null, null, certificateValue),
 | 
			
		||||
                null);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static String certTrimNewLinesForChainInDeviceProfile(String input) {
 | 
			
		||||
        return input.replaceAll("\n", "")
 | 
			
		||||
                .replaceAll("\r", "")
 | 
			
		||||
                .replaceAll("-----BEGIN CERTIFICATE-----", "-----BEGIN CERTIFICATE-----\n")
 | 
			
		||||
                .replaceAll("-----END CERTIFICATE-----", "\n-----END CERTIFICATE-----\n")
 | 
			
		||||
                .trim();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private String[] fetchLeafCertificateFromChain(String value) {
 | 
			
		||||
        List<String> chain = new ArrayList<>();
 | 
			
		||||
        String regex = "-----BEGIN CERTIFICATE-----\\s*.*?\\s*-----END CERTIFICATE-----";
 | 
			
		||||
        Pattern pattern = Pattern.compile(regex);
 | 
			
		||||
        Matcher matcher = pattern.matcher(value);
 | 
			
		||||
        while (matcher.find()) {
 | 
			
		||||
            chain.add(matcher.group(0));
 | 
			
		||||
        }
 | 
			
		||||
        return chain.toArray(new String[0]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private String getCNFromX509Certificate(String x509Value) {
 | 
			
		||||
        try {
 | 
			
		||||
            return SslUtil.parseCommonName(SslUtil.readCertFile(x509Value));
 | 
			
		||||
        } catch (Exception e) {
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -109,7 +109,8 @@ public class DefaultTransportApiServiceTest {
 | 
			
		||||
 | 
			
		||||
    @Before
 | 
			
		||||
    public void setUp() {
 | 
			
		||||
        String filePath = "src/test/resources/mqtt/x509ChainProvisionTest.pem";
 | 
			
		||||
 | 
			
		||||
        String filePath = "src/test/resources/provision/x509ChainProvisionTest.pem";
 | 
			
		||||
        try {
 | 
			
		||||
            certificateChain = Files.readString(Paths.get(filePath));
 | 
			
		||||
            certificateChain = certTrimNewLinesForChainInDeviceProfile(certificateChain);
 | 
			
		||||
@ -120,7 +121,7 @@ public class DefaultTransportApiServiceTest {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void validateExistingDeviceX509Certificate() {
 | 
			
		||||
    public void validateExistingDeviceByX509CertificateStrategy() {
 | 
			
		||||
        var device = createDevice();
 | 
			
		||||
        when(deviceService.findDeviceByIdAsync(any(), any())).thenReturn(Futures.immediateFuture(device));
 | 
			
		||||
 | 
			
		||||
@ -145,13 +146,13 @@ public class DefaultTransportApiServiceTest {
 | 
			
		||||
        when(deviceCredentialsService.updateDeviceCredentials(any(), any())).thenReturn(deviceCredentials);
 | 
			
		||||
 | 
			
		||||
        var provisionResponse = createProvisionResponse(deviceCredentials);
 | 
			
		||||
        when(deviceProvisionService.provisionDevice(any())).thenReturn(provisionResponse);
 | 
			
		||||
        when(deviceProvisionService.provisionDeviceViaX509Chain(any(), any())).thenReturn(provisionResponse);
 | 
			
		||||
 | 
			
		||||
        service.validateOrCreateDeviceX509Certificate(certificateChain);
 | 
			
		||||
        verify(deviceProfileService, times(1)).findDeviceProfileByProvisionDeviceKey(any());
 | 
			
		||||
        verify(deviceService, times(1)).findDeviceByIdAsync(any(), any());
 | 
			
		||||
        verify(deviceCredentialsService, times(1)).findDeviceCredentialsByCredentialsId(any());
 | 
			
		||||
        verify(deviceProvisionService, times(1)).provisionDevice(any());
 | 
			
		||||
        verify(deviceProvisionService, times(1)).provisionDeviceViaX509Chain(any(), any());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private DeviceProfile createDeviceProfile(String certificateValue) {
 | 
			
		||||
 | 
			
		||||
@ -24,5 +24,5 @@ public interface DeviceProvisionService {
 | 
			
		||||
 | 
			
		||||
    ProvisionResponse provisionDevice(ProvisionRequest provisionRequest) throws ProvisionFailedException;
 | 
			
		||||
 | 
			
		||||
    ProvisionResponse provisionDeviceViaX509Chain(DeviceProfile deviceProfile, ProvisionRequest provisionRequest);
 | 
			
		||||
    ProvisionResponse provisionDeviceViaX509Chain(DeviceProfile deviceProfile, ProvisionRequest provisionRequest) throws ProvisionFailedException;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -408,8 +408,8 @@ public class DeviceProfileDataValidator extends AbstractHasOtaPackageValidator<D
 | 
			
		||||
                    return true;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        } catch (Exception ignored) {
 | 
			
		||||
            log.trace("Failed to validate certificate due to: ", ignored);
 | 
			
		||||
        } catch (Exception e) {
 | 
			
		||||
            log.trace("Failed to validate certificate due to: ", e);
 | 
			
		||||
        }
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -1,15 +1,21 @@
 | 
			
		||||
#### Examples of RegEx usage
 | 
			
		||||
 | 
			
		||||
* **Pattern:** <code>.*</code> - matches any character (until line terminators)
 | 
			
		||||
  <br>**CN sample:** <code>DeviceName\nAdditionalInfo</code>
 | 
			
		||||
  <br>**Pattern matches:** <code>DeviceName</code>
 | 
			
		||||
The regular expression is required to extract device name from the X509 certificate's common name.
 | 
			
		||||
The regular expression syntax is based on Java [Pattern](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/regex/Pattern.html).
 | 
			
		||||
You may also use this [resource](https://regex101.com/) to test your expressions but make sure you select Java 8 flavor.
 | 
			
		||||
 | 
			
		||||
* **Pattern:** <code>^([^@]+)</code> - matches any string that starts with one or more characters that are not the <code>@</code> symbol (<code>@</code> could be replaced by any other symbol)
 | 
			
		||||
  <br>**CN sample:** <code>DeviceName@AdditionalInfo</code>
 | 
			
		||||
  <br>**Pattern matches:** <code>DeviceName</code>
 | 
			
		||||
* **Pattern:**<code>(.*)\.company.com</code>- matches any characters before the ".company.com".
 | 
			
		||||
  <br>**CN sample:**<code>DeviceA.company.com</code>
 | 
			
		||||
  <br>**Result:**<code>DeviceA</code>
 | 
			
		||||
 | 
			
		||||
* **Pattern:** <code>[\w]*$</code> (equivalent to <code>[a-zA-Z0-9_]\*$</code>) - matches zero or more occurences of any word character  (letter, digit or underscore) at the end of the string
 | 
			
		||||
  <br>**CN sample:** <code>AdditionalInfo2110#DeviceName_01</code>
 | 
			
		||||
  <br>**Pattern matches:** <code>DeviceName_01</code>
 | 
			
		||||
* **Pattern:** <code>(.*)@company.com</code>- matches any characters before the "@company.com".
 | 
			
		||||
  <br>**CN sample:**<code>DeviceA@company.com</code>
 | 
			
		||||
  <br>**Result:**<code>DeviceA</code>
 | 
			
		||||
 | 
			
		||||
**Note:** Client will get error response in case regex is failed to match.
 | 
			
		||||
* **Pattern:** <code>prefix(.*)suffix@company.com</code>- matches characters between "prefix" and "suffix@company.com".
 | 
			
		||||
  <br>**CN sample:**<code>prefixDeviceAsuffix@company.com</code>
 | 
			
		||||
  <br>**Pattern matches:** <code>DeviceA</code>
 | 
			
		||||
 | 
			
		||||
* **Pattern:** <code>\\D+\\.(.*)\\.\\d+@company.com</code>- matches characters between not digits prefix followed by period and sequence of digits with "@company.com" ending.
 | 
			
		||||
  <br>**CN sample:**<code>region.DeviceA.220423@company.com</code>
 | 
			
		||||
  <br>**Pattern matches:** <code>DeviceA</code>
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user