com.helger.smpclient.bdxr2.BDXR2ClientReadOnly Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of peppol-smp-client Show documentation
Show all versions of peppol-smp-client Show documentation
Peppol and OASIS BDXR SMP client
/*
* Copyright (C) 2015-2024 Philip Helger
* philip[at]helger[dot]com
*
* 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 com.helger.smpclient.bdxr2;
import java.net.URI;
import java.security.KeyStore;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import org.apache.hc.client5.http.classic.methods.HttpGet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.helger.commons.ValueEnforcer;
import com.helger.commons.annotation.Nonempty;
import com.helger.commons.collection.impl.CommonsArrayList;
import com.helger.commons.collection.impl.ICommonsList;
import com.helger.peppol.sml.ISMLInfo;
import com.helger.peppol.smp.ISMPTransportProfile;
import com.helger.peppolid.CIdentifier;
import com.helger.peppolid.IDocumentTypeIdentifier;
import com.helger.peppolid.IParticipantIdentifier;
import com.helger.peppolid.IProcessIdentifier;
import com.helger.peppolid.factory.IIdentifierFactory;
import com.helger.peppolid.simple.doctype.SimpleDocumentTypeIdentifier;
import com.helger.peppolid.simple.process.SimpleProcessIdentifier;
import com.helger.security.certificate.CertificateHelper;
import com.helger.smpclient.bdxr2.marshal.BDXR2MarshallerServiceGroup;
import com.helger.smpclient.bdxr2.marshal.BDXR2MarshallerServiceMetadata;
import com.helger.smpclient.exception.SMPClientBadRequestException;
import com.helger.smpclient.exception.SMPClientException;
import com.helger.smpclient.exception.SMPClientNotFoundException;
import com.helger.smpclient.exception.SMPClientUnauthorizedException;
import com.helger.smpclient.httpclient.AbstractGenericSMPClient;
import com.helger.smpclient.httpclient.SMPHttpResponseHandlerSigned;
import com.helger.smpclient.httpclient.SMPHttpResponseHandlerUnsigned;
import com.helger.smpclient.url.ISMPURLProvider;
import com.helger.smpclient.url.SMPDNSResolutionException;
import com.helger.xsds.bdxr.smp2.ServiceGroupType;
import com.helger.xsds.bdxr.smp2.ServiceMetadataType;
import com.helger.xsds.bdxr.smp2.ac.CertificateType;
import com.helger.xsds.bdxr.smp2.ac.EndpointType;
import com.helger.xsds.bdxr.smp2.ac.ProcessMetadataType;
import com.helger.xsds.bdxr.smp2.ac.ProcessType;
import com.helger.xsds.bdxr.smp2.ac.RedirectType;
import com.helger.xsds.bdxr.smp2.ac.ServiceReferenceType;
import com.helger.xsds.bdxr.smp2.bc.IDType;
import com.helger.xsds.xmldsig.X509DataType;
import jakarta.xml.bind.JAXBElement;
/**
* This class is used for calling the OASIS BDXR SMP v2 REST interface. This
* class only contains the read-only methods defined in the SMP specification
* and nothing else.
*
* Note: this class is also licensed under Apache 2 license, as it was not part
* of the original implementation
*
*
* @author Philip Helger
*/
public class BDXR2ClientReadOnly extends AbstractGenericSMPClient implements
IBDXR2ServiceGroupProvider,
IBDXR2ServiceMetadataProvider
{
public static final String PATH_OASIS_BDXR_SMP_2 = "bdxr-smp-2/";
public static final String URL_PART_SERVICES = "services";
private static final Logger LOGGER = LoggerFactory.getLogger (BDXR2ClientReadOnly.class);
/**
* Constructor with SML lookup
*
* @param aURLProvider
* The URL provider to be used. May not be null
.
* @param aParticipantIdentifier
* The participant identifier to be used. Required to build the SMP
* access URI.
* @param aSMLInfo
* The SML to be used. Required to build the SMP access URI.
* @throws SMPDNSResolutionException
* If DNS resolution failed
* @see ISMPURLProvider#getSMPURIOfParticipant(IParticipantIdentifier,
* ISMLInfo)
*/
public BDXR2ClientReadOnly (@Nonnull final ISMPURLProvider aURLProvider,
@Nonnull final IParticipantIdentifier aParticipantIdentifier,
@Nonnull final ISMLInfo aSMLInfo) throws SMPDNSResolutionException
{
this (aURLProvider.getSMPURIOfParticipant (aParticipantIdentifier, aSMLInfo));
}
/**
* Constructor with SML lookup
*
* @param aURLProvider
* The URL provider to be used. May not be null
.
* @param aParticipantIdentifier
* The participant identifier to be used. Required to build the SMP
* access URI.
* @param sSMLZoneName
* The SML DNS zone name to be used. Required to build the SMP access
* URI. Must end with a trailing dot (".") and may neither be
* null
nor empty to build a correct URL. May not start
* with "http://". Example: sml.peppolcentral.org.
* @throws SMPDNSResolutionException
* if DNS resolution failed
* @see ISMPURLProvider#getSMPURIOfParticipant(IParticipantIdentifier, String)
*/
public BDXR2ClientReadOnly (@Nonnull final ISMPURLProvider aURLProvider,
@Nonnull final IParticipantIdentifier aParticipantIdentifier,
@Nonnull @Nonempty final String sSMLZoneName) throws SMPDNSResolutionException
{
this (aURLProvider.getSMPURIOfParticipant (aParticipantIdentifier, sSMLZoneName));
}
/**
* Constructor with a direct SMP URL.
* Remember: must be HTTP and using port 80 only!
*
* @param aSMPHost
* The address of the SMP service. Must be port 80 and basic http only
* (no https!). Example: http://smpcompany.company.org
*/
public BDXR2ClientReadOnly (@Nonnull final URI aSMPHost)
{
super (aSMPHost, false);
}
/**
* Returns a service group. A service group references to the service
* metadata. This is a specification compliant method.
*
* @param aServiceGroupID
* The service group id corresponding to the service group which one
* wants to get.
* @return The service group. Never null
.
* @throws SMPClientException
* in case something goes wrong
* @throws SMPClientUnauthorizedException
* A HTTP Forbidden was received, should not happen.
* @throws SMPClientNotFoundException
* The service group id did not exist.
* @throws SMPClientBadRequestException
* The request was not well formed.
* @see #getServiceGroupOrNull(IParticipantIdentifier)
*/
@Nonnull
public ServiceGroupType getServiceGroup (@Nonnull final IParticipantIdentifier aServiceGroupID) throws SMPClientException
{
ValueEnforcer.notNull (aServiceGroupID, "ServiceGroupID");
final String sURI = getSMPHostURI () + PATH_OASIS_BDXR_SMP_2 + aServiceGroupID.getURIPercentEncoded ();
if (LOGGER.isDebugEnabled ())
LOGGER.debug ("BDXR2Client getServiceGroup@" + sURI);
final HttpGet aRequest = new HttpGet (sURI);
final BDXR2MarshallerServiceGroup aMarshaller = new BDXR2MarshallerServiceGroup ();
aMarshaller.setUseSchema (isXMLSchemaValidation ());
customizeMarshaller (aMarshaller);
final ServiceGroupType ret = executeGenericRequest (aRequest, new SMPHttpResponseHandlerUnsigned <> (aMarshaller));
if (LOGGER.isDebugEnabled ())
LOGGER.debug ("Received response: " + ret);
return ret;
}
@Nullable
public ServiceGroupType getServiceGroupOrNull (@Nonnull final IParticipantIdentifier aServiceGroupID) throws SMPClientException
{
try
{
return getServiceGroup (aServiceGroupID);
}
catch (final SMPClientNotFoundException ex)
{
if (LOGGER.isDebugEnabled ())
LOGGER.debug ("Found no ServiceGroup");
return null;
}
}
/**
* Extract all document types from the passed Service group.
*
* @param aSG
* The service group to parse. May be null
.
* @param aIdentifierFactory
* The identifier factory to be used. May not be null
.
* @return Never null
but a maybe empty list.
* @since 8.0.4
*/
@Nonnull
public static ICommonsList getAllDocumentTypes (@Nullable final ServiceGroupType aSG,
@Nonnull final IIdentifierFactory aIdentifierFactory)
{
ValueEnforcer.notNull (aIdentifierFactory, "IdentifierFactory");
final ICommonsList ret = new CommonsArrayList <> ();
if (aSG != null)
for (final ServiceReferenceType aSMR : aSG.getServiceReference ())
{
final IDType aID = aSMR.getID ();
if (aID != null)
{
final IDocumentTypeIdentifier aDocType = aIdentifierFactory.createDocumentTypeIdentifier (aID.getSchemeID (),
aID.getValue ());
if (aDocType != null)
{
// Found a document type
ret.add (aDocType);
}
}
}
return ret;
}
/**
* Gets a signed service metadata object given by its service group id and its
* document type. This is a specification compliant method.
*
* @param aServiceGroupID
* The service group id of the service metadata to get. May not be
* null
.
* @param aDocumentTypeID
* The document type of the service metadata to get. May not be
* null
.
* @return A signed service metadata object. Never null
.
* @throws SMPClientException
* in case something goes wrong
* @throws SMPClientUnauthorizedException
* A HTTP Forbidden was received, should not happen.
* @throws SMPClientNotFoundException
* The service group id or document type did not exist.
* @throws SMPClientBadRequestException
* The request was not well formed.
* @see #getServiceMetadataOrNull(IParticipantIdentifier,
* IDocumentTypeIdentifier)
*/
@Nonnull
public ServiceMetadataType getServiceMetadata (@Nonnull final IParticipantIdentifier aServiceGroupID,
@Nonnull final IDocumentTypeIdentifier aDocumentTypeID) throws SMPClientException
{
ValueEnforcer.notNull (aServiceGroupID, "ServiceGroupID");
ValueEnforcer.notNull (aDocumentTypeID, "DocumentTypeID");
final String sURI = getSMPHostURI () +
PATH_OASIS_BDXR_SMP_2 +
aServiceGroupID.getURIPercentEncoded () +
"/" +
URL_PART_SERVICES +
"/" +
aDocumentTypeID.getURIPercentEncoded ();
if (LOGGER.isDebugEnabled ())
LOGGER.debug ("BDXR2Client getServiceRegistration@" + sURI);
final boolean bXSDValidation = isXMLSchemaValidation ();
final boolean bVerifySignature = isVerifySignature ();
final boolean bSecureValidation = isSecureValidation ();
final KeyStore aTrustStore = getTrustStore ();
if (bVerifySignature && aTrustStore == null)
LOGGER.error ("BDXR2 SMP client Verify Signature is enabled, but no TrustStore is provided. This will not work.");
ServiceMetadataType aMetadata;
{
final HttpGet aRequest = new HttpGet (sURI);
final BDXR2MarshallerServiceMetadata aMarshaller = new BDXR2MarshallerServiceMetadata ();
aMarshaller.setUseSchema (bXSDValidation);
customizeMarshaller (aMarshaller);
// Deal with signed responses
final SMPHttpResponseHandlerSigned aResponseHandler = new SMPHttpResponseHandlerSigned <> (aMarshaller,
aTrustStore);
aResponseHandler.setVerifySignature (bVerifySignature);
aResponseHandler.setSecureValidation (bSecureValidation);
// Main execution
aMetadata = executeGenericRequest (aRequest, aResponseHandler);
if (LOGGER.isDebugEnabled ())
LOGGER.debug ("Received response: " + aMetadata);
}
if (!SimpleDocumentTypeIdentifier.wrap (aMetadata.getID ()).hasSameContent (aDocumentTypeID))
{
// Inconsistency between request and response
throw new SMPClientException ("Requested document type '" +
aDocumentTypeID.getURIEncoded () +
"' and received '" +
CIdentifier.getURIEncoded (aMetadata.getID ()) +
"' - mismatch. Ignoring request.");
}
// If the Redirect element is present, then follow 1 redirect.
if (isFollowSMPRedirects ())
{
for (final ProcessMetadataType aPM : aMetadata.getProcessMetadata ())
{
final RedirectType aRedirect = aPM.getRedirect ();
if (aRedirect != null)
{
// Follow the redirect
LOGGER.info ("Following a redirect from '" + sURI + "' to '" + aRedirect.getPublisherURIValue () + "'");
final HttpGet aRequest = new HttpGet (aRedirect.getPublisherURIValue ());
// Create a new Marshaller to make sure customization is easy
final BDXR2MarshallerServiceMetadata aMarshaller = new BDXR2MarshallerServiceMetadata ();
aMarshaller.setUseSchema (bXSDValidation);
customizeMarshaller (aMarshaller);
// Deal with signed responses
final SMPHttpResponseHandlerSigned aResponseHandler = new SMPHttpResponseHandlerSigned <> (aMarshaller,
aTrustStore);
aResponseHandler.setVerifySignature (bVerifySignature);
aResponseHandler.setSecureValidation (bSecureValidation);
// Main execution
aMetadata = executeGenericRequest (aRequest, aResponseHandler);
// Check that the certificateUID is correct.
boolean bCertificateSubjectFound = false;
if (aMetadata.hasSignatureEntries ())
outer: for (final Object aObj : aMetadata.getSignatureAtIndex (0).getKeyInfo ().getContent ())
if (aObj instanceof JAXBElement >)
{
final Object aInfoValue = ((JAXBElement >) aObj).getValue ();
if (aInfoValue instanceof X509DataType)
{
final X509DataType aX509Data = (X509DataType) aInfoValue;
for (final Object aX509Obj : aX509Data.getX509IssuerSerialOrX509SKIOrX509SubjectName ())
if (aX509Obj instanceof JAXBElement >)
{
final JAXBElement > aX509element = (JAXBElement >) aX509Obj;
// Find the first subject (of type string)
if (aX509element.getValue () instanceof X509Certificate)
{
final X509Certificate aSecondCert = (X509Certificate) aX509element.getValue ();
// Check all certs of the source redirect
boolean bFound = false;
final ICommonsList aAllRedirectCerts = new CommonsArrayList <> ();
for (final CertificateType aCT : aRedirect.getCertificate ())
{
try
{
final X509Certificate aRedirectCert = CertificateHelper.convertByteArrayToCertficate (aCT.getContentBinaryObjectValue ());
if (aRedirectCert != null)
{
aAllRedirectCerts.add (aRedirectCert);
// Certificate match?
if (aRedirectCert.equals (aSecondCert))
{
bFound = true;
break;
}
}
}
catch (final CertificateException ex)
{
// Error in certificate in SMP response
LOGGER.error ("SMP Redirect contains an invalid certificate", ex);
}
}
if (!bFound)
throw new SMPClientException ("No certificate of the redirect matched the provided certificate. Retrieved certificate is '" +
aSecondCert +
"'. Allowed certificates according to the redirect are: " +
aAllRedirectCerts);
bCertificateSubjectFound = true;
break outer;
}
}
}
}
if (!bCertificateSubjectFound)
throw new SMPClientException ("The X509 certificate did not contain a certificate subject.");
}
}
}
else
{
if (LOGGER.isDebugEnabled ())
LOGGER.debug ("Following SMP redirects is disabled");
}
return aMetadata;
}
@Nullable
public ServiceMetadataType getServiceMetadataOrNull (@Nonnull final IParticipantIdentifier aServiceGroupID,
@Nonnull final IDocumentTypeIdentifier aDocumentTypeID) throws SMPClientException
{
try
{
return getServiceMetadata (aServiceGroupID, aDocumentTypeID);
}
catch (final SMPClientNotFoundException ex)
{
if (LOGGER.isDebugEnabled ())
LOGGER.debug ("Found no ServiceMetadata");
return null;
}
}
/**
* Extract the Endpoint from the ServiceMetadata that matches the passed
* process ID and the optional required transport profile.
*
* @param aServiceMetadata
* The service meta data object (e.g. from a call to
* {@link #getServiceMetadataOrNull(IParticipantIdentifier, IDocumentTypeIdentifier)}
* . May not be null
.
* @param aProcessID
* The process identifier to be looked up. May not be null
* .
* @param aTransportProfile
* The required transport profile to be used. May not be
* null
.
* @return null
if no matching endpoint was found
*/
@Nullable
public static EndpointType getEndpoint (@Nonnull final ServiceMetadataType aServiceMetadata,
@Nonnull final IProcessIdentifier aProcessID,
@Nonnull final ISMPTransportProfile aTransportProfile)
{
ValueEnforcer.notNull (aServiceMetadata, "SignedServiceMetadata");
ValueEnforcer.notNull (aProcessID, "ProcessID");
ValueEnforcer.notNull (aTransportProfile, "TransportProfile");
// Iterate all processes
for (final ProcessMetadataType aPM : aServiceMetadata.getProcessMetadata ())
{
boolean bMatchesProcess = false;
for (final ProcessType aP : aPM.getProcess ())
if (SimpleProcessIdentifier.wrap (aP.getID ()).hasSameContent (aProcessID))
{
bMatchesProcess = true;
break;
}
if (bMatchesProcess)
{
final ICommonsList aRelevantEndpoints = new CommonsArrayList <> ();
for (final EndpointType aEndpoint : aPM.getEndpoint ())
if (aTransportProfile.getID ().equals (aEndpoint.getTransportProfileIDValue ()))
aRelevantEndpoints.add (aEndpoint);
if (aRelevantEndpoints.size () != 1)
{
LOGGER.warn ("Found " +
aRelevantEndpoints.size () +
" endpoints for process '" +
aProcessID.getURIEncoded () +
"' and transport profile '" +
aTransportProfile.getID () +
"'" +
(aRelevantEndpoints.isEmpty () ? ""
: ": " +
aRelevantEndpoints.toString () +
" - using the first one"));
}
// Use the first endpoint or null
final EndpointType ret = aRelevantEndpoints.getFirstOrNull ();
if (LOGGER.isDebugEnabled ())
LOGGER.debug ("Found matching endpoint: " + ret);
return ret;
}
}
if (LOGGER.isDebugEnabled ())
LOGGER.debug ("Found no matching SMP endpoint");
return null;
}
/**
* Get the endpoint address URI from the provided SMP endpoint.
*
* @param aEndpoint
* The endpoint to be used. May be null
.
* @return null
if the endpoint is null
if the
* endpoint has no address URI.
*/
@Nullable
public static String getEndpointAddress (@Nullable final EndpointType aEndpoint)
{
return aEndpoint == null ? null : aEndpoint.getAddressURIValue ();
}
/**
* Get the certificate bytes from the provided SMP endpoint.
* Note: if the endpoint has more than one certificate, the first one is
* returned.
*
* @param aEndpoint
* The endpoint to be used. May be null
.
* @return null
if the endpoint is null
if the
* endpoint has no certificate.
*/
@Nullable
public static byte [] getEndpointCertificateBytes (@Nullable final EndpointType aEndpoint)
{
if (aEndpoint == null)
return null;
if (aEndpoint.getCertificateCount () == 0)
return null;
return aEndpoint.getCertificateAtIndex (0).getContentBinaryObjectValue ();
}
/**
* Get the certificate bytes from the specified endpoint.
*
* @param aEndpoint
* The endpoint to be used. May be null
.
* @return null
if no such endpoint exists, or if the endpoint
* has no certificate
* @throws CertificateException
* In case the conversion from byte to X509 certificate failed
*/
@Nullable
public static X509Certificate getEndpointCertificate (@Nullable final EndpointType aEndpoint) throws CertificateException
{
final byte [] aCertBytes = getEndpointCertificateBytes (aEndpoint);
return CertificateHelper.convertByteArrayToCertficateDirect (aCertBytes);
}
/**
* Returns a service group. A service group references to the service
* metadata.
*
* @param aURLProvider
* The URL provider to be used. May not be null
.
* @param aSMLInfo
* The SML object to be used
* @param aServiceGroupID
* The service group id corresponding to the service group which one
* wants to get.
* @return The service group
* @throws SMPClientException
* in case something goes wrong
* @throws SMPClientUnauthorizedException
* A HTTP Forbidden was received, should not happen.
* @throws SMPClientNotFoundException
* The service group id did not exist.
* @throws SMPClientBadRequestException
* The request was not well formed.
* @throws SMPDNSResolutionException
* if DNS resolution fails
*/
@Nonnull
public static ServiceGroupType getServiceGroupByDNS (@Nonnull final ISMPURLProvider aURLProvider,
@Nonnull final ISMLInfo aSMLInfo,
@Nonnull final IParticipantIdentifier aServiceGroupID) throws SMPClientException,
SMPDNSResolutionException
{
return new BDXR2ClientReadOnly (aURLProvider, aServiceGroupID, aSMLInfo).getServiceGroup (aServiceGroupID);
}
/**
* Gets a signed service metadata object given by its service group id and its
* document type.
*
* @param aURLProvider
* The URL provider to be used. May not be null
.
* @param aSMLInfo
* The SML object to be used
* @param aServiceGroupID
* The service group id of the service metadata to get.
* @param aDocumentTypeID
* The document type of the service metadata to get.
* @return A signed service metadata object.
* @throws SMPClientException
* in case something goes wrong
* @throws SMPClientUnauthorizedException
* A HTTP Forbidden was received, should not happen.
* @throws SMPClientNotFoundException
* The service group id or document type did not exist.
* @throws SMPClientBadRequestException
* The request was not well formed.
* @throws SMPDNSResolutionException
* if DNS resolution fails
*/
@Nonnull
public static ServiceMetadataType getServiceRegistrationByDNS (@Nonnull final ISMPURLProvider aURLProvider,
@Nonnull final ISMLInfo aSMLInfo,
@Nonnull final IParticipantIdentifier aServiceGroupID,
@Nonnull final IDocumentTypeIdentifier aDocumentTypeID) throws SMPClientException,
SMPDNSResolutionException
{
return new BDXR2ClientReadOnly (aURLProvider, aServiceGroupID, aSMLInfo).getServiceMetadata (aServiceGroupID,
aDocumentTypeID);
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy