Skip to content

Commit

Permalink
Added new callback for incoming MDNs
Browse files Browse the repository at this point in the history
  • Loading branch information
phax committed Mar 29, 2021
1 parent acf2e33 commit 92d1e28
Show file tree
Hide file tree
Showing 3 changed files with 192 additions and 29 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@
import com.helger.as2lib.util.http.AS2InputStreamProviderSocket;
import com.helger.as2lib.util.http.HTTPHelper;
import com.helger.as2lib.util.http.IAS2HttpResponseHandler;
import com.helger.as2lib.util.http.IAS2IncomingMDNCallback;
import com.helger.commons.ValueEnforcer;
import com.helger.commons.annotation.Nonempty;
import com.helger.commons.collection.ArrayHelper;
Expand Down Expand Up @@ -103,6 +104,7 @@ public class AS2MDNReceiverHandler extends AbstractReceiverHandler

private final AS2MDNReceiverModule m_aReceiverModule;
private IMICMatchingHandler m_aMICMatchingHandler = new LoggingMICMatchingHandler ();
private IAS2IncomingMDNCallback m_aIncomingMDNCallback;

/**
* @param aModule
Expand Down Expand Up @@ -147,13 +149,35 @@ public final void setMICMatchingHandler (@Nonnull final IMICMatchingHandler aMIC
m_aMICMatchingHandler = aMICMatchingHandler;
}

// Asynch MDN 2007-03-12
/**
* @return The incoming MDN callback. May be <code>null</code>.
* @since v4.7.1
*/
@Nullable
public final IAS2IncomingMDNCallback getIncomingMDNCallback ()
{
return m_aIncomingMDNCallback;
}

/**
* Set the incoming MDN callback that is invoked for each received MDN.
*
* @param aIMC
* The callback to be invoked. May be null.
* @since v4.7.1
*/
public final void setIncomingMDNCallback (@Nullable final IAS2IncomingMDNCallback aIMC)
{
m_aIncomingMDNCallback = aIMC;
}

/**
* verify if the mic is matched.
*
* @param aMsg
* Message
* @return true if mdn processed
* @return <code>true</code> if the MDN was processed, <code>false</code> e.g.
* on MIC mismatch
* @throws AS2Exception
* In case of error; e.g. MIC mismatch
*/
Expand Down Expand Up @@ -202,7 +226,6 @@ public boolean checkAsyncMDN (@Nonnull final AS2Message aMsg) throws AS2Exceptio
return false;
}

// TODO NPE if file does not exist
// Get the original mic from the first line of pending information
// file
sOriginalMIC = aPendingInfoReader.readLine ();
Expand All @@ -218,12 +241,21 @@ public boolean checkAsyncMDN (@Nonnull final AS2Message aMsg) throws AS2Exceptio
if (LOGGER.isInfoEnabled ())
LOGGER.info ("received MDN [" + sDisposition + "]" + aMsg.getLoggingText ());

if (aOriginalMIC == null || aReturnMIC == null || !aReturnMIC.equals (aOriginalMIC))
final boolean bMICMatch = aOriginalMIC != null && aReturnMIC != null && aReturnMIC.equals (aOriginalMIC);

if (!bMICMatch)
{
if (LOGGER.isInfoEnabled ())
LOGGER.info ("MIC was not matched, so the pending file '" +
aPendingFile.getAbsolutePath () +
"' will NOT be deleted.");

// MIC was not matched
m_aMICMatchingHandler.onMICMismatch (aMsg, sOriginalMIC, sReturnMIC);
return false;
}

// MIC was matched
m_aMICMatchingHandler.onMICMatch (aMsg, sReturnMIC);

// delete the pendinginfo & pending file if mic is matched
Expand Down Expand Up @@ -292,7 +324,7 @@ protected final void receiveMDN (@Nonnull final AS2Message aMsg,
aMDN.headers ().setAllHeaders (aMsg.headers ());

final MimeBodyPart aPart = new MimeBodyPart (AS2HttpHelper.getAsInternetHeaders (aMDN.headers ()), aData);
aMsg.getMDN ().setData (aPart);
aMDN.setData (aPart);

// get the MDN partnership info
aMDN.partnership ().setSenderAS2ID (aMDN.getHeader (CHttpHeader.AS2_FROM));
Expand Down Expand Up @@ -331,7 +363,7 @@ protected final void receiveMDN (@Nonnull final AS2Message aMsg,
aMsg.partnership ().setSenderAS2ID (aMDN.getHeader (CHttpHeader.AS2_TO));
aMsg.partnership ().setReceiverAS2ID (aMDN.getHeader (CHttpHeader.AS2_FROM));
getModule ().getSession ().getPartnershipFactory ().updatePartnership (aMsg, false);
aMsg.setMessageID (aMsg.getMDN ().attrs ().getAsString (AS2MessageMDN.MDNA_ORIG_MESSAGEID));
aMsg.setMessageID (aMDN.attrs ().getAsString (AS2MessageMDN.MDNA_ORIG_MESSAGEID));
try
{
getModule ().getSession ().getMessageProcessor ().handle (IProcessorStorageModule.DO_STOREMDN, aMsg, null);
Expand All @@ -343,19 +375,29 @@ protected final void receiveMDN (@Nonnull final AS2Message aMsg,
}

// check if the mic (message integrity check) is correct
if (checkAsyncMDN (aMsg))
HTTPHelper.sendSimpleHTTPResponse (aResponseHandler, CHttp.HTTP_OK);
else
HTTPHelper.sendSimpleHTTPResponse (aResponseHandler, CHttp.HTTP_NOT_FOUND);
final boolean bMICMatch = checkAsyncMDN (aMsg);
HTTPHelper.sendSimpleHTTPResponse (aResponseHandler, bMICMatch ? CHttp.HTTP_OK : CHttp.HTTP_NOT_FOUND);

final String sDisposition = aMDN.attrs ().getAsString (AS2MessageMDN.MDNA_DISPOSITION);

if (m_aIncomingMDNCallback != null)
m_aIncomingMDNCallback.onIncomingMDN (false,
aMDN,
aMDN.getHeader (CHttpHeader.AS2_FROM),
aMDN.getHeader (CHttpHeader.AS2_TO),
sDisposition,
aMDN.attrs ().getAsString (AS2MessageMDN.MDNA_MIC),
aMDN.attrs ().getAsString (AS2MessageMDN.MDNA_ORIG_MESSAGEID),
aMDN.attrs ().getAsBoolean (AS2Message.ATTRIBUTE_RECEIVED_SIGNED, false),
bMICMatch);

final String sDisposition = aMsg.getMDN ().attrs ().getAsString (AS2MessageMDN.MDNA_DISPOSITION);
try
{
DispositionType.createFromString (sDisposition).validate ();
}
catch (final AS2DispositionException ex)
{
ex.setText (aMsg.getMDN ().getText ());
ex.setText (aMDN.getText ());
if (ex.getDisposition ().isWarning ())
{
// Warning
Expand Down Expand Up @@ -434,7 +476,7 @@ public void reparse (@Nonnull final AS2Message aMsg,
{
LOGGER.error ("Error creating MimeBodyPart", ex);
}
aMsg.getMDN ().setData (aPart);
aMDN.setData (aPart);

// get the MDN partnership info
aMDN.partnership ().setSenderAS2ID (aMDN.getHeader (CHttpHeader.AS2_FROM));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@
import com.helger.as2lib.util.dump.IHTTPOutgoingDumper;
import com.helger.as2lib.util.http.AS2HttpClient;
import com.helger.as2lib.util.http.AS2HttpHeaderSetter;
import com.helger.as2lib.util.http.IAS2IncomingMDNCallback;
import com.helger.commons.ValueEnforcer;
import com.helger.commons.annotation.OverrideOnDemand;
import com.helger.commons.http.CHttpHeader;
Expand Down Expand Up @@ -113,6 +114,7 @@ public class AS2SenderModule extends AbstractHttpSenderModule
private static final Logger LOGGER = LoggerFactory.getLogger (AS2SenderModule.class);

private IMICMatchingHandler m_aMICMatchingHandler = new LoggingMICMatchingHandler ();
private IAS2IncomingMDNCallback m_aIncomingMDNCallback;
private Consumer <? super X509Certificate> m_aVerificationCertificateConsumer;

public AS2SenderModule ()
Expand Down Expand Up @@ -144,6 +146,28 @@ public final AS2SenderModule setMICMatchingHandler (@Nonnull final IMICMatchingH
return this;
}

/**
* @return The incoming MDN callback. May be <code>null</code>.
* @since v4.7.1
*/
@Nullable
public final IAS2IncomingMDNCallback getIncomingMDNCallback ()
{
return m_aIncomingMDNCallback;
}

/**
* Set the incoming MDN callback that is invoked for each received MDN.
*
* @param aIMC
* The callback to be invoked. May be null.
* @since v4.7.1
*/
public final void setIncomingMDNCallback (@Nullable final IAS2IncomingMDNCallback aIMC)
{
m_aIncomingMDNCallback = aIMC;
}

/**
* @return The consumer for the effective certificate upon signature
* verification. May be <code>null</code>. The default is
Expand Down Expand Up @@ -712,7 +736,7 @@ protected void receiveSyncMDN (@Nonnull final AS2Message aMsg,

final MimeBodyPart aPart = new MimeBodyPart (AS2HttpHelper.getAsInternetHeaders (aMDN.headers ()),
aMDNStream.getBufferOrCopy ());
aMsg.getMDN ().setData (aPart);
aMDN.setData (aPart);

// get the MDN partnership info
aMDN.partnership ().setSenderAS2ID (aMDN.getHeader (CHttpHeader.AS2_FROM));
Expand Down Expand Up @@ -751,36 +775,49 @@ protected void receiveSyncMDN (@Nonnull final AS2Message aMsg,
// Or no module found in message processor
}

final String sDisposition = aMsg.getMDN ().attrs ().getAsString (AS2MessageMDN.MDNA_DISPOSITION);
final String sDisposition = aMDN.attrs ().getAsString (AS2MessageMDN.MDNA_DISPOSITION);

if (LOGGER.isInfoEnabled ())
LOGGER.info ("received MDN [" + sDisposition + "]" + aMsg.getLoggingText ());

// Asynch MDN 2007-03-12
// Verify if the original mic is equal to the mic in returned MDN
final String sReturnMIC = aMsg.getMDN ().attrs ().getAsString (AS2MessageMDN.MDNA_MIC);
final String sReturnMIC = aMDN.attrs ().getAsString (AS2MessageMDN.MDNA_MIC);
final MIC aReturnMIC = MIC.parse (sReturnMIC);

// Catch ReturnMIC == null in case the attribute is simply missing
if (aOriginalMIC == null || aReturnMIC == null || !aReturnMIC.equals (aOriginalMIC))
final boolean bMICMatch = aOriginalMIC != null && aReturnMIC != null && aReturnMIC.equals (aOriginalMIC);
if (bMICMatch)
{
// MIC was matched - all good
m_aMICMatchingHandler.onMICMatch (aMsg, sReturnMIC);
}
else
{
// file was sent completely but the returned mic was not matched,
m_aMICMatchingHandler.onMICMismatch (aMsg,
aOriginalMIC == null ? null : aOriginalMIC.getAsAS2String (),
sReturnMIC);
}
else
{
m_aMICMatchingHandler.onMICMatch (aMsg, sReturnMIC);
}

if (m_aIncomingMDNCallback != null)
m_aIncomingMDNCallback.onIncomingMDN (true,
aMDN,
aMDN.getHeader (CHttpHeader.AS2_FROM),
aMDN.getHeader (CHttpHeader.AS2_TO),
sDisposition,
aMDN.attrs ().getAsString (AS2MessageMDN.MDNA_MIC),
aMDN.attrs ().getAsString (AS2MessageMDN.MDNA_ORIG_MESSAGEID),
aMDN.attrs ().getAsBoolean (AS2Message.ATTRIBUTE_RECEIVED_SIGNED, false),
bMICMatch);

try
{
DispositionType.createFromString (sDisposition).validate ();
}
catch (final AS2DispositionException ex)
{
ex.setText (aMsg.getMDN ().getText ());
ex.setText (aMDN.getText ());
if (ex.getDisposition ().isWarning ())
{
// Warning
Expand Down Expand Up @@ -873,13 +910,13 @@ private void _sendViaHTTP (@Nonnull final AS2Message aMsg,

if (getOutgoingHttpCallback () != null)
getOutgoingHttpCallback ().onOutgoingHttpMessage (true,
aMsg.getAS2From (),
aMsg.getAS2To (),
aMsg.getMessageID (),
aMIC == null ? null : aMIC.getClone (),
eCTE,
sUrl,
nHttpResponseCode);
aMsg.getAS2From (),
aMsg.getAS2To (),
aMsg.getMessageID (),
aMIC == null ? null : aMIC.getClone (),
eCTE,
sUrl,
nHttpResponseCode);

// Check the HTTP Response code
if (AS2HttpClient.isErrorResponseCode (nHttpResponseCode))
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/**
* The FreeBSD Copyright
* Copyright 1994-2008 The FreeBSD Project. All rights reserved.
* Copyright (C) 2013-2021 Philip Helger philip[at]helger[dot]com
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE FREEBSD PROJECT ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FREEBSD PROJECT OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* The views and conclusions contained in the software and documentation
* are those of the authors and should not be interpreted as representing
* official policies, either expressed or implied, of the FreeBSD Project.
*/
package com.helger.as2lib.util.http;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;

import com.helger.as2lib.message.IMessageMDN;

/**
* Callback interface to be invoked for incoming MDNs to simplify logging.
*
* @author Philip Helger
* @since 4.7.1
*/
public interface IAS2IncomingMDNCallback
{
/**
* Invoked for every incoming MDN message.
*
* @param bSynchronousMDN
* <code>true</code> if it is a synchronous MDN, <code>false</code> if
* it is asynchronous.
* @param aMDN
* The MDN object itself. May be used to extract further information
* when needed. Never <code>null</code>.
* @param sSenderAS2ID
* The sender AS2 ID of the MDN. May be <code>null</code>.
* @param sReceiverAS2ID
* The receiver AS2 ID of the MDN. May be <code>null</code>.
* @param sDisposition
* The disposition string of the MDN. Can be used to determine success
* or error. May be <code>null</code>.
* @param sReturnMIC
* The returned MIC. May be <code>null</code>.
* @param sOriginalAS2MessageID
* The original AS2 message ID this MDN is referring to.
* @param bMDNWasSigned
* <code>true</code> if the MDN was electronically signed,
* <code>false</code> if not.
* @param bMICMatched
* <code>true</code> if the MDN MIC matched the original MIC,
* <code>false</code> if not.
*/
void onIncomingMDN (boolean bSynchronousMDN,
@Nonnull IMessageMDN aMDN,
@Nullable String sSenderAS2ID,
@Nullable String sReceiverAS2ID,
@Nullable String sDisposition,
@Nullable String sReturnMIC,
@Nullable String sOriginalAS2MessageID,
boolean bMDNWasSigned,
boolean bMICMatched);
}

0 comments on commit 92d1e28

Please sign in to comment.