AS4 Peppol
Purpose¶
Peppol AS4 Messaging Support
This DSL is based on the open source library Phase4: https://github.com/phax/phase4
See the folder https://github.com/phax/phase4/tree/master/phase4-peppol-client/src/test/java/com/helger/phase4/peppol for different examples on how to send messages via the Peppol AS4 client.
Warning
p6core 6.9.0 and above uses Java 17 internally. This has a know security constraint/issue: https://github.com/phax/phase4/wiki/Known-Limitations To work around this constraint requires the creation of the file $P6_DATA/conf/security/java.security (see examples)
Methods¶
Binding name: p6.as4peppol
Security recommandations
Since 6.9.0
Recommended java.security file for p6core
jdk.xml.dsig.secureValidationPolicy=\
disallowAlg http://www.w3.org/TR/1999/REC-xslt-19991116,\
disallowAlg http://www.w3.org/2001/04/xmldsig-more#rsa-md5,\
disallowAlg http://www.w3.org/2001/04/xmldsig-more#hmac-md5,\
disallowAlg http://www.w3.org/2001/04/xmldsig-more#md5,\
disallowAlg http://www.w3.org/2000/09/xmldsig#dsa-sha1,\
disallowAlg http://www.w3.org/2007/05/xmldsig-more#sha1-rsa-MGF1,\
disallowAlg http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha1,\
maxTransforms 5,\
maxReferences 30,\
disallowReferenceUriSchemes file http https,\
minKeySize RSA 1024,\
minKeySize DSA 1024,\
minKeySize EC 224,\
noDuplicateIds,\
noRetrievalMethodLoops
Logging, Debug & Testing
The configuration properties global.production
and global.debug
are used to switch between Test and Production certificate usage and control the generation of DEBUG logs
In Addition, the following line should be added to the log4j.properties file to enable DEBUG logging from the Phase4 library:
log4j.logger.com.helger.phase4=DEBUG
buildContextConfiguration¶
Syntax
AutoCloseable p6.as4peppol.buildContextConfiguration(Map<String, String> configuration)
Phase4 is designed to gather configuration from properties files provided at runtime. This does not suite the platform6 execution architecture hence the introduction of the context configuration
Context configuration replaces the use of properties files with a Map[:] of configuration attributes defined at runtime.
It is therefore essential that all calls to the DSL are made within the configuration context and the context is closed at the end of execution.
This method returns an AutoClosable
making the lifecycle of the context easy to control in Groovy:
Parameter: configuration
To reduce the configuration required to build a context each time, many of the Phase4 required properties have been defaults so only overrides are required. The defaults are:
Key | Default Value |
---|---|
phase4.wss4j.syncsecurity | “true” |
global.debug | “false” |
global.production | “true” |
global.nostartupinfo | “true” |
global.datapath | P6_DATA + “/resources/as4” |
phase4.dump.path | P6_DATA + “/resources/as4/dump” |
org.apache.wss4j.crypto.provider | “org.apache.wss4j.common.crypto.Merlin” |
org.apache.wss4j.crypto.merlin.keystore.type | “pkcs12” |
org.apache.wss4j.crypto.merlin.keystore.file | P6_DATA + “/resources/as4/as4peppol-ap.p12” |
org.apache.wss4j.crypto.merlin.keystore.password | “peppol” |
org.apache.wss4j.crypto.merlin.keystore.provider | “BC” |
org.apache.wss4j.crypto.merlin.keystore.alias | “cert” |
org.apache.wss4j.crypto.merlin.load.cacerts | “false” |
org.apache.wss4j.crypto.merlin.truststore.type | “jks” |
org.apache.wss4j.crypto.merlin.truststore.file | P6_DATA + “/resources/as4/as4peppol-complete-truststore.jks” |
org.apache.wss4j.crypto.merlin.truststore.password | “peppol” |
org.apache.wss4j.crypto.merlin.keystore.private.password” | same as org.apache.wss4j.crypto.merlin.keystore.password |
Example
p6.as4peppol.buildContextConfiguration( configMap ).withCloseable {
// Context aware DSL calls here...
}
buildHttpSettings¶
Create a Phase4 HttpClientSettings
object from a given SSLContext.
This allows the p6.securesocket DSL to be used to define the HttpClient attributes when POSTing AS4
Syntax
HttpClientSettings p6.as4peppol.buildHttpSettings(SSLContext ctx)
Example
def ctx = p6.securesocket.contextBuilder()
.setType( SecureContext.BundleType.ONE_WAY_TRUST_ANY )
.setProtocol( "TLSv1.2" )
.build();
def httpSettings = p6.as4peppol.buildHttpSettings( p6.securesocket.contextBuild( ctx ) )
sendBuilder¶
Create a new Builder for AS4 messages if the payload is present and the SBDH (openPEPPOL Business Message Envelope) should be created internally
Syntax
Phase4PeppolSender.Builder p6.as4peppol.sendBuilder()
Example: HTTP openPEPPOL AS4 Send with Receipt checker
The following is an example of sending a UBL Invoice document (XML defined via script resource) to the Austrian Governments test endpoint:
import groovy.xml.DOMBuilder
import com.helger.peppol.sml.ESML
import com.helger.smpclient.peppol.SMPClientReadOnly
import com.helger.phase4.dump.AS4RawResponseConsumerWriteToFile
def xml = p6.resource.get( "UBLInvoice" )
def payloadElement = DOMBuilder.parse(new StringReader(xml)).getDocumentElement()
// Austrian Government id
def receiverID = p6.as4peppol.idFactory().createParticipantIdentifierWithDefaultScheme ( "9915:test" )
def configMap = [:]
configMap["global.debug"] = "true"
configMap["global.production"] = "false"
configMap["org.apache.wss4j.crypto.merlin.keystore.password"] = "password-in-here"
p6.as4peppol.buildContextConfiguration( configMap ).withCloseable {
def ctx = p6.securesocket.contextBuilder()
.setType( SecureContext.BundleType.ONE_WAY_TRUST_ANY )
.setProtocol( "TLSv1.2" )
.build();
def httpSettings = p6.as4peppol.buildHttpSettings( p6.securesocket.contextBuild( ctx ) )
def result = p6.as4peppol.sendBuilder()
.documentTypeID ( p6.as4peppol.idFactory().createDocumentTypeIdentifierWithDefaultScheme ("urn:oasis:names:specification:ubl:schema:xsd:Invoice-2::Invoice##urn:cen.eu:en16931:2017#compliant#urn:fdc:peppol.eu:2017:poacc:billing:3.0::2.1") )
.processID ( p6.as4peppol.idFactory().createProcessIdentifierWithDefaultScheme ("urn:fdc:peppol.eu:2017:poacc:billing:01:1.0") )
.senderParticipantID ( p6.as4peppol.idFactory().createParticipantIdentifierWithDefaultScheme ("9915:phase4-test-sender") )
.receiverParticipantID ( receiverID )
.senderPartyID ( "YOUR_POP_ID_HERE" )
.payload ( payloadElement )
.smpClient ( new SMPClientReadOnly (p6.as4peppol.sender.URL_PROVIDER, receiverID, ESML.DIGIT_TEST) )
.rawResponseConsumer ( new AS4RawResponseConsumerWriteToFile () )
.validationConfiguration ( null )
.buildMessageCallback ( p6.as4peppol.simpleMessageSendLogger() )
.httpClientFactory ( httpSettings )
.sendMessageAndCheckForReceipt ()
println result
}
sendSBDHBuilder¶
Create a new Builder for AS4 messages if the SBDH payload is already present
Syntax
Phase4PeppolSender.SBDHBuilder p6.as4peppol.sendSBDHBuilder()
idFactory¶
Simple wrapper over Phase4PeppolSender.IF
to allow id generation
Syntax
PeppolIdentifierFactory p6.as4peppol.idFactory()
Example
def receiverID = p6.as4peppol.idFactory().createParticipantIdentifierWithDefaultScheme ( "9915:test" )
Simple wrapper¶
Simple wrapper to reference the Phase4PeppolSender class
Syntax
Class<Phase4PeppolSender> sender = Phase4PeppolSender.class
simpleMessageSendLogger¶
A simple implementation of IAS4ClientBuildMessageCallback to log message details during the send process
Syntax
IAS4ClientBuildMessageCallback p6.as4peppol.simpleMessageSendLogger()
Example
def logger = p6.as4peppol.simpleMessageSendLogger()
receive¶
Receive an openPEPPOL AS4 Message. A byte array containing the message content and a separate list of MIME headers are supplied as a Map as parameters.
Syntax
Tuple3<Integer, byte[], Map<String, String>> p6.as4peppol.receive(
final Map<String, String> headers,
final byte[] ba,
final Closure<Void>... messageReceipt
)
This method takes an optional callback method to use during the receipt processing:
- Closure - messageReceipt: is called when an inbound openPEPPOL Message is identified. The parameters passed are:
filePath
Path of received message,messageId
: Unique id of the received message
The method returns a Tuple3 containing the following:
- Integer: the HTTP status code to response with (200 is good)
- String: the response entity as a byte array (typically a receipt)
- Map: map of headers to use in a response to this request
Example
Receive HTTP POSTed AS4 openPEPPOL Message and Generate Receipt
rest('/public')
.post('/as4')
.to('p6cmb://scripts?platform6.request.action=execute&id=AS4PeppolInbound')
.id('AS4PeppolInbound')
def configMap = [:]
configMap["global.debug"] = "true";
configMap["global.production"] = "false";
configMap["org.apache.wss4j.crypto.merlin.keystore.password"] = "password-in-here"
p6.as4peppol.buildContextConfiguration( configMap ).withCloseable {
def tpl = p6.as4peppol.receive(
p6.pipeline.toStringMap(),
p6.pipeline.getBytes('body'),
{ sbdFilePath, messageId -> println '++++++ RECEIVED openPEPPOL Message\n' + '\nPath: ' + sbdFilePath + '\nMessageID: ' + messageId}
)
// --- Build the Http POST response into the pipeline ---
// Response code
p6.pipeline.put('CamelHttpResponseCode', '' + tpl.first)
// The response or error-message body
p6.pipeline.put('body', tpl.second)
// Additional headers - prefixed with 'p6rest.' to ensure they are part of the final HTTP response
tpl.third.each { key, val ->
p6.pipeline.put( 'p6rest.' + key, val )
}
}
setPerformSBDHValueChecking¶
Enable value checks when reading Peppol SBDH documents (inbound message validation) default: false
Syntax
void p6.as4peppol.setPerformSBDHValueChecking(boolean performSBDHValueChecking)
setPerformReceiverChecking¶
Enable receiver consistency checks (inbound validation) default: false
Syntax
void p6.as4peppol.setPerformReceiverChecking(boolean performReceiverChecking)