phax / phase4

phase4 - AS4 client and server for integration into existing systems. Specific support for Peppol and CEF eDelivery built-in.
Apache License 2.0
147 stars 48 forks source link

PMode.Leg[1].BusinessInfo.Service 'null' is unsupported #213

Open ErrorCode-404 opened 7 months ago

ErrorCode-404 commented 7 months ago

Hello guys thank you for this awesome as4 library.

I maybe have encountered an problem which I don't know how to handle it. First of all I am using this library to implement an as4 receiver with the bdew profile. Therefore i have created an simple springboot project according to the given springboot peppol example but with bdew support. On the other side I am using the bdew client project in this repo which sends messages to the spring boot as4 receiver.

After some required configs (keystore, message content) the client can send as4 messages to the receiver. But the receiver checks the parsed message and pmode and I get the following error:

PMode.Leg[1].BusinessInfo.Service 'null' is unsupported This comes from the BDEWCompatibilityValidator which does some validation.

...
  final PModeLegBusinessInformation aBusinessInfo = aPModeLeg.getBusinessInfo ();
      if (aBusinessInfo == null)
      {
        aErrorList.add (_createError (sFieldPrefix + "BusinessInfo is missing"));
      }
      else
      {
        final String sService = aBusinessInfo.getService ();
        if (sService == null || !BDEWPMode.containsService (sService))
        {
          aErrorList.add (_createError (sFieldPrefix + "BusinessInfo.Service '" + sService + "' is unsupported"));
        }

        final String sAction = aBusinessInfo.getAction ();
        if (sAction == null || !BDEWPMode.containsAction (sAction))
        {
          aErrorList.add (_createError (sFieldPrefix + "BusinessInfo.Action '" + sAction + "' is unsupported"));
        }
      }
...

After an inspection with the debugger it seems that the property serviceValue in the businessInfo is null which may be the cause of the error.

Bildschirmfoto 2024-01-29 um 14 59 56

After further inspection of the code I saw that there will be created a default pmode (BDEWPMode in this case)

...
@Nonnull
  public static PModeLegBusinessInformation generatePModeLegBusinessInformation ()
  {
    final String sService = null;
    final String sAction = CAS4.DEFAULT_ACTION_URL;
    final Long nPayloadProfileMaxKB = null;
    final String sMPCID = CAS4.DEFAULT_MPC_ID;
    return PModeLegBusinessInformation.create (sService, sAction, nPayloadProfileMaxKB, sMPCID);
  }
...

If you compare these defaults with the debugger output you can see that the values in the debugger are still the same. I have expected that the values like serviceValue will be exactly that what the client has send (e.g. "https://www.bdew.de/as4/communication/services/MP") but that is not the case.

Does the library does not extract those information or do I have an error in my thinking?

phax commented 7 months ago

@ErrorCode-404 I hope you previously had a look at https://github.com/phax/phase4/wiki/Profile-BDEW and saw that phase4 does not work with BDEW out of the box and that you need implement a custom key exchange (which is kind of a pain in the a...).

For the client it is recommended to use ther phase4-bdew-client submodule that ships with a decent Phase4BDEWSender.builder() to assemble and send the message in a decent way. I assume you need to fill .action() and .service() manually. Via sendMessageAndCheckForReceipt of the builder you can send the AS4 user message and check the returned AS4 SignalMessage.

Unfortunately I am myself not involved in the BDEW implementations, so I cannot give other then general advice

ErrorCode-404 commented 7 months ago

Thank you for your response. I already have checked the docu and also brought the basic communication between a sender and a receiver to work. I used the Phase4BDEWSender.builder() like below:

Phase4BDEWSender.builder()
                    //Communication Configs
                    .endpointURL("http://localhost:8080/as4")
                    .encryptionKeyIdentifierType(ECryptoKeyIdentifierType.X509_KEY_IDENTIFIER)
                    .signingKeyIdentifierType(ECryptoKeyIdentifierType.BST_DIRECT_REFERENCE)

                    //----MessageInfo----
                    .messageID(UUID.randomUUID().toString())

                    //----PartyInfo----
                    .fromPartyIDType("urn:oasis:names:tc:ebcore:partyid-type:iso6523:0088")
                    .fromPartyID("myCompany")
                    .fromRole(CAS4.DEFAULT_INITIATOR_URL)
                    .toPartyIDType("urn:oasis:names:tc:ebcore:partyid-type:iso6523:0088")
                    .toPartyID("otherCompany")
                    .toRole(CAS4.DEFAULT_RESPONDER_URL)
                    //----PartyInfo----

                    //----CollaborationInfo----
                    .agreementRef("https://www.bdew.de/as4/communication/agreement")
                    .service("https://www.bdew.de/as4/communication/services/MP")
                    .action("http://docs.oasis-open.org/ebxml-msg/as4/200902/action")
                    //----CollaborationInfo----

                    //----PayloadInfo----
                    .payload(AS4OutgoingAttachment.builder()
                                    .data(aPayloadBytes)
                                    .compressionGZIP()
                                    .mimeTypeXML()
                                    .charset(StandardCharsets.UTF_8),
                            new BDEWPayloadParams()
                                    .setDocumentType("MSCONS")
                                    .setDocumentDate(PDTFactory.getCurrentLocalDate())
                                    .setDocumentNumber("28198611A")
                                    .setFulfillmentDate(PDTFactory.getCurrentLocalDate().minusMonths(2))
                                    .setSubjectPartyId("123456")
                                    .setSubjectPartyRole("MSCONS-AS4-Receiver"))
                    .sendMessageAndCheckForReceipt(e -> {
                        LOGGER.error("Error sending BDEW message via AS4", e);
                    });

After some more debugger investigations I saw that the UserMessage will be parsed and processed correctly with all the information I send.

Bildschirmfoto 2024-01-30 um 10 00 55

So the problem lies in the pmode which will be created with default values (mostly constants which must be set according to the as4 profile) but not all fields will replaced with the values the sender sent.

With #187 there were added some validations for the pmode like the m_sServiceValue in PModeLegBusinessInformation. Here the service is checked but is never set which leads to the problem above if a real client-receiver-communication happens.

phax commented 7 months ago

Okay, that indeed is a tricky thing to comment without the actual code. My assumption is, that you have more then one AS4 profile in your build path (peppol and bdew) and therefore the automatic PMode detection fails. By removing "phase4-profile-peppol" from your build path may resolve the issue....

phax commented 7 months ago

@ErrorCode-404 were you able to resolve the issue?

ErrorCode-404 commented 7 months ago

I only have this both dependencies in my project:

<dependency>
  <groupId>com.helger.phase4</groupId>
  <artifactId>phase4-lib</artifactId>
</dependency>
<dependency>
  <groupId>com.helger.phase4</groupId>
  <artifactId>phase4-profile-bdew</artifactId>
</dependency>

So there is only one profile at the same time. But I think I have found a kind of a workaround.

phax commented 7 months ago

Okay, on the receiver side, when debugging, all the AS4 profiles should be contained in the manager class AS4ProfileManager. If you could run a getAllProfiles() you should get exactly 1 profile back. If you get anything else then 1, please let me know

ErrorCode-404 commented 7 months ago

Okay I can confirm that exactly one profile (bdew) will be registered.

phax commented 7 months ago

The problem is indeed the validation of the determined PMode and not of the UserMessage, The PMode is resolved based on the incoming message. There is a way to circumvent the PMode validation, but it is a bit tedious:

  1. You need to call AS4RequestHandler.setIncomingProfileSelector (...)
  2. This however cannot be done directly, but through an IHandlerCustomizer that you need to pass into the AS4XServletHandler

So in the SpringBoot example code, class ServletConfig add this snippet and the validation should be offline:

   hdl.setHandlerCustomizer (new IHandlerCustomizer ()
      {
        public void customizeBeforeHandling (@Nonnull final IRequestWebScopeWithoutResponse aRequestScope,
                                             @Nonnull   final AS4UnifiedResponse aUnifiedResponse,
                                             @Nonnull  final AS4RequestHandler aHandler)
        {
          aHandler.setIncomingProfileSelector (new AS4IncomingProfileSelectorFromGlobal ()
          {
            public boolean validateAgainstProfile ()
            {
              // override;
              return false;
            }
          });
        }
      });

Edit: with final AS4XServletHandler hdl = new AS4XServletHandler ();

Without looking at your code I don't know how to resolve this, as PModeResolution can be quite tricky, also depending on the PModeManager you chose/implemented

ErrorCode-404 commented 7 months ago

Hi I have posted my code below:

@Configuration
public class ServletConfig {

    @Bean
    public ServletRegistrationBean<AS4BDEWServlet> registerAS4BDWEWServlet(final ServletContext servletContext) {
        init(servletContext);
        final AS4BDEWServlet servlet = new AS4BDEWServlet();
        ServletRegistrationBean<AS4BDEWServlet> bean = new ServletRegistrationBean<>(servlet, true, "as4");
        bean.setLoadOnStartup(1);
        return bean;
    }

    private void init(final ServletContext servletContext) {

        // Do it only once
        if (!WebScopeManager.isGlobalScopePresent()) {
            WebScopeManager.onGlobalBegin(servletContext);
            initGlobalSettings(servletContext);
            initAS4();
        }
    }

    /**
     * This method is used to initialize the global settings for the application.
     *
     * @param servletContext The servlet context of the application.
     */
    private void initGlobalSettings(@Nonnull final ServletContext servletContext) {
        // Logging: JUL to SLF4J
        SLF4JBridgeHandler.removeHandlersForRootLogger();
        SLF4JBridgeHandler.install();

        if (GlobalDebug.isDebugMode()) {
            RequestTrackerSettings.setLongRunningRequestsCheckEnabled(false);
            RequestTrackerSettings.setParallelRunningRequestsCheckEnabled(false);
            HttpDebugger.setEnabled(false);
        }

        // Sanity check
        if (CommandMap.getDefaultCommandMap().createDataContentHandler(CMimeType.MULTIPART_RELATED.getAsString()) ==
                null) {
            throw new IllegalStateException("No DataContentHandler for MIME Type '" +
                    CMimeType.MULTIPART_RELATED.getAsString() +
                    "' is available. There seems to be a problem with the dependencies/packaging");
        }

        // Init the data path
        {
            // Get the ServletContext base path
            final String sServletContextPath = ServletHelper.getServletContextBasePath (servletContext);
            // Get the data path
            final String sDataPath = AS4Configuration.getDataPath ();
            if (StringHelper.hasNoText (sDataPath))
                throw new InitializationException ("No data path was provided!");
            final boolean bFileAccessCheck = false;
            // Init the IO layer
            WebFileIO.initPaths (new File (sDataPath).getAbsoluteFile (), sServletContextPath, bFileAccessCheck);
        }
    }

    private void initAS4() {
        AS4ProfileSelector.setCustomAS4ProfileID(AS4BDEWProfileRegistarSPI.AS4_PROFILE_ID);
        AS4ServerInitializer.initAS4Server();
    }

    public static IAS4CryptoFactory getCryptoFactoryToUse() {
        // If you have a custom crypto factory, build/return it here
        return AS4CryptoFactoryProperties.getDefaultInstance();
    }
}
public class AS4BDEWServlet extends AbstractXServlet {

    public AS4BDEWServlet() {
        AS4DumpManager.setIncomingDumper(new AS4IncomingDumperFileBased());
        AS4DumpManager.setOutgoingDumper(new AS4OutgoingDumperFileBased());
        // Multipart is handled specifically inside
        settings().setMultipartEnabled(false);
        final AS4XServletHandler as4ServletHandler = new AS4XServletHandler();
        as4ServletHandler.setHandlerCustomizer((aRequestScope, aUnifiedResponse, aHandler)->{
            aHandler.setIncomingProfileSelector(new AS4IncomingProfileSelectorFromGlobal(){
                @Override
                public boolean validateAgainstProfile() {
                    return false;
                }
            });
        });
        as4ServletHandler.setCryptoFactorySupplier(ServletConfig::getCryptoFactoryToUse);
        handlerRegistry().registerHandler(EHttpMethod.POST, as4ServletHandler);
    }
}

Thats my current setup and I also added your suggestion. And indeed I can confirm that I now can receive messages without any error and the sender gets back a response. But If I see it right your code means that validation against the profile will be disabled entirely. Correct?

phax commented 6 months ago

Yes, that is correct :-/ And I don't see an obvious flaw in your code....

ErrorCode-404 commented 6 months ago

Well unfortunately the deactivation of the validation against the profile doesn't solves the problem. I still think that the error will be caused through the validator of the bdew profile. There the PModeLegBusinessInformation or more precisely the service and action will be checked against the constants given in the BDEWPMode . But the PModeLegBusinessInformation was not filled with all PMode relevant data when a user message is received. On the other side the received Ebms3UserMessage has all the data set in the Ebms3CollaborationInfo like service and action. So maybe the data which should be validated are more likely to find in the user message and not in the PMode.

phax commented 6 months ago

Hmm, this is really a pitty. I am really curious what's going and would like to ask, if you are willing for a quick screensharing session, so that we can "pair debug" :) If that sounds interesting for you, you find my email address in the pom.xml of this project - just let me know your timezone and we will figure something out....

phax commented 2 weeks ago

@ErrorCode-404 were you able to resolve the issue in the meantime?