From 8463b7c44c3a356ccb75c844395471bde80e9283 Mon Sep 17 00:00:00 2001
From: Sebastian-Ion TINCU <Sebastian-Ion.TINCU@ext.ec.europa.eu>
Date: Tue, 6 Feb 2024 17:10:37 +0100
Subject: [PATCH] EDELIVERY-11317 User should be able to check connection with
 SMP when changing certificate.

Add domain validation integration with SML.
Add exists participant integration with SML.
---
 .../conversion/SmlIdentifierConverter.java    |  38 ++++---
 .../edelivery/smp/exceptions/ErrorCode.java   |   2 +-
 .../edelivery/smp/services/DomainService.java |   4 +-
 .../smp/services/SMLIntegrationService.java   |  31 +++++-
 .../smp/services/ui/UIDomainService.java      |  12 +-
 .../ec/edelivery/smp/sml/SmlConnector.java    | 104 ++++++++++++++----
 6 files changed, 151 insertions(+), 40 deletions(-)

diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/conversion/SmlIdentifierConverter.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/conversion/SmlIdentifierConverter.java
index e65e73ef1..0e60154ab 100644
--- a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/conversion/SmlIdentifierConverter.java
+++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/conversion/SmlIdentifierConverter.java
@@ -19,6 +19,7 @@
 
 package eu.europa.ec.edelivery.smp.conversion;
 
+import ec.services.wsdl.bdmsl.data._1.ParticipantsType;
 import ec.services.wsdl.bdmsl.data._1.SMPAdvancedServiceForParticipantType;
 import eu.europa.ec.edelivery.smp.identifiers.Identifier;
 import org.busdox.servicemetadata.locator._1.ServiceMetadataPublisherServiceForParticipantType;
@@ -31,12 +32,7 @@ import static org.apache.commons.lang3.StringUtils.isBlank;
 public class SmlIdentifierConverter {
 
     public static ServiceMetadataPublisherServiceForParticipantType toBusdoxParticipantId(Identifier participantId, String smpId) {
-        if (isBlank(smpId)) {
-            throw new IllegalStateException("SMP ID is null or empty");
-        }
-        if (participantId == null || isBlank(participantId.getValue())) {
-            throw new IllegalStateException("Participant Scheme or Id is null or empty");
-        }
+        validate(participantId, smpId);
 
         ServiceMetadataPublisherServiceForParticipantType busdoxIdentifier = new ServiceMetadataPublisherServiceForParticipantType();
         busdoxIdentifier.setServiceMetadataPublisherID(smpId);
@@ -47,20 +43,36 @@ public class SmlIdentifierConverter {
         return busdoxIdentifier;
     }
 
+    public static ParticipantsType toParticipantsType(Identifier participantId, String smpId) {
+        validate(participantId, smpId);
+
+        ParticipantsType participantsType = new ParticipantsType();
+        org.busdox.transport.identifiers._1.ParticipantIdentifierType parId = new org.busdox.transport.identifiers._1.ParticipantIdentifierType();
+        parId.setScheme(participantId.getScheme());
+        parId.setValue(participantId.getValue());
+        participantsType.setParticipantIdentifier(parId);
+        participantsType.setServiceMetadataPublisherID(smpId);
+        return participantsType;
+    }
+
 
     public static SMPAdvancedServiceForParticipantType toBDMSLAdvancedParticipantId(Identifier participantId, String smpId, String serviceMetadata) {
-        if (isBlank(smpId)) {
-            throw new IllegalStateException("SMP ID is null or empty");
-        }
-        if (participantId == null || isBlank(participantId.getValue())) {
-            throw new IllegalStateException("Participant Scheme or Id is null or empty");
-        }
+        validate(participantId, smpId);
 
         SMPAdvancedServiceForParticipantType bdmslRequest = new SMPAdvancedServiceForParticipantType();
         bdmslRequest.setServiceName(serviceMetadata);
-
         ServiceMetadataPublisherServiceForParticipantType bdxlRequest = toBusdoxParticipantId(participantId, smpId);
         bdmslRequest.setCreateParticipantIdentifier(bdxlRequest);
+
         return bdmslRequest;
     }
+
+    private static void validate(Identifier participantId, String smpId) {
+        if (isBlank(smpId)) {
+            throw new IllegalStateException("SMP ID is null or empty");
+        }
+        if (participantId == null || isBlank(participantId.getValue())) {
+            throw new IllegalStateException("Participant Scheme or Id is null or empty");
+        }
+    }
 }
diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/exceptions/ErrorCode.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/exceptions/ErrorCode.java
index 776116f9c..6efe2bb61 100644
--- a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/exceptions/ErrorCode.java
+++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/exceptions/ErrorCode.java
@@ -75,7 +75,7 @@ public enum ErrorCode {
     ILLEGAL_STATE_SMD_ON_MULTIPLE_SGD (500,"SMP:144",ErrorBusinessCode.TECHNICAL,"Found than one service group domain for metadata id [%s] and user id [%s]!"),
 
     // SML integration
-    SML_INTEGRATION_EXCEPTION (500,"SMP:150",ErrorBusinessCode.TECHNICAL,"Could not create new DNS entry through SML! Error: %s "),
+    SML_INTEGRATION_EXCEPTION (500,"SMP:150",ErrorBusinessCode.TECHNICAL,"SML integration error! Error: %s "),
     //
     XML_SIGNING_EXCEPTION (500,"SMP:500",ErrorBusinessCode.TECHNICAL,"Error occurred while signing response!"),
     JAXB_INITIALIZATION (500,"SMP:511",ErrorBusinessCode.TECHNICAL, "Could not create Unmarshaller for class [%s]!"),
diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/DomainService.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/DomainService.java
index b36bafe1e..19d2900c9 100644
--- a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/DomainService.java
+++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/DomainService.java
@@ -94,8 +94,8 @@ public class DomainService {
     }
 
     /**
-     * If domain is not yet registered and sml integration is on. Than it tries to register domain and  all participants
-     * on that domain. If integration is off it return an configuration exception.
+     * If domain is not yet registered and SML integration is on, it tries to register a domain and all participants
+     * on that domain. If integration is off, it returns a configuration exception.
      * <p>
      * Method is not in transaction - but sub-methods are. if registering domain or particular serviceGroup succeed
      * then the database flag (SML_REGISTERED) is turned on ( if method fails
diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/SMLIntegrationService.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/SMLIntegrationService.java
index c97bfb50a..a3bf40b54 100644
--- a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/SMLIntegrationService.java
+++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/SMLIntegrationService.java
@@ -65,6 +65,22 @@ public class SMLIntegrationService {
     @Autowired
     private IdentifierService identifierService;
 
+    /**
+     * Checks whether the participant exists in SML or not.
+     *
+     * @param resource the resource entity
+     * @param domain the domain entity
+     * @return {@code true} if the participant exists in SML; otherwise, {@code false} (also when SML integration is disabled).
+     */
+    public boolean participantExists(DBResource resource, DBDomain domain) {
+        if (!isSMLIntegrationEnabled()) {
+            throw new SMPRuntimeException(CONFIGURATION_ERROR, ERROR_MESSAGE_DNS_NOT_ENABLED);
+        }
+        Identifier normalizedParticipantId = identifierService
+                .normalizeParticipant(resource.getIdentifierScheme(), resource.getIdentifierValue());
+
+        return smlConnector.participantExists(normalizedParticipantId, domain);
+    }
 
     /**
      * Method in transaction update domain status and registers domain to SML.
@@ -82,6 +98,19 @@ public class SMLIntegrationService {
         smlConnector.registerDomain(domain);
     }
 
+    /**
+     * Checks whether the domain is valid by trying to read it from SML.
+     *
+     * @param domain the domain entity to verify whether it's valid or not.
+     *
+     * @return {@code true} if the domain can be successfully read from SML; otherwise, {@code false} (also when SML integration is disabled).
+     */
+    public boolean isDomainValid(DBDomain domain) {
+        if (!isSMLIntegrationEnabled()) {
+            throw new SMPRuntimeException(CONFIGURATION_ERROR, ERROR_MESSAGE_DNS_NOT_ENABLED);
+        }
+        return smlConnector.isDomainValid(domain);
+    }
 
     /**
      * Method in transaction update domain status and registers domain to SML.
@@ -171,7 +200,7 @@ public class SMLIntegrationService {
             return;
         }
 
-        // unregister only  registered participants
+        // unregister only registered participants
         if (resource.isSmlRegistered()) {
             // update value
             resource.setSmlRegistered(false);
diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/ui/UIDomainService.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/ui/UIDomainService.java
index 699aa2afb..f2394bf4e 100644
--- a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/ui/UIDomainService.java
+++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/ui/UIDomainService.java
@@ -35,6 +35,7 @@ import eu.europa.ec.edelivery.smp.exceptions.ErrorCode;
 import eu.europa.ec.edelivery.smp.exceptions.SMPRuntimeException;
 import eu.europa.ec.edelivery.smp.logging.SMPLogger;
 import eu.europa.ec.edelivery.smp.logging.SMPLoggerFactory;
+import eu.europa.ec.edelivery.smp.services.SMLIntegrationService;
 import org.apache.commons.lang3.StringUtils;
 import org.springframework.core.convert.ConversionService;
 import org.springframework.stereotype.Service;
@@ -64,7 +65,7 @@ public class UIDomainService extends UIServiceBase<DBDomain, DomainRO> {
     private final ConversionService conversionService;
     private final GroupDao groupDao;
     private final GroupMemberDao groupMemberDao;
-
+    private final SMLIntegrationService smlIntegrationService;
 
     public UIDomainService(ConversionService conversionService,
                            DomainDao domainDao,
@@ -73,7 +74,7 @@ public class UIDomainService extends UIServiceBase<DBDomain, DomainRO> {
                            ResourceDefDao resourceDefDao,
                            DomainResourceDefDao domainResourceDefDao,
                            GroupDao groupDao,
-                           GroupMemberDao groupMemberDao) {
+                           GroupMemberDao groupMemberDao, SMLIntegrationService smlIntegrationService) {
         this.conversionService = conversionService;
         this.domainDao = domainDao;
         this.resourceDao = resourceDao;
@@ -82,6 +83,7 @@ public class UIDomainService extends UIServiceBase<DBDomain, DomainRO> {
         this.domainMemberDao = domainMemberDao;
         this.groupDao = groupDao;
         this.groupMemberDao = groupMemberDao;
+        this.smlIntegrationService = smlIntegrationService;
     }
 
     @Override
@@ -175,6 +177,12 @@ public class UIDomainService extends UIServiceBase<DBDomain, DomainRO> {
         domain.setSmlSmpId(StringUtils.trim(data.getSmlSmpId()));
         domain.setSmlClientKeyAlias(data.getSmlClientKeyAlias());
         domain.setSmlClientCertAuth(data.isSmlClientCertAuth());
+
+        // if registered, validate the updated domain to ensure its SML integration certificate is valid
+        if(domain.isSmlRegistered() && !smlIntegrationService.isDomainValid(domain)) {
+            String msg = "The SML-SMP certificate for domain [" + domain.getDomainCode() + "] is not valid!";
+            throw new BadRequestException(ErrorBusinessCode.NOT_FOUND, msg);
+        }
     }
 
     @Transactional
diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/sml/SmlConnector.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/sml/SmlConnector.java
index 269fdc29c..2fa54ea76 100644
--- a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/sml/SmlConnector.java
+++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/sml/SmlConnector.java
@@ -19,6 +19,8 @@
 
 package eu.europa.ec.edelivery.smp.sml;
 
+import ec.services.wsdl.bdmsl.data._1.ExistsParticipantResponseType;
+import ec.services.wsdl.bdmsl.data._1.ParticipantsType;
 import ec.services.wsdl.bdmsl.data._1.SMPAdvancedServiceForParticipantType;
 import eu.europa.ec.bdmsl.ws.soap.*;
 import eu.europa.ec.edelivery.smp.config.enums.SMPPropertyEnum;
@@ -61,8 +63,7 @@ import java.net.MalformedURLException;
 import java.net.URL;
 import java.util.*;
 
-import static eu.europa.ec.edelivery.smp.conversion.SmlIdentifierConverter.toBDMSLAdvancedParticipantId;
-import static eu.europa.ec.edelivery.smp.conversion.SmlIdentifierConverter.toBusdoxParticipantId;
+import static eu.europa.ec.edelivery.smp.conversion.SmlIdentifierConverter.*;
 import static eu.europa.ec.edelivery.smp.exceptions.SMLErrorMessages.*;
 
 /**
@@ -97,16 +98,13 @@ public class SmlConnector implements ApplicationContextAware {
 
     private ApplicationContext ctx;
 
-
     public boolean registerInDns(Identifier normalizedParticipantId, DBDomain domain, String customNaptrService) {
-
-
         if (!configurationService.isSMLIntegrationEnabled()) {
             return false;
         }
         String normalizedParticipantString = identifierService.formatParticipant(normalizedParticipantId);
         if (!domain.isSmlRegistered()) {
-            LOG.info("Participant {} is not registered to SML because domain {} is not registered!",
+            LOG.warn("Participant {} is not registered to SML because domain {} is not registered!",
                     normalizedParticipantString, domain.getDomainCode());
             return false;
         }
@@ -131,11 +129,47 @@ public class SmlConnector implements ApplicationContextAware {
         }
     }
 
+    /**
+     * Checks whether the participant identified by the provided ID exists or not. In case the integration with SML is
+     * disabled, it returns {@code false}.
+     *
+     * @param normalizedParticipantId the participant ID
+     * @param domain the domain entity
+     * @return {@code true} if the participant exists; otherwise, {@code false} (also when SML integration is disabled).
+     */
+    public boolean participantExists(Identifier normalizedParticipantId, DBDomain domain) {
+        if (!configurationService.isSMLIntegrationEnabled()) {
+            return false;
+        }
+
+        String normalizedParticipantString = identifierService.formatParticipant(normalizedParticipantId);
+        if (!domain.isSmlRegistered()) {
+            LOG.warn("Cannot check if Participant {} exists when domain {} is not registered!",
+                    normalizedParticipantString, domain.getDomainCode());
+            return false;
+        }
+
+        LOG.debug("Checking if Participant: {} exists in domain: {}.", normalizedParticipantString, domain.getDomainCode());
+        try {
+            ParticipantsType smlRequest = toParticipantsType(normalizedParticipantId, domain.getSmlSmpId());
+            ExistsParticipantResponseType existsParticipantResponseType = getBDMSLWSClient(domain).existsParticipantIdentifier(smlRequest);
+            return existsParticipantResponseType.isExist();
+        } catch (BadRequestFault e) {
+            return processSMLErrorMessage(e, normalizedParticipantId);
+        } catch (NotFoundFault e) {
+            return processSMLErrorMessage(e, normalizedParticipantId);
+        } catch (Exception e) {
+            LOG.error(e.getClass().getName() + e.getMessage(), e);
+            throw new SMPRuntimeException(ErrorCode.SML_INTEGRATION_EXCEPTION, e, ExceptionUtils.getRootCauseMessage(e));
+        }
+    }
+
     protected void createRegularDNSRecord(Identifier normalizedParticipantId, DBDomain domain) throws UnauthorizedFault, BadRequestFault, NotFoundFault, InternalErrorFault {
         LOG.debug("Set regular DNS record for Participant: [{}] and domain: [{}].", normalizedParticipantId, domain.getDomainCode());
         ServiceMetadataPublisherServiceForParticipantType smlRequest = toBusdoxParticipantId(normalizedParticipantId, domain.getSmlSmpId());
         getParticipantWSClient(domain).create(smlRequest);
     }
+
     protected void createCustomServiceNaptrDNSRecord(Identifier normalizedParticipantId, DBDomain domain, String customNaptrService) throws UnauthorizedFault, BadRequestFault, NotFoundFault, InternalErrorFault {
         LOG.debug("Set custom naptr service [{}] DNS record for Participant: [{}] and domain: [{}].", customNaptrService, normalizedParticipantId, domain.getDomainCode());
         SMPAdvancedServiceForParticipantType smlRequest = toBDMSLAdvancedParticipantId(normalizedParticipantId, domain.getSmlSmpId(), customNaptrService);
@@ -182,19 +216,13 @@ public class SmlConnector implements ApplicationContextAware {
      * @return
      */
     public boolean registerDomain(DBDomain domain) {
-
         if (!configurationService.isSMLIntegrationEnabled()) {
             return false;
         }
-        String smpLogicalAddress = configurationService.getSMLIntegrationSMPLogicalAddress();
-        String smpPhysicalAddress = configurationService.getSMLIntegrationSMPPhysicalAddress();
-        LOG.info("Registering new Domain  to SML: (smpCode {} smp-smp-id {}) ", domain.getDomainCode(), domain.getSmlSmpId());
+        String smlSmpId = domain.getSmlSmpId();
+        LOG.info("Registering new Domain to SML: (smpCode {} smp-smp-id {}) ", domain.getDomainCode(), smlSmpId);
         try {
-            ServiceMetadataPublisherServiceType smlSmpRequest = new ServiceMetadataPublisherServiceType();
-            smlSmpRequest.setPublisherEndpoint(new PublisherEndpointType());
-            smlSmpRequest.getPublisherEndpoint().setLogicalAddress(smpLogicalAddress);
-            smlSmpRequest.getPublisherEndpoint().setPhysicalAddress(smpPhysicalAddress);
-            smlSmpRequest.setServiceMetadataPublisherID(domain.getSmlSmpId());
+            ServiceMetadataPublisherServiceType smlSmpRequest = getServiceMetadataPublisherServiceType(smlSmpId);
             getSMPManagerWSClient(domain).create(smlSmpRequest);
         } catch (BadRequestFault e) {
             processSMLErrorMessage(e, domain);
@@ -206,6 +234,46 @@ public class SmlConnector implements ApplicationContextAware {
         return true;
     }
 
+    /**
+     * Checks whether a domain is valid or not. In case the integration with SML is disabled, it returns {@code false}.
+     *
+     * @param domain the domain entity
+     * @return {@code true} if the domain exists and is valid; otherwise, {@code false} (also when SML integration is disabled).
+     */
+    public boolean isDomainValid(DBDomain domain) {
+        if (!configurationService.isSMLIntegrationEnabled()) {
+            return false;
+        }
+        String smlSmpId = domain.getSmlSmpId();
+        LOG.info("Validating Domain to SML: (smpCode {} smp-smp-id {}) ", domain.getDomainCode(), smlSmpId);
+        try {
+            ServiceMetadataPublisherServiceType smlSmpRequest = getServiceMetadataPublisherServiceType(smlSmpId);
+            getSMPManagerWSClient(domain).read(smlSmpRequest);
+        } catch (BadRequestFault e) {
+            processSMLErrorMessage(e, domain);
+        } catch (NotFoundFault e) {
+            processSMLErrorMessage(e, domain);
+        } catch (Exception e) {
+            LOG.error(e.getClass().getName() + e.getMessage(), e);
+            throw new SMPRuntimeException(ErrorCode.SML_INTEGRATION_EXCEPTION, e, ExceptionUtils.getRootCauseMessage(e));
+        }
+        // if not error is thrown - the domain exists and is valid
+        return true;
+    }
+
+    private ServiceMetadataPublisherServiceType getServiceMetadataPublisherServiceType(String smlSmpId) {
+        String smpLogicalAddress = configurationService.getSMLIntegrationSMPLogicalAddress();
+        String smpPhysicalAddress = configurationService.getSMLIntegrationSMPPhysicalAddress();
+
+        ServiceMetadataPublisherServiceType smlSmpRequest = new ServiceMetadataPublisherServiceType();
+        smlSmpRequest.setPublisherEndpoint(new PublisherEndpointType());
+        smlSmpRequest.getPublisherEndpoint().setLogicalAddress(smpLogicalAddress);
+        smlSmpRequest.getPublisherEndpoint().setPhysicalAddress(smpPhysicalAddress);
+        smlSmpRequest.setServiceMetadataPublisherID(smlSmpId);
+
+        return smlSmpRequest;
+    }
+
     private void processSMLErrorMessage(BadRequestFault e, DBDomain domain) {
         if (!isOkMessage(domain, e.getMessage())) {
             LOG.error(e.getMessage(), e);
@@ -303,13 +371,10 @@ public class SmlConnector implements ApplicationContextAware {
     }
 
     private IManageServiceMetadataWS getSMPManagerWSClient(DBDomain domain) {
-
-
         IManageServiceMetadataWS iManageServiceMetadataWS = ctx.getBean(IManageServiceMetadataWS.class);
         // configure value connection
         configureClient(SERVICE_METADATA_CONTEXT, iManageServiceMetadataWS, domain);
 
-
         return iManageServiceMetadataWS;
     }
 
@@ -330,9 +395,7 @@ public class SmlConnector implements ApplicationContextAware {
         return alias;
     }
 
-
     public void configureClient(String serviceEndpoint, Object smlPort, DBDomain domain) {
-
         String clientKeyAlias = getSmlClientKeyAliasForDomain(domain);
         boolean clientCertAuthentication = domain.isSmlClientCertAuth();
         Client client = ClientProxy.getClient(smlPort);
@@ -383,7 +446,6 @@ public class SmlConnector implements ApplicationContextAware {
 
     }
 
-
     public void configureClientAuthentication(HTTPConduit httpConduit, Map<String, Object> requestContext, CertificateRO certificateRO, boolean clientCertAuthentication, boolean useTLS) {
         LOG.info("Connect to SML (smlClientAuthentication: [{}] use Client-CertHeader: [{}])", certificateRO, clientCertAuthentication);
 
-- 
GitLab