diff --git a/docs/nemhandel-edelivery.md b/docs/nemhandel-edelivery.md index 2b4d7b0e5ff2411212c683a538361e89a977db86..09f99606ed9f80ae7d4e44e780c848f7b2ec566d 100644 --- a/docs/nemhandel-edelivery.md +++ b/docs/nemhandel-edelivery.md @@ -1,743 +1,2 @@ -# OBS: This was written for AP1.0 and is conserved for historical purposes. Find the up-to-date version here: https://rep.erst.dk/git/openebusiness/common/-/blob/master/guidelines/Developer_Documentation.pdf - - -# NemHandel eDelivery - -## 1 Introduction -This document describes the reference implemenation (RI) of the Access Point (AP) from a technical and code-centric perspective. -For an introduction to the concepts and the 4-corner model that the RI is a part of, please refer to https://rep.erst.dk/git/openebusiness/common/-/blob/master/guidelines/vejledning_-_forretning_og_forvaltning.pdf (in Danish). - -This RI is a basic, generic implementation of an AP that can be compiled, installed and used 'as-is' in a Peppol or a Nemhandel eDelivery infrastructure. -The basic installation and configuration procedures are described in https://rep.erst.dk/git/openebusiness/common/-/blob/master/guidelines/vejledning_-_drift.pdf - -However, a service vendor may want to customize the RI and add new features, or implement other changes to the codebase that provide business value in context of the specific service provider. - -For instance, the service provider may choose to expose the APIs allowing Corners C1 to send and C4 to receive documents via the RI in an alternative manner - for example by allowing C1 and C4 to send/receive document via shared folders. The RI is not capable of this out-of-the-box but it is an example of some new functionality that a service vendor may choose to add if it makes sense and adds value. - -This document is targeted towards the development team that has cloned the code repository provided by ERST in order to implement new features and add new functionality to the codebase in context of their own business. To be able to do that, the team needs to understand the structure of the codebase, the various parts of it, and where and how to hook in to add new features. - -The codebase contains a number of key components that implement various key parts of the overall functionality of the RI: - -![Oxalis ER-diagram](nemhandel-edelivery/oxalis-overview-packages.png) - -These key areas are described in the sections below: - -* Data model - and how the RI stores information in the relationel database -* REST API - and how the RI exposes and 'Outbox' API to C1 and an 'Inbox' API to C4 -* Validation - and how the RI validates document in C2 before sending them to C3, and how documents are validated in C3 -* Signing - and how the RI handles signatures and transfer delegations -* Async processing - and how C3 implements Schematron-validation asynchronously when receiving documents from C2 -* AS4-endpoint - and how C3 exposes the endpoint that is capable of receiving documents from C2 according to the AS4-protocol. - -In addition to this document, the REST APIs exposed by RI are documented via Swagger and can be reached at /openapi-ui and at /openapi.json once the RI is up and running. -The Swagger documentation is also available [here](https://rep.erst.dk/git/openebusiness/common/-/blob/master/guidelines/api/ap-openapi-docs.json). - -## 1.1 Technologies and requirements -To compile and build the RI, you need: - -* Java JDK 8 Update >= 252. The RI has been developed using Eclipse Temurin OpenJDK 8 -* Maven >= 3.1.1 - -To run the RI, you also need: - -* Tomcat 9 -* MySQL 8 or another relational database which is supported by one of the Hibernate SQL-dialects. The RI has been developed and tested using MySQL 8. -* An operating system that supports Tomcat 9 and MySQL 8 - the RI has been developed using both Oracle Linux Server 7.9 and Windows 10. - - -## 2 The codebase - The codebase for the RI consists of two repos provided by ERST: - -* https://rep.erst.dk/git/openebusiness/nemhandeledelivery/oxalis -* https://rep.erst.dk/git/openebusiness/nemhandeledelivery/oxalis-as4 - -The content of these repos is based on the Oxalis repos: - -* https://github.com/OxalisCommunity/oxalis -* https://github.com/OxalisCommunity/Oxalis-AS4 - -The Oxalis-based repos provide a RI which can be used only in a Peppol eDelivery infrastructure, whereas the ERST-based repos provide a RI which can be used in either a Peppol- or a Nemhandel eDelivery infrastructure - depending on its configuration. -To enable the RI to be part of both infrastructures, ERST has added a set of new features: - -* Support for MitID Erhverv signatures (FOCES3) - allowing an RI to identify and encrypt/decrypt using FOCES3 when transferring documents from C2 to C3 via the AS4-protocol -* Support for NemHandel eDelivery transport profile `nemhandel-transport-as4-v2_0` -* Synchronous Schema- and Schematron-Validation in C2 before sending a document -* Synchronous Schema-validation and asynchronous Schematron-validation in C3 when receiving a document -* Support for tracability using transfer delegations when handing over documents between corners - -ERST has also added some features that to allow the RI to be flexible and technology-agnostic and to allow the RI to fit into various infrastructures depending on the service vendor's preferred technologies: - -* REST APIs to make it easy for C1 and C4 to use the RI -* Storing documents and corresponding document-related logging in a relational DB for added control - - -### 2.1 Guice -Guice s a lightweight dependency injection framework for Java that allow developers to decouple the implementation of new features from the existing codebase thus making it easier to maintain the implementation of features as codebase evolves. -Guice is used throughout Oxalis codebase and has also been used by ERST when implementing new features on top of the Oxalis. In the sections below describing each of the features that ERST has added to the codebase, we refer to the Guice modules that have been used to implement each feature. -We encourage service vendors to actively use Guice when implementing new features to make it easier to maintain the codebase and easier to absorb upstream changes to the ERST codebase. - -For more information on Guice - please refer to https://github.com/google/guice/wiki/Motivation - -Generally, all the new functionality is, wherever possible, added as classes under the `dk.erst.oxalis.as4` package -to separate it from the standard Oxalis-AS4 codebase. Some minor changes are necessary in the Oxalis code itself, but we have -strived to separate our code from Oxalis' code, which simplifies pulling in upstream changes. To this end, there is a `NemHandelModule` which is -considered a "top-level" Guice module for all additions to Oxalis - meaning this module is the only module that is -directly configured in the `references.conf` module configuration, and our "sub-modules" are all installed via this -module. - -## 3 Key components - -### 3.1 Data model -To facilitate the REST API, a set of tables a relational database have been introduced. The tables store inbound and outbound -messages (documents) and corresponding log-entries, as well as account information allowing the RI to handle basic multi-tenancy where multiple C1s can use a single instance of an RI to send documents. -Out-of-the-box, the code is configured to use an in-memory H2 database, but this can and should be changed to use another database. - -> **_NOTE:_** We do not recommend using the H2 database in a production-like environment. See below for instructions how to switch -> to another database. - -The RI uses Liquibase to handle database schema changes. The database is automatically rolled forward on -application startup. To maximize database compatibility, we use Liquibase's XML-based changelog format. -Liquibase uses the same JDBC connection string and username/password as the running application, so the user needs the proper -schema privileges to make changes to tables etc. - -In Liquibase, database changes are added as changesets that represents some change to the database schema. Each -changeset is an independent change to the schema, for example 'add a new table' or 'add a new column to an existing table'. -Changesets can be qualified with a context to facilitate changesets that should only run in specific situations (e.g. -environment-specific data or unit test data etc). -Currently, there are no environment-specific Liquibase changesets except some for creating some Accounts for testing purposes, but in case this will be -relevant later, the code is prepared to use Liquibase's contexts feature to allow qualifying which changesets to run. -See the Configuration section below for how to specify it for a given environment. - -Technically, Hibernate/JPA is used through the Guice-persist module. This is configured in `JdbcModule`. -This allows us to work more logically with the data-model instead of raw JDBC Result sets. All entities inherits from -`AbstractEntity`, which handles equality/hashCode and audit columns. There is -an `AuditEntityListener` hooked up on the entity base class `AbstractEntity` which handles the -audit columns in all entities (created_by, created_date, updated_by, updated_date). - -The image below shows an ER diagram of the datamodel. - -![Oxalis ER-diagram](nemhandel-edelivery/oxalis-ER.png) - -A short description of each table is given below: - -| Table | Description | -|-----------------------|-------------------------------------------------------------------------------------------------------------------------------------------| -| Account | Table holding user accounts that can send/receive through the access point | -| Account_Receiver | Maps participant identifier to user accounts. This is used to match received documents to user accounts | -| Message | Holds metadata about the document being sent/received, including status of the message | -| Message_Content | Holds the actual content of a given document, both actual documents and receipts | -| databasechangelog | Liquibase system table that keeps track of which changesets have been applied to the database | -| databasechangeloglock | Liquibase system table which is used to handle concurrency and ensure only a single liquibase instance is updating the schema at the time | - - -#### 3.1.1 Database connection -You can customize the database configuration if you do not want to use the in-memory -database. The following example shows how you can configure the system to use a MySQL database: -``` -jdbc { - url: jdbc:mysql:... - driver.class.name: com.mysql.cj.jdbc.Driver - user: db-user - password: foobar - hibernate.dialect: org.hibernate.dialect.MySQL8Dialect - # connection pool size - pool.size: 10 - # liquibase contexts (defaults to none) - liquibase.contexts: some-custom-contexts -} -``` - -## 3.2 REST API -A REST API is exposed to facilitate both inbound and outbound message delivery. This allows C1 to send a message through the C2-part of the AP by calling the 'Outbox'-part of the REST API, and C4 can -fetch incoming documents from the C3-part of the AP via the 'Inbox'-part of the REST API. -The REST api is set up in the `ResteasyModule`. This REST api is implemented using the Resteasy library (which is an implementation of JAX-RS). Resteasy is integrated -with Guice through the `ResteasyServlet`. - -The API is protected by simple HTTP Basic Authentication which allows for simple multi-tenancy. This is achieved -via a simple Servlet Filter `SecurityFilter` which handles the HTTP basic Authentication and validates the supplied -credentials against the Account table. Password storage/hashing uses PBKDF2-HMAC-SHA256 based on the recommendations -from OWASP (see `PasswordHasher` for details). - -The logic behind each API endpoint is implemented in a vertical fashion, meaning there is a handler class that is -specific to a given endpoint, which implements the logic required, see f.x. `SendSbdHandler`. This allows an easy -overview of what code is relevant for a given endpoint. - -The API is documented via OpenAPI 3.0 and is exposed at the following urls when running locally: - -* OpenAPI Specification: http://localhost:8080/oxalis/openapi.json -* Swagger UI: http://localhost:8080/oxalis/openapi-ui - -## 3.3 Validation - using Schema and Schematron -Messages can be validated using a `MessageValidator`. The default Message validator will perform inspection of the -message content to determine which document type the message contains (e.g. an Invoice or an Order). This is done using -a `DocumentTypeResolver` which will return a `DocumentTypeConfig` configuration object that specifies which XSD to use -for schema validation and which Schematron files (if any) to use for validation of the actual message content. - -The `DocumentTypeResolver` will attempt to detect the document type using the following -1. The namespace and local name of the root element of the payload (e.g. `urn:oasis:names:specification:ubl:schema:xsd:Invoice-2` and `Invoice` respectively) -2. Zero or more XPath-based "identifier discriminators". Each discriminator consists of an XPath expression and an expected value or regex pattern. This is often used to match on the `CustomizationID` element (e.g. `CustomizationID = OIOUBL-2.1`). -3. In special cases where the document type cannot be uniquely resolved by the above, we need to introduce a `ProfileID` element (e.g. `ProfileID = urn:fdc:peppol.eu:2017:poacc:billing:01:1.0`) - -> **Note:** If the document type cannot be uniquely resolved the validation will fail. Thus, care must be taken when -> configuring the document types to make sure they are distinct. - -The default supported document types are configured in the `reference.conf` file. The configuration for each document -type is loaded as a `DocumentTypeConfig` in the `DocumentTypeModule` and the set of all `DocumentTypeConfig` is registered in Guice. -An example of the configuration for an Peppol BIS Credit Note is as follows -``` -document.type = { - peppolBISBillingCreditNote = { - friendlyName: "Peppol BIS Credit Note" - payloadRootLocalName: "CreditNote" - payloadRootNamespace: "urn:oasis:names:specification:ubl:schema:xsd:CreditNote-2" - schemaPath: "META-INF/Schemas/UBL_v2.1/maindoc/UBL-CreditNote-2.1.xsd" - identifierDiscriminators = [ - { - xpathExpression: "/sbd:StandardBusinessDocument/root:CreditNote/cbc:CustomizationID" - xpathExpectedResult: "urn:cen\\.eu:en16931:2017#compliant#urn:fdc:peppol\\.eu:2017:poacc:billing:3\\.0" - }, - { - xpathExpression: "/sbd:StandardBusinessDocument/root:CreditNote/cbc:ProfileID" - xpathExpectedResult: "urn:fdc:peppol\\.eu:2017:poacc:billing:01:1\\.0" - - }] - namespaces = [ - { namespace: "http://www.unece.org/cefact/namespaces/StandardBusinessDocumentHeader", prefix: "sbd" }, - { namespace: "urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2", prefix: "cac" }, - { namespace: "urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2", prefix: "cbc" }, - { namespace: "urn:oasis:names:specification:ubl:schema:xsd:CoreComponentParameters-2", prefix: "ccts" }, - { namespace: "urn:oasis:names:specification:ubl:schema:xsd:SpecializedDatatypes-2", prefix: "sdt"}, - { namespace: "urn:un:unece:uncefact:data:specification:UnqualifiedDataTypesSchemaModule:2", prefix: "udt"}, - { namespace: "urn:oasis:names:specification:ubl:schema:xsd:CreditNote-2", prefix: "root"} - ] - schematronDocuments = [ - { - schematronDocumentPath: "META-INF/Schematron/PEPPOL/CEN-EN16931-UBL.xslt" - errorXPath: "/svrl:schematron-output/svrl:failed-assert[@flag='fatal']" - errorMessageXPath: "svrl:text" - errorLocationXPath: "@location" - warningXPath: "/svrl:schematron-output/svrl:failed-assert[@flag='warning']" - warningMessageXPath: "svrl:text" - warningLocationXPath: "@location" - }, - { - schematronDocumentPath: "META-INF/Schematron/PEPPOL/PEPPOL-EN16931-UBL.xslt" - errorXPath: "/svrl:schematron-output/svrl:failed-assert[@flag='fatal']" - errorMessageXPath: "svrl:text" - errorLocationXPath: "@location" - warningXPath: "/svrl:schematron-output/svrl:failed-assert[@flag='warning']" - warningMessageXPath: "svrl:text" - warningLocationXPath: "@location" - }, - { - schematronDocumentPath: "META-INF/Schematron/PEPPOL/DK-EN16931-UBL.xslt" - errorXPath: "/svrl:schematron-output/svrl:failed-assert[@flag='fatal']" - errorMessageXPath: "svrl:text" - errorLocationXPath: "@location" - warningXPath: "/svrl:schematron-output/svrl:failed-assert[@flag='warning']" - warningMessageXPath: "svrl:text" - warningLocationXPath: "@location" - } - ] - } - - // other supported document types -} -``` -Once the document type has been determined the validation will perform schema (XSD) and schematron validation for the message. -Schematron validation will only be performed if the message passes schema validation. - -> **Note:** The schema and schematron validation will only be performed on the payload of the StandardBusinessDocument -> (e.g. an invoice or similar). - -As per the Peppol SBDH 2.0 specification, the SBDH must contain a business scope called -`COUNTRY_C1` with the ISO-3166-1 2-letter country code of the sender. The default `MessageValidator` validates -that the incoming message contains this scope and it contains a 2 letter country code. - -For Schematron validation it is possible to configure one or more schematron stylesheets. All configured stylesheets will be used during -validation. Additionally, for each schematron stylesheet the configuration will contain XPath expressions for -extracting errors and warnings from the resulting document. Note that it is optional to specify XPath expressions for -extracting warnings. - -#### 3.3.1 Adding additional document types -All that is needed to add validation support for additional document types is to add it to the configuration in your `oxalis.conf` like so (some details have been omitted for brevity) - -``` -document.type.myNewDocumentType = { - friendlyName: "My custom document type" - payloadRootLocalName: "CreditNote" - payloadRootNamespace: "urn:oasis:names:specification:ubl:schema:xsd:CreditNote-2" - schemaPath: "path/to/schema.xsd" - identifierDiscriminators = [...] - namespaces = [ - { namespace: "http://www.unece.org/cefact/namespaces/StandardBusinessDocumentHeader", prefix: "sbd" }, - { namespace: "urn:oasis:names:specification:ubl:schema:xsd:CreditNote-2", prefix: "root"} - ... - ] - schematronDocuments = [ - { - schematronDocumentPath: "path/to/schematron.xslt" - errorXPath: "..." - errorMessageXPath: "..." - errorLocationXPath: "..." - } - ] -} -``` -The above configuration will automatically loaded at runtime and the Nemhandel e-Delivery reference access point will recognize the given document type. -> *NOTE*: The Nemhandel e-Delivery reference access point can support practically any document type as long as you can specify sufficient identification discriminators. -> However, to be of practical use the document type must be supported by the Nemhandelsregister. - - -#### 3.3.2 Supported document types -By default, the Nemhandel eDelivery RI supports the following document types in terms of schema and Schematron validation - -| Document type | Configuration property | Payload namespace | Payload local name | Identifier discriminators | -|------------------------------------------------|---------------------------------------------------|-----------------------------------------------------------------------------------|------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| OIOUBL Reminder 2.1 | `document.type.reminder` | `urn:oasis:names:specification:ubl:schema:xsd:Reminder-2` | `Reminder` | `/sbd:StandardBusinessDocument/root:Reminder/cbc:CustomizationID` = `OIOUBL-2.(01|02|1)` | -| OIOUBL Utility Statement 2.1 | `document.type.utilityStatement` | `urn:oasis:names:specification:ubl:schema:xsd:UtilityStatement-2` | `UtilityStatement` | `/sbd:StandardBusinessDocument/root:UtilityStatement/cbc:CustomizationID` = `OIOUBL-2.1` | -| OIOUBL Invoice 2.1 | `document.type.invoice` | `urn:oasis:names:specification:ubl:schema:xsd:Invoice-2` | `Invoice` | `/sbd:StandardBusinessDocument/root:Invoice/cbc:CustomizationID` = `OIOUBL-2.(01|02|1)` | -| OIOUBL Credit Note 2.1 | `document.type.creditNote` | `urn:oasis:names:specification:ubl:schema:xsd:CreditNote-2` | `CreditNote` | `/sbd:StandardBusinessDocument/root:CreditNote/cbc:CustomizationID` = `OIOUBL-2.(01|02|1)` | -| OIOUBL Application Response 2.1 | `document.type.applicationResponse` | `urn:oasis:names:specification:ubl:schema:xsd:ApplicationResponse-2` | `ApplicationResponse` | `/sbd:StandardBusinessDocument/root:ApplicationResponse/cbc:CustomizationID` = `OIOUBL-2.(01|02|1)` | -| OIOUBL Statement 2.1 | `document.type.statement` | `urn:oasis:names:specification:ubl:schema:xsd:Statement-2` | `Statement` | `/sbd:StandardBusinessDocument/root:Statement/cbc:CustomizationID` = `OIOUBL-2.(01|02|1)` | -| OIOUBL Order 2.1 | `document.type.order` | `urn:oasis:names:specification:ubl:schema:xsd:Order-2` | `Order` | `/sbd:StandardBusinessDocument/root:Order/cbc:CustomizationID` = `OIOUBL-2.(01|02|1)` | -| OIOUBL Order Response Simple 2.1 | `document.type.orderResponseSimple` | `urn:oasis:names:specification:ubl:schema:xsd:OrderResponseSimple-2` | `OrderResponseSimple` | `/sbd:StandardBusinessDocument/root:OrderResponseSimple/cbc:CustomizationID` = `OIOUBL-2.(01|02|1)` | -| OIOUBL Order Response 2.1 | `document.type.orderResponse` | `urn:oasis:names:specification:ubl:schema:xsd:OrderResponse-2` | `OrderResponse` | `/sbd:StandardBusinessDocument/root:OrderResponse/cbc:CustomizationID` = `OIOUBL-2.(01|02|1)` | -| OIOUBL Order Cancellation 2.1 | `document.type.orderCancellation` | `urn:oasis:names:specification:ubl:schema:xsd:OrderCancellation-2` | `OrderCancellation` | `/sbd:StandardBusinessDocument/root:OrderCancellation/cbc:CustomizationID` = `OIOUBL-2.(01|02|1)` | -| OIOUBL Order Change 2.1 | `document.type.orderChange` | `urn:oasis:names:specification:ubl:schema:xsd:OrderChange-2` | `OrderChange` | `/sbd:StandardBusinessDocument/root:OrderChange/cbc:CustomizationID` = `OIOUBL-2.(01|02|1)` | -| OIOUBL Catalogue 2.1 | `document.type.catalogue` | `urn:oasis:names:specification:ubl:schema:xsd:Catalogue-2` | `Catalogue` | `/sbd:StandardBusinessDocument/root:Catalogue/cbc:CustomizationID` = `OIOUBL-2.(01|02|1)` | -| OIOUBL Cataloge Deletion 2.1 | `document.type.catalogueDeletion` | `urn:oasis:names:specification:ubl:schema:xsd:CatalogueDeletion-2` | `CatalogueDeletion` | `/sbd:StandardBusinessDocument/root:CatalogueDeletion/cbc:CustomizationID` = `OIOUBL-2.(01|02|1)` | -| OIOUBL Catalogue Request 2.1 | `document.type.catalogueRequest` | `urn:oasis:names:specification:ubl:schema:xsd:CatalogueRequest-2` | `CatalogueRequest` | `/sbd:StandardBusinessDocument/root:CatalogueRequest/cbc:CustomizationID` = `OIOUBL-2.(01|02|1)` | -| OIOUBL Catalogue Item Specification Update 2.1 | `document.type.catalogueItemSpecificationUpdate` | `urn:oasis:names:specification:ubl:schema:xsd:CatalogueItemSpecificationUpdate-2` | `CatalogueItemSpecificationUpdate` | `/sbd:StandardBusinessDocument/root:CatalogueItemSpecificationUpdate/cbc:CustomizationID` = `OIOUBL-2.(01|02|1)` | -| OIOUBL Catalogue Pricing Update 2.1 | `document.type.cataloguePricingUpdate` | `urn:oasis:names:specification:ubl:schema:xsd:CataloguePricingUpdate-2` | `CataloguePricingUpdate` | `/sbd:StandardBusinessDocument/root:CataloguePricingUpdate/cbc:CustomizationID` = `OIOUBL-2.(01|02|1)` | -| DK Peppol Catalogue Pricing Update | `document.type.cataloguePricingUpdateDKPeppol` | `urn:oasis:names:specification:ubl:schema:xsd:CataloguePricingUpdate-2` | `CataloguePricingUpdate` | `/sbd:StandardBusinessDocument/root:CataloguePricingUpdate/cbc:CustomizationID` = `urn:fdc:peppol.eu:poacc:trns:cataloguepricingupdate:3` | -| DK Peppol Catalogue Update Response | `document.type.catalogueUpdateResponseDKPeppol` | `urn:oasis:names:specification:ubl:schema:xsd:ApplicationResponse-2` | `ApplicationResponse` | `/sbd:StandardBusinessDocument/root:ApplicationResponse/cbc:CustomizationID` = `urn:fdc:peppol.eu:poacc:trns:catalogue_update_response:3` | -| DK Peppol Order Agreement | `document.type.orderAgreementPeppol` | `urn:oasis:names:specification:ubl:schema:xsd:OrderResponse-2` | `OrderResponse` | `/sbd:StandardBusinessDocument/root:OrderResponse/cbc:CustomizationID` = `urn:fdc:peppol.eu:poacc:trns:order_agreement:3` | -| DK Peppol Order Agreement Response | `document.type.orderAgreementResponseDKPeppol` | `urn:oasis:names:specification:ubl:schema:xsd:ApplicationResponse-2` | `ApplicationResponse` | `/sbd:StandardBusinessDocument/root:ApplicationResponse/cbc:CustomizationID` = `urn:fdc:peppol.eu:poacc:trns:order_agreement_response:3` | -| OIOUBL Reminder 3.0 | `document.type.reminder30` | `urn:oasis:names:specification:ubl:schema:xsd:Invoice-2` | `Invoice` | `/sbd:StandardBusinessDocument/root:Invoice/cbc:CustomizationID` = `urn:cen.eu:en16931:2017#compliant#urn:fdc:peppol.eu:2017:poacc:billing:3.0#conformant#urn:fdc:oioubl.dk:2020:oioubl:reminder:3.0` | -| OIOUBL Simplified Invoice 3.0 | `document.type.simplifiedInvoice30` | `urn:oasis:names:specification:ubl:schema:xsd:Invoice-2` | `Invoice` | `/sbd:StandardBusinessDocument/root:Invoice/cbc:CustomizationID` = `urn:cen.eu:en16931:2017#compliant#urn:fdc:peppol.eu:2017:poacc:billing:3.0#conformant#urn:fdc:oioubl.dk:2020:oioubl:simplifiedinvoice:3.0` | -| OIOUBL Invoice Response Transaction 3.0 | `document.type.invoiceResponseTransaction30` | `urn:oasis:names:specification:ubl:schema:xsd:ApplicationResponse-2` | `ApplicationResponse` | `/sbd:StandardBusinessDocument/root:ApplicationResponse/cbc:CustomizationID` = `urn:fdc:peppol.eu:poacc:trns:invoice_response:3` | -| OIOUBL Message Level Response Transaction 3.0 | `document.type.messageLevelResponseTransaction30` | `urn:oasis:names:specification:ubl:schema:xsd:ApplicationResponse-2` | `ApplicationResponse` | `/sbd:StandardBusinessDocument/root:ApplicationResponse/cbc:CustomizationID` = `urn:fdc:peppol.eu:poacc:trns:mlr:3` | -| Peppol BIS Billing - Invoice | `document.type.peppolBISBillingInvoice` | `urn:oasis:names:specification:ubl:schema:xsd:Invoice-2` | `Invoice` | `/sbd:StandardBusinessDocument/root:Invoice/cbc:CustomizationID` = `urn:cen.eu:en16931:2017#compliant#urn:fdc:peppol.eu:2017:poacc:billing:3.0` in conjunction with `/sbd:StandardBusinessDocument/root:Invoice/cbc:ProfileID` = `urn:fdc:peppol.eu:2017:poacc:billing:01:1.0` | -| Peppol BIS Billing - Credit Note | `document.type.peppolBISBillingCreditNote` | `urn:oasis:names:specification:ubl:schema:xsd:CreditNote-2` | `CreditNote` | `/sbd:StandardBusinessDocument/root:CreditNote/cbc:CustomizationID` = `urn:cen.eu:en16931:2017#compliant#urn:fdc:peppol.eu:2017:poacc:billing:3.0` in conjunction with `/sbd:StandardBusinessDocument/root:CreditNote/cbc:ProfileID` = `urn:fdc:peppol.eu:2017:poacc:billing:01:1.0` | -| Peppol Catalogue | `document.type.peppolCatalogue` | `urn:oasis:names:specification:ubl:schema:xsd:Catalogue-2` | `Catalogue` | `/sbd:StandardBusinessDocument/root:Catalogue/cbc:CustomizationID` = `urn:fdc:peppol.eu:poacc:trns:catalogue:3` | -| Peppol Catalogue Response | `document.type.peppolCatalogueResponse` | `urn:oasis:names:specification:ubl:schema:xsd:ApplicationResponse-2` | `ApplicationResponse` | `/sbd:StandardBusinessDocument/root:ApplicationResponse/cbc:CustomizationID` = `urn:fdc:peppol.eu:poacc:trns:catalogue_response:3` | -| Peppol Order | `document.type.peppolOrder` | `urn:oasis:names:specification:ubl:schema:xsd:Order-2` | `Order` | `/sbd:StandardBusinessDocument/root:Order/cbc:CustomizationID` = `urn:fdc:peppol.eu:poacc:trns:order:3` | -| Peppol Order Response | `document.type.peppolOrderAgreement` | `urn:oasis:names:specification:ubl:schema:xsd:OrderResponse-2` | `OrderResponse` | `/sbd:StandardBusinessDocument/root:OrderResponse/cbc:CustomizationID` = `urn:fdc:peppol.eu:poacc:trns:order_agreement:3` | -| Peppol Despatch Advice | `document.type.peppolDespatchAdvice` | `urn:oasis:names:specification:ubl:schema:xsd:DespatchAdvice-2` | `DespatchAdvice` | `/sbd:StandardBusinessDocument/root:DespatchAdvice/cbc:CustomizationID` = `urn:fdc:peppol.eu:poacc:trns:despatch_advice:3` | -| Peppol Punch Out | `document.type.peppolPunchOut` | `urn:oasis:names:specification:ubl:schema:xsd:Catalogue-2` | `Catalogue` | `/sbd:StandardBusinessDocument/root:Catalogue/cbc:CustomizationID` = `urn:fdc:peppol.eu:poacc:trns:punch_out:3` | -| Peppol Order Change | `document.type.peppolOrderChange` | `urn:oasis:names:specification:ubl:schema:xsd:OrderChange-2` | `OrderChange` | `/sbd:StandardBusinessDocument/root:OrderChange/cbc:CustomizationID` = `urn:fdc:peppol.eu:poacc:trns:order_change:3` | -| Peppol Order Cancellation | `document.type.peppolOrderCancellation` | `urn:oasis:names:specification:ubl:schema:xsd:OrderCancellation-2` | `OrderCancellation` | `/sbd:StandardBusinessDocument/root:OrderCancellation/cbc:CustomizationID` = `urn:fdc:peppol.eu:poacc:trns:order_cancellation:3` | - -For additional information regarding the XSD and schematron files used for these document types see the configuration in `reference.conf`. - -#### 3.3.3 Overriding the built-in document types -The built-in, supported document types from the previous section can changed by specifying the relevant properties in your `oxalis.conf` file. This can be useful if you want to update e.g. the Schematron stylesheet for a given document type. -As as example, assume a new schematron stylesheet has been released for the OIOUBL Invoice 2.1 document type and you need to use this instead of the built-in one. As specified in the table of the built-in document types in the previous section, -the OIOUBL Invoice 2.1 document type is defined by the `document.type.invoice` configuration property. Changing the schematron file for this document type is as simple as adding the following to your `oxalis.conf` file: -``` -document.type.invoice.schematronDocuments = [ - { - schematronDocumentPath: "path/to/invoice/schematron.xslt" - errorXPath: "/Schematron/Error" - errorMessageXPath: "Description" - errorLocationXPath: "Xpath" - } - ] -} -``` -> **NOTE**: for this example, it is assumed that the new schematron produces an equivalent result document as the old schematron (i.e. error messages are located in the same place). -> In case the resulting document is different, you have to change the `errorXPath`, `errorMessageXPath`, `errorLocationXPath` accordingly. - -All configuration properties for the built-in document types can be changed in a similar fashion. Please refer to the `reference.conf` file to see which configuration properties are available for the document types. - - -### 3.4 Signing and processing the Nemhandel e-Delivery signature (ASiC-E) -The RI supports processing documents containing Nemhandel e-Delivery signatures when started in Nemhandel mode. -It is currently not mandatory for C1 to include a Nemhandel e-Delivery signature in documents handed over to the RI using the Outbox REST API, but if present, the RI will validate the signature(s). - -The RI processes Nemhandel e-Delivery signatures only when started in Nemhandel e-Delivery mode (`NEMHANDEL_TEST` or `NEMHANDEL_PRODUCTION`). -The Nemhandel e-Delivery signature is contained in the header part of the SBD message in the shape of an ASiC-E container. -An ASiC-E container is basically a ZIP file containing the documents to be signed as well as the signatures and metadata. An ASiC-E container can be signed in various "formats" but Nemhandel e-Delivery uses the XAdES format. - -The ASiC-E container containing the signature must be placed in the Standard Business Document Header under the `` element with the type `NEMHANDEL_EDELIVERY_SIGNATURE`. The content of the ASiC-E container should be Base64 encoded and placed in the `` element like the following snippet: -```xml - - - ... - ... - ... - ... - - - DOCUMENTID - ... - - - PROCESSID - ... - - - COUNTRY_C1 - DK - - - NEMHANDEL_EDELIVERY_SIGNATURE - - - - - ... - -``` -> Note: there must only be one `NEMHANDEL_EDELIVERY_SIGNATURE` scope in a given SBD. - -The contents of the ASiC-E container is expected to be the following: -``` -asic-container.asice: - | - +-- mimetype - | - +-- standard-business-document.xml (2) - | - +-- original-standard-business-document.xml (3) - | - +-- transfer-delegation.xml (4) - | - +-- META-INF/ - | - +-- manifest.xml - | - +-- signatures001.xml (1) -``` -The following files are of specific interest: -1. The actual signature of the ASiC-E container is contained in this file. An ASiC-E container may contain multiple signatures though we only require one, which must include the following 3 files (1 optional). -2. The Standard Business Document (SBD) to be signed (thus this is excluding the ASiC-E container). This is the file that Corner X will send to Corner X+1. -3. The full "original" Standard Business Document including the ASiC-E container which has been received from Corner X-1. Note that for Corner 1 this file is optional in case this corner is responsible for generating the original SBD. For C2 this file should only be included if C1's SBD is signed! -4. A transfer delegation documents the intention of Corner X to transfer the SBD to Corner X+1. - -> Note: C1 is currently allowed to send unsigned SBDs through the C2 `/outbox` REST API. If C1 sends an unsigned SBD it will **_not_** be included in the ASiC-E container as `original-standard-business-document.xml`, because its contents cannot be trusted by C3 and C4 anyway. -> If C1 sends a signed SBD, C2 will include it as `original-standard-business-document.xml`. For C3, a document signature is required. Thus, C2 must always sign the document before sending it to C3 regardless C1s signature. - -Each Corner X will effectively have to resign the standard business document since it must at least produce a new transfer delegation between itself and Corner X+1. -Further, if Corner X wants to transform/convert the SBD in any way it must also resign the resulting document. -This includes producing and including a new ASiC-E container which must include the raw original SBD (including the ASiC-E container from Corner X-1). - -This is illustrated in the following figure: -![Nemhandel e-Delivery signature - resign example](nemhandel-edelivery/nemhandel-signature-resign-example.png) - -The Nemhandel e-Delivery signature is recursively validated. As an example consider validation (by C3) of the C2 SBD (righthand side of the above figure. In this case we assume that C1 has also signed the SBD). -First C2's ASiC-E container will be validated. Since C2's ASiC-E container contains `original-standard-business-document.xml` (C1's SBD) then the ASiC-E container of this document will be validated as well and so on until all nested ASiC-E containers have been validated. - -The following is a rough sketch of the algorithm used to validate this container - -1. Verify integrity of ASiC-E container - 1. Verify the signature - 2. Verify the signature certificate is a valid MitID certificate -2. Verify ASiC-E container contains the required files: - 1. standard-business-document.xml - 2. transfer-delegation.xml -3. Verify standard-business-document.xml matches SBD (minus the ASiC-E container). Example: this will verify the C2 SBD on the righthand side of the above figure (red text) minus the `NEMHANDEL_EDELIVERY_SIGNATURE` Scope element, matches the content of standard-business-document.xml -4. Verify transfer-delegation.xml -5. If original-standard-business-document.xml is present, perform step 1-5 on that document as well. - -The [Digital Signature Service (DSS)](https://github.com/esig/dss) framework is used both in the creation and validation of the ASiC-E containers (specifically the dss-asic-xades submodule of the framework). - -The signature validation code is found in the `dk.erst.oxalis.as4.validation.signature` and `dk.erst.oxalis.as4.signature` packages and are registered Guice through [DSSModule](../src/main/java/dk/erst/oxalis/as4/validation/signature/DSSModule.java). - -#### 3.4.1 Signature certificates -The certificates used to in the creation of a Nemhandel e-Delivery signature must be valid MitID certificates (FOCES3), and must be issued by the appropriate Certificate Authority and must contain a CVR-number. -For MitID certificates the CVR-number can be found in the Organization Identifier (OID.2.5.4.97) attribute of the subject. -See the truststores with the relevant CA certificates in [src/main/resources/truststore](../src/main/resources/truststore). - -In terms of the Nemhandel e-Delivery reference AP, the signature creation will use the private key of the AP's configured certificate. -The two added modes NEMHANDEL_TEST and NEMHANDEL_PRODUCTION are only applicable when the AP is configured to use a MitID certificate (see [Support for MitID system certificates using mode detection](#363-support-for-mitid-system-certificates-using-mode-detection) for further details). - - -#### 3.4.2 Transfer delegation -A transfer delegation describes Corner X's intention of transferring a standard business document to the next Corner X+1. -Thus, it only covers the transfer between two consecutive Corners and then a new transfer delegation will cover the transfer between the next two Corners. - -It is an XML document containing information about a sender and a receiver. -The following is an example of a transfer delegation from CVR 12345678 to CVR 98765432. -```xml - - - 12345678 - 98765432 - -``` - -Currently, both the sender and the receiver must be identified by a CVR number, thus only `SchemeID = DK:CVR` is supported. - -When validating a transfer delegation at a given recursion level, the sender CVR must match the CVR number of the MitID certificate used to sign the ASiC-E container at that level. -This serves to prove that transfer delegation is, in fact, created by the sender (and thus can be trusted) because only the actual sender (as per CVR) will be able to sign the ASiC-E container (and thus also the transfer delegation) with a valid MitID certificate containing that CVR. - -Further, for the top-level transfer delegation (recursion level 0) the receiver of the transfer delegation must match the CVR number of the MitID certificate which is configured for the access point. -This check is intended to verify that the access point is the intended receiver of the standard business document. -For non-top-level transfer delegations (recursion level > 0) the receiver of the transfer delegation should match the CVR number of the signing certificate for the ASiC-E container and the sender CVR number of the previous recursion level (because the validation is going in top-down order). -This ensures that transfer delegation "chain" is unbroken and no unexpected CVR number appears in the chain. - -The creation of transfer delegations is handled by [TransferDelegationFactoryImpl](../src/main/java/dk/erst/oxalis/as4/signature/TransferDelegationFactoryImpl.java) and validation is handled as part of the signature validation in [SignatureValidatorImpl](../src/main/java/dk/erst/oxalis/as4/validation/signature/SignatureValidatorImpl.java). - -### 3.5 Asynchronous processing -In order to facilitate Message Level Responses regarding validation errors in C3, Oxalis has been extended with support for asynchronous processing of messages received. -The validation of messages has been split into a "synchronous" and "asynchronous" part. - -| Validation type | Validations | -|-----------------|--------------------------------------------| -| Synchronous | Schema (XSD) | -| Asynchronous | Schematron, Nemhandel e-Delivery signature | - -> **Note:** The split of the validation only affects the processing when receiving messages (C3). When sending messages (C2) the Oxalis reference AP will perform all validations synchronously. - -#### 3.5.1 Simple asynchronous processing (thread-based) and Message Level Responses -A simple asynchronous processing mechanism is based on the java ExecutorService interface has been impleemented in the RI. -During startup the RI will create a thread-pool-based ExecutorService of a configurable size. -This ExecutorService maintains a queue of asynchronous processing tasks and will execute these as threads available in its thread-pool. - -> **NOTE:** the simple asynchronous processing mechanism is *neither* persistent nor durable. -> Further, it contains only very limited error handling and there is no failure recovery or retry functionality. -> If errors do occur, we will do our best to mark messages with an ERROR status but even this may fail in extreme cases. - - -The simple asynchronous processing is configurable using the following properties. - -| Property | Type | Purpose | -|---------------------------------------------|--------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| async.executor.pool.size.core | Number | The core number of threads in the thread-pool | -| async.executor.pool.keep.alive.time.ms | Number | The time (in ms) to keep idle threads alive waiting for new tasks before they are terminated (when the thread-pool has more than the core number of threads) | -| async.executor.graceful.shutdown.timeout.ms | Number | The time (in ms) to wait for termination of in-progress tasks, after a shutdown has been requested, before forcefully shutting down the ExecutorService (including all threads in the pool). | -| async.executor.runnable.factory | String | The named Guice binding for the implementation of the [AsyncProcessingRunnableFactory](../src/main/java/dk/erst/oxalis/as4/async/AsyncProcessingRunnableFactory.java) interface to use for instantiating tasks (e.g. instances of the `Runnable` interface). Use this to override the default asynchronous processing. | - -The default values for these can be seen in [ExecutorServiceConf](../src/main/java/dk/erst/oxalis/as4/async/ExecutorServiceConf.java). - -When a new message is received via the AS4-protocol, the AP will perform the "synchronous" part of the validation and save the message in the database. -If the message is ok according to this validation, the message will be marked with status PROCESSING and finally be queued for asynchronous processing before a synchronous AS4-response is returned to C2. - -The asynchronous processing will start once the task reaches the front of the queue and a thread becomes available. The default asynchronous processing will include the following steps: - -1. Perform the "asynchronous" part of the message validation - currently Schematron validation and signature validation -2. Update message status based on validation result -3. If no validation errors (only in Nemhandel Mode) - 1. Create transport delegation for C3 -> C4 - 2. Resign document (i.e. create Nemhandel e-Delivery signature) - 3. Save the resigned document -4. Generate and send an appropriate Message Level Response back to C2 if necessary - -For implementation details please refer to [AsyncProcessingRunnable](../src/main/java/dk/erst/oxalis/as4/async/AsyncProcessingRunnable.java). - -#### 3.5.2 Overriding the default asynchronous processing (thread-based) -The asynchronous processing mechanism can be overridden using the `async.executor.runnable.factory` configuration property mentioned above. - -First, create your custom implementation of the `Runnable` interface. -```java -public class CustomRunnable implements Runnable { - // your custom async processing implementation -} -``` - -Next, create a custom implementation of the `AsyncProcessingRunnableFactory` interface which instantiates your `CustomRunnable` for a given message. - -```java -public class CustomAsyncProcessingFactory implements AsyncProcessingRunnableFactory { - - private final Injector injector; - - public CustomAsyncProcessingFactory(Injector injector) { - this.injector = injector; - } - - @Override - public Runnable createRunnable(String messageUuid) { - return new CustomRunnable(injector, messageUuid); - } -} -``` -You are free to inject any registered Guice bindings into your `AsyncProcessingRunnableFactory` and provide these to your `CustomRunnable` - the above example just provides the full Guice `Injector` to the `CustomRunnable`. - -> NOTE: When using JPA in your `CustomRunnable` take care to handle the EntityManager properly. -> Prefer to use a thread-local EntityManager and perform the appropriate cleanup when your `CustomRunnable` terminates. - -Next, create a new Guice module which registers a named binding for the `CustomAsyncProcessingFactory`. The example below uses a -`@Provides` method, but the binding DSL can also be used if preferred. - -```java -public class CustomAsyncProcessingModule extends AbstractModule { - - @Provides - @Singleton - @Named("custom-async-factory") - AsyncProcessingRunnableFactory customAsyncProcessingFactory(Injector injector) { - return new CustomAsyncProcessingFactory(injector); - } -} -``` -> NOTE: the example uses the name `custom-async-factory` for the binding, but you can use whichever name you like. - -In order to get Oxalis to load this module, include a `reference.conf` file in the `src/main/resources` folder with the -following content -```properties -# the can be whatever your prefer your module to be called -oxalis.module..class = com.example.CustomAsyncProcessingModule -``` -Package the code as a jar file and place the jar on the classpath for Oxalis to load. - -Finally, configure the simple asynchronous processing mechanism in your `${OXALIS_HOME}\oxalis.conf` file using the -name of the binding from the module. -```properties -async.executor.runnable.factory = custom-async-factory -``` -The `CustomAsyncProcessingFactory` should now be used at runtime to create asynchronous processing tasks. -Thus, messages will be asynchronously processed using the `CustomRunnable` implementation instead of the default [AsyncProcessingRunnable](../src/main/java/dk/erst/oxalis/as4/async/AsyncProcessingRunnable.java) implementation. - -#### 3.5.3 Message Level Response -In the event of validation errors during the asynchronous processing, the AP will generate a Message Level Response which will be sent back to the sender of the document. -A Message Level Response will list all validation errors as well as reference the id of the original document. - -> NOTE: currently, no message level response is sent for documents which contain no validation errors. - -An example of an (unsigned) message level response is shown here: -```xml - - - - 1.0 - - 0184:22334455 - - - 0184:12345678 - - - urn:oasis:names:specification:ubl:schema:xsd:ApplicationResponse-2 - 2.1 - 76ce2330-39b2-412d-a76c-02dbc6164066 - ApplicationResponse - 2023-03-09T08:16:37.216+01:00 - - - - DOCUMENTID - urn:oasis:names:specification:ubl:schema:xsd:ApplicationResponse-2::ApplicationResponse##urn:fdc:peppol.eu:poacc:trns:mlr:3::2.1 - - - PROCESSID - urn:fdc:peppol.eu:poacc:bis:mlr:3 - - - COUNTRY_C1 - DK - - - - - urn:fdc:peppol.eu:poacc:trns:mlr:3 - urn:fdc:peppol.eu:poacc:bis:mlr:3 - 76ce2330-39b2-412d-a76c-02dbc6164066 - 2023-03-09 - 08:16:37+01:00 - - 22334455 - - - 12345678 - - - - RE - Document rejected due to validation errors - - - a2806f03-57d6-4aa1-b1fe-907f3a024bb1 - 1 - 2 - - - - /sbd:StandardBusinessDocument/sbd:StandardBusinessDocumentHeader/sbd:BusinessScope/sbd:Scope[sbd:Type='NEMHANDEL_EDELIVERY_SIGNATURE']/sbd:InstanceIdentifier - - - RE - E-REF21001: No Nemhandel e-Delivery documentsignature found for level 0 - - BV - - - - - - -``` - -The `DocumentReference/ID` element contains the id of the original document to which the message level response corresponds and the `LineResponse` elements lists the individual validation errors. - -Since a message level response is a new document, it is subject to the same requirements as all other documents in Nemhandel e-Delivery. That is, it must pass schema and schematron validation and be signed according to the description in [Nemhandel e-Delivery Signature (ASiC-E)](#27-nemhandel-e-delivery-signature-asic-e). - -When generating the message level response, a country code of the sender of the message level response must be included -in the response. This is sourced from the `Account` row that the incoming message is related to. - -#### 3.5.4 OIOUBL Application Response -The Message Level Response mentioned in the previous section is converted to an OIOUBL Application Response if the "original" -document is a OIOUBL document (e.g. an OIOUBL Invoice). For details regarding which response type is used for a specific document type, -please refer to the `messageLevelResponseType` configuration property for each document type. - -The conversion is performed using the [MLR_2_OIOUBL_ApplicationResponse](../src/main/resources/META-INF/Conversion/MLR_2_OIOUBL_ApplicationResponse.xslt) XSLT. - - -## 3.6 Misc. information -Apart from the key component described in the previous section, a number of minor new features have been added. - -### 3.6.1 NemHandel eDelivery transport profile support -Oxalis has been extended with support for the transport profile `nemhandel-transport-as4-v2_0`. -The transport profile is defined in [reference.conf](../src/main/resources/reference.conf). -This transport profile is similar to Peppols `peppol-transport-as4-v2_0` transport profile in that it relies -on the same AS4 protocol for the transmission. - -> **Note:** By default the `nemhandel-transport-as4-v2_0` transport profile is only enabled when -the Nemhandel e-Delivery Reference Access Point is started in Nemhandel mode. In this case, it will -be prioritized over Peppols `peppol-transport-as4-v2_0` transport profile. - - -### 3.6.2 Configuration -Configuration of the additional features, e.g. database connection, is done through the oxalis.conf file -that you set up as part of an Oxalis installation. - - -A requirement for this to work is that you install the relevant JDBC driver (in this case MySQL) in Tomcat's lib -folder. It is possible to distribute the JDBC driver together with the Oxalis-AS4 plugin but this requires code changes, -as you have to register the JDBC-driver manually (like we do with the H2 JDBC driver in `JdbcModule`). This is due to -the way Oxalis-AS4 is loaded by Tomcat in relation to the Oxalis war file. - - -### 3.6.3 Support for MitID system certificates using mode detection -The RI comes with a mode concept, which defines a number of properties for the trust store, certificate validation, etc. -Certificate validation is a recipe for which validations must take place on or certificates that are used in AP management (i.e. AS4 transport) and in SMP management (i.e. in connection with SMP). -These perform issuer check, certificate expiration, certificate chains (root and intermediate CA), CRL, OCSP, etc. - -There are two modes TEST mode and a PRODUCTION mode. Both of these built-in modes are for Peppol certificates and the primary difference is that TEST mode supports Peppol test certificates and PRODUCTION mode supports Peppol production certificates.  -At startup, Oxalis will automatically detect the active mode based on the certificate configured by the AP. - -Since the reference AP is configured in either "Peppol mode" or "Nemhandel mode", we have introduced two new TEST and PRODUCTION modes to support Nemhandel e-Delivery: NEMHANDEL_TEST and NEMHANDEL_PRODUCTION. - -NEMHANDEL_TEST supports MitID test certificates (which are issued by MitID's test CA) and NEMHANDEL_PRODUCTION will support certificates issued by MitID's production CA. Like the built-in Peppol modes, Nemhandel modes will also perform CRL checks. - - -### 3.6.4 Receiver exceptions -If an error occurs on the receiver side during reception of the SOAP message, exceptions will be thrown. Oxalis -automatically wraps these in an ebMS 3.0-compliant SoapFault. This is also utilized for exceptions thrown by NemHandel -eDelivery code, specifically the synchronous part. That is to say, if an exception occurs while processing the message -synchronously (e.g. XSD schema validation error, database connection issue or similar) we throw a proper -`IOException` from `NemHandelPersister` since these are automatically converted to an ebMS 3.0-compliant SoapFault -with error code `EBMS:0004`. - -## 3.6.5 Automatic update notifications (version validation) -This RI comes with functionality to validate the running application version and the schematron version. -If a version is outdated or is soon to be outdated a notification will be shown in the application logs telling it is time to update. - -> **Note: If a mandatory version is outdated it will NOT be possible to send any messages until the version is updated.** - -Note that the information about new versions is delivered by the Nemhandel SMP service. If another SMP service is being used, -there will most likely not be any version information available. diff --git a/docs/nemhandel-edelivery/nemhandel-signature-resign-example.png b/docs/nemhandel-edelivery/nemhandel-signature-resign-example.png deleted file mode 100644 index 78306ddb22e614ac6826de483f586d275e6830e6..0000000000000000000000000000000000000000 Binary files a/docs/nemhandel-edelivery/nemhandel-signature-resign-example.png and /dev/null differ diff --git a/docs/nemhandel-edelivery/oxalis-ER.png b/docs/nemhandel-edelivery/oxalis-ER.png deleted file mode 100644 index 48fb75be9d5239efd3d6f1d26ba7123a03455436..0000000000000000000000000000000000000000 Binary files a/docs/nemhandel-edelivery/oxalis-ER.png and /dev/null differ diff --git a/docs/nemhandel-edelivery/oxalis-overview-packages.png b/docs/nemhandel-edelivery/oxalis-overview-packages.png deleted file mode 100644 index 8406992c4974d5e9d552906d57f6c442896ed7f4..0000000000000000000000000000000000000000 Binary files a/docs/nemhandel-edelivery/oxalis-overview-packages.png and /dev/null differ diff --git a/pom.xml b/pom.xml index ce15262d28983bb8b8df0f8478b8d9cf8e7bb740..7eb5eb1477014a460aaa8d03644836ae45228999 100755 --- a/pom.xml +++ b/pom.xml @@ -27,12 +27,12 @@ dk.erst.oxalis oxalis - 1.1.1-cdcd75c57f1c18846d6950469e8f4fb53b3b8584 + 1.2.0-RC-482e9dc69c402ff235d9a34dba51310428093e10 dk.erst.oxalis oxalis-as4 - 1.1.1 + 1.2.0-RC jar Oxalis :: AS4 Extension adding Nemhandel e-Delivery AS4 support to Oxalis @@ -79,7 +79,7 @@ 2.23.4 4.7.7.Final - 5.11.1 + 5.13 3.1.8 @@ -421,10 +421,10 @@ 10.3 - + eu.europa.ec.joinup.sd-dss - dss-asic-xades + dss-xades ${dss.version} @@ -447,6 +447,12 @@ dss-crl-parser-stream ${dss.version} + + eu.europa.ec.joinup.sd-dss + dss-asic-xades + ${dss.version} + test + @@ -529,11 +535,11 @@ gson 2.10 - + - org.apache.maven.plugins - maven-javadoc-plugin - 3.5.0 + org.apache.maven + maven-artifact + 3.9.5 @@ -614,12 +620,6 @@ 2.9.0 test - - org.jetbrains - annotations - 23.0.0 - compile - @@ -637,19 +637,6 @@ jaxb2-maven-plugin 2.3 - - xjc - - xjc - - - - src/main/resources/META-INF/Schemas/transfer-delegation/transferDelegation.xsd - - dk.erst.oxalis.transferdelegation - true - - xjc-ubl @@ -659,7 +646,7 @@ src/main/resources/META-INF/Schemas/UBL_v2.1/maindoc/UBL-ApplicationResponse-2.1.xsd - false + true diff --git a/src/main/java/dk/erst/oxalis/as4/EDeliverySpecification.java b/src/main/java/dk/erst/oxalis/as4/EDeliverySpecification.java new file mode 100644 index 0000000000000000000000000000000000000000..311a2b637bb82cad7da0eaef54513014a89c3fc4 --- /dev/null +++ b/src/main/java/dk/erst/oxalis/as4/EDeliverySpecification.java @@ -0,0 +1,39 @@ +package dk.erst.oxalis.as4; + +/** + * Enumeration of the Nemhandel e-Delivery specification versions. + */ +public enum EDeliverySpecification { + /** + * Represents the Nemhandel e-Delivery specification version 1.2 + */ + NEMHANDEL_EDELIVERY_1_2("nemhandel-edelivery-1.2"); + + private final String value; + private EDeliverySpecification(String value) { + this.value = value; + } + + /** + * The value of the Nemhandel e-Delivery specification suitable for use in the Nemhandel e-Delivery document signature scope in the SBD. + * @return the value of the Nemhandel e-Delivery specification. + */ + public String value() { + return value; + } + + /** + * Finds the NemhandelEDeliverySpecification constant which matches the provided value. + * @param value The value to match to a NemhandelEDeliverySpecification. + * @return The NemhandelEDeliverySpecification constant which matches the value, or null if no matching constant is found + */ + public static EDeliverySpecification fromValue(String value) { + if(value == null) return null; + for(EDeliverySpecification spec : EDeliverySpecification.values()) { + if (spec.value().equals(value)) { + return spec; + } + } + return null; + } +} diff --git a/src/main/java/dk/erst/oxalis/as4/async/AsyncProcessingRunnable.java b/src/main/java/dk/erst/oxalis/as4/async/AsyncProcessingRunnable.java index 2a7b27035ded1be43f2399f5a721ba29b9f13a43..57cd71db88ab1e4acf733bf51dc0aa5d56766c71 100644 --- a/src/main/java/dk/erst/oxalis/as4/async/AsyncProcessingRunnable.java +++ b/src/main/java/dk/erst/oxalis/as4/async/AsyncProcessingRunnable.java @@ -9,11 +9,9 @@ import dk.erst.oxalis.as4.async.response.MessageLevelResponseCreationException; import dk.erst.oxalis.as4.async.response.MessageLevelResponseFactory; import dk.erst.oxalis.as4.config.documenttype.*; import dk.erst.oxalis.as4.error.ErrorCodes; -import dk.erst.oxalis.as4.handlers.NemhandelPersister; import dk.erst.oxalis.as4.handlers.OutboundException; import dk.erst.oxalis.as4.handlers.SendSbdHandler; import dk.erst.oxalis.as4.handlers.dto.ValidationResult; -import dk.erst.oxalis.as4.mode.Mode; import dk.erst.oxalis.as4.persistence.model.*; import dk.erst.oxalis.as4.util.*; import dk.erst.oxalis.as4.validation.MessageValidator; @@ -21,7 +19,6 @@ import dk.erst.oxalis.as4.validation.ValidationException; import dk.erst.oxalis.as4.validation.ValidationType; import network.oxalis.api.model.Direction; import network.oxalis.vefa.peppol.common.lang.PeppolParsingException; -import network.oxalis.vefa.peppol.common.model.ParticipantIdentifier; import network.oxalis.vefa.peppol.sbdh.Ns; import org.apache.cxf.helpers.MapNamespaceContext; import org.slf4j.Logger; @@ -66,7 +63,7 @@ public class AsyncProcessingRunnable implements Runnable { /** * Performs the asynchronous processing of the message. - *
+ *
* Note: The whole asynchronous processing (including sending an MLR) runs in a single transaction. * If any exception occurs transaction will be rolled back and the message will be marked with an error status (in a separate transaction). */ @@ -98,9 +95,10 @@ public class AsyncProcessingRunnable implements Runnable { if(message != null) { logger.info("Marking message with status: {}", MessageStatus.ERROR); message.setStatus(MessageStatus.ERROR); - try(PrintWriter pw = new PrintWriter(new StringWriter())){ + StringWriter sw = new StringWriter(); + try(PrintWriter pw = new PrintWriter(sw)){ t.printStackTrace(pw); - message.setStacktrace(pw.toString()); + message.setStacktrace(sw.toString()); } em.merge(message); } else { @@ -154,23 +152,6 @@ public class AsyncProcessingRunnable implements Runnable { if (!result.hasErrors() && documentTypeConfig.isResponseType()) { handleMessageLevelResponse(em, message, document, documentTypeConfig); } - - Mode mode = injector.getInstance(Mode.class); - if (mode.isNemhandelEdeliveryMode() && !result.hasErrors()) { - DocumentSender documentSender = injector.getInstance(DocumentSender.class); - NemhandelPersister nemhandelPersister = injector.getInstance(NemhandelPersister.class); - - String receiverCVR = nemhandelPersister.getReceiverCvrNumber(ParticipantIdentifier.parse(message.getReceiver()).getIdentifier()); - - NemhandelLog.createMessageLog(em, message, ErrorCodes.C3_SIGNING_DOCUMENT_INIT); - NemhandelLog.createMessageLog(em, message, ErrorCodes.C3_CREATE_TRANSFER_DELEGATION_INIT); - byte[] sbd = message.getMessageContent().getData(); - byte[] signedSBD = documentSender.signMessage(sbd, sbd, receiverCVR, msgCtx); - message.getMessageContent().setData(signedSBD); - NemhandelLog.createMessageLog(em, message, ErrorCodes.C3_CREATE_TRANSFER_DELEGATION_DONE); - NemhandelLog.createMessageLog(em, message, ErrorCodes.C3_SIGNING_DOCUMENT_DONE); - - } } message = updateMessage(em, message, result); @@ -183,7 +164,7 @@ public class AsyncProcessingRunnable implements Runnable { /** * Creates and sends a Message Level Response (MLR) for the given message. Normally, a Peppol Message Level Response (MLR) is created. * In case the given message is OIOUBL 2.1, the Peppol MLR is converted to an OIOUBL 2.1 ApplicationResponse which is sent instead. - *

+ *

* Note: the actual sending uses the normal C2 sending process (including validation, signing, etc). Refer to {@link SendSbdHandler#handle(Account, String, String, SBDMessageContext)} for details. * * @param message The message for which to send a MLR. @@ -299,6 +280,7 @@ public class AsyncProcessingRunnable implements Runnable { if (originalMessage != null) { // If we know about the original document log into its document log, that we've received an MLR NemhandelLog.createMessageLog(em, originalMessage, ErrorCodes.C2_RECEIVES_MLR_OR_AR, ErrorCodes.C2_RECEIVES_MLR_OR_AR.formatErrorMessage(mlrMessage.getMessageUuid())); + mlrMessage.setOriginalMessage(originalMessage); String responseCode = XPathUtil.evaluateXPath(mlrDocument, xpath, mlrDocumentTypeConfig.getMessageLevelResponse().getResponseCodeLocation(), null); if(mlrDocumentTypeConfig.getMessageLevelResponse().getRejectionCodes().contains(responseCode)) { diff --git a/src/main/java/dk/erst/oxalis/as4/async/ExecutorServiceAsyncModule.java b/src/main/java/dk/erst/oxalis/as4/async/ExecutorServiceAsyncModule.java index 1dd4dc2b59998c9a5f4a5ae0667dbecd101a3fc9..708134c9af2c47809d2d74b7034190451c94b53f 100644 --- a/src/main/java/dk/erst/oxalis/as4/async/ExecutorServiceAsyncModule.java +++ b/src/main/java/dk/erst/oxalis/as4/async/ExecutorServiceAsyncModule.java @@ -21,7 +21,7 @@ import java.util.concurrent.*; /** * Guice module which configures relevant bindings related to asynchronous processing of messages in C3 * using a {@link ExecutorService} thread-pool. - *
+ *
* Note: The module is a servlet module mainly to register the {@link ExecutorServiceLifecycleFilter} Servlet filter * for lifecycle management of the {@link ExecutorService}. */ diff --git a/src/main/java/dk/erst/oxalis/as4/async/ExecutorServiceConf.java b/src/main/java/dk/erst/oxalis/as4/async/ExecutorServiceConf.java index 0d77426f2ebb84eb190e31b01105e2f9e61589bf..af0a3b6afe36f61ff09dc33a1d65a814a1e02768 100644 --- a/src/main/java/dk/erst/oxalis/as4/async/ExecutorServiceConf.java +++ b/src/main/java/dk/erst/oxalis/as4/async/ExecutorServiceConf.java @@ -6,7 +6,7 @@ import network.oxalis.api.settings.Title; /** * Enum constants for configuration properties relating to asynchronous processing. - * Intended for use with {@link network.oxalis.api.settings.Settings Settings}. + * Intended for use with {@link network.oxalis.api.settings.Settings Settings<ExecutorServiceConf>}. */ @Title("Executor Service Settings") public enum ExecutorServiceConf { diff --git a/src/main/java/dk/erst/oxalis/as4/async/ExecutorServiceLifecycleFilter.java b/src/main/java/dk/erst/oxalis/as4/async/ExecutorServiceLifecycleFilter.java index 8a08e30a8e1e3368bd0a73902068b08439988002..3fb9e3ec1402990ab91395794a4beb39a89c2232 100644 --- a/src/main/java/dk/erst/oxalis/as4/async/ExecutorServiceLifecycleFilter.java +++ b/src/main/java/dk/erst/oxalis/as4/async/ExecutorServiceLifecycleFilter.java @@ -61,7 +61,7 @@ public class ExecutorServiceLifecycleFilter implements Filter { * Destroys the filter and shuts down the {@link ExecutorService} managed by this filter. * The filter attempts instructs the ExecutorService to shut down and waits for the configured * time for a graceful shutdown (See {@link ExecutorServiceConf#GRACEFUL_SHUTDOWN_TIMEOUT}). - *
+ *
* If the graceful shutdown did not occur within the configured time the filter will try to force shutdown the ExecutorService. */ @Override diff --git a/src/main/java/dk/erst/oxalis/as4/async/response/MessageLevelResponseFactoryImpl.java b/src/main/java/dk/erst/oxalis/as4/async/response/MessageLevelResponseFactoryImpl.java index 930426328527474762f101511fd48ff6292e581a..044ad1a9a4d92425f78a2ad79c8abffb01e86015 100644 --- a/src/main/java/dk/erst/oxalis/as4/async/response/MessageLevelResponseFactoryImpl.java +++ b/src/main/java/dk/erst/oxalis/as4/async/response/MessageLevelResponseFactoryImpl.java @@ -260,7 +260,11 @@ public class MessageLevelResponseFactoryImpl implements MessageLevelResponseFact resp.setResponseCode(codeType); DescriptionType desc = new DescriptionType(); - desc.setValue(String.format("%s: %s", l.getCode(), l.getValidationLineReference())); + String msg = l.getDescription(); + if(!msg.startsWith(l.getCode() + ":")) { + msg = String.format("%s: %s", l.getCode(), l.getDescription()); + } + desc.setValue(msg); resp.getDescription().add(desc); StatusType status = new StatusType(); diff --git a/src/main/java/dk/erst/oxalis/as4/config/documenttype/DocumentTypeConfig.java b/src/main/java/dk/erst/oxalis/as4/config/documenttype/DocumentTypeConfig.java index c0ce15206637748f100deb72b74c5de45fcc7076..456bc1d8d35e31d691ca786aa3426b5d2a31a724 100644 --- a/src/main/java/dk/erst/oxalis/as4/config/documenttype/DocumentTypeConfig.java +++ b/src/main/java/dk/erst/oxalis/as4/config/documenttype/DocumentTypeConfig.java @@ -24,6 +24,8 @@ public class DocumentTypeConfig { private MessageLevelResponseType messageLevelResponseType; @Optional + private boolean preloadSchema; + @Optional private boolean preloadSchematron; @Optional private boolean responseType; @@ -229,8 +231,8 @@ public class DocumentTypeConfig { } /** - * Sets whether this document type should be preloaded and cached during startup. - * @param preloadSchematron true if the document type should be preloaded, false otherwise + * Sets whether the schematron documents for this document type should be preloaded and cached during startup. + * @param preloadSchematron true if the schematron documents should be preloaded, false otherwise */ public void setPreloadSchematron(boolean preloadSchematron) { this.preloadSchematron = preloadSchematron; @@ -270,4 +272,23 @@ public class DocumentTypeConfig { public void setMessageLevelResponse(MessageLevelResponseConfig messageLevelResponse) { this.messageLevelResponse = messageLevelResponse; } + + /** + * Gets the boolean indicating if the XSD schema for this document type should be preloaded and cached during startup. + * + * Note: Even if the schema for a document type is not preloaded, it will still be loaded and cached on first use. + * + * @return true if the XSD schema should be preloaded, false otherwise + */ + public boolean isPreloadSchema() { + return preloadSchema; + } + + /** + * Sets whether the XSD schema for this document type should be preloaded and cached during startup. + * @param preloadSchema true if the XSD schema for this document type should be preloaded, false otherwise + */ + public void setPreloadSchema(boolean preloadSchema) { + this.preloadSchema = preloadSchema; + } } diff --git a/src/main/java/dk/erst/oxalis/as4/config/documenttype/DocumentTypeConfigResolverImpl.java b/src/main/java/dk/erst/oxalis/as4/config/documenttype/DocumentTypeConfigResolverImpl.java index b550a5045e1a21c9ff8d0c7bea913c5b1f932aed..4f197414f978104f238f84f1c87fbb4c322b60b4 100644 --- a/src/main/java/dk/erst/oxalis/as4/config/documenttype/DocumentTypeConfigResolverImpl.java +++ b/src/main/java/dk/erst/oxalis/as4/config/documenttype/DocumentTypeConfigResolverImpl.java @@ -18,7 +18,7 @@ import java.util.regex.Pattern; * Identifies the document type of a {@link Document} and resolves the configuration for that document type. * The known/supported document types is the set of {@link DocumentTypeConfig} provided in the constructor (and is normally resolved by Guice) * See {@link DocumentTypeModule} for the configuration of the various document types in Guice. - *
+ *
* Generally the document type is determined by the local name and namespace of the root element of the SBD payload as well as one of more * "identifier discriminators" which are a list of XPath expressions and a expected results (regex). Usually these match on the value in the CustomizationID element of the payload (but could be any value which can be extracted through XPath). */ diff --git a/src/main/java/dk/erst/oxalis/as4/error/ErrorCodes.java b/src/main/java/dk/erst/oxalis/as4/error/ErrorCodes.java index efaa9ded6d7d0024f35bbfc0516e9cad4c65d544..f8513d53ea6dbd90433ab0dce94c03d8e55a50a0 100644 --- a/src/main/java/dk/erst/oxalis/as4/error/ErrorCodes.java +++ b/src/main/java/dk/erst/oxalis/as4/error/ErrorCodes.java @@ -54,59 +54,15 @@ public enum ErrorCodes { * Error relating to SMP lookup when no endpoint is found for a given participant identifier and document type. */ OUTBOUND_SMP_NOT_FOUND_ERROR(Level.ERROR, Component.SENDER, Process.OUTBOUND, 5, "No endpoint found during SMP lookup."), - - /** - * Error relating to missing Nemhandel e-Delivery signature in the Standard Business Document header (Sender - C2). - */ - SENDER_EDELIVERY_SIGNATURE_MISSING(Level.ERROR, Component.SENDER, Process.VALIDATION, 1, "No Nemhandel e-Delivery document signature found at level %d"), - /** - * Error relating to invalid Nemhandel e-Delivery signature (Sender - C2). - */ - SENDER_EDELIVERY_SIGNATURE_INVALID(Level.ERROR, Component.SENDER, Process.VALIDATION, 2, "Invalid Nemhandel e-Delivery document signature at level %d: %s"), - /** - * Error relating to missing standard-business-document.xml file as part of the signed documents in the Nemhandel e-Delivery signature (Sender - C2). - */ - SENDER_EDELIVERY_MISSING_SBD_IN_ASIC(Level.ERROR, Component.SENDER, Process.VALIDATION, 3, "No standard-business-document.xml file found in Nemhandel e-Delivery document signature at level %d"), - /** - * Error relating to mismatch between the standard-business-document.xml file in the Nemhandel e-Delivery signature and the standard business document in which the signature is embedded (Sender - C2). - */ - SENDER_EDELIVERY_SBD_DOES_NOT_MATCH_ASIC(Level.ERROR, Component.SENDER, Process.VALIDATION, 4, "Standard Business Document does not match the standard-business-document.xml in the Nemhandel e-Delivery document signature at level %d. The document may have been altered by a third party without re-signing."), - /** - * Error relating to the number of Nemhandel e-Delivery signatures in a document (Sender - C2). - */ - SENDER_EDELIVERY_TOO_MANY_SIGNATURES(Level.ERROR, Component.SENDER, Process.VALIDATION, 5, "Found %d Nemhandel e-Delivery document signatures at level %d. Only one document signature is allowed per level."), - /** - * Error relating to missing transfer-delegation.xml file as part of the signed documents in the Nemhandel e-Delivery signature (Sender - C2). - */ - SENDER_EDELIVERY_MISSING_TRANSFER_DELEGATION(Level.ERROR, Component.SENDER, Process.VALIDATION, 6, "No Nemhandel e-Delivery transfer delegation found in document signature at level %d"), - /** - * Error relating to the contents of the original-standard-business-document.xml in the Nemhandel e-Delivery signature not being a valid standard business document (Sender - C2). - */ - SENDER_EDELIVERY_ORIGINAL_SBD_IN_ASIC_IS_NOT_SBD(Level.ERROR, Component.SENDER, Process.VALIDATION, 7, "original-standard-business-document.xml is not a StandardBusinessDocument in Nemhandel e-Delivery document signature at level %d"), - /** - * Error regarding an unexpected receiver CVR in the transfer delegation of the Nemhandel e-Delivery signature (Sender - C2). - */ - SENDER_EDELIVERY_TRANSFER_DELEGATION_INVALID_RECEIVER_CVR(Level.ERROR, Component.SENDER, Process.VALIDATION, 8, "The transfer delegation's Receiver CVR %s does not match the expected CVR %s at level %d"), - /** - * Error regarding an unexpected SchemeID for the receiver CVR of the transfer delegation of the Nemhandel e-Delivery signature (Sender - C2). - */ - SENDER_EDELIVERY_TRANSFER_DELEGATION_INVALID_RECEIVER_SCHEME(Level.ERROR, Component.SENDER, Process.VALIDATION, 9, "The transfer delegation's Receiver CVR SchemeID %s does not match the expected SchemeID %s at level %d"), - /** - * Error regarding an unexpected SchemeID for the sender CVR of the transfer delegation of the Nemhandel e-Delivery signature (Sender - C2). - */ - SENDER_EDELIVERY_TRANSFER_DELEGATION_INVALID_SENDER_SCHEME(Level.ERROR, Component.SENDER, Process.VALIDATION, 10, "The transfer delegation's Sender CVR SchemeID %s does not match the expected SchemeID %s at level %d"), /** - * Error regarding an unexpected sender CVR in the transfer delegation of the Nemhandel e-Delivery signature (Sender - C2). + * Error relating to receiver not registrered correctly in NHR. */ - SENDER_EDELIVERY_TRANSFER_DELEGATION_INVALID_SENDER_CVR(Level.ERROR, Component.SENDER, Process.VALIDATION, 11, "The transfer delegation's Sender CVR %s does not match the expected CVR %s at level %d"), + OUTBOUND_RECEIVER_DOES_NOT_EXIST_ERROR(Level.ERROR, Component.SENDER, Process.OUTBOUND, 6, "The receiver '%s' does not support document type '%s' according to the SMP lookup."), /** - * Error regarding a missing sender CVR in the transfer delegation of the Nemhandel e-Delivery signature (Sender - C2). + * Error relating to sender not registrered correctly in NHR. */ - SENDER_EDELIVERY_TRANSFER_DELEGATION_MISSING_SENDER_CVR(Level.ERROR, Component.SENDER, Process.VALIDATION, 12, "Missing Sender CVR in the transfer delegation at level %d"), - /** - * Error regarding a missing receiver CVR in the transfer delegation of the Nemhandel e-Delivery signature (Sender - C2). - */ - SENDER_EDELIVERY_TRANSFER_DELEGATION_MISSING_RECEIVER_CVR(Level.ERROR, Component.SENDER, Process.VALIDATION, 13, "Missing Receiver CVR in the transfer delegation at level %d"), + OUTBOUND_SENDER_DOES_NOT_EXIST_ERROR(Level.ERROR, Component.SENDER, Process.OUTBOUND, 7, "The sender '%s' does not support process '%s' document type '%s' according to the SMP lookup."), + /** * Schematron validation error (Sender - C2). */ @@ -129,59 +85,6 @@ public enum ErrorCodes { SENDER_MISSING_COUNTRY_C1_SCOPE_C2(Level.ERROR, Component.SENDER, Process.VALIDATION, 19, "Missing or invalid COUNTRY_C1 business scope"), SENDER_MISSING_COUNTRY_C1_SCOPE_C3(Level.ERROR, Component.RECEIVER, Process.VALIDATION, 19, "Missing or invalid COUNTRY_C1 business scope"), - - /** - * Error relating to missing Nemhandel e-Delivery signature in the Standard Business Document header (Receiver - C3). - */ - RECEIVER_EDELIVERY_SIGNATURE_MISSING(Level.ERROR, Component.RECEIVER, Process.VALIDATION, 1, "No Nemhandel e-Delivery document signature found at level %d"), - /** - * Error relating to invalid Nemhandel e-Delivery signature (Receiver - C3). - */ - RECEIVER_EDELIVERY_SIGNATURE_INVALID(Level.ERROR, Component.RECEIVER, Process.VALIDATION, 2, "Invalid Nemhandel e-Delivery document signature at level %d: %s"), - /** - * Error relating to missing standard-business-document.xml file as part of the signed documents in the Nemhandel e-Delivery signature (Receiver - C3). - */ - RECEIVER_EDELIVERY_MISSING_SBD_IN_ASIC(Level.ERROR, Component.RECEIVER, Process.VALIDATION, 3, "No standard-business-document.xml file found in Nemhandel e-Delivery document signature at level %d"), - /** - * Error relating to mismatch between the standard-business-document.xml file in the Nemhandel e-Delivery signature and the standard business document in which the signature is embedded (Receiver - C3). - */ - RECEIVER_EDELIVERY_SBD_DOES_NOT_MATCH_ASIC(Level.ERROR, Component.RECEIVER, Process.VALIDATION, 4, "Standard Business Document does not match the standard-business-document.xml in the Nemhandel e-Delivery document signature at level %d. The document may have been altered by a third party without re-signing."), - /** - * Error relating to the number of Nemhandel e-Delivery signatures in a document (Receiver - C3). - */ - RECEIVER_EDELIVERY_TOO_MANY_SIGNATURES(Level.ERROR, Component.RECEIVER, Process.VALIDATION, 5, "Found %d Nemhandel e-Delivery document signatures at level %d. Only one document signature is allowed per level."), - /** - * Error relating to missing transfer-delegation.xml file as part of the signed documents in the Nemhandel e-Delivery signature (Receiver - C3). - */ - RECEIVER_EDELIVERY_MISSING_TRANSFER_DELEGATION(Level.ERROR, Component.RECEIVER, Process.VALIDATION, 6, "No Nemhandel e-Delivery transfer delegation found in document signature at level %d"), - /** - * Error relating to the contents of the original-standard-business-document.xml in the Nemhandel e-Delivery signature not being a valid standard business document (Receiver - C3). - */ - RECEIVER_EDELIVERY_ORIGINAL_SBD_IN_ASIC_IS_NOT_SBD(Level.ERROR, Component.RECEIVER, Process.VALIDATION, 7, "original-standard-business-document.xml is not a StandardBusinessDocument in Nemhandel e-Delivery document signature at level %d"), - /** - * Error regarding an unexpected receiver CVR in the transfer delegation of the Nemhandel e-Delivery signature (Receiver - C3). - */ - RECEIVER_EDELIVERY_TRANSFER_DELEGATION_INVALID_RECEIVER_CVR(Level.ERROR, Component.RECEIVER, Process.VALIDATION, 8, "The transfer delegation's Receiver CVR %s does not match the expected CVR %s at level %d"), - /** - * Error regarding an unexpected SchemeID for the receiver CVR of the transfer delegation of the Nemhandel e-Delivery signature (Receiver - C3). - */ - RECEIVER_EDELIVERY_TRANSFER_DELEGATION_INVALID_RECEIVER_SCHEME(Level.ERROR, Component.RECEIVER, Process.VALIDATION, 9, "The transfer delegation's Receiver CVR SchemeID %s does not match the expected SchemeID %s at level %d"), - /** - * Error regarding an unexpected SchemeID for the sender CVR of the transfer delegation of the Nemhandel e-Delivery signature (Receiver - C3). - */ - RECEIVER_EDELIVERY_TRANSFER_DELEGATION_INVALID_SENDER_SCHEME(Level.ERROR, Component.RECEIVER, Process.VALIDATION, 10, "The transfer delegation's Sender CVR SchemeID %s does not match the expected SchemeID %s at level %d"), - /** - * Error regarding an unexpected sender CVR in the transfer delegation of the Nemhandel e-Delivery signature (Receiver - C3). - */ - RECEIVER_EDELIVERY_TRANSFER_DELEGATION_INVALID_SENDER_CVR(Level.ERROR, Component.RECEIVER, Process.VALIDATION, 11, "The transfer delegation's Sender CVR %s does not match the expected CVR %s at level %d"), - /** - * Error regarding a missing sender CVR in the transfer delegation of the Nemhandel e-Delivery signature (Receiver - C3). - */ - RECEIVER_EDELIVERY_TRANSFER_DELEGATION_MISSING_SENDER_CVR(Level.ERROR, Component.RECEIVER, Process.VALIDATION, 12, "Missing Sender CVR in the transfer delegation at level %d"), - /** - * Error regarding a missing receiver CVR in the transfer delegation of the Nemhandel e-Delivery signature (Receiver - C3). - */ - RECEIVER_EDELIVERY_TRANSFER_DELEGATION_MISSING_RECEIVER_CVR(Level.ERROR, Component.RECEIVER, Process.VALIDATION, 13, "Missing Receiver CVR in the transfer delegation at level %d"), /** * Schematron validation error (Receiver - C3). */ @@ -203,31 +106,6 @@ public enum ErrorCodes { * Error regarding null or empty standard business document to sign with a Nemhandel e-Delivery signature. */ SIGNING_EDELIVERY_SBD_EMPTY_OR_NULL(Level.ERROR, Component.REF, Process.SIGNING, 1,"Standard Business Document is null or empty"), - /** - * Error regarding null or empty transfer delegation when signing a document with a Nemhandel e-Delivery signature. - */ - SIGNING_EDELIVERY_TRANSFER_DELEGATION_EMPTY_OR_NULL(Level.ERROR, Component.REF, Process.SIGNING, 2,"Transfer delegation is null or empty"), - /** - * Error relating to missing sender CVR when creating a transfer delegation. - */ - SIGNING_EDELIVERY_TRANSFER_DELEGATION_EMPTY_OR_NULL_SENDER_CVR(Level.ERROR, Component.REF, Process.SIGNING, 3, "Missing Sender CVR in the transfer delegation."), - /** - * Error relating to missing receiver CVR when creating a transfer delegation. - */ - SIGNING_EDELIVERY_TRANSFER_DELEGATION_EMPTY_OR_NULL_RECEIVER_CVR(Level.ERROR, Component.REF, Process.SIGNING, 4, "Missing Receiver CVR in the transfer delegation."), - /** - * Error relating to marshalling of transfer delegation to XML. - */ - SIGNING_EDELIVERY_TRANSFER_DELEGATION_MARSHALLING_ERROR(Level.ERROR, Component.REF, Process.SIGNING, 5, "Could not marshal transfer delegation."), - /** - * Error relating to serializing an ASiC container as a byte array. - */ - SIGNING_EDELIVERY_ASIC_CONTAINER_TRANSFORMING_ERROR(Level.ERROR, Component.REF, Process.SIGNING, 6, "Could not convert ASiC container to Byte[]."), - /** - * Error regarding null or empty original standard business document to sign with a Nemhandel e-Delivery signature. - */ - SIGNING_EDELIVERY_ORIGINAL_SBD_EMPTY_OR_NULL(Level.ERROR, Component.REF, Process.SIGNING, 7,"Original Standard Business Document is null or empty."), - /** * Error relating to missing SchemeID in a participant identifier when creating a Message Level Response. @@ -279,22 +157,15 @@ public enum ErrorCodes { * Information message regarding the end of Nemhandel e-Delivery signature validation in C2. */ C2_SIGNATURE_VALIDATION_DONE(Level.INFORMATION, Component.SENDER, Process.OUTBOUND, 121, "C2 is done with signature validation on document"), - /** - * Information message regarding the start of creating a transfer delegation in C2. - */ - C2_CREATE_TRANSFER_DELEGATION_INIT(Level.INFORMATION, Component.SENDER, Process.OUTBOUND, 200, "C2 is starting creation of OVERDRAGELSEFORHOLD for document"), - /** - * Information message regarding the end of creating a transfer delegation in C2. - */ - C2_CREATE_TRANSFER_DELEGATION_DONE(Level.INFORMATION, Component.SENDER, Process.OUTBOUND, 201, "C2 is done with the creation of OVERDRAGELSEFORHOLD for document"), + /** * Information message regarding the start of signing a document in C2. */ - C2_SINGNING_DOCUMENT_INIT(Level.INFORMATION, Component.SENDER, Process.OUTBOUND, 210, "C2 is starting signing the document"), + C2_SIGNING_DOCUMENT_INIT(Level.INFORMATION, Component.SENDER, Process.OUTBOUND, 210, "C2 is starting signing the document"), /** * Information message regarding the end of signing a document in C2. */ - C2_SINGNING_DOCUMENT_DONE(Level.INFORMATION, Component.SENDER, Process.OUTBOUND, 211, "C2 is done with signing the document"), + C2_SIGNING_DOCUMENT_DONE(Level.INFORMATION, Component.SENDER, Process.OUTBOUND, 211, "C2 is done with signing the document"), /** * Information message regarding the start of a SMP lookup in C2. */ @@ -351,22 +222,7 @@ public enum ErrorCodes { * Information message regarding the end of Nemhandel e-Delivery signature validation in C3. */ C3_SIGNATURE_VALIDATION_DONE(Level.INFORMATION, Component.RECEIVER, Process.INBOUND, 121, "C3 is done with the signature validation on the Document"), - /** - * Information message regarding the start of creating a transfer delegation in C3. - */ - C3_CREATE_TRANSFER_DELEGATION_INIT(Level.INFORMATION, Component.RECEIVER, Process.INBOUND, 200, "C3 is starting creation of OVERDRAGELSEFORHOLD for document"), - /** - * Information message regarding the end of creating a transfer delegation in C3. - */ - C3_CREATE_TRANSFER_DELEGATION_DONE(Level.INFORMATION, Component.RECEIVER, Process.INBOUND, 201, "C3 is done with the creation of OVERDRAGELSEFORHOLD for document"), - /** - * Information message regarding the start of signing a document in C3. - */ - C3_SIGNING_DOCUMENT_INIT(Level.INFORMATION, Component.RECEIVER, Process.INBOUND, 210,"C3 has started signing the document"), - /** - * Information message regarding the end of signing a document in C3. - */ - C3_SIGNING_DOCUMENT_DONE(Level.INFORMATION, Component.RECEIVER, Process.INBOUND, 211, "C3 is done with signing the document"), + /** * Information message regarding the start of fetching message content of document for C4 through inbox the API. */ @@ -400,6 +256,39 @@ public enum ErrorCodes { */ C3_RECEIVES_DOCUMENT_WITH_RECEIVER_NOT_KNOWN(Level.ERROR, Component.RECEIVER, Process.OUTBOUND, 123, "Receiver %s is not recognized by the receiving endpoint. The document was rejected by the receiving endpoint"), + /** + * Error relating to missing Nemhandel e-Delivery signature in the Standard Business Document header. + */ + SENDER_EDELIVERY_SIGNATURE_MISSING(Level.ERROR, Component.SENDER, Process.VALIDATION, 20, "No Nemhandel e-Delivery document signature found"), + /** + * Error relating to invalid Nemhandel e-Delivery signature. + */ + SENDER_EDELIVERY_SIGNATURE_INVALID(Level.ERROR, Component.SENDER, Process.VALIDATION, 21, "Invalid Nemhandel e-Delivery document signature: %s"), + /** + * Error relating to number of documents signatures found. + */ + SENDER_EDELIVERY_TOO_MANY_SIGNATURES(Level.ERROR, Component.SENDER, Process.VALIDATION, 22, "Found %d Nemhandel e-Delivery document signatures. Only one document signature is allowed."), + /** + * Error relating to missing or invalid document signature version. + */ + SENDER_EDELIVERY_SIGNATURE_INVALID_SPECIFICATION_VERSION(Level.ERROR, Component.SENDER, Process.VALIDATION, 23, "Missing or invalid Nemhandel e-Delivery specification version in Nemhandel e-Delivery document signature scope. Accepted values are %s"), + + /** + * Error relating to missing Nemhandel e-Delivery signature in the Standard Business Document header. + */ + RECEIVER_EDELIVERY_SIGNATURE_MISSING(Level.ERROR, Component.RECEIVER, Process.VALIDATION, 20, "No Nemhandel e-Delivery document signature found"), + /** + * Error relating to invalid Nemhandel e-Delivery signature. + */ + RECEIVER_EDELIVERY_SIGNATURE_INVALID(Level.ERROR, Component.RECEIVER, Process.VALIDATION, 21, "Invalid Nemhandel e-Delivery document signature: %s"), + /** + * Error relating to number of documents signatures found. + */ + RECEIVER_EDELIVERY_TOO_MANY_SIGNATURES(Level.ERROR, Component.RECEIVER, Process.VALIDATION, 22, "Found %d Nemhandel e-Delivery document signatures. Only one document signature is allowed."), + /** + * Error relating to missing or invalid document signature version. + */ + RECEIVER_EDELIVERY_SIGNATURE_INVALID_SPECIFICATION_VERSION(Level.ERROR, Component.RECEIVER, Process.VALIDATION, 23, "Missing or invalid Nemhandel e-Delivery specification version in Nemhandel e-Delivery document signature scope. Accepted values are %s"), ; private final Level level; diff --git a/src/main/java/dk/erst/oxalis/as4/error/Process.java b/src/main/java/dk/erst/oxalis/as4/error/Process.java index f098c5c3aa8204599903eee63afc253a49580eab..fe54e2fa886359916161a841c6a32ec53895f4d2 100644 --- a/src/main/java/dk/erst/oxalis/as4/error/Process.java +++ b/src/main/java/dk/erst/oxalis/as4/error/Process.java @@ -20,7 +20,7 @@ public enum Process { */ FOLDER_MONITOR_SENDER(22), /** - * The signing process including creation of transfer delegations and creation of Nemhandel e-Delivery document signatures. + * The signing process and creation of Nemhandel e-Delivery document signatures. */ SIGNING(23), /** diff --git a/src/main/java/dk/erst/oxalis/as4/handlers/GetInboxMessagesHandler.java b/src/main/java/dk/erst/oxalis/as4/handlers/GetInboxMessagesHandler.java index d3e9f44c7260215d8607aafea4dbc4fc8e68b5d5..31783ca59beb1d8c7bd66e6aecb1deb5551aea2f 100644 --- a/src/main/java/dk/erst/oxalis/as4/handlers/GetInboxMessagesHandler.java +++ b/src/main/java/dk/erst/oxalis/as4/handlers/GetInboxMessagesHandler.java @@ -89,7 +89,7 @@ public class GetInboxMessagesHandler { * } * * @param uuid the uuid of the message - * @param request the status to update to (either RECEIVED or CREATED) + * @param request the status to update to (either {@code RECEIVED} or {@code CREATED}) * @param account the account of the requester */ public void updateStatus(String uuid, UpdateStatusRequest request, Account account) { diff --git a/src/main/java/dk/erst/oxalis/as4/handlers/GetMessageLogHandler.java b/src/main/java/dk/erst/oxalis/as4/handlers/GetMessageLogHandler.java index e907a46928b9ca099e36678ed19d6e79027bd3ee..bc511185bf42de3592b89e3b8366d360794162c0 100644 --- a/src/main/java/dk/erst/oxalis/as4/handlers/GetMessageLogHandler.java +++ b/src/main/java/dk/erst/oxalis/as4/handlers/GetMessageLogHandler.java @@ -14,7 +14,7 @@ import java.util.stream.Collectors; /** * A handler class for fetching MessageLog records associated with a given message. - *
+ *
* The handler fetches MessageLog entities for a specific message identified by a UUID, account and direction. * It throws an {@link OutboundException} if no corresponding message logs are found. */ diff --git a/src/main/java/dk/erst/oxalis/as4/handlers/GetOutboxMessagesHandler.java b/src/main/java/dk/erst/oxalis/as4/handlers/GetOutboxMessagesHandler.java index 83d1ade0b8e1f6d4df0bc57b587b41d69ac909a9..8433c8abb02f0b05a46bdf053bca7705d00e5d81 100644 --- a/src/main/java/dk/erst/oxalis/as4/handlers/GetOutboxMessagesHandler.java +++ b/src/main/java/dk/erst/oxalis/as4/handlers/GetOutboxMessagesHandler.java @@ -2,8 +2,11 @@ package dk.erst.oxalis.as4.handlers; import com.google.inject.Inject; import com.google.inject.Provider; +import dk.erst.oxalis.as4.error.ErrorCodes; import dk.erst.oxalis.as4.handlers.dto.ListMessageResponse; +import dk.erst.oxalis.as4.handlers.dto.MessageAndResponses; import dk.erst.oxalis.as4.handlers.dto.MessageModel; +import dk.erst.oxalis.as4.persistence.model.AbstractEntity; import dk.erst.oxalis.as4.persistence.model.Account; import dk.erst.oxalis.as4.persistence.model.Message; import dk.erst.oxalis.as4.persistence.model.MessageDirection; @@ -15,12 +18,14 @@ import org.slf4j.LoggerFactory; import javax.persistence.EntityManager; import javax.persistence.NoResultException; import javax.ws.rs.NotFoundException; +import java.io.ByteArrayInputStream; +import java.io.InputStream; import java.util.List; import java.util.stream.Collectors; /** * A handler class for fetching outbox messages associated with a given account. - *
+ *
* This handler fetches 'Message' entities from the database for a specific account and message direction (outbound). * It supports pagination with a fixed page size of 20. */ @@ -94,6 +99,39 @@ public class GetOutboxMessagesHandler { return model; } + /** + * Returns the message with the given UUID from the outbox along with any received responses (from the inbox) relating to the message. + * @param account The account of the requester + * @param uuid The UUID of the message + * @return Information regarding the message and any received responses. + */ + public MessageAndResponses getMessageAndAllResponses(Account account, String uuid) { + Message m; + try { + m = em.get().createQuery("FROM Message m WHERE m.direction = :direction AND m.messageUuid = :uuid AND m.account = :account", Message.class) + .setParameter("direction", MessageDirection.OUT) + .setParameter("account", account) + .setParameter("uuid", uuid) + .getSingleResult(); + } catch (NoResultException e) { + throw new NotFoundException(); + } + + MessageAndResponses result = new MessageAndResponses(); + result.setOriginalDocument(MessageModel.fromMessage(m)); + + List responses = em.get().createQuery("FROM Message r WHERE r.originalMessage = :original AND r.account = :account order by r.received, r.id", Message.class) + .setParameter("account", account) + .setParameter("original", m) + .getResultStream() + .map(MessageModel::fromMessage) + .collect(Collectors.toList()); + + result.setResponses(responses); + + return result; + } + /** * Gets the status of a message. * @@ -129,4 +167,24 @@ public class GetOutboxMessagesHandler { NemhandelLog.WithMDC.setMessageUuid(m.getMessageUuid()); return m; } + + /** + * Gets the content of a message. Will return a stream of the data + * + * @param uuid the uuid of the message + * @param account the account of the requester + * @return inputstream of data for the message + */ + public InputStream getMessageContent(String uuid, Account account) { + return TxUtil.doInTxResult(em, entityManager -> { + try { + if (isNotAuthorized(account, uuid, entityManager)) throw new NotFoundException(); + Message m = getMessage(uuid, entityManager); + return new ByteArrayInputStream(m.getMessageContent().getData()); + } catch (NoResultException e) { + logger.debug("No message with id: {} found in outbox", uuid); + throw new NotFoundException(); + } + }); + } } diff --git a/src/main/java/dk/erst/oxalis/as4/handlers/NemhandelPersister.java b/src/main/java/dk/erst/oxalis/as4/handlers/NemhandelPersister.java index 2a046c1f9111d01da90e2ec94754910dcec4c607..2ae06b6f6f9bbf153fb88f0261ee24aaba91cc63 100644 --- a/src/main/java/dk/erst/oxalis/as4/handlers/NemhandelPersister.java +++ b/src/main/java/dk/erst/oxalis/as4/handlers/NemhandelPersister.java @@ -25,8 +25,10 @@ import network.oxalis.api.model.Direction; import network.oxalis.api.model.TransmissionIdentifier; import network.oxalis.api.util.Type; import network.oxalis.commons.persist.TempPersister; +import network.oxalis.vefa.peppol.common.model.DocumentTypeIdentifier; import network.oxalis.vefa.peppol.common.model.Header; import network.oxalis.vefa.peppol.common.model.ParticipantIdentifier; +import network.oxalis.vefa.peppol.common.model.ProcessIdentifier; import network.oxalis.vefa.peppol.common.model.Scheme; import org.apache.commons.lang3.StringUtils; import org.apache.cxf.helpers.MapNamespaceContext; @@ -53,7 +55,7 @@ import java.util.concurrent.ExecutorService; /** * Provides a way to (in C3) save received documents from C2, such that the documents will be available in C4. - *
+ *
* NOTE: The document will only be validated using the synchronous validation (e.g. schema validation). If everything is ok * the message will be queued for asynchronous processing which will perform asynchronous validation (schematron and document signature validation) * as well as generate and send an appropriate Message Level Response if necessary. @@ -195,10 +197,30 @@ public class NemhandelPersister extends TempPersister { throw new IOException("Xml is not a StandardBusinessDocument, or a with DOCUMENTID is missing"); } + String documentIdentifier = evaluateXPath(document, xpath, + "/sbd:StandardBusinessDocument/sbd:StandardBusinessDocumentHeader/sbd:BusinessScope/sbd:Scope[sbd:Type='DOCUMENTID']/sbd:Identifier", + null); + if(documentIdentifier != null && !documentIdentifier.isEmpty()) { + documentTypeId = DocumentTypeIdentifier.of(documentTypeId, Scheme.of(documentIdentifier)).toString(); + } else { + documentTypeId = DocumentTypeIdentifier.of(documentTypeId).toString(); // use default scheme! + } + String processId = evaluateXPath(document, xpath, "/sbd:StandardBusinessDocument/sbd:StandardBusinessDocumentHeader/sbd:BusinessScope/sbd:Scope[sbd:Type='PROCESSID']/sbd:InstanceIdentifier", null); + String processIdentifier = evaluateXPath(document, xpath, + "/sbd:StandardBusinessDocument/sbd:StandardBusinessDocumentHeader/sbd:BusinessScope/sbd:Scope[sbd:Type='PROCESSID']/sbd:Identifier", + null); + if(processId != null) { + if (processIdentifier != null && !processIdentifier.isEmpty()) { + processId = ProcessIdentifier.of(processId, Scheme.of(processIdentifier)).toString(); + } else { + processId = ProcessIdentifier.of(processId).toString(); // use default scheme + } + } + messageUuid = evaluateXPath(document, xpath, "/sbd:StandardBusinessDocument/sbd:StandardBusinessDocumentHeader/sbd:DocumentIdentification/sbd:InstanceIdentifier", UUID.randomUUID().toString()); @@ -218,6 +240,7 @@ public class NemhandelPersister extends TempPersister { documentTypeId, content ); + message.setTransmissionId(transmissionIdentifier.getIdentifier()); if (documentTypeConfig.isResponseType()) { messageLogList.add(NemhandelLog.createMessageLog(message, ErrorCodes.C2_RECEIVING_OF_MLR_INT)); @@ -334,18 +357,6 @@ public class NemhandelPersister extends TempPersister { .evaluate(document, XPathConstants.NODE); } - /** - * Extracts the CVR number from the database, for the receiver. - * - * @param receiver the receiver to find the CVR number from - * @return the receiver cvr number - * @throws NullPointerException if there is no receiver - */ - public String getReceiverCvrNumber(String receiver) { - AccountReceiver accountReceiver = getAccountReceiverTiedToReceiver(receiver, em.get()); - return accountReceiver.getCvrNumber(); - } - /** * Queries the database for an {@link AccountReceiver} based on the participant id. * diff --git a/src/main/java/dk/erst/oxalis/as4/handlers/SendSbdHandler.java b/src/main/java/dk/erst/oxalis/as4/handlers/SendSbdHandler.java index 8848f0d0231cd5f1f86a355a2e23a6df94356e9f..d5bbd8596ff141dc44e32fa8d435c75872291484 100644 --- a/src/main/java/dk/erst/oxalis/as4/handlers/SendSbdHandler.java +++ b/src/main/java/dk/erst/oxalis/as4/handlers/SendSbdHandler.java @@ -36,10 +36,8 @@ import java.io.StringWriter; import java.nio.charset.StandardCharsets; import java.time.LocalDateTime; import java.time.ZoneId; -import java.util.ArrayList; -import java.util.LinkedList; -import java.util.List; -import java.util.UUID; +import java.util.*; +import java.util.stream.Collectors; import static dk.erst.oxalis.as4.util.ExceptionUtil.findIfCauseIsMessageUUIDConstraint; @@ -98,33 +96,33 @@ public class SendSbdHandler { } } catch(OutboundValidationException e) { Message finalMessage = message; - List messageLogList; - - if (e.getMessageLogList() == null) { - messageLogList = new ArrayList<>(); - if (e.getValidationResult() != null && e.getValidationResult().hasErrors()) { - message.setStatus(MessageStatus.ERROR); - - e.getValidationResult().getErrors().stream() - .map(v -> NemhandelLog.createMessageLog(finalMessage, v)) - .forEach(messageLogList::add); - logger.error("Validation errors: {}", e.getValidationResult()); - } + List messageLogList = e.getMessageLogList() != null ? e.getMessageLogList() : new ArrayList<>(); + final Set codes = messageLogList.stream() + .map(MessageLog::getCode) + .collect(Collectors.toSet()); + + finalMessage.setStatus(MessageStatus.ERROR); + + if (e.getValidationResult() != null && e.getValidationResult().hasErrors()) { + e.getValidationResult().getErrors().stream() + .filter(v -> !codes.contains(v.getCode())) + .map(v -> NemhandelLog.createMessageLog(finalMessage, v)) + .forEach(messageLogList::add); + logger.error("Validation errors: {}", e.getValidationResult()); + } - if (e.getValidationResult() != null && e.getValidationResult().hasWarnings()) { - e.getValidationResult().getWarnings().stream() - .map(v -> NemhandelLog.createMessageLog(finalMessage, v)) - .forEach(messageLogList::add); - if (!e.getValidationResult().hasErrors()) { - logger.warn("Validation warnings: {}", e.getValidationResult()); - } + if (e.getValidationResult() != null && e.getValidationResult().hasWarnings()) { + e.getValidationResult().getWarnings().stream() + .filter(v -> !codes.contains(v.getCode())) + .map(v -> NemhandelLog.createMessageLog(finalMessage, v)) + .forEach(messageLogList::add); + if (!e.getValidationResult().hasErrors()) { + logger.warn("Validation warnings: {}", e.getValidationResult()); } - } else { - messageLogList = e.getMessageLogList(); } if(messageContext != null && messageContext.isInternalMessageLevelResponse()) { - em.get().persist(finalMessage); + em.get().merge(finalMessage); messageLogList.forEach(vrl -> vrl.persist(em.get())); // in this case an active transaction already exists (from the async processing) } else { TxUtil.doInTx(em, entityManager -> { @@ -174,7 +172,7 @@ public class SendSbdHandler { } if(messageContext != null && messageContext.isInternalMessageLevelResponse()) { - em.get().persist(message); // in this case an active transaction already exists (from the async processing) + em.get().merge(message); // in this case an active transaction already exists (from the async processing) } else { TxUtil.doInTx(em, entityManager -> { entityManager.merge(message); diff --git a/src/main/java/dk/erst/oxalis/as4/handlers/dto/ErrorMessage.java b/src/main/java/dk/erst/oxalis/as4/handlers/dto/ErrorMessage.java index 982c29e54cbf75d1461b7375bc462fd396857a50..d6cd2edea31687ed9a423849e297fe4d06d3283a 100644 --- a/src/main/java/dk/erst/oxalis/as4/handlers/dto/ErrorMessage.java +++ b/src/main/java/dk/erst/oxalis/as4/handlers/dto/ErrorMessage.java @@ -1,5 +1,7 @@ package dk.erst.oxalis.as4.handlers.dto; +import io.swagger.v3.oas.annotations.media.Schema; + import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlType; @@ -10,7 +12,9 @@ import javax.xml.bind.annotation.XmlType; @XmlType @XmlAccessorType(XmlAccessType.FIELD) public class ErrorMessage { + @Schema(description = "Error code") private String errorCode; + @Schema(description = "Error message") private String errorMessage; /** diff --git a/src/main/java/dk/erst/oxalis/as4/handlers/dto/ErrorResponse.java b/src/main/java/dk/erst/oxalis/as4/handlers/dto/ErrorResponse.java index e84ee05af786d72bbb9a9640ad7c0248cd05eff7..c51be75dc0364462617ddd48a4c7c7c11a5418dc 100644 --- a/src/main/java/dk/erst/oxalis/as4/handlers/dto/ErrorResponse.java +++ b/src/main/java/dk/erst/oxalis/as4/handlers/dto/ErrorResponse.java @@ -1,6 +1,7 @@ package dk.erst.oxalis.as4.handlers.dto; import dk.erst.oxalis.as4.handlers.OutboundException; +import io.swagger.v3.oas.annotations.media.Schema; import javax.xml.bind.annotation.*; import java.util.ArrayList; @@ -9,14 +10,16 @@ import java.util.List; /** * Represents an error response with a message UUID and a list of {@link ErrorMessage}. */ -@XmlRootElement +@XmlRootElement(namespace = "http://www.erst.dk/oxalis/api", name ="errorResponse") @XmlAccessorType(XmlAccessType.FIELD) @XmlType public class ErrorResponse { @XmlElement + @Schema(description = "MessageUUID this error response belongs to") private String messageUuid; @XmlElement + @Schema(description = "List of error messages") private List errors; /** diff --git a/src/main/java/dk/erst/oxalis/as4/handlers/dto/ListMessageResponse.java b/src/main/java/dk/erst/oxalis/as4/handlers/dto/ListMessageResponse.java index 0f66c72d597e01cdb2495f77ec22c75dec5f2010..9056566f5891263f153f6510850ae9245a765952 100644 --- a/src/main/java/dk/erst/oxalis/as4/handlers/dto/ListMessageResponse.java +++ b/src/main/java/dk/erst/oxalis/as4/handlers/dto/ListMessageResponse.java @@ -12,11 +12,11 @@ import java.util.List; /** * Represents a response containing a list of {@link MessageModel} messages. */ -@XmlRootElement +@XmlRootElement(namespace = "http://www.erst.dk/oxalis/api", name ="listMessageResponse") @XmlAccessorType(XmlAccessType.FIELD) public class ListMessageResponse { @XmlElement - @Schema(description = "a list of messages") + @Schema(description = "a list of messages...") private List messages = new ArrayList<>(); @XmlElement @Schema(description = "The page of messages returned") diff --git a/src/main/java/dk/erst/oxalis/as4/handlers/dto/MessageAndResponses.java b/src/main/java/dk/erst/oxalis/as4/handlers/dto/MessageAndResponses.java new file mode 100644 index 0000000000000000000000000000000000000000..59a66508725a8e4aa27c8ad0721514dd78fef91f --- /dev/null +++ b/src/main/java/dk/erst/oxalis/as4/handlers/dto/MessageAndResponses.java @@ -0,0 +1,65 @@ +package dk.erst.oxalis.as4.handlers.dto; + +import io.swagger.v3.oas.annotations.media.Schema; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; +import java.util.List; + +/** + * Response class for the REST api returning a message from the outbox + * as well as any received response messages. + */ +@XmlRootElement(namespace = "http://www.erst.dk/oxalis/api", name ="messageAndResponses") +@XmlAccessorType(XmlAccessType.FIELD) +public class MessageAndResponses { + + @XmlElement + @Schema(implementation = MessageModel.class, description = "The original document") + private MessageModel originalDocument; + @XmlElement + @Schema(implementation = MessageModel.class, description = "The list of all responses for the original document") + private List responses; + + /** + * Constructs a new instance of the class + */ + public MessageAndResponses() { + } + + /** + * Gets the original document - i.e. the document from the outbox. + * @return The Message as a {@link MessageModel} + */ + public MessageModel getOriginalDocument() { + return originalDocument; + } + + /** + * Sets the original document - i.e. the document from the outbox. + * @param originalDocument The original document + */ + public void setOriginalDocument(MessageModel originalDocument) { + this.originalDocument = originalDocument; + } + + /** + * Gets the list of associated responses received for the original document - i.e. the responses from the inbox + * which reference the original document. + * @return The list of responses received for the original document. + */ + public List getResponses() { + return responses; + } + + /** + * Sets the list of associated responses received for the original document - i.e. the responses from the inbox + * which reference the original document. + * @param responses The list of responses associated with the original document + */ + public void setResponses(List responses) { + this.responses = responses; + } +} diff --git a/src/main/java/dk/erst/oxalis/as4/handlers/dto/MessageLogResponse.java b/src/main/java/dk/erst/oxalis/as4/handlers/dto/MessageLogResponse.java index 2013da2695833b55872bcf81ca052a9f0c856f68..fb8e5fe44944ea1cf5f8a84ab4f21eb2aa49a238 100644 --- a/src/main/java/dk/erst/oxalis/as4/handlers/dto/MessageLogResponse.java +++ b/src/main/java/dk/erst/oxalis/as4/handlers/dto/MessageLogResponse.java @@ -12,7 +12,7 @@ import java.util.List; /** * Represents a message log response with a unique message UUID, an optional Message level response UUID and a {@link MessageLogModel} list of validations. */ -@XmlRootElement +@XmlRootElement(namespace = "http://www.erst.dk/oxalis/api", name ="messageLogResponse") @XmlAccessorType(XmlAccessType.FIELD) public class MessageLogResponse { diff --git a/src/main/java/dk/erst/oxalis/as4/handlers/dto/MessageModel.java b/src/main/java/dk/erst/oxalis/as4/handlers/dto/MessageModel.java index da4a526df806404d6a4e8c104f0cf9e20ca050a2..d65fdb3fc5fce715bbf4ff88f7eb61f4e0d448f3 100644 --- a/src/main/java/dk/erst/oxalis/as4/handlers/dto/MessageModel.java +++ b/src/main/java/dk/erst/oxalis/as4/handlers/dto/MessageModel.java @@ -27,7 +27,8 @@ import java.time.LocalDateTime; "transmissionId", "documentTypeId", "peppolProcessId", - "remoteHost" + "remoteHost", + "messageLevelResponseUuid" }) public class MessageModel { @XmlElement @@ -75,6 +76,10 @@ public class MessageModel { example = "http://localhost:8080/oxalis/as4") private String remoteHost; + @XmlElement + @Schema(description = "The UUID of the Message Level Response or OIOUBL Application Response which has been sent for this message in the event of validation errors") + private String messageLevelResponseUuid; + /** * Returns the status of the document. * @@ -311,6 +316,25 @@ public class MessageModel { model.setDocumentTypeId(m.getDocumentTypeId()); model.setPeppolProcessId(m.getPeppolProcessId()); model.setTransmissionId(m.getTransmissionId()); + model.setMessageLevelResponseUuid(m.getMessageLevelResponseUuid()); return model; } + + /** + * Gets the UUID of the Message Level Response or OIOUBL Application Response which + * has been sent regarding this message in the event of validation errors. + * @return The UUID of the response. + */ + public String getMessageLevelResponseUuid() { + return messageLevelResponseUuid; + } + + /** + * Sets the UUID of the Message Level Response or OIOUBL Application Response which + * has been sent regarding this message in the event of validation errors. + * @param messageLevelResponseUuid The UUID of the response. + */ + public void setMessageLevelResponseUuid(String messageLevelResponseUuid) { + this.messageLevelResponseUuid = messageLevelResponseUuid; + } } diff --git a/src/main/java/dk/erst/oxalis/as4/handlers/dto/MessageModelResponse.java b/src/main/java/dk/erst/oxalis/as4/handlers/dto/MessageModelResponse.java index a3cd5f20dda4944fb89d787312522dec087cdaf4..8222ed4903442b01135e7798b6c8d1837567adf7 100644 --- a/src/main/java/dk/erst/oxalis/as4/handlers/dto/MessageModelResponse.java +++ b/src/main/java/dk/erst/oxalis/as4/handlers/dto/MessageModelResponse.java @@ -12,7 +12,7 @@ import java.util.List; /** * Represents a response containing a {@link MessageModel}. */ -@XmlRootElement +@XmlRootElement(namespace = "http://www.erst.dk/oxalis/api", name ="messageModelResponse") @XmlAccessorType(XmlAccessType.FIELD) public class MessageModelResponse { @XmlElement diff --git a/src/main/java/dk/erst/oxalis/as4/handlers/dto/MessageResponse.java b/src/main/java/dk/erst/oxalis/as4/handlers/dto/MessageResponse.java index 42ae51939e5e7b76cc0309e841bd46f24a919ec6..0a22d84976324a98f1369a56029fc85113e8f7ca 100644 --- a/src/main/java/dk/erst/oxalis/as4/handlers/dto/MessageResponse.java +++ b/src/main/java/dk/erst/oxalis/as4/handlers/dto/MessageResponse.java @@ -10,7 +10,7 @@ import javax.xml.bind.annotation.XmlRootElement; /** * Represents a message response with a unique message UUID. */ -@XmlRootElement +@XmlRootElement(namespace = "http://www.erst.dk/oxalis/api", name ="messageResponse") @XmlAccessorType(XmlAccessType.FIELD) public class MessageResponse { diff --git a/src/main/java/dk/erst/oxalis/as4/handlers/dto/UpdateStatusRequest.java b/src/main/java/dk/erst/oxalis/as4/handlers/dto/UpdateStatusRequest.java index 7e34160621eef447b7cf516ff8b852f5f9be6a4a..273f991cfa89290e78feb0c582da452c250ebd7a 100644 --- a/src/main/java/dk/erst/oxalis/as4/handlers/dto/UpdateStatusRequest.java +++ b/src/main/java/dk/erst/oxalis/as4/handlers/dto/UpdateStatusRequest.java @@ -11,10 +11,10 @@ import javax.xml.bind.annotation.XmlRootElement; /** * Represents a request to update the status of a message. */ -@XmlRootElement(name = "UpdateStatusRequest") +@XmlRootElement(namespace = "http://www.erst.dk/oxalis/api", name = "updateStatusRequest") @XmlAccessorType(XmlAccessType.FIELD) public class UpdateStatusRequest { - @XmlElement(name = "MessageStatus") + @XmlElement @Schema(description = "Status to set on the message. Must be either RECEIVED or CREATED.", example = "RECEIVED") private MessageStatus messageStatus; diff --git a/src/main/java/dk/erst/oxalis/as4/handlers/dto/ValidationResult.java b/src/main/java/dk/erst/oxalis/as4/handlers/dto/ValidationResult.java index 45c2af601e1b36b4cfc44a2689185592b6bca0ff..adc75497024bafccc00f1178e621610bee19fa82 100644 --- a/src/main/java/dk/erst/oxalis/as4/handlers/dto/ValidationResult.java +++ b/src/main/java/dk/erst/oxalis/as4/handlers/dto/ValidationResult.java @@ -14,7 +14,7 @@ import java.util.stream.Collectors; /** * Validation result containing any errors or warnings. */ -@XmlRootElement +@XmlRootElement(namespace = "http://www.erst.dk/oxalis/api", name ="validationResult") @XmlAccessorType(XmlAccessType.FIELD) @XmlType(name = "ValidationResult", propOrder = { "validationType", @@ -253,23 +253,26 @@ public class ValidationResult { if(validationType != null) { sb.append(" (validation type: ").append(validationType).append(")"); } - sb.append(": \n"); + sb.append(": "); if(hasErrors()) { - sb.append(getErrors().size() + " errors: \n") - .append(getErrors().stream().map(e -> e.toString()).collect(Collectors.joining("\n"))) - .append("\n"); + sb.append(getErrors().size()) + .append(" errors: [") + .append(getErrors().stream().map(e -> e.toString()).collect(Collectors.joining(", "))) + .append("]"); } else { - sb.append("0 errors \n"); + sb.append("0 errors"); } + sb.append(", "); if(hasWarnings()) { - sb.append(getWarnings().size() + " warnings: \n") - .append(getWarnings().stream().map(e -> e.toString()).collect(Collectors.joining("\n"))) - .append("\n"); + sb.append(getWarnings().size()) + .append(" warnings: [") + .append(getWarnings().stream().map(e -> e.toString()).collect(Collectors.joining(", "))) + .append("]"); } else { - sb.append("0 Warnings \n"); + sb.append("0 warnings"); } return sb.toString(); diff --git a/src/main/java/dk/erst/oxalis/as4/jdbc/JdbcConf.java b/src/main/java/dk/erst/oxalis/as4/jdbc/JdbcConf.java index 64dc8490c72df7fdb5285b4d2bfbc97af853e3ee..733309be16e740792e1ab6dac03385f9f7b4219d 100644 --- a/src/main/java/dk/erst/oxalis/as4/jdbc/JdbcConf.java +++ b/src/main/java/dk/erst/oxalis/as4/jdbc/JdbcConf.java @@ -115,10 +115,18 @@ public enum JdbcConf { @DefaultValue("none") JDBC_LIQUIBASE_CONTEXTS, /** - * If set to true, run the liquibase located in /src/main/resources/db/oxalis-as4-db-changelog.xml + * If set to true, run the configured {@link #JDBC_LIQUIBASE_CHANGELOG} file. * Make sure to take a look at the {@link JdbcConf#JDBC_LIQUIBASE_CONTEXTS} before setting this to true */ @Path("jdbc.liquibase.run") @DefaultValue("true") - JDBC_LIQUIBASE_RUN; + JDBC_LIQUIBASE_RUN, + + /** + * The changelog file is to be run by liquibase during startup (provided liquibase is enabled via {@link #JDBC_LIQUIBASE_RUN}). + * Default is to use the changelog file located in /src/main/resources/db/oxalis-as4-db-changelog.xml + */ + @Path("jdbc.liquibase.changelog") + @DefaultValue("db/oxalis-as4-db-changelog.xml") + JDBC_LIQUIBASE_CHANGELOG; } diff --git a/src/main/java/dk/erst/oxalis/as4/jdbc/JdbcFilter.java b/src/main/java/dk/erst/oxalis/as4/jdbc/JdbcFilter.java index e5e66491f1b2b27edb19f9465288122f350edd0c..58878d7682c47a7ae748f95e2e4aa8b29e0ad64e 100644 --- a/src/main/java/dk/erst/oxalis/as4/jdbc/JdbcFilter.java +++ b/src/main/java/dk/erst/oxalis/as4/jdbc/JdbcFilter.java @@ -14,7 +14,7 @@ import java.util.Enumeration; /** * A singleton filter class that applies JDBC operations. - *
+ *
* This filter class is responsible for managing JDBC driver deregistration, as well as * cleaning up any abandoned connections. It doesn't apply any filtering operations. */ diff --git a/src/main/java/dk/erst/oxalis/as4/jdbc/JdbcModule.java b/src/main/java/dk/erst/oxalis/as4/jdbc/JdbcModule.java index 15487df69da02e8456639acaeb25204b20828202..cc7ea94e2a214ddedf4739cff5a9c8d93e623766 100644 --- a/src/main/java/dk/erst/oxalis/as4/jdbc/JdbcModule.java +++ b/src/main/java/dk/erst/oxalis/as4/jdbc/JdbcModule.java @@ -21,7 +21,7 @@ import java.util.Map; /** * This module handles JPA/JDBC configuration by reading the configuration specified from {@link JdbcConf}. - *
+ *
* eg.; *
{@code
  * jpaProperties.put("hibernate.connection.driver_class", settings.getString(JdbcConf.JDBC_DRIVER_CLASS))
@@ -65,7 +65,7 @@ public class JdbcModule extends ServletModule {
     /**
      *
      * This method is responsible for initializing the Database. All the configurations that are passed into the JPA properties module, is taken from the Oxalis.conf file (or default values).
-     * 
+ *
* Note: if a H2 database is used (based on the configured dialect) a default connection pool is configured (with a maximum size), otherwise a C3P0 connection pool is configured with a min size and a max size. * * @param settings {@link JdbcConf} - the configured settings injected into the method @@ -96,14 +96,14 @@ public class JdbcModule extends ServletModule { // we have to do liquibase update here (as opposed to configureServlets()), since it requires // the above values to be present if(settings.getString(JdbcConf.JDBC_LIQUIBASE_RUN).contentEquals("true")) { - runLiquibaseMigration(settings.getString(JdbcConf.JDBC_LIQUIBASE_CONTEXTS)); + runLiquibaseMigration(settings.getString(JdbcConf.JDBC_LIQUIBASE_CHANGELOG), settings.getString(JdbcConf.JDBC_LIQUIBASE_CONTEXTS)); } else { logger.info("Skipping liquibase run."); } } - private void runLiquibaseMigration(String liquibaseContexts) { + private void runLiquibaseMigration(String changelogFile, String liquibaseContexts) { logger.info("Running liquibase update with contexts: {}", liquibaseContexts); String url = jpaProperties.get("hibernate.connection.url"); @@ -122,7 +122,7 @@ public class JdbcModule extends ServletModule { // see http://www.h2database.com/html/features.html#in_memory_databases try (Connection connection = DriverManager.getConnection(url, user, password)) { JdbcConnection liquibaseConnection = new JdbcConnection(connection); - Liquibase liquibase = new Liquibase("db/oxalis-as4-db-changelog.xml", new ClassLoaderResourceAccessor(), liquibaseConnection); + Liquibase liquibase = new Liquibase(changelogFile, new ClassLoaderResourceAccessor(), liquibaseConnection); liquibase.update(liquibaseContexts); } catch (SQLException | LiquibaseException e) { throw new RuntimeException(e); diff --git a/src/main/java/dk/erst/oxalis/as4/persistence/model/AccountReceiver.java b/src/main/java/dk/erst/oxalis/as4/persistence/model/AccountReceiver.java index 7de08e390e14828c457ccdebc234497bf11ad12f..194ea19a8227cd94a85a0c0fd36559ccc4845544 100644 --- a/src/main/java/dk/erst/oxalis/as4/persistence/model/AccountReceiver.java +++ b/src/main/java/dk/erst/oxalis/as4/persistence/model/AccountReceiver.java @@ -17,10 +17,6 @@ public class AccountReceiver extends AbstractEntity { @Column(name = "participant_id", nullable = false, unique = true) private String participantId; - // currently, transferDelegation identifies parties by cvr number, thus it is required - @Column(name = "cvr_number", nullable = false) - private String cvrNumber; - /** * Instantiates a new Account receiver. */ @@ -32,12 +28,10 @@ public class AccountReceiver extends AbstractEntity { * * @param account the account * @param participantId the participant id - * @param cvrNumber the cvr number */ - public AccountReceiver(Account account, String participantId, String cvrNumber){ + public AccountReceiver(Account account, String participantId){ this.account = account; this.participantId = participantId; - this.cvrNumber = cvrNumber; } /** @@ -76,21 +70,4 @@ public class AccountReceiver extends AbstractEntity { this.participantId = participantId; } - /** - * Gets cvr number. - * - * @return the cvr number - */ - public String getCvrNumber() { - return cvrNumber; - } - - /** - * Sets cvr number. - * - * @param cvrNumber the cvr number - */ - public void setCvrNumber(String cvrNumber) { - this.cvrNumber = cvrNumber; - } } diff --git a/src/main/java/dk/erst/oxalis/as4/persistence/model/Message.java b/src/main/java/dk/erst/oxalis/as4/persistence/model/Message.java index d0a1c8844e566f793c936522cb6e6065d73926bc..8c0bd0a98f37ab8e12176e3fd46d2207ca3bcf3b 100644 --- a/src/main/java/dk/erst/oxalis/as4/persistence/model/Message.java +++ b/src/main/java/dk/erst/oxalis/as4/persistence/model/Message.java @@ -18,6 +18,13 @@ public class Message extends AbstractEntity { @JoinColumns({ @JoinColumn(name = "account_id", nullable = false) }) private Account account; + @ManyToOne(fetch = FetchType.LAZY, optional = true) + @JoinColumn(name = "original_message", nullable = true) + private Message originalMessage; + + @OneToMany(fetch = FetchType.LAZY, mappedBy = "originalMessage") + private Set responses; + @Enumerated(EnumType.STRING) @Column(name = "status", nullable = false) private MessageStatus status = MessageStatus.CREATED; @@ -468,4 +475,40 @@ public class Message extends AbstractEntity { public void setMessageLevelResponseUuid(String responseMessageUuid) { this.messageLevelResponseUuid = responseMessageUuid; } + + /** + * If message is a representation of a response, then this will have a relation to the original message. + * + * @return the original message + */ + public Message getOriginalMessage() { + return originalMessage; + } + + /** + * Sets the original message that this Message is a response to + * + * @param originalMessage the original message + */ + public void setOriginalMessage(Message originalMessage) { + this.originalMessage = originalMessage; + } + + /** + * Gets all responses for the Message + * + * @return the responses + */ + public Set getResponses() { + return responses; + } + + /** + * Sets all responses to the Message. + * + * @param responses the responses + */ + public void setResponses(Set responses) { + this.responses = responses; + } } diff --git a/src/main/java/dk/erst/oxalis/as4/persistence/model/MessageLog.java b/src/main/java/dk/erst/oxalis/as4/persistence/model/MessageLog.java index a6566120a38411fd9d8c50dfa3cb3c9e049410e9..a9db8e08b0080d2ed94b0dbba433c2da3f1cfcc3 100644 --- a/src/main/java/dk/erst/oxalis/as4/persistence/model/MessageLog.java +++ b/src/main/java/dk/erst/oxalis/as4/persistence/model/MessageLog.java @@ -54,7 +54,7 @@ public class MessageLog { /** *
-        Creates a MessageLog and accompanying application log.
+        Persists a MessageLog and accompanying application log.
         Use this when you have created a Message Log object somewhere else,
         but now wish to save it.
      * 
@@ -120,6 +120,8 @@ public class MessageLog { msgLog.setType(type); msgLog.setValidationLineReference(validationLineReference); + logger.debug(NemhandelLog.EVENT_TEXT, msgLog.getDescription()); + return msgLog; } diff --git a/src/main/java/dk/erst/oxalis/as4/persistence/model/MessageStatus.java b/src/main/java/dk/erst/oxalis/as4/persistence/model/MessageStatus.java index 3e02ee4dbb6261eb2447045ada1e8cb7595b7716..6998177f2f1b2f4edc5c1aa2f4487280a6db6e41 100644 --- a/src/main/java/dk/erst/oxalis/as4/persistence/model/MessageStatus.java +++ b/src/main/java/dk/erst/oxalis/as4/persistence/model/MessageStatus.java @@ -1,6 +1,5 @@ package dk.erst.oxalis.as4.persistence.model; -import org.jetbrains.annotations.NotNull; /** * Used to describe the status of the Message passing through the flow @@ -37,7 +36,7 @@ public enum MessageStatus { * @return the MessageStatus * @throws IllegalArgumentException if no MessageStatus if found with the given name. */ - public static @NotNull MessageStatus getByName(String n) { + public static MessageStatus getByName(String n) { for (MessageStatus v : MessageStatus.values()) { if (v.name().equalsIgnoreCase(n)) return v; diff --git a/src/main/java/dk/erst/oxalis/as4/persistence/model/NemhandelLog.java b/src/main/java/dk/erst/oxalis/as4/persistence/model/NemhandelLog.java index 5fbde49d6a1e22774b8ad46e6453ed16ddfc1e66..8ff65a431e30dbdd6549a56847b310548e4bc6e9 100644 --- a/src/main/java/dk/erst/oxalis/as4/persistence/model/NemhandelLog.java +++ b/src/main/java/dk/erst/oxalis/as4/persistence/model/NemhandelLog.java @@ -37,6 +37,7 @@ import java.util.Set; */ public class NemhandelLog { + static final String EVENT_TEXT = "DocumentLog event recorded with text: \"{}\""; static final String ENTRY_TEXT = "Entry inserted into DocumentLog with id = [{}], and text: \"{}\""; static final String MERGE_TEXT = "Entry updated in DocumentLog with id = [{}], and text \"{}\""; @@ -46,8 +47,8 @@ public class NemhandelLog { /** * Creates a MessageLog and accompanying application log - * @See {@link MessageLog#persist(EntityManager)} - * @See {@link MessageLog#merge(EntityManager)} + * @see MessageLog#persist(EntityManager) + * @see MessageLog#merge(EntityManager) * @param em entity manager * @param message {@link Message} * @param errorCode error codes as string @@ -62,8 +63,8 @@ public class NemhandelLog { } /** * Creates a MessageLog and accompanying application log - * @See {@link MessageLog#persist(EntityManager)} - * @See {@link MessageLog#merge(EntityManager)} + * @see MessageLog#persist(EntityManager) + * @see MessageLog#merge(EntityManager) * @param em entity manager * @param message {@link Message} * @param errorCode error codes object @@ -76,8 +77,8 @@ public class NemhandelLog { } /** * Creates a MessageLog and accompanying application log - * @See {@link MessageLog#persist(EntityManager)} - * @See {@link MessageLog#merge(EntityManager)} + * @see MessageLog#persist(EntityManager) + * @see MessageLog#merge(EntityManager) * @param em entity manager * @param message {@link Message} * @param errorCode error codes object @@ -88,8 +89,8 @@ public class NemhandelLog { } /** * Creates a MessageLog and accompanying application log - * @See {@link MessageLog#persist(EntityManager)} - * @See {@link MessageLog#merge(EntityManager)} + * @see MessageLog#persist(EntityManager) + * @see MessageLog#merge(EntityManager) * @param em entity manager * @param message {@link Message} * @param errorCode {@link ErrorCodes} @@ -114,8 +115,8 @@ public class NemhandelLog { } /** * Creates a MessageLog and accompanying application log - * @See {@link MessageLog#persist(EntityManager)} - * @See {@link MessageLog#merge(EntityManager)} + * @see MessageLog#persist(EntityManager) + * @see MessageLog#merge(EntityManager) * @param em entity manager * @param message {@link Message} * @param errorCode error codes object @@ -128,8 +129,8 @@ public class NemhandelLog { } /** * Creates a MessageLog and accompanying application log - * @See {@link MessageLog#persist(EntityManager)} - * @See {@link MessageLog#merge(EntityManager)} + * @see MessageLog#persist(EntityManager) + * @see MessageLog#merge(EntityManager) * @param em entity manager * @param message {@link Message} * @param validationMessage {@link ValidationMessage} @@ -142,10 +143,10 @@ public class NemhandelLog { /** * OBS: * Should use {@link MessageLog#persist(EntityManager)} or {@link MessageLog#merge(EntityManager)} - * when persisting or merging.


+ * when persisting or merging.


* Should only be used in async env. to avoid breaking the transactions, instead use {@link NemhandelLog#createMessageLog(EntityManager, Message, ValidationMessage)} instead - * @See {@link MessageLog#persist(EntityManager)} - * @See {@link MessageLog#merge(EntityManager)} + * @see MessageLog#persist(EntityManager) + * @see MessageLog#merge(EntityManager) * @param msg {@link Message} * @param validationMessage {@link ValidationMessage} * @return {@link MessageLog} (not persisted) @@ -172,7 +173,7 @@ public class NemhandelLog { /** * Helper class for adding parameters to the MDC for logging purposes. - * MDC => the stored variables that Logback utilizes for parametrization of a loggable line + * MDC => the stored variables that Logback utilizes for parametrization of a loggable line */ public static class WithMDC { private static final String UUID_KEY = "messageUuid"; diff --git a/src/main/java/dk/erst/oxalis/as4/providers/NemhandelProvider.java b/src/main/java/dk/erst/oxalis/as4/providers/NemhandelProvider.java index 5b1eb67853b696c39be80df2723e00b8a4605819..2fe5500a932d73ad8cc68f1224cd93174909b8fc 100644 --- a/src/main/java/dk/erst/oxalis/as4/providers/NemhandelProvider.java +++ b/src/main/java/dk/erst/oxalis/as4/providers/NemhandelProvider.java @@ -4,7 +4,6 @@ import network.oxalis.vefa.peppol.common.model.DocumentTypeIdentifier; import network.oxalis.vefa.peppol.common.model.ParticipantIdentifier; import network.oxalis.vefa.peppol.common.util.ModelUtils; import network.oxalis.vefa.peppol.lookup.api.MetadataProvider; -import org.jetbrains.annotations.NotNull; import java.net.URI; import java.net.URISyntaxException; @@ -78,7 +77,6 @@ public class NemhandelProvider implements MetadataProvider { return resolvedServiceMetaDataURIList; } - @NotNull private static URI getUri(URI location) { if (!location.toString().endsWith("/")) { try { diff --git a/src/main/java/dk/erst/oxalis/as4/rest/ForwardedHeaderFilter.java b/src/main/java/dk/erst/oxalis/as4/rest/ForwardedHeaderFilter.java index ed4e689b09c400766aa6d55f3702b6146ed12f55..f6a5ad7e18c5ddd5fa8f598bfe3ad0f88984d9e8 100644 --- a/src/main/java/dk/erst/oxalis/as4/rest/ForwardedHeaderFilter.java +++ b/src/main/java/dk/erst/oxalis/as4/rest/ForwardedHeaderFilter.java @@ -79,7 +79,7 @@ public class ForwardedHeaderFilter implements javax.servlet.Filter { if (request.getHeader("X-Forwarded-Port") != null) { forwardedPortHeader = request.getHeader("X-Forwarded-Port"); } else { - if (this.host.equalsIgnoreCase("localhost")) { + if (this.host.equalsIgnoreCase("localhost") || this.host.equals("127.0.0.1")) { forwardedPortHeader = String.valueOf(request.getServerPort()); } else { forwardedPortHeader = String.valueOf(-1); diff --git a/src/main/java/dk/erst/oxalis/as4/rest/openapi/CustomOpenApiServlet.java b/src/main/java/dk/erst/oxalis/as4/rest/openapi/CustomOpenApiServlet.java index f5d7d78ccce8008d2e998e0094815ba6c5f0710f..6a455f94dd446d64c9ff9feb77e02f336832093a 100644 --- a/src/main/java/dk/erst/oxalis/as4/rest/openapi/CustomOpenApiServlet.java +++ b/src/main/java/dk/erst/oxalis/as4/rest/openapi/CustomOpenApiServlet.java @@ -3,13 +3,13 @@ package dk.erst.oxalis.as4.rest.openapi; import com.google.inject.Inject; import dk.erst.oxalis.as4.rest.resources.OutboxResource; import dk.erst.oxalis.as4.util.BaseConfig; +import dk.erst.oxalis.as4.validation.version.OxalisAS4Version; import io.swagger.v3.jaxrs2.integration.JaxrsOpenApiContextBuilder; import io.swagger.v3.jaxrs2.integration.ServletConfigContextUtils; import io.swagger.v3.oas.integration.OpenApiConfigurationException; import io.swagger.v3.oas.integration.SwaggerConfiguration; import io.swagger.v3.oas.models.Components; import io.swagger.v3.oas.models.OpenAPI; -import io.swagger.v3.oas.models.info.Contact; import io.swagger.v3.oas.models.info.Info; import io.swagger.v3.oas.models.info.License; import io.swagger.v3.oas.models.security.SecurityRequirement; @@ -58,6 +58,7 @@ public class CustomOpenApiServlet extends io.swagger.v3.jaxrs2.integration.OpenA OpenAPI oas = new OpenAPI(); Info info = new Info() .title("NemHandel eDelivery Reference Access Point API") + .version(OxalisAS4Version.getVersion()) .description("This is API allows a client to interact with the access point to both send and receive " + "documents through the NemHandel eDelivery network. It uses simple HTTP basic authentication " + "for security.\n\n" + diff --git a/src/main/java/dk/erst/oxalis/as4/rest/resources/InboxResource.java b/src/main/java/dk/erst/oxalis/as4/rest/resources/InboxResource.java index f95d3b54b373fe63412d820ea9ea87a9414ac6e8..f97ced48fd2fda09f5eea2ce40a57d2224fa9398 100644 --- a/src/main/java/dk/erst/oxalis/as4/rest/resources/InboxResource.java +++ b/src/main/java/dk/erst/oxalis/as4/rest/resources/InboxResource.java @@ -41,7 +41,8 @@ public class InboxResource { description = "This is a paged API over inbox messages (i.e. messages received by this AP).", tags = "Inbox", responses = { - @ApiResponse(responseCode = "200", content = @Content(schema = @Schema(implementation = ListMessageResponse.class))), + @ApiResponse(responseCode = "200", description = "The list of inbox messages", + content = @Content(schema = @Schema(implementation = ListMessageResponse.class))), @ApiResponse(responseCode = "401", description = "Unauthorized") }) public Response getList( @@ -86,7 +87,8 @@ public class InboxResource { description = "Returns the status of the specified message", tags = "Inbox", responses = { - @ApiResponse(responseCode = "200", content = @Content(schema = @Schema(implementation = MessageLogResponse.class))), + @ApiResponse(responseCode = "200", description = "The status of the inbox message", + content = @Content(schema = @Schema(implementation = MessageModelResponse.class))), @ApiResponse(responseCode = "401", description = "Unauthorized"), @ApiResponse(responseCode = "404", description = "Message with the specified UUID does not exist " + "(for security purposes this is also the result if specifying a message uuid tied to " + @@ -107,7 +109,8 @@ public class InboxResource { description = "Returns errors, warnings and general info, if any, related to the specified message", tags = "Inbox", responses = { - @ApiResponse(responseCode = "200", content = @Content(schema = @Schema(implementation = MessageLogResponse.class))), + @ApiResponse(responseCode = "200", description = "The document log related to the inbox message", + content = @Content(schema = @Schema(implementation = MessageLogResponse.class))), @ApiResponse(responseCode = "401", description = "Unauthorized"), @ApiResponse(responseCode = "404", description = "Message with the specified UUID does not exist " + "(for security purposes this is also the result if specifying a message uuid tied to " + diff --git a/src/main/java/dk/erst/oxalis/as4/rest/resources/OutboxResource.java b/src/main/java/dk/erst/oxalis/as4/rest/resources/OutboxResource.java index 72c36f4a4b72446137f5c369681de6092ae33b3f..08d9a9d3ff640c3b61a70cbc792cfd791c545ed4 100644 --- a/src/main/java/dk/erst/oxalis/as4/rest/resources/OutboxResource.java +++ b/src/main/java/dk/erst/oxalis/as4/rest/resources/OutboxResource.java @@ -5,14 +5,15 @@ import dk.erst.oxalis.as4.handlers.GetOutboxMessagesHandler; import dk.erst.oxalis.as4.handlers.GetMessageLogHandler; import dk.erst.oxalis.as4.handlers.OutboundException; import dk.erst.oxalis.as4.handlers.SendSbdHandler; +import dk.erst.oxalis.as4.handlers.dto.*; +import dk.erst.oxalis.as4.persistence.model.Account; +import dk.erst.oxalis.as4.persistence.model.MessageDirection; import dk.erst.oxalis.as4.handlers.dto.MessageModel; import dk.erst.oxalis.as4.handlers.dto.MessageModelResponse; import dk.erst.oxalis.as4.handlers.dto.MessageResponse; import dk.erst.oxalis.as4.handlers.dto.MessageLogResponse; import dk.erst.oxalis.as4.handlers.dto.ListMessageResponse; import dk.erst.oxalis.as4.handlers.dto.ValidationResult; -import dk.erst.oxalis.as4.persistence.model.Account; -import dk.erst.oxalis.as4.persistence.model.MessageDirection; import dk.erst.oxalis.as4.persistence.model.NemhandelLog; import dk.erst.oxalis.as4.rest.security.SecurityContextHolder; import dk.erst.oxalis.as4.util.SBDMessageContext; @@ -58,7 +59,7 @@ public class OutboxResource { summary = "Sends the specified document through the AP. ", description = "If the AP is running in NemHandel eDelivery mode, then the document can be signed " + - "via an ASiC-E container in the header including a transfer-delegation.\n\n" + + "via a Nemhandel e-Delivery document signature in the header.\n\n" + "Example of NemHandel eDelivery compliant document:\n" + "```" + "\n" + @@ -87,44 +88,53 @@ public class OutboxResource { " PROCESSID\n" + " urn:www.cenbii.eu:profile:bii04:ver2.0\n" + " \n" + + " \n" + " \n" + " NEMHANDEL_EDELIVERY_SIGNATURE\n" + - " \n" + + " \n" + + " nemhandel-edelivery-1.2\n" + " \n" + " \n" + " \n" + " ...\n" + "\n" + "```\n\n" + - "The contents of the ASiC-E container is expected to be the following:\n" + - "```\n" + - "asic-container.asice:\n" + - " |\n" + - " +-- mimetype\n" + - " |\n" + - " +-- standard-business-document.xml (2)\n" + - " |\n" + - " +-- original-standard-business-document.xml (3)\n" + - " |\n" + - " +-- transfer-delegation.xml (4)\n" + - " |\n" + - " +-- META-INF/\n" + - " |\n" + - " +-- manifest.xml\n" + - " |\n" + - " +-- signatures001.xml (1)\n" + - "```\n" + - "The following files are of specific interest:\n" + - "1. The actual signature of the ASiC-E container is contained in this file. An ASiC-E container may contain multiple signatures though we only require one, which must include the following 3 files (1 optional).\n" + - "2. The Standard Business Document (SBD) to be signed (thus this is excluding the ASiC-E container). This is the file that Corner X will send to Corner X+1.\n" + - "3. The full \"original\" Standard Business Document including the ASiC-E container which has been received from Corner X-1. Note that for Corner 1 this file is optional in case this corner is responsible for generating the original SBD.\n" + - "4. A transfer delegation documents the intention of Corner X to transfer the SBD to Corner X+1. \n\n" + + "The Nemhandel e-Delivery document signature is an detached XAdES signature. Please refer to the specification and the developer documentation for further information.\n\n" + "*NOTE:* Parts of the validation of the message on the receiver end runs asynchronously and returns a MessageLevelResponse in the event of errors. More documentation can be found in `/docs/nemhandel-edelivery.md` in the git repository.", tags = "Outbox", responses = { - @ApiResponse(responseCode = "200", content = @Content(schema = @Schema(implementation = MessageResponse.class))), - @ApiResponse(responseCode = "400", content = @Content(schema = @Schema(implementation = ValidationResult.class))), - @ApiResponse(responseCode = "401", description = "Unautorized") + @ApiResponse(responseCode = "200", description = "Message response with a unique message UUID", + content = @Content(mediaType = MediaType.APPLICATION_XML, schema = @Schema(implementation = MessageResponse.class))), + @ApiResponse(responseCode = "400", description = "Indicates that an error occurred", + content = @Content(mediaType = MediaType.APPLICATION_XML, schema = @Schema(anyOf = {ErrorResponse.class, ValidationResult.class}, implementation = ErrorResponse.class), + examples = {@ExampleObject(name = "ErrorResponse", description = "errorResponse", + value = "\n" + + "\n" + + "d4a28d16-bf5e-4332-aca7-eb10b09ddf48\n" + + "\n" + + " E-APS24003\n" + + " Error while transmitting request\n" + + "\n" + + ""), + @ExampleObject(name = "ValidationResult", + value = "\n" + + "\n" + + " ALL\n" + + " \n" + + " ERROR\n" + + " SCHMTRN-1\n" + + " [BR-CO-16]-Amount due for payment (BT-115) = Invoice total amount with VAT (BT-112) -Paid amount (BT-113) +Rounding amount (BT-114).\n" + + " /*:CreditNote[namespace-uri()='urn:oasis:names:specification:ubl:schema:xsd:CreditNote-2'][1]/*:LegalMonetaryTotal[namespace-uri()='urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2'][1]\n" + + " \n" + + " \n" + + " ERROR\n" + + " SCHMTRN-1\n" + + " [BR-CO-16]-Amount due for payment (BT-115) = Invoice total amount with VAT (BT-112) -Paid amount (BT-113) +Rounding amount (BT-114).\n" + + " /*:CreditNote[namespace-uri()='urn:oasis:names:specification:ubl:schema:xsd:CreditNote-2'][1]/*:LegalMonetaryTotal[namespace-uri()='urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2'][1]\n" + + " \n" + + " cfa510ec-2465-4a41-9289-db1290936785\n" + + "")})), + @ApiResponse(responseCode = "401", description = "Unauthorized") } ) public Response sendStandardBusinessDocument( @@ -135,8 +145,8 @@ public class OutboxResource { mediaType = MediaType.APPLICATION_XML, examples = @ExampleObject(name ="NemHandel eDelivery-compliant document", summary = "NemHandel eDelivery-compliant document", - description = "This is the structure of NemHandel eDelivery-compliant document with an " + - "ASiC-E container in the StandardBusinessDocumentHeader", + description = "This is the structure of NemHandel eDelivery-compliant document with a Nemhandel e-Delivery document signature " + + "in the StandardBusinessDocumentHeader", value = "\n" + " \n" + " 1.0\n" + @@ -164,7 +174,8 @@ public class OutboxResource { " \n" + " \n" + " NEMHANDEL_EDELIVERY_SIGNATURE\n" + - " \n" + + " \n" + + " nemhandel-edelivery-1.2\n" + " \n" + " \n" + " \n" + @@ -186,7 +197,7 @@ public class OutboxResource { description = "This is a paged API over outbox messages (i.e. messages sent through this AP).", tags = "Outbox", responses = { - @ApiResponse(responseCode = "200", content = @Content(schema = @Schema(implementation = ListMessageResponse.class))), + @ApiResponse(responseCode = "200", description = "A list of outbox messages", content = @Content(schema = @Schema(implementation = ListMessageResponse.class))), @ApiResponse(responseCode = "401", description = "Unauthorized") }) public Response listOutboxMessages( @@ -197,6 +208,28 @@ public class OutboxResource { return Response.ok(dataResponse, MediaType.APPLICATION_XML_TYPE).build(); } + @GET + @Path("/{uuid}") + @Produces(MediaType.APPLICATION_OCTET_STREAM) + @Operation(summary = "Fetches a specific message from the outbox.", + description = "Returns the actual message as binary. Note that the message has been signed by C2 (assuming no validation errors) and thus may not match the message which was sent from C1 exactly", + tags = "Outbox", + responses = { + @ApiResponse(responseCode = "200", description = "The actual message as a binary octet stream"), + @ApiResponse(responseCode = "401", description = "Unauthorized"), + @ApiResponse(responseCode = "404", description = "Message with the specified UUID does not exist " + + "(for security purposes this is also the result if specifying a message uuid tied to " + + "another account)") + }) + public Response getMessageContent( + @Parameter(description = "UUID of the message to fetch") + @PathParam("uuid") String uuid) { + logger.debug("Trying to get specific doc with uuid {}", uuid); + Account account = SecurityContextHolder.getSecurityContext().getAccount(); + NemhandelLog.WithMDC.cleanMDC(); + return Response.status(Response.Status.OK).entity(getOutboxMessagesHandler.getMessageContent(uuid, account)).build(); + } + @GET @Produces(MediaType.APPLICATION_XML) @Path("/{messageUUID}/document-log") @@ -204,7 +237,8 @@ public class OutboxResource { description = "Returns errors, warnings and general info, if any, related to the specified message", tags = "Outbox", responses = { - @ApiResponse(responseCode = "200", content = @Content(schema = @Schema(implementation = MessageLogResponse.class))), + @ApiResponse(responseCode = "200", description = "The document log related to the outbox message", + content = @Content(schema = @Schema(implementation = MessageLogResponse.class))), @ApiResponse(responseCode = "401", description = "Unauthorized"), @ApiResponse(responseCode = "404", description = "Message with the specified UUID does not exist " + "(for security purposes this is also the result if specifying a message uuid tied to " + @@ -217,6 +251,25 @@ public class OutboxResource { } + @GET + @Produces(MediaType.APPLICATION_XML) + @Path("/{messageUUID}/with-responses") + @Operation(summary = "Fetches the original document and a list of all responses tied to document", + tags = "Outbox", + responses = { + @ApiResponse(responseCode = "200", description = "The original document and a list of all responses tied to the document", + content = @Content(schema = @Schema(implementation = MessageAndResponses.class))), + @ApiResponse(responseCode = "401", description = "Unauthorized"), + @ApiResponse(responseCode = "404", description = "Message with the specified UUID does not exist " + + "(for security purposes this is also the result if specifying a message uuid tied to " + + "another account)") + }) + public Response getMessageAndResponses(@PathParam("messageUUID") String uuid) { + Account account = SecurityContextHolder.getSecurityContext().getAccount(); + MessageAndResponses dataResponse = getOutboxMessagesHandler.getMessageAndAllResponses(account, uuid); + return Response.ok(dataResponse, MediaType.APPLICATION_XML_TYPE).build(); + } + @GET @Path("/{messageUUID}/status") @Produces(MediaType.APPLICATION_XML) @@ -224,7 +277,8 @@ public class OutboxResource { description = "Returns the status of the specified message", tags = "Outbox", responses = { - @ApiResponse(responseCode = "200", content = @Content(schema = @Schema(implementation = MessageLogResponse.class))), + @ApiResponse(responseCode = "200", description = "The status of the outbox message", + content = @Content(schema = @Schema(implementation = MessageModelResponse.class))), @ApiResponse(responseCode = "401", description = "Unauthorized"), @ApiResponse(responseCode = "404", description = "Message with the specified UUID does not exist " + "(for security purposes this is also the result if specifying a message uuid tied to " + diff --git a/src/main/java/dk/erst/oxalis/as4/signature/SignatureException.java b/src/main/java/dk/erst/oxalis/as4/signature/SignatureException.java new file mode 100644 index 0000000000000000000000000000000000000000..1dc54ecd57651d9f7ea745af1ad0ce3cfed876a9 --- /dev/null +++ b/src/main/java/dk/erst/oxalis/as4/signature/SignatureException.java @@ -0,0 +1,54 @@ +package dk.erst.oxalis.as4.signature; + +/** + * Exception thrown when an unexpected error occurs during signature. + */ +public class SignatureException extends Exception { + + /** + * Constructs a new empty signature exception. + */ + public SignatureException() { + } + + /** + * Constructs a new signature exception with the specified detail message. + * + * @param message the detail message. + */ + public SignatureException(String message) { + super(message); + } + + /** + * Constructs a new signature exception with the specified detail message and cause. + * + * @param message the detail message. + * @param cause the cause of the exception. + */ + public SignatureException(String message, Throwable cause) { + super(message, cause); + } + + /** + * Constructs a new signature exception with the specified {@link Throwable} cause. + * + * @param cause the cause of the exception. + */ + public SignatureException(Throwable cause) { + super(cause); + } + + /** + * Constructs a new signature exception with the specified detail message, cause, + * suppression enabled or disabled, and writable stack trace enabled or disabled. + * + * @param message the detail message. + * @param cause the cause of the exception. + * @param enableSuppression whether suppression is enabled or disabled. + * @param writableStackTrace whether the stack trace should be writable. + */ + public SignatureException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } +} diff --git a/src/main/java/dk/erst/oxalis/as4/signature/SignatureFactory.java b/src/main/java/dk/erst/oxalis/as4/signature/SignatureFactory.java index 8027ee8b51181df648570b9c5b52b7de7d4a0bce..ee04454ff39802ed7bc923314ff56fcac15bb4c1 100644 --- a/src/main/java/dk/erst/oxalis/as4/signature/SignatureFactory.java +++ b/src/main/java/dk/erst/oxalis/as4/signature/SignatureFactory.java @@ -5,6 +5,7 @@ import dk.erst.oxalis.as4.util.SBDMessageContext; import org.apache.xml.security.exceptions.XMLSecurityException; import org.xml.sax.SAXException; +import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.TransformerException; import javax.xml.xpath.XPathExpressionException; import java.io.IOException; @@ -18,15 +19,9 @@ public interface SignatureFactory { * Signs a document using the XAdES signature format. * * @param sbdDocument the SBD document to be signed - * @param originalDocument the original document that is being signed - * @param transferDelegation the transfer delegation associated with the document * @param messageContext the SBD message context * @return the signed document as a byte array - * @throws IOException if an I/O error occurs during the signing process - * @throws SAXException if an error occurs while parsing the document - * @throws TransformerException if an error occurs during the transformation process - * @throws XPathExpressionException if an error occurs during XPath expression evaluation - * @throws XMLSecurityException This exception is thrown if canonicalization fails (e.g. malformed input XML). Also thrown if canonicalization engine cannot be initialized. + * @throws SignatureException if an exception occurs while signing the document */ - public byte[] signDocument(byte[] sbdDocument, byte[] originalDocument, byte[] transferDelegation, SBDMessageContext messageContext) throws IOException, SAXException, TransformerException, XPathExpressionException, XMLSecurityException; + public byte[] signDocument(byte[] sbdDocument, SBDMessageContext messageContext) throws SignatureException; } diff --git a/src/main/java/dk/erst/oxalis/as4/signature/SignatureFactoryImpl.java b/src/main/java/dk/erst/oxalis/as4/signature/SignatureFactoryImpl.java index eaf72b101929e2b5840369046ac0eaddc7507767..7cdd5cb9af58eaac91364057cfc36446bc5c47ec 100644 --- a/src/main/java/dk/erst/oxalis/as4/signature/SignatureFactoryImpl.java +++ b/src/main/java/dk/erst/oxalis/as4/signature/SignatureFactoryImpl.java @@ -1,112 +1,130 @@ package dk.erst.oxalis.as4.signature; import com.google.inject.Provider; +import dk.erst.oxalis.as4.EDeliverySpecification; import dk.erst.oxalis.as4.error.ErrorCodes; import dk.erst.oxalis.as4.util.DocumentUtil; import dk.erst.oxalis.as4.util.SBDMessageContext; +import dk.erst.oxalis.as4.util.SBDPayloadExtractor; import dk.erst.oxalis.as4.util.XPathUtil; import dk.erst.oxalis.as4.validation.signature.SignatureValidator; -import eu.europa.esig.dss.asic.xades.ASiCWithXAdESSignatureParameters; -import eu.europa.esig.dss.asic.xades.signature.ASiCWithXAdESService; -import eu.europa.esig.dss.enumerations.ASiCContainerType; -import eu.europa.esig.dss.enumerations.SignatureLevel; -import eu.europa.esig.dss.model.*; -import eu.europa.esig.dss.model.x509.CertificateToken; +import eu.europa.esig.dss.enumerations.MimeTypeEnum; +import eu.europa.esig.dss.model.DSSDocument; +import eu.europa.esig.dss.model.InMemoryDocument; +import eu.europa.esig.dss.model.SignatureValue; +import eu.europa.esig.dss.model.ToBeSigned; import eu.europa.esig.dss.token.AbstractKeyStoreTokenConnection; import eu.europa.esig.dss.token.DSSPrivateKeyEntry; +import eu.europa.esig.dss.xades.XAdESSignatureParameters; +import eu.europa.esig.dss.xades.signature.XAdESService; import network.oxalis.vefa.peppol.sbdh.Ns; import org.apache.cxf.helpers.MapNamespaceContext; -import org.apache.xml.security.exceptions.XMLSecurityException; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; -import org.xml.sax.SAXException; import javax.xml.parsers.DocumentBuilder; -import javax.xml.transform.TransformerException; import javax.xml.xpath.XPath; import javax.xml.xpath.XPathExpressionException; import javax.xml.xpath.XPathFactory; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; -import java.util.ArrayList; +import java.nio.charset.StandardCharsets; import java.util.Base64; -import java.util.Date; -import java.util.List; /** - * Implementation of the signature ASiC creation. + * Signature factory which can sign Standard Business Documents in compliance with the Nemhandel e-Delivery 1.2 specification */ public class SignatureFactoryImpl implements SignatureFactory { - private final ASiCWithXAdESService signatureService; + private final Provider signatureServiceProvider; private final AbstractKeyStoreTokenConnection signatureToken; private final DSSPrivateKeyEntry signaturePrivateKey; private final Provider documentBuilderProvider; + private final SBDPayloadExtractor payloadExtractor; + private final Provider signatureParametersProvider; /** * Instantiates a new Signature factory. * - * @param signatureService the signature service, see {@link ASiCWithXAdESService} + * @param signatureServiceProvider the signature service, see {@link XAdESService} * @param signatureToken the signature token, see {@link AbstractKeyStoreTokenConnection} * @param signaturePrivateKey the signature private key, see {@link DSSPrivateKeyEntry} * @param documentBuilderProvider the document builder provider, see {@link DocumentBuilder} + * @param payloadExtractor The extractor to use in extracting the payload from the SBD. + * @param signatureParametersProvider A provider of {@link XAdESSignatureParameters} to use for the signing process. */ - public SignatureFactoryImpl(ASiCWithXAdESService signatureService, AbstractKeyStoreTokenConnection signatureToken, DSSPrivateKeyEntry signaturePrivateKey, Provider documentBuilderProvider) { - this.signatureService = signatureService; + public SignatureFactoryImpl(Provider signatureServiceProvider, + AbstractKeyStoreTokenConnection signatureToken, + DSSPrivateKeyEntry signaturePrivateKey, + Provider documentBuilderProvider, + SBDPayloadExtractor payloadExtractor, + Provider signatureParametersProvider) { + this.signatureServiceProvider = signatureServiceProvider; this.signatureToken = signatureToken; this.signaturePrivateKey = signaturePrivateKey; this.documentBuilderProvider = documentBuilderProvider; + this.payloadExtractor = payloadExtractor; + this.signatureParametersProvider = signatureParametersProvider; } /** - * Creates a signed ASiC container from a SBD, the original SBD (if present) and a transfer delegation and adds it to returned SBD. - * Note: If the {@code originalDocument} is from C1 and unsigned, it is not included in the ASiC container. + * Signs the provided SBD {@code document} in compliance with the Nemhandel e-Delivery 1.2 specification. + *

+ * The signature is added to the provided SBD header as a {@code } with Type = NEMHANDEL_EDELIVERY_SIGNATURE. + *

+ * Note: only the payload of the SBD is signed and it is canonicalized using Exclusive XML Canonicalization v1.0 specification (xml-exc-c14n for short, algorithm name "http://www.w3.org/2001/10/xml-exc-c14n#") before signing. * - * @param document the SBD the ASiC container is added to - * @param originalDocument the original SBD if present - * @param transferDelegation the transfer delegation + * @param document the SBD containing the payload to sign. * @param messageContext the message context {@link SBDMessageContext} - * @return the signed SBD a byte array containing the signed SBD - * @throws IOException if an I/O error occurs during the signing process - * @throws SAXException if an error occurs while parsing the document - * @throws TransformerException if an error occurs during the transformation process - * @throws XPathExpressionException if an error occurs during XPath expression evaluation + * @return the SBD as a byte array containing with the signature added as a business scope in the SBD header. + * @throws SignatureException if an error occurs during the signing process */ @Override - public byte[] signDocument(byte[] document, byte[] originalDocument, byte[] transferDelegation, SBDMessageContext messageContext) throws IOException, SAXException, TransformerException, XPathExpressionException, XMLSecurityException { - // Create ASiC container Xpath + public byte[] signDocument(byte[] document, SBDMessageContext messageContext) throws SignatureException { + if (document == null || document.length == 0) { + throw new IllegalArgumentException(ErrorCodes.SIGNING_EDELIVERY_SBD_EMPTY_OR_NULL.formatErrorMessage()); + } + XPathFactory xPathFactory = XPathFactory.newInstance(); XPath xpath = xPathFactory.newXPath(); MapNamespaceContext nsCtx = new MapNamespaceContext(); nsCtx.addNamespace("sbd", Ns.SBDH); xpath.setNamespaceContext(nsCtx); - // Create ASiC container - byte[] cleanSbd = removeAsicInSbdh(document, xpath); - cleanSbd = DocumentUtil.canonicalize(cleanSbd); - byte [] asicContainer = createNemhandelEDeliverySignature(cleanSbd, originalDocument, transferDelegation, messageContext); - DocumentBuilder db = documentBuilderProvider.get(); - try (ByteArrayInputStream bis = new ByteArrayInputStream(cleanSbd)) { + try (ByteArrayInputStream bis = new ByteArrayInputStream(document)) { Document doc = db.parse(bis); - InsertAsicInSbdh(doc, asicContainer, xpath); + + Document payload = payloadExtractor.extractPayload(doc); + + /** + * NOTE: we need to serialize to byte-array before canonicalization, + * Otherwise namespaces may not be correctly available during canonicalization which will lead to validation errors. + * To avoid this SBDPayloadExtractor implementation must be changed to e.g. use XPath to extract payload + */ + DSSDocument unsigned = new InMemoryDocument(DocumentUtil.canonicalize(DocumentUtil.toByteArray(payload)), SignatureValidator.XAdES_SIGNATURE_PAYLOAD_NAME, MimeTypeEnum.XML); + DSSDocument signature = createXAdESSignature(unsigned, signatureParametersProvider.get()); + + appendNemhandelSignatureBusinessScope(doc, signature, xpath); // Convert document to byte array byte[] updatedDocByteArray = DocumentUtil.toByteArray(doc); return updatedDocByteArray; + } catch (Exception e) { + throw new SignatureException("Error while signing document", e); } } /** - * Adds the ASiC container to the SBD in the NEMHANDEL_EDELIVERY_SIGNATURE Scope. + * Adds the Nemhandel e-Delivery document signature to the SBD in the NEMHANDEL_EDELIVERY_SIGNATURE Scope. * - * @param document the SBD the ASiC container should be added to - * @param asicContainer a byte array containing the ASiC container + * @param document the SBD the signature should be added to + * @param signature the Nemhandel e-Delivery signature to include in the business scope * @param xpath the xpath instance * @throws XPathExpressionException if an error occurs during XPath expression evaluation */ - private void InsertAsicInSbdh(Document document, byte[] asicContainer, XPath xpath) throws XPathExpressionException { + private void appendNemhandelSignatureBusinessScope(Document document, DSSDocument signature, XPath xpath) throws XPathExpressionException, IOException { Element scope = document.createElementNS(Ns.SBDH, "Scope"); Element type = document.createElementNS(Ns.SBDH, "Type"); @@ -114,9 +132,18 @@ public class SignatureFactoryImpl implements SignatureFactory { scope.appendChild(type); Element instanceId = document.createElementNS(Ns.SBDH, "InstanceIdentifier"); - instanceId.setTextContent(Base64.getEncoder().encodeToString(asicContainer)); + try(ByteArrayOutputStream bos = new ByteArrayOutputStream()) { + signature.writeTo(bos); + byte[] signatureContent = bos.toByteArray(); + String encoded = new String(Base64.getEncoder().encode(signatureContent), StandardCharsets.UTF_8); + instanceId.setTextContent(encoded); + } scope.appendChild(instanceId); + Element identifier = document.createElementNS(Ns.SBDH, "Identifier"); + identifier.setTextContent(EDeliverySpecification.NEMHANDEL_EDELIVERY_1_2.value()); + scope.appendChild(identifier); + String expr = "/sbd:StandardBusinessDocument/sbd:StandardBusinessDocumentHeader/sbd:BusinessScope"; Node node = XPathUtil.evaluateXPathNode(document, xpath, expr); if(node != null) { @@ -124,98 +151,11 @@ public class SignatureFactoryImpl implements SignatureFactory { } } - /** - * Removes the ASiC container if present. - * This is done to ensure that the signed document itself does not contain the old ASiC container if any is present. - * - * @param document the SBD the ASiC container is added to - * @param xpath the xpath instance - * @return the SBD a byte array containing the SBD without the ASiC container - * @throws IOException if an I/O error occurs during the creation of the input stream - * @throws SAXException if an error occurs while parsing the document - * @throws TransformerException if an error occurs during the transformation process - * @throws XPathExpressionException if an error occurs during XPath expression evaluation - */ - private byte[] removeAsicInSbdh(byte[] document, XPath xpath) throws XPathExpressionException, IOException, SAXException, TransformerException { - - if (document == null || document.length == 0) { - throw new IllegalArgumentException(ErrorCodes.SIGNING_EDELIVERY_SBD_EMPTY_OR_NULL.formatErrorMessage()); - } - DocumentBuilder db = documentBuilderProvider.get(); - try (ByteArrayInputStream bis = new ByteArrayInputStream(document)) { - Document doc = db.parse(bis); - - String exprAsic = "/sbd:StandardBusinessDocument/sbd:StandardBusinessDocumentHeader/sbd:BusinessScope/sbd:Scope[sbd:Type='NEMHANDEL_EDELIVERY_SIGNATURE']"; - Node nodeAsic = XPathUtil.evaluateXPathNode(doc, xpath, exprAsic); - if(nodeAsic != null) { - nodeAsic.getParentNode().removeChild(nodeAsic); - } - - byte[] res = DocumentUtil.toByteArray(doc); - return res; - } - } - - /** - * Creates a Nemhandel E Delivery Signature ASiC container from a SBD, the original SBD (if present) and a transfer delegation. - * Note: If the {@code originalDocument} is from C1 and unsigned, it is not included in the ASiC container. - * - * @param sbd the SBD the ASiC container is added to - * @param originalDoc the original SBD if present - * @param transferDelegation the transfer delegation - * @param messageContext the message context {@link SBDMessageContext} - * @return the ASiC container a byte array containing the signed ASiC container - * @throws IOException if an I/O error occurs during the signing process - * @throws SAXException if an error occurs while parsing the document - * @throws TransformerException if an error occurs during the transformation process - * @throws XPathExpressionException if an error occurs during XPath expression evaluation - */ - private byte[] createNemhandelEDeliverySignature(byte[] sbd, byte[] originalDoc, byte[] transferDelegation, SBDMessageContext messageContext) { - List documentsToSign = new ArrayList<>(); - if (sbd != null && sbd.length > 0) { - documentsToSign.add(new InMemoryDocument(sbd, SignatureValidator.ASIC_SBD_FILENAME, MimeType.XML)); - } else { - throw new IllegalArgumentException(ErrorCodes.SIGNING_EDELIVERY_SBD_EMPTY_OR_NULL.formatErrorMessage()); - } - - if(originalDoc != null && originalDoc.length > 0) { - documentsToSign.add(new InMemoryDocument(originalDoc, SignatureValidator.ASIC_ORIGINAL_SBD_FILENAME, MimeType.XML)); - } else if(messageContext == null || (!messageContext.isInternalMessageLevelResponse() && messageContext.isDocumentSignatureRequired())) { - // only allow original to be null for internal MLR or when C1s SBD is unsigned. The messageContext.isDocumentSignatureRequired() controls when an SBD is required to be signed. - // Currently this is only the SBD sent by C1. For everything else the original SBD should be present - throw new IllegalArgumentException(ErrorCodes.SIGNING_EDELIVERY_ORIGINAL_SBD_EMPTY_OR_NULL.formatErrorMessage()); - } - - if(transferDelegation != null && transferDelegation.length > 0) { - documentsToSign.add(new InMemoryDocument(transferDelegation, SignatureValidator.ASIC_TRANSFER_DELEGATION_FILENAME, MimeType.XML)); - } else { - throw new IllegalArgumentException(ErrorCodes.SIGNING_EDELIVERY_TRANSFER_DELEGATION_EMPTY_OR_NULL.formatErrorMessage()); - } - - DSSDocument asic = createASiCContainer(documentsToSign, getSignatureParameters(signaturePrivateKey.getCertificate(), signaturePrivateKey.getCertificateChain())); - - try(ByteArrayOutputStream bos = new ByteArrayOutputStream()) { - asic.writeTo(bos); - return bos.toByteArray(); - } catch (IOException e) { - throw new RuntimeException(ErrorCodes.SIGNING_EDELIVERY_ASIC_CONTAINER_TRANSFORMING_ERROR.formatErrorMessage()); - } - } - - private DSSDocument createASiCContainer(List documentsToSign, ASiCWithXAdESSignatureParameters signatureParameters) { - ToBeSigned dataToSign = signatureService.getDataToSign(documentsToSign, signatureParameters); + private DSSDocument createXAdESSignature(DSSDocument unsigned, XAdESSignatureParameters signatureParameters) { + XAdESService service = signatureServiceProvider.get(); + ToBeSigned dataToSign = service.getDataToSign(unsigned, signatureParameters); SignatureValue signatureValue = signatureToken.sign(dataToSign, signatureParameters.getDigestAlgorithm(), signaturePrivateKey); - DSSDocument signedDocument = signatureService.signDocument(documentsToSign, signatureParameters, signatureValue); - return signedDocument; + return service.signDocument(unsigned, signatureParameters, signatureValue); } - private ASiCWithXAdESSignatureParameters getSignatureParameters(CertificateToken certificateToken, CertificateToken[] certificateChain) { - ASiCWithXAdESSignatureParameters signatureParameters = new ASiCWithXAdESSignatureParameters(); - signatureParameters.bLevel().setSigningDate(new Date()); - signatureParameters.setSigningCertificate(certificateToken); - signatureParameters.setCertificateChain(certificateChain); - signatureParameters.setSignatureLevel(SignatureLevel.XAdES_BASELINE_B); - signatureParameters.aSiC().setContainerType(ASiCContainerType.ASiC_E); - return signatureParameters; - } } diff --git a/src/main/java/dk/erst/oxalis/as4/signature/TransferDelegationException.java b/src/main/java/dk/erst/oxalis/as4/signature/TransferDelegationException.java deleted file mode 100644 index 1b2409f7cb56043c13b4fb7917f69dc2565ff5f4..0000000000000000000000000000000000000000 --- a/src/main/java/dk/erst/oxalis/as4/signature/TransferDelegationException.java +++ /dev/null @@ -1,52 +0,0 @@ -package dk.erst.oxalis.as4.signature; - -/** - * Exception for errors relating to the creation of transfer delegations. - */ -public class TransferDelegationException extends Throwable { - /** - * Instantiates a new Transfer delegation exception. - */ - public TransferDelegationException() { - } - - /** - * Instantiates a new Transfer delegation exception with a message. - * - * @param message the Transfer delegation exception message - */ - public TransferDelegationException(String message) { - super(message); - } - - /** - * Instantiates a new Transfer delegation exception with a message and root cause. - * - * @param message the Transfer delegation exception message - * @param cause the Transfer delegation exception cause - */ - public TransferDelegationException(String message, Throwable cause) { - super(message, cause); - } - - /** - * Instantiates a new Transfer delegation exception with a root cause. - * - * @param cause the Transfer delegation exception cause - */ - public TransferDelegationException(Throwable cause) { - super(cause); - } - - /** - * Instantiates a new Transfer delegation exception with a message, a root cause and a flag for if we enable suppression and a flag for if the stacktrace is writable. - * - * @param message the Transfer delegation exception message - * @param cause the Transfer delegation exception cause - * @param enableSuppression the enable suppression see {@link Throwable} - * @param writableStackTrace the writable stack trace see {@link Throwable} - */ - public TransferDelegationException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { - super(message, cause, enableSuppression, writableStackTrace); - } -} diff --git a/src/main/java/dk/erst/oxalis/as4/signature/TransferDelegationFactory.java b/src/main/java/dk/erst/oxalis/as4/signature/TransferDelegationFactory.java deleted file mode 100644 index 510c26d33961524c8a30bdb0857abf91b6714a13..0000000000000000000000000000000000000000 --- a/src/main/java/dk/erst/oxalis/as4/signature/TransferDelegationFactory.java +++ /dev/null @@ -1,24 +0,0 @@ -package dk.erst.oxalis.as4.signature; - -import javax.xml.bind.JAXBException; - -/** - * The interface Transfer delegation factory provides a contract for implementing classes that wants to create a transfer delegation. - */ -public interface TransferDelegationFactory { - /** - * The constant ASIC_TRANSFER_DELEGATION_SCHEME_CVR. - */ - String ASIC_TRANSFER_DELEGATION_SCHEME_CVR = "DK:CVR"; - - /** - * Create transfer delegation from a sender CVR and receiver CVR. - * - * @param senderCVR the sender cvr - * @param receiverCVR the receiver cvr - * @return a byte array contains the transfer delegation - * @throws JAXBException if an error occurs during the jaxb marshalling - * @throws TransferDelegationException if an error occurs during the creation of the transfer delegation - */ - public byte[] createTransferDelegation(String senderCVR, String receiverCVR) throws JAXBException, TransferDelegationException; -} diff --git a/src/main/java/dk/erst/oxalis/as4/signature/TransferDelegationFactoryImpl.java b/src/main/java/dk/erst/oxalis/as4/signature/TransferDelegationFactoryImpl.java deleted file mode 100644 index 2078b6a4440c1c095dd19b284785fd18426b3444..0000000000000000000000000000000000000000 --- a/src/main/java/dk/erst/oxalis/as4/signature/TransferDelegationFactoryImpl.java +++ /dev/null @@ -1,75 +0,0 @@ -package dk.erst.oxalis.as4.signature; - -import dk.erst.oxalis.as4.error.ErrorCodes; -import dk.erst.oxalis.transferdelegation.ObjectFactory; -import dk.erst.oxalis.transferdelegation.Receiver; -import dk.erst.oxalis.transferdelegation.Sender; -import dk.erst.oxalis.transferdelegation.TransferDelegation; -import org.apache.commons.lang3.StringUtils; - -import javax.xml.bind.JAXBContext; -import javax.xml.bind.JAXBElement; -import javax.xml.bind.JAXBException; -import javax.xml.bind.Marshaller; -import java.io.ByteArrayOutputStream; -import java.io.IOException; - -/** - * Implementation of the transfer delegation creation. - */ -public class TransferDelegationFactoryImpl implements TransferDelegationFactory{ - - private JAXBContext jaxbContext; - - /** - * Instantiates a new Transfer delegation factory. - * - * @throws JAXBException if an error occurs while creating a JAXBContext. - */ - public TransferDelegationFactoryImpl() throws JAXBException { - jaxbContext = JAXBContext.newInstance(TransferDelegation.class); - } - - /** - * Create transfer delegation from a sender CVR and receiver CVR. - * - * @param senderCVR the sender cvr - * @param receiverCVR the receiver cvr - * @return a byte array contains the transfer delegation - * @throws JAXBException if an error occurs during the JAXB marshalling - * @throws TransferDelegationException if an error occurs during the creation of the transfer delegation - */ - public byte[] createTransferDelegation(String senderCVR, String receiverCVR) throws JAXBException, TransferDelegationException { - if(senderCVR == null || StringUtils.isBlank(senderCVR)) { - throw new TransferDelegationException(ErrorCodes.SIGNING_EDELIVERY_TRANSFER_DELEGATION_EMPTY_OR_NULL_SENDER_CVR.formatErrorMessage()); - } else if (receiverCVR == null || StringUtils.isBlank(receiverCVR)) { - throw new TransferDelegationException(ErrorCodes.SIGNING_EDELIVERY_TRANSFER_DELEGATION_EMPTY_OR_NULL_RECEIVER_CVR.formatErrorMessage()); - } - - Sender tdSender = new Sender(); - tdSender.setSchemeID(TransferDelegationFactory.ASIC_TRANSFER_DELEGATION_SCHEME_CVR); - tdSender.setValue(senderCVR); - - Receiver tdReceiver = new Receiver(); - tdReceiver.setSchemeID(TransferDelegationFactory.ASIC_TRANSFER_DELEGATION_SCHEME_CVR); - tdReceiver.setValue(receiverCVR); - - TransferDelegation td = new TransferDelegation(); - td.setSender(tdSender); - td.setReceiver(tdReceiver); - - ObjectFactory of = new ObjectFactory(); - - Marshaller mar = jaxbContext.createMarshaller(); - mar.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE); - byte[] transferDelegation; - try (ByteArrayOutputStream bos = new ByteArrayOutputStream()) { - JAXBElement element = of.createTransferDelegation(td); - mar.marshal(element, bos); - transferDelegation = bos.toByteArray(); - return transferDelegation; - } catch (IOException e) { - throw new RuntimeException(ErrorCodes.SIGNING_EDELIVERY_TRANSFER_DELEGATION_MARSHALLING_ERROR.formatErrorMessage()); - } - } -} diff --git a/src/main/java/dk/erst/oxalis/as4/signature/XAdESServiceProvider.java b/src/main/java/dk/erst/oxalis/as4/signature/XAdESServiceProvider.java new file mode 100644 index 0000000000000000000000000000000000000000..b7fcad6b9557728b9cabef67e250854914d90fc7 --- /dev/null +++ b/src/main/java/dk/erst/oxalis/as4/signature/XAdESServiceProvider.java @@ -0,0 +1,28 @@ +package dk.erst.oxalis.as4.signature; + +import com.google.inject.Inject; +import com.google.inject.Provider; +import eu.europa.esig.dss.validation.CertificateVerifier; +import eu.europa.esig.dss.xades.signature.XAdESService; + +/** + * Provider for constructing {@link XAdESService} instances. + *

+ * Note: It is not completely clear from the DSS documentation whether {@link XAdESService} is thread-safe. + * They mention that {@link CertificateVerifier} (which is a constructor parameter for {@link XAdESService}) is not always thread-safe - especially when using adjunct certificate sources. + * While we don't currently use adjunct certificate sources, we will still err on the side of caution to be safe and construct a new instance for each signing operation, since there does not seem to be much cost associated with constructing a new {@link XAdESService}. + */ +public class XAdESServiceProvider implements Provider { + + private Provider certificateVerifierProvider; + + @Inject + public XAdESServiceProvider(Provider certificateVerifierProvider) { + this.certificateVerifierProvider = certificateVerifierProvider; + } + + @Override + public XAdESService get() { + return new XAdESService(certificateVerifierProvider.get()); + } +} diff --git a/src/main/java/dk/erst/oxalis/as4/signature/XAdESSignatureParametersProvider.java b/src/main/java/dk/erst/oxalis/as4/signature/XAdESSignatureParametersProvider.java new file mode 100644 index 0000000000000000000000000000000000000000..88ef06ddd8434304d3c5352e03d87ab1f70a08c5 --- /dev/null +++ b/src/main/java/dk/erst/oxalis/as4/signature/XAdESSignatureParametersProvider.java @@ -0,0 +1,46 @@ +package dk.erst.oxalis.as4.signature; + +import com.google.inject.Inject; +import com.google.inject.Provider; +import eu.europa.esig.dss.enumerations.SignatureLevel; +import eu.europa.esig.dss.enumerations.SignaturePackaging; +import eu.europa.esig.dss.token.DSSPrivateKeyEntry; +import eu.europa.esig.dss.xades.XAdESSignatureParameters; + +import java.util.Date; + +/** + * Provider for constructing {@link XAdESSignatureParameters} instances. + */ +public class XAdESSignatureParametersProvider implements Provider { + + private final DSSPrivateKeyEntry signaturePrivateKey; + + /** + * Instantiates a new XAdESSignatureParametersProvider with the given dependencies. + * The dependencies are automatically injected by Guice.
+ *
+ * @param signaturePrivateKey The private key from which to extract the certificate and certificate chain to include in the signature. + */ + @Inject + public XAdESSignatureParametersProvider(DSSPrivateKeyEntry signaturePrivateKey) { + this.signaturePrivateKey = signaturePrivateKey; + } + + /** + * Provides an instance of {@link XAdESSignatureParameters} suitable for use in creating a + * Nemhandel e-Delivery 1.2 compliant signature.
+ *
+ * @return the instantiated {@link XAdESSignatureParameters} object. + */ + @Override + public XAdESSignatureParameters get() { + XAdESSignatureParameters signatureParameters = new XAdESSignatureParameters(); + signatureParameters.bLevel().setSigningDate(new Date()); + signatureParameters.setSigningCertificate(signaturePrivateKey.getCertificate()); + signatureParameters.setCertificateChain(signaturePrivateKey.getCertificateChain()); + signatureParameters.setSignatureLevel(SignatureLevel.XAdES_BASELINE_B); + signatureParameters.setSignaturePackaging(SignaturePackaging.DETACHED); + return signatureParameters; + } +} diff --git a/src/main/java/dk/erst/oxalis/as4/util/DocumentSender.java b/src/main/java/dk/erst/oxalis/as4/util/DocumentSender.java index 43bc261b91eb332fb254955c6539d7266037332a..727f9188bfa25fdc8e507b387270beb4b70cc3db 100644 --- a/src/main/java/dk/erst/oxalis/as4/util/DocumentSender.java +++ b/src/main/java/dk/erst/oxalis/as4/util/DocumentSender.java @@ -16,19 +16,7 @@ public interface DocumentSender { * @param message the message to send. * @param messageContext context object used to pass additional information when sending an SBD. * @return the result of the transmission as a {@link TransmissionResponse} object. - * @throws OutboundException + * @throws OutboundException if an error occurs while sending the message */ TransmissionResponse send(Message message, SBDMessageContext messageContext) throws OutboundException; - - /** - * Signs a message and returns the signed document as a byte array. - * - * @param sbd the document to sign. - * @param originalDocument the original document, if any, to include in the signature. - * @param receiverCVR the receivers CVR number. - * @param messageContext context object used to pass additional information when sending an SBD. - * @return the signed document as a byte array. - * @throws OutboundException if an error occurs while signing the message or creating the transfer delegation. - */ - byte[] signMessage(byte[] sbd, byte[] originalDocument, String receiverCVR, SBDMessageContext messageContext) throws OutboundException; } diff --git a/src/main/java/dk/erst/oxalis/as4/util/DocumentUtil.java b/src/main/java/dk/erst/oxalis/as4/util/DocumentUtil.java index 8267d71fde90de51bd1bdee2baf257ce6f674097..fbd52ae72e32507c1c18814e41c6688e77d5ac6b 100644 --- a/src/main/java/dk/erst/oxalis/as4/util/DocumentUtil.java +++ b/src/main/java/dk/erst/oxalis/as4/util/DocumentUtil.java @@ -3,6 +3,7 @@ package dk.erst.oxalis.as4.util; import org.apache.xml.security.c14n.Canonicalizer; import org.apache.xml.security.exceptions.XMLSecurityException; import org.w3c.dom.Document; +import org.w3c.dom.Node; import javax.xml.XMLConstants; import javax.xml.transform.Transformer; @@ -53,7 +54,7 @@ public class DocumentUtil { * @return The input XML-document as a byte array, canonicalized according to "xml-exc-c14n" specification * @throws XMLSecurityException This exception is thrown if canonicalization fails (e.g. malformed input XML). Also * thrown if canonicalization engine cannot be initialized. - * @throws IOException Should never be thrown, since internally the method uses ByteArrayOutputStream which will + * @throws IOException Should never be thrown, since internally the method uses ByteArrayOutputStream which will not * throw this exception (but to satisfy compiler it is added). */ public static byte[] canonicalize(byte[] xml) throws XMLSecurityException, IOException { @@ -66,5 +67,4 @@ public class DocumentUtil { } } - } diff --git a/src/main/java/dk/erst/oxalis/as4/util/OxalisDocumentSender.java b/src/main/java/dk/erst/oxalis/as4/util/OxalisDocumentSender.java index 952bdf2a48b96f1a2c9d93138950db21733df015..b49865bec6c887abe710276dcd9895c452701eb3 100644 --- a/src/main/java/dk/erst/oxalis/as4/util/OxalisDocumentSender.java +++ b/src/main/java/dk/erst/oxalis/as4/util/OxalisDocumentSender.java @@ -6,38 +6,34 @@ import com.google.inject.Singleton; import dk.erst.oxalis.as4.error.ErrorCodes; import dk.erst.oxalis.as4.handlers.OutboundException; import dk.erst.oxalis.as4.handlers.OutboundValidationException; +import dk.erst.oxalis.as4.handlers.dto.ValidationResult; import dk.erst.oxalis.as4.mode.Mode; import dk.erst.oxalis.as4.persistence.model.Message; import dk.erst.oxalis.as4.persistence.model.MessageLog; import dk.erst.oxalis.as4.persistence.model.NemhandelLog; +import dk.erst.oxalis.as4.signature.SignatureException; import dk.erst.oxalis.as4.signature.SignatureFactory; -import dk.erst.oxalis.as4.signature.TransferDelegationException; -import dk.erst.oxalis.as4.signature.TransferDelegationFactoryImpl; import dk.erst.oxalis.as4.validation.MessageValidator; import dk.erst.oxalis.as4.validation.ValidationException; -import dk.erst.oxalis.as4.handlers.dto.ValidationResult; import dk.erst.oxalis.as4.validation.ValidationType; import dk.erst.oxalis.as4.validation.signature.SignatureValidatorImpl; import network.oxalis.api.lang.OxalisContentException; import network.oxalis.api.lang.OxalisTransmissionException; -import network.oxalis.api.lookup.LookupService; import network.oxalis.api.outbound.TransmissionRequest; import network.oxalis.api.outbound.TransmissionResponse; import network.oxalis.api.outbound.Transmitter; import network.oxalis.as4.lang.OxalisAs4TransmissionException; -import network.oxalis.commons.header.SbdhHeaderParser; import network.oxalis.outbound.transmission.TransmissionRequestBuilder; -import network.oxalis.sniffer.PeppolStandardBusinessHeader; -import network.oxalis.sniffer.identifier.PeppolDocumentTypeId; import network.oxalis.vefa.peppol.common.lang.PeppolParsingException; -import network.oxalis.vefa.peppol.common.model.*; +import network.oxalis.vefa.peppol.common.model.DocumentTypeIdentifier; +import network.oxalis.vefa.peppol.common.model.ParticipantIdentifier; +import network.oxalis.vefa.peppol.common.model.ProcessIdentifier; import network.oxalis.vefa.peppol.lookup.api.LookupException; import network.oxalis.vefa.peppol.lookup.api.NotFoundException; import network.oxalis.vefa.peppol.sbdh.Ns; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.exception.ExceptionUtils; import org.apache.cxf.helpers.MapNamespaceContext; -import org.apache.xml.security.exceptions.XMLSecurityException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.w3c.dom.Document; @@ -46,16 +42,16 @@ import org.w3c.dom.NodeList; import org.xml.sax.SAXException; import javax.persistence.EntityManager; -import javax.xml.bind.JAXBException; import javax.xml.parsers.DocumentBuilder; import javax.xml.transform.TransformerException; import javax.xml.xpath.XPath; import javax.xml.xpath.XPathExpressionException; import javax.xml.xpath.XPathFactory; -import java.io.*; -import java.security.cert.X509Certificate; -import java.util.Date; +import java.io.ByteArrayInputStream; +import java.io.IOException; import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import static dk.erst.oxalis.as4.util.XPathUtil.evaluateXPath; @@ -72,13 +68,11 @@ public class OxalisDocumentSender implements DocumentSender { private final Transmitter transmitter; private final MessageValidator messageValidator; private final SignatureFactory signatureFactory; - private final TransferDelegationFactoryImpl transferDelegationFactoryImpl; - private final LookupService lookupService; - private final X509Certificate accessPointCertificate; - private final SbdhHeaderParser headerParser; private final Mode mode; private final Provider documentBuilderProvider; + private final Pattern OXALIS_SMP_LOOKUP_ERROR_PATTERN = Pattern.compile("^Combination of receiver \\((\\S+)\\) and document type identifier \\((\\S+)\\) is not supported\\.$"); + /** * Constructs a new instance of this class with the specified parameters. * @@ -86,10 +80,6 @@ public class OxalisDocumentSender implements DocumentSender { * @param transmitter Executes transmission requests by sending the payload to the requested destination. * @param messageValidator Performs validation for a message. * @param signatureFactory Contains the logic for creating signatures and signing documents. - * @param transferDelegationFactoryImpl Contains the logic for creating transfer delegations. - * @param lookupService An SMP lookup service. - * @param accessPointCertificate The Access Point's own certificate. - * @param headerParser For parsing of the Standard Business Document Header (SBDH). * @param mode The type mode. Is used to identify the current mode of the Access Point. * @param documentBuilderProvider {@link Provider} for providing {@link DocumentBuilder} objects. Resolved by Guice. * @param em A {@link Provider} for providing {@link EntityManager} objects. Resolved by Guice. @@ -99,10 +89,6 @@ public class OxalisDocumentSender implements DocumentSender { Provider requestBuilderProvider, Transmitter transmitter, MessageValidator messageValidator, SignatureFactory signatureFactory, - TransferDelegationFactoryImpl transferDelegationFactoryImpl, - LookupService lookupService, - X509Certificate accessPointCertificate, - SbdhHeaderParser headerParser, Mode mode, Provider documentBuilderProvider, Provider em){ @@ -111,40 +97,10 @@ public class OxalisDocumentSender implements DocumentSender { this.transmitter = transmitter; this.messageValidator = messageValidator; this.signatureFactory = signatureFactory; - this.transferDelegationFactoryImpl = transferDelegationFactoryImpl; - this.lookupService = lookupService; - this.accessPointCertificate = accessPointCertificate; - this.headerParser = headerParser; this.mode = mode; this.documentBuilderProvider = documentBuilderProvider; } - @Override - public byte[] signMessage(byte[] sbd, byte[] originalDocument, String receiverCVR, SBDMessageContext messageContext) throws OutboundException { - String senderCvr = CertificateUtil.extractCVR(accessPointCertificate); - try { - byte[] transferDelegation = transferDelegationFactoryImpl.createTransferDelegation(senderCvr, receiverCVR); - return signatureFactory.signDocument(sbd, originalDocument, transferDelegation, messageContext); - } - catch (JAXBException e) { - throw new OutboundException(messageContext.getMessageUuid(), ErrorCodes.OUTBOUND_EXCEPTION.getErrorCode(), "JAXB error while signing message", e); - } catch (IOException e) { - throw new OutboundException(messageContext.getMessageUuid(), ErrorCodes.OUTBOUND_EXCEPTION.getErrorCode(), "IO error while signing message", e); - } catch (SAXException e) { - throw new OutboundException(messageContext.getMessageUuid(), ErrorCodes.OUTBOUND_EXCEPTION.getErrorCode(), "SAX error while signing message", e); - } catch (TransformerException e) { - throw new OutboundException(messageContext.getMessageUuid(), ErrorCodes.OUTBOUND_EXCEPTION.getErrorCode(),"Transformer error while signing message", e); - } catch (XPathExpressionException e) { - throw new OutboundException(messageContext.getMessageUuid(), ErrorCodes.OUTBOUND_EXCEPTION.getErrorCode(),"XPath error while signing message", e); - } catch (TransferDelegationException e) { - throw new OutboundException(messageContext.getMessageUuid(), ErrorCodes.OUTBOUND_EXCEPTION.getErrorCode(), "Error while creating transfer delegation", e); - } catch (XMLSecurityException e) { - throw new OutboundException(messageContext.getMessageUuid(), ErrorCodes.OUTBOUND_EXCEPTION.getErrorCode(), "Error during canonicalization while signing message", e); - } - - } - - @Override public TransmissionResponse send(Message message, SBDMessageContext messageContext) throws OutboundException { NemhandelLog.WithMDC.cleanMDC(); @@ -152,8 +108,6 @@ public class OxalisDocumentSender implements DocumentSender { List messageLogList; try { - byte[] originalDocument = message.getMessageContent().getData(); - DataPairs.Tuple2, ValidationResult> tuplePair = messageValidator.validate(message, ValidationType.ALL, messageContext); messageLogList = tuplePair.t1; @@ -168,34 +122,30 @@ public class OxalisDocumentSender implements DocumentSender { // addUUID checks if a UUID is sent from C1 otherwise it adds one to MessageContent // A separate SBD is made because C2 can potentially change the original document. byte[] sbd = addUUID(message); + message.getMessageContent().setData(sbd); TransmissionRequestBuilder requestBuilder = requestBuilderProvider.get(); requestBuilder.reset(); - if(mode.isNemhandelEdeliveryMode()) { - Endpoint endpoint = lookupEndpoint(message); - String receiverCVR = CertificateUtil.extractCVR(endpoint.getCertificate()); - messageLogList.add(NemhandelLog.createMessageLog(message, ErrorCodes.C2_CREATE_TRANSFER_DELEGATION_INIT)); - messageLogList.add(NemhandelLog.createMessageLog(message, ErrorCodes.C2_SINGNING_DOCUMENT_INIT)); + if(mode.isNemhandelEdeliveryMode() && !isSigned(sbd)) { + messageLogList.add(NemhandelLog.createMessageLog(message, ErrorCodes.C2_SIGNING_DOCUMENT_INIT)); - // for internal MLR do not include an original-standard-business-document in signed documents (the MLR would be the first document in this case) - // if original document is unsigned then we dont include that in the signature as it cannot be trusted by subsequent corners anyway - boolean includeOriginalSbd = (messageContext != null && !messageContext.isInternalMessageLevelResponse()) && isSigned(originalDocument); - byte[] signedSBD = signMessage(sbd, includeOriginalSbd ? originalDocument : null, receiverCVR, messageContext); + byte[] signedSBD = signatureFactory.signDocument(sbd, messageContext); message.getMessageContent().setData(signedSBD); - messageLogList.add(NemhandelLog.createMessageLog(message, ErrorCodes.C2_CREATE_TRANSFER_DELEGATION_DONE)); - messageLogList.add(NemhandelLog.createMessageLog(message, ErrorCodes.C2_SINGNING_DOCUMENT_DONE)); - - final Message msg = message; - if(messageContext != null && messageContext.isInternalMessageLevelResponse()) { - message = em.get().merge(msg); - } else { - message = TxUtil.doInTxResult(em, entityManager -> entityManager.merge(msg)); - } + messageLogList.add(NemhandelLog.createMessageLog(message, ErrorCodes.C2_SIGNING_DOCUMENT_DONE)); + } else { + logger.info("Skipping signing of message {} because the document is already signed.", message.getMessageUuid()); + } + final Message msg = message; + if(messageContext != null && messageContext.isInternalMessageLevelResponse()) { + message = em.get().merge(msg); + } else { + message = TxUtil.doInTxResult(em, entityManager -> entityManager.merge(msg)); } + messageLogList.add(NemhandelLog.createMessageLog(message, ErrorCodes.C2_SMP_LOOK_UP_INIT)); requestBuilder = requestBuilder .sender(ParticipantIdentifier.parse(message.getSender())) @@ -238,6 +188,15 @@ public class OxalisDocumentSender implements DocumentSender { if (notFoundException >= 0) { throw new OutboundException(message.getMessageUuid(), ErrorCodes.OUTBOUND_SMP_NOT_FOUND_ERROR.getErrorCode(), ErrorCodes.OUTBOUND_SMP_NOT_FOUND_ERROR.formatErrorMessage(), e); } else { + if(e.getCause() != null && e.getCause().getMessage() != null) { + // check if the error is because the document type is not supported by the receiver according to SMP lookup + Matcher m = OXALIS_SMP_LOOKUP_ERROR_PATTERN.matcher(e.getCause().getMessage()); + if(m.matches()) { + String receiverIdent = m.group(1); + String docType = m.group(2); + throw new OutboundException(message.getMessageUuid(), ErrorCodes.OUTBOUND_RECEIVER_DOES_NOT_EXIST_ERROR.getErrorCode(), ErrorCodes.OUTBOUND_RECEIVER_DOES_NOT_EXIST_ERROR.formatErrorMessage(receiverIdent, docType), e); + } + } throw new OutboundException(message.getMessageUuid(), ErrorCodes.OUTBOUND_SMP_ERROR.getErrorCode(), ErrorCodes.OUTBOUND_SMP_ERROR.formatErrorMessage(), e); } } else if (e.getClass().equals(OxalisAs4TransmissionException.class) @@ -254,34 +213,11 @@ public class OxalisDocumentSender implements DocumentSender { throw new OutboundException(message.getMessageUuid(), ErrorCodes.OUTBOUND_EXCEPTION.getErrorCode(), "Error while parsing peppol values", e); } catch (SAXException e) { throw new OutboundException(message.getMessageUuid(), ErrorCodes.OUTBOUND_EXCEPTION.getErrorCode(), "Error while parsing xml", e); + } catch (SignatureException e) { + throw new OutboundException(messageContext.getMessageUuid(), ErrorCodes.OUTBOUND_EXCEPTION.getErrorCode(), "Error while signing document", e); } } - private Endpoint lookupEndpoint(Message message) throws PeppolParsingException, OxalisTransmissionException, OxalisContentException, OutboundException { - DocumentTypeIdentifier documentTypeIdentifier = DocumentTypeIdentifier.parse(message.getDocumentTypeId()); - - PeppolStandardBusinessHeader parsedSbdh = null; - try { - Header header = this.headerParser.parse(new ByteArrayInputStream(message.getMessageContent().getData())); - parsedSbdh = new PeppolStandardBusinessHeader(header); - } catch (OxalisContentException var3) { - throw new OutboundException(message.getMessageUuid(), ErrorCodes.SBD_HEADER_PARSE_ERROR.getErrorCode(), ErrorCodes.SBD_HEADER_PARSE_ERROR.formatErrorMessage()); - } - - PeppolDocumentTypeId documentTypeId = PeppolDocumentTypeId.valueOf(documentTypeIdentifier.getIdentifier()); - InstanceIdentifier identifier = parsedSbdh.toVefa().getIdentifier(); - Header header = Header.of( - ParticipantIdentifier.parse(message.getSender()), - ParticipantIdentifier.parse(message.getReceiver()), - message.getPeppolProcessId() != null ? ProcessIdentifier.parse(message.getPeppolProcessId()) : null, - documentTypeIdentifier, - identifier != null ? identifier : InstanceIdentifier.generateUUID(), - InstanceType.of(documentTypeId.getRootNameSpace(), documentTypeId.getLocalName(), documentTypeId.getVersion()), - parsedSbdh.getCreationDateAndTime() != null ? parsedSbdh.getCreationDateAndTime() : new Date()); - Endpoint endpoint = lookupService.lookup(header); - return endpoint; - } - private byte[] addUUID(Message message) throws OutboundException, XPathExpressionException, IOException, TransformerException { byte[] sbd = message.getMessageContent().getData(); String messageUuid = null; diff --git a/src/main/java/dk/erst/oxalis/as4/util/SBDMessageContext.java b/src/main/java/dk/erst/oxalis/as4/util/SBDMessageContext.java index e525109b670e4890481fed7d17c169e7e6c149dd..ff8797ba6e6ae6e312e200bf764c709e78952bac 100644 --- a/src/main/java/dk/erst/oxalis/as4/util/SBDMessageContext.java +++ b/src/main/java/dk/erst/oxalis/as4/util/SBDMessageContext.java @@ -117,7 +117,7 @@ public class SBDMessageContext { /** * Indicates whether the document signature is required. Document signature is optional for C1 when sending through C2. * Thus, this property is only used in signature validation to determine whether to accept unsigned SBDs. - *

+ *

* Note: the document signature is always required in C3, so C2 must sign the document before sending to C3. * * @return true if the document signature is required, false otherwise. diff --git a/src/main/java/dk/erst/oxalis/as4/util/XPathUtil.java b/src/main/java/dk/erst/oxalis/as4/util/XPathUtil.java index 8e4c11ff4d59be0d582aa10f5a430b4a5e99edd3..3417ab0da129f1c9f9c07df0b2d5cccd9c1252c6 100644 --- a/src/main/java/dk/erst/oxalis/as4/util/XPathUtil.java +++ b/src/main/java/dk/erst/oxalis/as4/util/XPathUtil.java @@ -25,19 +25,19 @@ public class XPathUtil { * Evaluates an XPath expression on the given XML document using the provided XPath object * and returns the result as a String. * - * @param document The XML document on which to evaluate the XPath expression. + * @param node The XML document or node on which to evaluate the XPath expression. * @param xpath The XPath object used for evaluating the expression. * @param xpathExpression The XPath expression to be evaluated. * @param defaultValue The default value to be returned if no matching node is found or if an XPathExpressionException occurs. * @return The result of the XPath evaluation as a String. If no matching node is found or an exception occurs, the defaultValue is returned. */ - public static String evaluateXPath(Document document, XPath xpath, String xpathExpression, String defaultValue) { + public static String evaluateXPath(Node node, XPath xpath, String xpathExpression, String defaultValue) { try{ - Node node = evaluateXPathNode(document, xpath, xpathExpression); - if(node == null){ + Node n = evaluateXPathNode(node, xpath, xpathExpression); + if(n == null){ return defaultValue; }else{ - return node.getTextContent(); + return n.getTextContent(); } } catch(XPathExpressionException e){ logger.warn("Error evaluating xpath expression {}: {}", xpathExpression, e.getMessage()); @@ -49,18 +49,18 @@ public class XPathUtil { * Evaluates an XPath expression on the given XML document using the provided XPath object * and returns the resulting Node object. * - * @param document The XML document on which to evaluate the XPath expression. + * @param node The XML document or node on which to evaluate the XPath expression. * @param xpath The XPath object used for evaluating the expression. * @param xpathExpression The XPath expression to be evaluated. * @return The resulting Node object from the evaluation of the XPath expression. * @throws XPathExpressionException If an error occurs during the evaluation of the XPath expression. */ - public static Node evaluateXPathNode(Document document, XPath xpath, String xpathExpression) throws XPathExpressionException { - Node node = (Node)xpath + public static Node evaluateXPathNode(Node node, XPath xpath, String xpathExpression) throws XPathExpressionException { + Node n = (Node)xpath .compile(xpathExpression) - .evaluate(document, XPathConstants.NODE); + .evaluate(node, XPathConstants.NODE); - return node; + return n; } /** diff --git a/src/main/java/dk/erst/oxalis/as4/validation/MessageValidatorImpl.java b/src/main/java/dk/erst/oxalis/as4/validation/MessageValidatorImpl.java index 07ffff02085a1d154cb3e6f65f71653e01a5dcf1..818099a4f0ad540a13a93ef55367bbff962e8d06 100644 --- a/src/main/java/dk/erst/oxalis/as4/validation/MessageValidatorImpl.java +++ b/src/main/java/dk/erst/oxalis/as4/validation/MessageValidatorImpl.java @@ -11,6 +11,7 @@ import dk.erst.oxalis.as4.persistence.model.MessageLog; import dk.erst.oxalis.as4.persistence.model.NemhandelLog; import dk.erst.oxalis.as4.util.DataPairs; import dk.erst.oxalis.as4.util.SBDMessageContext; +import dk.erst.oxalis.as4.validation.registration.RegistrationValidator; import dk.erst.oxalis.as4.validation.schema.SchemaValidator; import dk.erst.oxalis.as4.validation.schematron.SchematronValidator; import dk.erst.oxalis.as4.validation.signature.SignatureValidator; @@ -47,6 +48,7 @@ public class MessageValidatorImpl implements MessageValidator { private final Provider documentBuilderProvider; private final SignatureValidator signatureValidator; private final VersionValidator versionValidator; + private final RegistrationValidator registrationValidator; /** * Constructs a new instance of this class with the specified {@link DocumentTypeConfigResolver}, {@link SchemaValidator} for XSD validation, @@ -61,13 +63,15 @@ public class MessageValidatorImpl implements MessageValidator { */ public MessageValidatorImpl(DocumentTypeConfigResolver documentTypeConfigResolver, SchemaValidator schemaValidator, SchematronValidator schematronValidator, Provider documentBuilderProvider, - SignatureValidator signatureValidator, VersionValidator versionValidator) { + SignatureValidator signatureValidator, VersionValidator versionValidator, + RegistrationValidator registrationValidator) { this.documentTypeConfigResolver = documentTypeConfigResolver; this.schemaValidator = schemaValidator; this.schematronValidator = schematronValidator; this.documentBuilderProvider = documentBuilderProvider; this.signatureValidator = signatureValidator; this.versionValidator = versionValidator; + this.registrationValidator = registrationValidator; } /** @@ -88,6 +92,7 @@ public class MessageValidatorImpl implements MessageValidator { if(validationType == null) validationType = ValidationType.ALL; boolean isC2 = message.getDirection().equals(MessageDirection.OUT) || (messageContext != null && messageContext.getDirection().equals(Direction.OUT)); + boolean isOriginator = isC2 && !messageContext.isInternalMessageLevelResponse(); List messageLogList = new LinkedList<>(); ValidationResult result = new ValidationResult(); @@ -117,6 +122,15 @@ public class MessageValidatorImpl implements MessageValidator { throw new ValidationException("No suitable configuration found for document type. Cannot perform validation of message " + message.getId()); } + if(isOriginator) { + ValidationResult partialValidationResult = registrationValidator.validate(message, documentTypeConfig); + if (partialValidationResult != null && partialValidationResult.hasErrors()) { + result.addAll(partialValidationResult); + messageLogList.add(NemhandelLog.createMessageLog(message, partialValidationResult.getErrors().get(0))); + return new DataPairs.Tuple2<>(messageLogList, result); + } + } + // perform schema (XSD validation) if(ValidationType.ALL.equals(validationType) || ValidationType.SYNCHRONOUS.equals(validationType)) { diff --git a/src/main/java/dk/erst/oxalis/as4/validation/ValidationModule.java b/src/main/java/dk/erst/oxalis/as4/validation/ValidationModule.java index 8513faf8567179de1d8e2e594153d3f1b35cc6ac..2392097a3633dd12670981397d04d9af7dba74b3 100644 --- a/src/main/java/dk/erst/oxalis/as4/validation/ValidationModule.java +++ b/src/main/java/dk/erst/oxalis/as4/validation/ValidationModule.java @@ -6,10 +6,9 @@ import com.google.inject.*; import dk.erst.oxalis.as4.config.documenttype.DocumentTypeConfig; import dk.erst.oxalis.as4.config.documenttype.DocumentTypeConfigResolver; import dk.erst.oxalis.as4.util.SBDPayloadExtractor; -import dk.erst.oxalis.as4.validation.schema.SchemaResolver; -import dk.erst.oxalis.as4.validation.schema.SchemaResolverImpl; -import dk.erst.oxalis.as4.validation.schema.SchemaValidator; -import dk.erst.oxalis.as4.validation.schema.SchemaValidatorImpl; +import dk.erst.oxalis.as4.validation.registration.RegistrationValidator; +import dk.erst.oxalis.as4.validation.registration.RegistrationValidatorImpl; +import dk.erst.oxalis.as4.validation.schema.*; import dk.erst.oxalis.as4.validation.schematron.*; import dk.erst.oxalis.as4.validation.signature.DSSModule; import dk.erst.oxalis.as4.validation.signature.SignatureValidator; @@ -21,6 +20,7 @@ import org.slf4j.LoggerFactory; import javax.xml.parsers.DocumentBuilder; import javax.xml.transform.Templates; +import javax.xml.validation.Schema; import java.util.List; import java.util.Set; import java.util.concurrent.ExecutionException; @@ -48,8 +48,8 @@ public class ValidationModule extends AbstractModule { @Provides @Singleton - SchemaResolver provideSchemaResolver() { - return new SchemaResolverImpl(); + SchemaResolver provideSchemaResolver(@SchemaCache LoadingCache schemaCache) { + return new SchemaResolverImpl(schemaCache); } @Provides @@ -75,8 +75,10 @@ public class ValidationModule extends AbstractModule { @Singleton MessageValidator provideDocumentValidator(DocumentTypeConfigResolver documentTypeConfigResolver, SchemaValidator schemaValidator, SchematronValidator schematronValidator, Provider documentBuilderProvider, - SignatureValidator signatureValidator, VersionValidator versionValidator) { - return new MessageValidatorImpl(documentTypeConfigResolver, schemaValidator, schematronValidator, documentBuilderProvider, signatureValidator, versionValidator); + SignatureValidator signatureValidator, VersionValidator versionValidator, + RegistrationValidator registrationValidator) { + return new MessageValidatorImpl(documentTypeConfigResolver, schemaValidator, schematronValidator, + documentBuilderProvider, signatureValidator, versionValidator, registrationValidator); } @Provides @@ -85,6 +87,12 @@ public class ValidationModule extends AbstractModule { return new VersionValidatorImpl(injector); } + @Provides + @Singleton + RegistrationValidator provideregistrationValidator(Injector injector) { + return new RegistrationValidatorImpl(injector); + } + @Provides @Singleton VersionCache provideVersionCache() { @@ -104,15 +112,45 @@ public class ValidationModule extends AbstractModule { return CacheBuilder.newBuilder().build(cacheLoader); } + @Provides + @Singleton + SchemaCacheLoader schemaCacheLoader() { + return new SchemaCacheLoader(); + } + + @Provides + @Singleton + @SchemaCache + LoadingCache provideSchemaCache(SchemaCacheLoader cacheLoader) { + return CacheBuilder.newBuilder().build(cacheLoader); + } + /** - * Preloads Schematron stylesheets by compiling and caching them using a provided XSLT cache. + * Preloads XSD Schemas and Schematron stylesheets by compiling and caching them using a provided caches. * - * @param xsltCache The XSLT cache used for loading and caching the stylesheets. + * @param xsltCache The cache used for loading and caching the XSLT stylesheets. + * @param schemaCache The cache used for loading and caching the XSD Schemas * @param documentTypeConfigs The set of {@link DocumentTypeConfig}. * @throws ExecutionException If an exception occurs while loading the {@link XSLTCache}. */ @Inject - public void preloadSchematronStylesheets(@XSLTCache LoadingCache xsltCache, Set documentTypeConfigs) throws ExecutionException { + public void preloadSchemaAndSchematron(@XSLTCache LoadingCache xsltCache, @SchemaCache LoadingCache schemaCache, Set documentTypeConfigs) throws ExecutionException { + List preloadSchemaPaths = documentTypeConfigs.stream() + .filter(DocumentTypeConfig::isPreloadSchema) + .map(DocumentTypeConfig::getSchemaPath) + .distinct() + .collect(Collectors.toList()); + if(!preloadSchemaPaths.isEmpty()) { + log.info("Starting preload of {} XSD schema (parsing and caching)", preloadSchemaPaths.size()); + /** + * Note: The cache is created with a CacheLoader implementation which can load XSD files. + * Calling get or getAll on the cache will load the XSD's which are not already cached! + */ + schemaCache.getAll(preloadSchemaPaths); + log.info("Preloading of XSD schemas done"); + + } + List preloadSchematronPaths = documentTypeConfigs.stream() .filter(DocumentTypeConfig::isPreloadSchematron) .flatMap(dc -> dc.getSchematronDocuments().stream()) diff --git a/src/main/java/dk/erst/oxalis/as4/validation/registration/RegistrationValidator.java b/src/main/java/dk/erst/oxalis/as4/validation/registration/RegistrationValidator.java new file mode 100644 index 0000000000000000000000000000000000000000..bf7ecbc7a8de32e7fc8a8eca9d8af9280fc93276 --- /dev/null +++ b/src/main/java/dk/erst/oxalis/as4/validation/registration/RegistrationValidator.java @@ -0,0 +1,27 @@ +package dk.erst.oxalis.as4.validation.registration; + + +import dk.erst.oxalis.as4.config.documenttype.DocumentTypeConfig; +import dk.erst.oxalis.as4.handlers.dto.ValidationResult; +import dk.erst.oxalis.as4.persistence.model.Message; +import dk.erst.oxalis.as4.validation.ValidationException; +import dk.erst.oxalis.as4.validation.version.VersionCache; +import dk.erst.oxalis.as4.validation.version.VersionValidationException; +import dk.erst.oxalis.as4.validation.version.model.EdelComponentVersion; +import network.oxalis.api.lang.OxalisTransmissionException; +import network.oxalis.peppol.busdox.jaxb.smp.ExtensionType; + +/** + * Performs a SMP lookup to validate that the sender (C2) is correctly registered to receive MLR/AR responses + */ +public interface RegistrationValidator { + + /** + * Validate by a SMP lookup that it is possible to send an asynchronous response to the sender of a message. + * @param message The message to validate. Currently only the sender identification of the message is used. + * @param documentTypeConfig The {@link DocumentTypeConfig} of the message. Currently only the + * MessageLevelResponseType of the DocumentTypeConfig is used. + */ + ValidationResult validate(Message message, DocumentTypeConfig documentTypeConfig) throws ValidationException, OxalisTransmissionException; + +} diff --git a/src/main/java/dk/erst/oxalis/as4/validation/registration/RegistrationValidatorImpl.java b/src/main/java/dk/erst/oxalis/as4/validation/registration/RegistrationValidatorImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..3ef8e0cb58d7152d55e414534e9d6d9248931274 --- /dev/null +++ b/src/main/java/dk/erst/oxalis/as4/validation/registration/RegistrationValidatorImpl.java @@ -0,0 +1,115 @@ +package dk.erst.oxalis.as4.validation.registration; + +import com.google.inject.Injector; +import com.google.inject.Key; +import com.google.inject.name.Names; +import dk.erst.oxalis.as4.config.documenttype.DocumentTypeConfig; +import dk.erst.oxalis.as4.config.documenttype.MessageLevelResponseType; +import dk.erst.oxalis.as4.handlers.dto.ValidationResult; +import dk.erst.oxalis.as4.persistence.model.Message; +import dk.erst.oxalis.as4.validation.ValidationException; +import network.oxalis.api.lang.OxalisTransmissionException; +import network.oxalis.api.lookup.LookupService; +import network.oxalis.vefa.peppol.common.lang.EndpointNotFoundException; +import network.oxalis.vefa.peppol.common.lang.PeppolParsingException; +import network.oxalis.vefa.peppol.common.model.DocumentTypeIdentifier; +import network.oxalis.vefa.peppol.common.model.Header; +import network.oxalis.vefa.peppol.common.model.ParticipantIdentifier; +import network.oxalis.vefa.peppol.common.model.ProcessIdentifier; +import network.oxalis.vefa.peppol.lookup.api.LookupException; +import org.hibernate.query.criteria.internal.expression.function.AggregationFunction; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.List; + +import static dk.erst.oxalis.as4.error.ErrorCodes.OUTBOUND_SENDER_DOES_NOT_EXIST_ERROR; +import static dk.erst.oxalis.as4.error.ErrorCodes.OUTBOUND_SMP_ERROR; + +/** + * Implementation: + * Performs a SMP lookup to validate that the sender (C2) is correctly registered to receive MLR/AR responses + */ +public class RegistrationValidatorImpl implements RegistrationValidator { + + private static final Logger log = LoggerFactory.getLogger(RegistrationValidatorImpl.class); + private final LookupService lookupService; + private final static long MAX_LOOP_COUNTER= 10000; + + /** + * Constructs a new {@code RegistrationValidatorImpl} with the specified injector. + * + * @param injector The injector used to retrieve an instance of {@link LookupService}. + */ + public RegistrationValidatorImpl(Injector injector) { + this.lookupService = injector.getInstance(LookupService.class); + + } + + public static final String OIOUBL_AR_DOCUMENTTYPEID = + "busdox-docid-qns::urn:oasis:names:specification:ubl:schema:xsd:ApplicationResponse-2::ApplicationResponse##OIOUBL-2.1::2.1"; + public static final String PEPPOL_MLR_DOCUMENTTYPEID = + "busdox-docid-qns::urn:oasis:names:specification:ubl:schema:xsd:ApplicationResponse-2::ApplicationResponse##urn:fdc:peppol.eu:poacc:trns:mlr:3::2.1"; + // The OIOUBL_TECRES_PROCESSID process is used for positive acknowledgements, which we do not support yet + //public static final String OIOUBL_TECRES_PROCESSID = "oioubl-procid-ubl::Procurement-TecRes-1.0"; + public static final String OIOUBL_BILSIM_PROCESSID = "oioubl-procid-ubl::Procurement-BilSim-1.0"; + public static final String PEPPOL_MLR_PROCESSID = "cenbii-procid-ubl::urn:fdc:peppol.eu:poacc:bis:mlr:3"; + + @Override + public ValidationResult validate(Message message, DocumentTypeConfig documentTypeConfig) throws ValidationException, OxalisTransmissionException { + log.info("RegistrationValidator has been called."); + ValidationResult result = new ValidationResult(); + ParticipantIdentifier receiver; + DocumentTypeIdentifier documentTypeIdentifier; + List processIdentifiers = new ArrayList<>(); + MessageLevelResponseType messageLevelResponseType = documentTypeConfig.getMessageLevelResponseType(); + try { + // sender for message is receiver for MLR/AR + receiver = ParticipantIdentifier.parse(message.getSender()); + if (MessageLevelResponseType.OIOUBL_APPLICATION_RESPONSE.equals(messageLevelResponseType)) { + documentTypeIdentifier = DocumentTypeIdentifier.parse(OIOUBL_AR_DOCUMENTTYPEID); + // The OIOUBL_TECRES_PROCESSID process is used for positive acknowledgements, which we do not support yet + //processIdentifiers.add(ProcessIdentifier.parse(OIOUBL_TECRES_PROCESSID)); + processIdentifiers.add(ProcessIdentifier.parse(OIOUBL_BILSIM_PROCESSID)); + } else if (MessageLevelResponseType.PEPPOL_MESSAGE_LEVEL_RESPONSE.equals(messageLevelResponseType)) { + documentTypeIdentifier = DocumentTypeIdentifier.parse(PEPPOL_MLR_DOCUMENTTYPEID); + processIdentifiers.add(ProcessIdentifier.parse(PEPPOL_MLR_PROCESSID)); + } else { + throw new ValidationException("Unknown MessageLevelResponseType"); + } + } + catch (PeppolParsingException e) { + throw new ValidationException(e); + } + + for(ProcessIdentifier process : processIdentifiers) { + Header header = Header.of(null, receiver, process, documentTypeIdentifier); + try { + lookupService.lookup(header); + } catch (OxalisTransmissionException e) { + Throwable cause = e; + // we want to find out whether the exception was caused by a missing SMP entry as opposed to a failure + // in performing the SMP lookup + for (int i =0; i < MAX_LOOP_COUNTER; i++) { + if(cause == null || cause instanceof LookupException || cause instanceof EndpointNotFoundException) { + break; + } + cause = cause.getCause(); + } + if(cause instanceof LookupException || cause instanceof EndpointNotFoundException) { + log.debug("Sender endpoint not found.", e); + result.addError(OUTBOUND_SENDER_DOES_NOT_EXIST_ERROR.getErrorCode(), + OUTBOUND_SENDER_DOES_NOT_EXIST_ERROR.formatErrorMessage(receiver.toString(), + process.toString(), documentTypeIdentifier.toString())); + } + else { + log.warn("SMP lookup failed", e); + throw e; + } + } + } + log.info("RegistrationValidator has completed."); + return result; + } +} diff --git a/src/main/java/dk/erst/oxalis/as4/validation/schema/SchemaCache.java b/src/main/java/dk/erst/oxalis/as4/validation/schema/SchemaCache.java new file mode 100644 index 0000000000000000000000000000000000000000..acf49bc2ee8748545cb52b9779593753a0eadfae --- /dev/null +++ b/src/main/java/dk/erst/oxalis/as4/validation/schema/SchemaCache.java @@ -0,0 +1,18 @@ +package dk.erst.oxalis.as4.validation.schema; + +import javax.inject.Qualifier; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * Qualifier for the cache used for caching XSD schemas. + */ +@Qualifier +@Target({ FIELD, PARAMETER, METHOD }) +@Retention(RUNTIME) +public @interface SchemaCache { + +} diff --git a/src/main/java/dk/erst/oxalis/as4/validation/schema/SchemaCacheLoader.java b/src/main/java/dk/erst/oxalis/as4/validation/schema/SchemaCacheLoader.java new file mode 100644 index 0000000000000000000000000000000000000000..f2ce06f5390cc9e97b3d245eef05be92f6ce5305 --- /dev/null +++ b/src/main/java/dk/erst/oxalis/as4/validation/schema/SchemaCacheLoader.java @@ -0,0 +1,112 @@ +package dk.erst.oxalis.as4.validation.schema; + +import com.google.common.cache.CacheLoader; +import com.sun.org.apache.xerces.internal.util.XMLCatalogResolver; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.xml.sax.SAXException; + +import javax.xml.XMLConstants; +import javax.xml.transform.Source; +import javax.xml.transform.stream.StreamSource; +import javax.xml.validation.Schema; +import javax.xml.validation.SchemaFactory; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.net.URL; +import java.nio.file.Paths; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +/** + * Loads and parses XSD Schemas to {@link Schema} for caching in a {@link com.google.common.cache.LoadingCache} + * using the file path as the key. + */ +public class SchemaCacheLoader extends CacheLoader { + private static final Logger log = LoggerFactory.getLogger(SchemaCacheLoader.class); + + private String[] catalogs; + + /** + * Default constructor + */ + public SchemaCacheLoader() { + String catalog = "META-INF/Schemas/catalog.xml"; + this.catalogs = new String[]{getAbsolutePathForResource(catalog)}; + } + + /** + * Loads and parses the XSD schema from the given path. + * Will first attempt to load the XSD as a resource and if that fails attempt to load it as an absolute path. + * + * @param schemaPath the path to the XSD schema file to be loaded. + * @return The parsed XSD schema as a {@link Schema} object suitable for caching. + * @throws IllegalArgumentException if {@code schemaPath} is null. + * @throws FileNotFoundException if {@code schemapath} is an absolute file path which doesn't exist. + * @throws Exception if the schema cannot be loaded. + */ + @Override + public Schema load(String schemaPath) throws Exception { + if (schemaPath == null) throw new IllegalArgumentException("schemaPath cannot be null"); + SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); + //to be able to parse XSD schemas with maxOccurs with value larger than 5000. + factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, false); + + XMLCatalogResolver xmlCatalogResolver = new XMLCatalogResolver(); + xmlCatalogResolver.setPreferPublic(true); + xmlCatalogResolver.setCatalogList(catalogs); + factory.setResourceResolver(xmlCatalogResolver); + return loadSchema(factory, schemaPath); + } + + private Schema loadSchema(SchemaFactory schemaFactory, String schemaPath) throws IOException, SAXException { + Schema schema = null; + // try to load schema from resources then afterwards as file + URL url = this.getClass().getClassLoader().getResource(schemaPath); + if (url != null) { + schema = schemaFactory.newSchema(url); + } else { + if (!Paths.get(schemaPath).toFile().exists()) { + throw new FileNotFoundException("Could not find XSD Schema. Path: " + schemaPath + " does not exist"); + } + + Source source = new StreamSource(schemaPath); + schema = schemaFactory.newSchema(source); + } + + return schema; + } + + /** + * Loads all the XSD schemas in the given iterable. + * + * @param xsdPaths An iterable containing the list of XSD Schema paths to load. + * @return A {@link Map} containing the loaded XSD schemas suitable for caching. The key will be the paths from the input iterable. + * @throws Exception if an error occurs while loading an XSD schema. + */ + @Override + public Map loadAll(Iterable xsdPaths) throws Exception { + if (xsdPaths == null) return Collections.emptyMap(); + + final SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); + Map loaded = new HashMap<>(); + for (String xsdPath : xsdPaths) { + Schema t = loadSchema(factory, xsdPath); + loaded.put(xsdPath, t); + } + return loaded; + } + + private String getAbsolutePathForResource(String filePath) { + URL url = this.getClass().getClassLoader().getResource(filePath); + if (url != null) { + if (log.isDebugEnabled()) { + log.debug("catalog url: {}", url); + } + return url.toString(); + } + log.error("Couldn't find resource: {}", filePath); + return null; + } +} diff --git a/src/main/java/dk/erst/oxalis/as4/validation/schema/SchemaResolver.java b/src/main/java/dk/erst/oxalis/as4/validation/schema/SchemaResolver.java index acb2b33835396aee683dc0e39dad2580b3fc6b0d..78991ec2fc71c09b305b1d4a65c02c10eae261be 100644 --- a/src/main/java/dk/erst/oxalis/as4/validation/schema/SchemaResolver.java +++ b/src/main/java/dk/erst/oxalis/as4/validation/schema/SchemaResolver.java @@ -5,6 +5,7 @@ import dk.erst.oxalis.as4.config.documenttype.DocumentTypeConfigResolverExceptio import org.w3c.dom.Document; import javax.xml.validation.Schema; +import java.util.concurrent.ExecutionException; /** * Interface for resolving schemas for a {@link Document} based on a {@link DocumentTypeConfig}. @@ -13,10 +14,8 @@ public interface SchemaResolver { /** * Resolves the {@link Schema} for a {@link Document} based on a {@link DocumentTypeConfig}. * - * @param document the {@link Document} for which to resolve the schema. - * @param documentTypeConfig the {@link DocumentTypeConfig} configuration for the document type. + * @param schemaPath the file path to the XSD schema. * @return the resolved {@link Schema} for the document. - * @throws DocumentTypeConfigResolverException if an error occurs while resolving the @{link DocumentTypeConfig} configuration. */ - public Schema resolveSchema(Document document, DocumentTypeConfig documentTypeConfig) throws DocumentTypeConfigResolverException; + public Schema resolveSchema(String schemaPath) throws ExecutionException; } diff --git a/src/main/java/dk/erst/oxalis/as4/validation/schema/SchemaResolverImpl.java b/src/main/java/dk/erst/oxalis/as4/validation/schema/SchemaResolverImpl.java index 34227982f3e1bd7afe9294c12f6b3f23a054ab29..64bcffd1a6d5358f89090407b862dbabb0c2ce7b 100644 --- a/src/main/java/dk/erst/oxalis/as4/validation/schema/SchemaResolverImpl.java +++ b/src/main/java/dk/erst/oxalis/as4/validation/schema/SchemaResolverImpl.java @@ -1,5 +1,6 @@ package dk.erst.oxalis.as4.validation.schema; +import com.google.common.cache.LoadingCache; import dk.erst.oxalis.as4.config.documenttype.DocumentTypeConfig; import dk.erst.oxalis.as4.config.documenttype.DocumentTypeConfigResolverException; import org.slf4j.Logger; @@ -11,6 +12,7 @@ import javax.xml.transform.stream.StreamSource; import javax.xml.validation.Schema; import javax.xml.validation.SchemaFactory; import java.net.URL; +import java.util.concurrent.ExecutionException; /** * Implementation for resolving schemas for a {@link Document} based on a {@link DocumentTypeConfig}. @@ -18,34 +20,19 @@ import java.net.URL; public class SchemaResolverImpl implements SchemaResolver { private static final Logger log = LoggerFactory.getLogger(SchemaResolverImpl.class); + private final LoadingCache schemaCache; /** * Constructs a new instance of this class. */ - public SchemaResolverImpl() { + public SchemaResolverImpl(LoadingCache schemaCache) { + this.schemaCache = schemaCache; } @Override - public Schema resolveSchema(Document document, DocumentTypeConfig documentTypeConfig) throws DocumentTypeConfigResolverException { - if(document == null || documentTypeConfig == null) return null; + public Schema resolveSchema(String schemaPath) throws ExecutionException { + if(schemaPath == null) return null; - String schemaPath = documentTypeConfig.getSchemaPath(); - SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); - - Schema schema = null; - // try to load schema from resources then afterwards as file - try { - URL url = this.getClass().getClassLoader().getResource(schemaPath); - schema = factory.newSchema(url); - } catch (Exception e) { - Source source = new StreamSource(schemaPath); - try { - schema = factory.newSchema(source); - } catch (Exception ex) { - log.debug("Could not resolve schema for element [{{}]{}", document.getDocumentElement().getNamespaceURI(), document.getDocumentElement().getLocalName()); - } - } - - return schema; + return schemaCache.get(schemaPath); } } diff --git a/src/main/java/dk/erst/oxalis/as4/validation/schema/SchemaValidatorImpl.java b/src/main/java/dk/erst/oxalis/as4/validation/schema/SchemaValidatorImpl.java index 4df28e8fca8d38a3ef42199105bb06efdabb46d5..94eac631a7631d45abec92c97314549fda3cb32b 100644 --- a/src/main/java/dk/erst/oxalis/as4/validation/schema/SchemaValidatorImpl.java +++ b/src/main/java/dk/erst/oxalis/as4/validation/schema/SchemaValidatorImpl.java @@ -21,6 +21,7 @@ import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.concurrent.ExecutionException; import java.util.stream.Collectors; /** @@ -48,7 +49,7 @@ public class SchemaValidatorImpl implements SchemaValidator { try { Document payload = payloadExtractor.extractPayload(document); - Schema schema = schemaResolver.resolveSchema(payload, documentTypeConfig); + Schema schema = schemaResolver.resolveSchema(documentTypeConfig.getSchemaPath()); if(schema != null) { Validator validator = schema.newValidator(); CollectingErrorHandler errorHandler = new CollectingErrorHandler(messageContext); @@ -58,7 +59,7 @@ public class SchemaValidatorImpl implements SchemaValidator { result.setErrors(errorHandler.getErrors()); result.setWarnings(errorHandler.getWarnings()); } - } catch (SAXException | IOException | ParserConfigurationException e) { + } catch (SAXException | IOException | ParserConfigurationException | ExecutionException e) { throw new SchemaValidationException("Error while performing schema validation", e); } diff --git a/src/main/java/dk/erst/oxalis/as4/validation/signature/DSSModule.java b/src/main/java/dk/erst/oxalis/as4/validation/signature/DSSModule.java index faebf4c4bf250c56054950b88723587d4e29fbd0..c6f039946c7b798c18f07b1a126af81fa2d70de4 100644 --- a/src/main/java/dk/erst/oxalis/as4/validation/signature/DSSModule.java +++ b/src/main/java/dk/erst/oxalis/as4/validation/signature/DSSModule.java @@ -4,11 +4,12 @@ import com.google.inject.AbstractModule; import com.google.inject.Provider; import com.google.inject.Provides; import com.google.inject.Singleton; +import dk.erst.oxalis.as4.mode.Mode; import dk.erst.oxalis.as4.signature.SignatureFactory; import dk.erst.oxalis.as4.signature.SignatureFactoryImpl; -import dk.erst.oxalis.as4.signature.TransferDelegationFactoryImpl; -import eu.europa.esig.dss.asic.xades.ASiCWithXAdESSignatureParameters; -import eu.europa.esig.dss.asic.xades.signature.ASiCWithXAdESService; +import dk.erst.oxalis.as4.signature.XAdESServiceProvider; +import dk.erst.oxalis.as4.signature.XAdESSignatureParametersProvider; +import dk.erst.oxalis.as4.util.SBDPayloadExtractor; import eu.europa.esig.dss.policy.ValidationPolicy; import eu.europa.esig.dss.service.crl.OnlineCRLSource; import eu.europa.esig.dss.service.http.commons.CommonsDataLoader; @@ -21,24 +22,27 @@ import eu.europa.esig.dss.spi.x509.KeyStoreCertificateSource; import eu.europa.esig.dss.spi.x509.revocation.crl.CRLSource; import eu.europa.esig.dss.token.DSSPrivateKeyEntry; import eu.europa.esig.dss.validation.CertificateVerifier; +import java.io.IOException; +import java.io.InputStream; +import java.security.KeyStore; +import java.security.Security; +import javax.inject.Named; +import javax.xml.parsers.DocumentBuilder; + +import eu.europa.esig.dss.validation.DocumentValidatorFactory; +import eu.europa.esig.dss.xades.XAdESSignatureParameters; +import eu.europa.esig.dss.xades.signature.XAdESService; +import eu.europa.esig.dss.xades.validation.XMLDocumentValidatorFactory; import network.oxalis.api.lang.OxalisLoadingException; import network.oxalis.api.settings.Settings; import network.oxalis.commons.security.KeyStoreConf; import network.oxalis.commons.settings.SettingsBuilder; -import dk.erst.oxalis.as4.mode.Mode; +import org.apache.wss4j.dom.engine.WSSConfig; import org.bouncycastle.jce.provider.BouncyCastleProvider; -import javax.inject.Named; -import javax.xml.bind.JAXBException; -import javax.xml.parsers.DocumentBuilder; -import java.io.IOException; -import java.io.InputStream; -import java.security.KeyStore; -import java.security.Security; -import java.security.cert.X509Certificate; /** - * Module containing classes for validating ASiC containers using the Digital Signature Service framework (DSS) + * Module containing classes for creating and validating Nemhandel e-Delivery document signatures using the Digital Signature Service framework (DSS) */ public class DSSModule extends AbstractModule { @@ -48,14 +52,21 @@ public class DSSModule extends AbstractModule { Security.addProvider(new BouncyCastleProvider()); } - // Initialize apache Santuario canonicalization - org.apache.xml.security.Init.init(); + /** + * Initialize WSS4J just in case since XML security is needed for the DSS framework + * NOTE: don't initialize Apache XML Security directly here through org.apache.xml.security.Init.init(); + * as that will cause issues with the resource bundles at runtime for certain errors while verifying + * the WS-security signature which is part of the AS4 protocol! + */ + WSSConfig.init(); // Bind settings SettingsBuilder.with(binder(), SignatureValidationConf.class); bind(ValidationPolicy.class).toProvider(SignatureValidationPolicyProvider.class); bind(CertificateVerifier.class).toProvider(CertificateVerifierProvider.class); + bind(XAdESSignatureParameters.class).toProvider(XAdESSignatureParametersProvider.class); + bind(XAdESService.class).toProvider(XAdESServiceProvider.class); } @Provides @@ -64,8 +75,9 @@ public class DSSModule extends AbstractModule { Provider policyProvider, Provider documentBuilderProvider, Mode mode, - X509Certificate accessPointCertificate) { - return new SignatureValidatorImpl(verifierProvider, policyProvider, documentBuilderProvider, mode, accessPointCertificate); + SBDPayloadExtractor payloadExtractor, + DocumentValidatorFactory documentValidatorFactory) { + return new SignatureValidatorImpl(verifierProvider, policyProvider, documentBuilderProvider, mode, payloadExtractor, documentValidatorFactory); } @Provides @@ -76,7 +88,7 @@ public class DSSModule extends AbstractModule { String trustStorePath = mode.getString("security.truststore.ap"); String keystoreType = trustStorePath.endsWith(".jks") ? "JKS" : "PKCS12"; try (InputStream inputStream = getClass().getResourceAsStream(trustStorePath)) { - CertificateSource source = new KeyStoreCertificateSource(inputStream, keystoreType, mode.getString("security.truststore.password")); + CertificateSource source = new KeyStoreCertificateSource(inputStream, keystoreType, mode.getString("security.truststore.password").toCharArray()); CommonTrustedCertificateSource trustSource = new CommonTrustedCertificateSource(); trustSource.importAsTrusted(source); return trustSource; @@ -85,13 +97,6 @@ public class DSSModule extends AbstractModule { } } - @Provides - @Singleton - ASiCWithXAdESService provideAsicXAdESService(Provider certificateVerifierProvider) { - ASiCWithXAdESService service = new ASiCWithXAdESService(certificateVerifierProvider.get()); - return service; - } - @Provides @Singleton CRLSource provideCRLSource(Settings settings) { @@ -104,7 +109,7 @@ public class DSSModule extends AbstractModule { properties.setHost(settings.getString(SignatureValidationConf.CRL_PROXY_HOST)); properties.setPort(settings.getInt(SignatureValidationConf.CRL_PROXY_PORT)); properties.setUser(settings.getString(SignatureValidationConf.CRL_PROXY_USER)); - properties.setPassword(settings.getString(SignatureValidationConf.CRL_PROXY_PASSWORD)); + properties.setPassword(settings.getString(SignatureValidationConf.CRL_PROXY_PASSWORD).toCharArray()); ProxyConfig proxyConfig = new ProxyConfig(); proxyConfig.setHttpProperties(properties); proxyConfig.setHttpsProperties(properties); @@ -112,7 +117,7 @@ public class DSSModule extends AbstractModule { } cacheDataLoader.setDataLoader(dataLoader); - cacheDataLoader.setCacheExpirationTime(3600000L); + cacheDataLoader.setCacheExpirationTime(1800_000L); source.setDataLoader(cacheDataLoader); return source; } @@ -131,13 +136,18 @@ public class DSSModule extends AbstractModule { @Provides @Singleton - SignatureFactory provideSignatureFactory(ASiCWithXAdESService signatureService, KeystoreSignatureTokenConnectionWrapper signatureToken, ASiCWithXAdESSignatureParameters signatureParameters, DSSPrivateKeyEntry signaturePrivateKey, Provider documentBuilderProvider) { - return new SignatureFactoryImpl(signatureService, signatureToken, signaturePrivateKey, documentBuilderProvider); + SignatureFactory provideSignatureFactory(Provider signatureServiceProvider, + KeystoreSignatureTokenConnectionWrapper signatureToken, + DSSPrivateKeyEntry signaturePrivateKey, + Provider documentBuilderProvider, + SBDPayloadExtractor payloadExtractor, + Provider signatureParametersProvider) { + return new SignatureFactoryImpl(signatureServiceProvider, signatureToken, signaturePrivateKey, documentBuilderProvider, payloadExtractor, signatureParametersProvider); } @Provides @Singleton - TransferDelegationFactoryImpl transferDelegationFactory () throws JAXBException { - return new TransferDelegationFactoryImpl(); + DocumentValidatorFactory provideXMLDocumentValidatorFactory() { + return new XMLDocumentValidatorFactory(); // Used to create Validator instances for XAdES } } diff --git a/src/main/java/dk/erst/oxalis/as4/validation/signature/SignatureValidationPolicyProvider.java b/src/main/java/dk/erst/oxalis/as4/validation/signature/SignatureValidationPolicyProvider.java index 33cd79dab3b49db22c8f87325b264fe643eb8e4c..dd848adad2fe8c6cf17f2977057792539272bc55 100644 --- a/src/main/java/dk/erst/oxalis/as4/validation/signature/SignatureValidationPolicyProvider.java +++ b/src/main/java/dk/erst/oxalis/as4/validation/signature/SignatureValidationPolicyProvider.java @@ -2,8 +2,11 @@ package dk.erst.oxalis.as4.validation.signature; import com.google.inject.Provider; import com.google.inject.ProvisionException; +import eu.europa.esig.dss.enumerations.SignatureLevel; import eu.europa.esig.dss.policy.ValidationPolicy; import eu.europa.esig.dss.policy.ValidationPolicyFacade; +import eu.europa.esig.dss.policy.jaxb.Level; +import eu.europa.esig.dss.policy.jaxb.MultiValuesConstraint; import org.xml.sax.SAXException; import javax.xml.bind.JAXBException; @@ -24,7 +27,13 @@ public class SignatureValidationPolicyProvider implements Provider validationPolicyProvider; private final Provider documentBuilderProvider; private final Mode mode; - private final X509Certificate accessPointCertificate; + private static final String EDELIVERY_SIGNATURE_SCOPE_XPATH = "/sbd:StandardBusinessDocument/sbd:StandardBusinessDocumentHeader/sbd:BusinessScope/sbd:Scope[sbd:Type='NEMHANDEL_EDELIVERY_SIGNATURE']"; /** - * Xpath to where the nemhandel e-delivery signature is located in the document structure. + * Xpath to where the Nemhandel e-delivery signature is located in the document structure. */ - public static final String EDELIVERY_SIGNATURE_XPATH = "/sbd:StandardBusinessDocument/sbd:StandardBusinessDocumentHeader/sbd:BusinessScope/sbd:Scope[sbd:Type='NEMHANDEL_EDELIVERY_SIGNATURE']/sbd:InstanceIdentifier"; + public static final String EDELIVERY_SIGNATURE_XPATH = EDELIVERY_SIGNATURE_SCOPE_XPATH + "/sbd:InstanceIdentifier"; + + /** + * XPath to where the Nemhandel e-delivery signature specification version is located in the document structure. + */ + public static final String EDELIVERY_SIGNATURE_SPEC_VERSION_XPATH = EDELIVERY_SIGNATURE_SCOPE_XPATH + "/sbd:Identifier"; + + /** + * Detail error message for when the signature validation is rejected because the signature is not in XAdES format or contains no signatures. + */ + public static final String INVALID_SIGNATURE_FORMAT_DETAIL_MESSAGE = "Signature is not in XAdES format or contains no signatures"; + private final SBDPayloadExtractor payloadExtractor; + private final DocumentValidatorFactory documentValidatorFactory; /** @@ -71,15 +81,21 @@ public class SignatureValidatorImpl implements SignatureValidator { * @param validationPolicy {@link Provider} for providing {@link ValidationPolicy} objects. Resolved by Guice. * @param documentBuilderProvider {@link Provider} for providing {@link DocumentBuilder} objects. Resolved by Guice. * @param mode The type mode. Is used to identify the current mode of the Access Point. - * @param accessPointCertificate The Access Point's own certificate. + * @param documentValidatorFactory Factory for creating {@link eu.europa.esig.dss.validation.DocumentValidator} instances. + * @param payloadExtractor The extractor to use in extracting the payload from the SBD. */ - public SignatureValidatorImpl(Provider certificateVerifier, Provider validationPolicy, - Provider documentBuilderProvider, Mode mode, X509Certificate accessPointCertificate) { + public SignatureValidatorImpl(Provider certificateVerifier, + Provider validationPolicy, + Provider documentBuilderProvider, + Mode mode, + SBDPayloadExtractor payloadExtractor, + DocumentValidatorFactory documentValidatorFactory) { this.certificateVerifierProvider = certificateVerifier; this.validationPolicyProvider = validationPolicy; this.documentBuilderProvider = documentBuilderProvider; this.mode = mode; - this.accessPointCertificate = accessPointCertificate; + this.payloadExtractor = payloadExtractor; + this.documentValidatorFactory = documentValidatorFactory; } @Override @@ -96,23 +112,15 @@ public class SignatureValidatorImpl implements SignatureValidator { ValidationResult result = new ValidationResult(); byte[] messageContent = message.getMessageContent().getData(); - MapNamespaceContext namespaceContext = new MapNamespaceContext(); - namespaceContext.addNamespace("sbd", Ns.SBDH); - XPath xpath = XPathFactory.newInstance().newXPath(); - xpath.setNamespaceContext(namespaceContext); try(ByteArrayInputStream bis = new ByteArrayInputStream(messageContent)) { DocumentBuilder documentBuilder = documentBuilderProvider.get(); Document document = documentBuilder.parse(bis); - ValidationContext context = new ValidationContext(); - context.setDocumentBuilder(documentBuilder); - context.setRecursionLevel(0); - context.setSbdXPath(xpath); - context.setMessageContext(messageContext); - validateDocumentSignature(document, result, context); - } catch(IOException | SAXException | XPathExpressionException | TransformerException | XMLSecurityException e) { + validateDocumentSignature(document, result, messageContext); + } catch(IOException | SAXException | XPathExpressionException | TransformerException | XMLSecurityException | + ParserConfigurationException e) { throw new ValidationException("Error while validating document", e); } @@ -134,235 +142,102 @@ public class SignatureValidatorImpl implements SignatureValidator { return documentSignatures; } - private void validateDocumentSignature(Document document, ValidationResult result, ValidationContext validationContext) throws IOException, SAXException, XPathExpressionException, TransformerException, XMLSecurityException { + private void validateDocumentSignature(Document document, ValidationResult result, SBDMessageContext messageContext) throws IOException, SAXException, XPathExpressionException, TransformerException, XMLSecurityException, ParserConfigurationException { if(logger.isTraceEnabled()) { - logger.trace("Validating Nemhandel e-Delivery signature for recursion level {}", validationContext.getRecursionLevel()); + logger.trace("Validating Nemhandel e-Delivery signature"); } - NodeList documentSignatures = getEdeliverySignatureNodes(document, validationContext.getSbdXPath()); - if(validationContext.getRecursionLevel() == 0 && !validationContext.getMessageContext().isDocumentSignatureRequired() && documentSignatures != null && documentSignatures.getLength() <= 0) { + MapNamespaceContext namespaceContext = new MapNamespaceContext(); + namespaceContext.addNamespace("sbd", Ns.SBDH); + + XPath xpath = XPathFactory.newInstance().newXPath(); + xpath.setNamespaceContext(namespaceContext); + + NodeList documentSignatures = getEdeliverySignatureNodes(document, xpath); + + if(!messageContext.isDocumentSignatureRequired() && documentSignatures != null && documentSignatures.getLength() <= 0) { logger.debug("No Nemhandel e-Delivery signature found. Skipping signature validation since the signature is marked as optional."); return; } if (documentSignatures == null || documentSignatures.getLength() <= 0) { - result.addError(ValidationMessage.fromErrorCode(validationContext.getMessageContext().isOutbound() ? ErrorCodes.SENDER_EDELIVERY_SIGNATURE_MISSING : ErrorCodes.RECEIVER_EDELIVERY_SIGNATURE_MISSING, EDELIVERY_SIGNATURE_XPATH, validationContext.getRecursionLevel())); - } else if(documentSignatures != null && documentSignatures.getLength() > 1) { - result.addError(ValidationMessage.fromErrorCode(validationContext.getMessageContext().isOutbound() ? ErrorCodes.SENDER_EDELIVERY_TOO_MANY_SIGNATURES : ErrorCodes.RECEIVER_EDELIVERY_TOO_MANY_SIGNATURES, EDELIVERY_SIGNATURE_XPATH, documentSignatures.getLength(), validationContext.getRecursionLevel())); + result.addError(ValidationMessage.fromErrorCode(messageContext.isOutbound() ? ErrorCodes.SENDER_EDELIVERY_SIGNATURE_MISSING : ErrorCodes.RECEIVER_EDELIVERY_SIGNATURE_MISSING, EDELIVERY_SIGNATURE_XPATH)); + } else if(documentSignatures.getLength() > 1) { + result.addError(ValidationMessage.fromErrorCode(messageContext.isOutbound() ? ErrorCodes.SENDER_EDELIVERY_TOO_MANY_SIGNATURES : ErrorCodes.RECEIVER_EDELIVERY_TOO_MANY_SIGNATURES, EDELIVERY_SIGNATURE_XPATH, documentSignatures.getLength())); } if(result.hasErrors()) { if(logger.isTraceEnabled()) { - logger.trace("Nemhandel e-Delivery signature for recursion level {} is not valid", validationContext.getRecursionLevel()); + logger.trace("Nemhandel e-Delivery signature is not valid"); } return; } - Node signatureNode = documentSignatures.item(0); - String txt = signatureNode.getTextContent(); - byte[] decoded = Base64.getDecoder().decode(txt); - - InMemoryDocument doc = new InMemoryDocument(decoded); - SignedDocumentValidator validator = SignedDocumentValidator.fromDocument(doc); - CertificateVerifier cv = certificateVerifierProvider.get(); - validator.setCertificateVerifier(cv); - ValidationPolicy policy = validationPolicyProvider.get(); - Reports reports = validator.validateDocument(policy); - if(!isValid(reports)) { - addSignatureValidationErrors(reports, result, validationContext); + String specVersion = XPathUtil.evaluateXPath(document, xpath, EDELIVERY_SIGNATURE_SPEC_VERSION_XPATH, null); + EDeliverySpecification spec = EDeliverySpecification.fromValue(specVersion); + if(!EDeliverySpecification.NEMHANDEL_EDELIVERY_1_2.equals(spec)) { + List specList = Arrays.stream(EDeliverySpecification.values()) + .map(e -> e.value()) + .sorted() + .collect(Collectors.toList()); + result.addError(ValidationMessage.fromErrorCode(messageContext.isOutbound() ? ErrorCodes.SENDER_EDELIVERY_SIGNATURE_INVALID_SPECIFICATION_VERSION : ErrorCodes.RECEIVER_EDELIVERY_SIGNATURE_INVALID_SPECIFICATION_VERSION, EDELIVERY_SIGNATURE_SPEC_VERSION_XPATH, specList)); } - DSSDocument originalSbd = null; - if(!result.hasErrors()) { - // Integrity of ASiC-E is ok, now check for presence of expected signed documents - ASiCWithXAdESContainerExtractor extractor = new ASiCWithXAdESContainerExtractor(doc); - ASiCContent content = extractor.extract(); - - List signedDocuments = content.getSignedDocuments(); - DSSDocument sbd = null; - - DSSDocument transferDelegation = null; - for(DSSDocument signed : signedDocuments) { - if(SignatureValidator.ASIC_SBD_FILENAME.equals(signed.getName())) { - sbd = signed; - } else if(SignatureValidator.ASIC_ORIGINAL_SBD_FILENAME.equals(signed.getName())) { - originalSbd = signed; - } else if(SignatureValidator.ASIC_TRANSFER_DELEGATION_FILENAME.equals(signed.getName())) { - transferDelegation = signed; - } - } - - validateTransferDelegation(transferDelegation, reports, result, validationContext); - validateStandardBusinessDocument(sbd, document, result, validationContext); - } - - if(result.hasErrors() && logger.isTraceEnabled()) { - logger.trace("Nemhandel e-Delivery signature for recursion level {} is not valid", validationContext.getRecursionLevel()); - } + Node signatureNode = documentSignatures.item(0); + String txt = signatureNode.getTextContent(); + byte[] decoded = Base64.getDecoder().decode(txt.getBytes(StandardCharsets.UTF_8)); - if(!result.hasErrors() && originalSbd != null) { - if(logger.isTraceEnabled()) { - logger.trace("Checking for recursively nested Nemhandel e-Delivery signature."); - } - // if original-standard-business-document.xml exists in ASiC container we need to recursively verify those as well. - try(InputStream bis = originalSbd.openStream()) { - Document originalDocument = validationContext.getDocumentBuilder().parse(bis); - - Element root = originalDocument.getDocumentElement(); - if(Ns.QNAME_SBD.getNamespaceURI().equals(root.getNamespaceURI()) && Ns.QNAME_SBD.getLocalPart().equals(root.getLocalName())) { - validationContext.setRecursionLevel(validationContext.getRecursionLevel() + 1); - validateDocumentSignature(originalDocument, result, validationContext); - } else { - result.addError(ValidationMessage.fromErrorCode(validationContext.getMessageContext().isOutbound() ? ErrorCodes.SENDER_EDELIVERY_ORIGINAL_SBD_IN_ASIC_IS_NOT_SBD : ErrorCodes.RECEIVER_EDELIVERY_ORIGINAL_SBD_IN_ASIC_IS_NOT_SBD, EDELIVERY_SIGNATURE_XPATH, validationContext.getRecursionLevel())); - } - } - } - } + InMemoryDocument signature = new InMemoryDocument(decoded); + try { + SignedDocumentValidator validator = documentValidatorFactory.create(signature); - private void validateStandardBusinessDocument(DSSDocument sbd, Document document, ValidationResult result, ValidationContext validationContext) throws XPathExpressionException, IOException, TransformerException, - XMLSecurityException { - if(sbd == null) { - result.addError(ValidationMessage.fromErrorCode(validationContext.getMessageContext().isOutbound() ? ErrorCodes.SENDER_EDELIVERY_MISSING_SBD_IN_ASIC : ErrorCodes.RECEIVER_EDELIVERY_MISSING_SBD_IN_ASIC, EDELIVERY_SIGNATURE_XPATH, validationContext.getRecursionLevel())); - } else { - /* - Verify that SBD matches the one from ASIC container. - Note: the only difference should be the added ASIC container so we remove that and compare the digest values. - This changes the document so we should not rely on the document afterwards! + Document payloadDocument = payloadExtractor.extractPayload(document); + /** + * NOTE: we need to serialize to byte-array before canonicalization, + * Otherwise namespaces may not be correctly available during canonicalization which will lead to validation errors. + * To avoid this SBDPayloadExtractor implementation must be changed to e.g. use XPath to extract payload */ - NodeList edeliverySignatureScopes = XPathUtil.evaluateXPathNodeList(document, - validationContext.getSbdXPath(), - "/sbd:StandardBusinessDocument/sbd:StandardBusinessDocumentHeader/sbd:BusinessScope/sbd:Scope[sbd:Type='NEMHANDEL_EDELIVERY_SIGNATURE']"); - if(edeliverySignatureScopes != null && edeliverySignatureScopes.getLength() > 0) { - for(int i = 0; i < edeliverySignatureScopes.getLength(); i++) { - Node sig = edeliverySignatureScopes.item(i); - sig.getParentNode().removeChild(sig); - } - } + DSSDocument payload = new InMemoryDocument(DocumentUtil.canonicalize(DocumentUtil.toByteArray(payloadDocument))); + validator.setDetachedContents(Collections.singletonList(payload)); - if(!documentMatchesSBDInAsicContainer(document, sbd)) { - result.addError(ValidationMessage.fromErrorCode(validationContext.getMessageContext().isOutbound() ? ErrorCodes.SENDER_EDELIVERY_SBD_DOES_NOT_MATCH_ASIC : ErrorCodes.RECEIVER_EDELIVERY_SBD_DOES_NOT_MATCH_ASIC, EDELIVERY_SIGNATURE_XPATH, validationContext.getRecursionLevel())); - } - } - } - - private void validateTransferDelegation(DSSDocument transferDelegation, Reports reports, ValidationResult result, ValidationContext validationContext) throws IOException, SAXException { - if(logger.isTraceEnabled()) { - logger.trace("Validating transfer-delegation.xml for recursion level {}", validationContext.getRecursionLevel()); - } - - if(transferDelegation == null) { - result.addError(ValidationMessage.fromErrorCode(validationContext.getMessageContext().isOutbound() ? ErrorCodes.SENDER_EDELIVERY_MISSING_TRANSFER_DELEGATION : ErrorCodes.RECEIVER_EDELIVERY_MISSING_TRANSFER_DELEGATION, EDELIVERY_SIGNATURE_XPATH, validationContext.getRecursionLevel())); - return; - } - - DiagnosticData diagnosticData = reports.getDiagnosticData(); + CertificateVerifier cv = certificateVerifierProvider.get(); + validator.setCertificateVerifier(cv); - try(InputStream is = transferDelegation.openStream()) { - Document document = validationContext.getDocumentBuilder().parse(is); - - XPath xpath = XPathFactory.newInstance().newXPath(); - MapNamespaceContext namespaceContext = new MapNamespaceContext(); - namespaceContext.addNamespace("td", "http://nemhandel.dk/e-delivery/transfer-delegation"); - xpath.setNamespaceContext(namespaceContext); - - String receiver = XPathUtil.evaluateXPath(document, xpath,"/td:TransferDelegation/td:Receiver", null); - if(receiver == null || receiver.isEmpty()) { - result.addError(ValidationMessage.fromErrorCode(validationContext.getMessageContext().isOutbound() ? ErrorCodes.SENDER_EDELIVERY_TRANSFER_DELEGATION_MISSING_RECEIVER_CVR : ErrorCodes.RECEIVER_EDELIVERY_TRANSFER_DELEGATION_MISSING_RECEIVER_CVR, EDELIVERY_SIGNATURE_XPATH, validationContext.getRecursionLevel())); - } else { - String receiverScheme = XPathUtil.evaluateXPath(document, xpath, "/td:TransferDelegation/td:Receiver/@schemeID", null); - if (!SignatureValidator.ASIC_TRANSFER_DELEGATION_SCHEME_CVR.equals(receiverScheme)) { - result.addError(ValidationMessage.fromErrorCode(validationContext.getMessageContext().isOutbound() ? ErrorCodes.SENDER_EDELIVERY_TRANSFER_DELEGATION_INVALID_RECEIVER_SCHEME : ErrorCodes.RECEIVER_EDELIVERY_TRANSFER_DELEGATION_INVALID_RECEIVER_SCHEME, EDELIVERY_SIGNATURE_XPATH, receiverScheme, SignatureValidator.ASIC_TRANSFER_DELEGATION_SCHEME_CVR, validationContext.getRecursionLevel())); - } - - if (validationContext.getRecursionLevel() == 0) { - // ensure we are the intended receiver (as per CVR) of the top-level transfer-delegation - String apCVR = CertificateUtil.extractCVR(accessPointCertificate); - - if (!receiver.equals(apCVR)) { - result.addError(ValidationMessage.fromErrorCode(validationContext.getMessageContext().isOutbound() ? ErrorCodes.SENDER_EDELIVERY_TRANSFER_DELEGATION_INVALID_RECEIVER_CVR : ErrorCodes.RECEIVER_EDELIVERY_TRANSFER_DELEGATION_INVALID_RECEIVER_CVR, EDELIVERY_SIGNATURE_XPATH, receiver, apCVR, validationContext.getRecursionLevel())); - } - } else if(validationContext.getRecursionLevel() > 0) { - // for non-top level transfer-delegations the receiver CVR should match the signing certificate of the previous ASiC container (since we are going top-down order) - // and the transfer delegation "chain" should be unbroken, i.e. receiver x = sender x-1 (because validation is going top-down) - String previousSigningCertCvr = CertificateUtil.extractCVR(validationContext.getPreviousSigningCertificate()); - - if (!receiver.equals(previousSigningCertCvr) || !receiver.equals(validationContext.getPreviousSenderCvr())) { - result.addError(ValidationMessage.fromErrorCode(validationContext.getMessageContext().isOutbound() ? ErrorCodes.SENDER_EDELIVERY_TRANSFER_DELEGATION_INVALID_RECEIVER_CVR : ErrorCodes.RECEIVER_EDELIVERY_TRANSFER_DELEGATION_INVALID_RECEIVER_CVR, EDELIVERY_SIGNATURE_XPATH, receiver, previousSigningCertCvr, validationContext.getRecursionLevel())); - } - } - } - - String sender = XPathUtil.evaluateXPath(document, xpath,"/td:TransferDelegation/td:Sender", null); - if(sender == null || sender.isEmpty()) { - result.addError(ValidationMessage.fromErrorCode(validationContext.getMessageContext().isOutbound() ? ErrorCodes.SENDER_EDELIVERY_TRANSFER_DELEGATION_MISSING_SENDER_CVR : ErrorCodes.RECEIVER_EDELIVERY_TRANSFER_DELEGATION_MISSING_SENDER_CVR, EDELIVERY_SIGNATURE_XPATH, validationContext.getRecursionLevel())); - } else { - String senderScheme = XPathUtil.evaluateXPath(document, xpath, "/td:TransferDelegation/td:Sender/@schemeID", null); - if (!SignatureValidator.ASIC_TRANSFER_DELEGATION_SCHEME_CVR.equals(senderScheme)) { - result.addError(ValidationMessage.fromErrorCode(validationContext.getMessageContext().isOutbound() ? ErrorCodes.SENDER_EDELIVERY_TRANSFER_DELEGATION_INVALID_SENDER_SCHEME : ErrorCodes.RECEIVER_EDELIVERY_TRANSFER_DELEGATION_INVALID_SENDER_SCHEME, EDELIVERY_SIGNATURE_XPATH, senderScheme, SignatureValidator.ASIC_TRANSFER_DELEGATION_SCHEME_CVR, validationContext.getRecursionLevel())); - } - - // validate that the Sender CVR specified in the transfer-delegation is the CVR from the signing certificate - // E.g. for a transfer delegation from C1 to C2 the sender should be C1s CVR and C1 should have signed the ASiC-E container - CertificateWrapper certWrapper = findTransferDelegationSignatureCertificate(diagnosticData); - String senderCertCvr = CertificateUtil.extractCVR(certWrapper); - if (!sender.equals(senderCertCvr)) { - result.addError(ValidationMessage.fromErrorCode(validationContext.getMessageContext().isOutbound() ? ErrorCodes.SENDER_EDELIVERY_TRANSFER_DELEGATION_INVALID_SENDER_CVR : ErrorCodes.RECEIVER_EDELIVERY_TRANSFER_DELEGATION_INVALID_SENDER_CVR, EDELIVERY_SIGNATURE_XPATH, sender, senderCertCvr, validationContext.getRecursionLevel())); - } - - validationContext.setPreviousSigningCertificate(certWrapper); - validationContext.setPreviousSenderCvr(sender); + ValidationPolicy policy = validationPolicyProvider.get(); + Reports reports = validator.validateDocument(policy); + if (!isValid(reports)) { + addSignatureValidationErrors(reports, result, messageContext); } + } catch(IllegalInputException e) { + // DSS throws IllegalInputException from SignedDocumentValidator.fromDocument(signature) if signature is not XML + result.addError(ValidationMessage.fromErrorCode(messageContext.isOutbound() ? ErrorCodes.SENDER_EDELIVERY_SIGNATURE_INVALID : ErrorCodes.RECEIVER_EDELIVERY_SIGNATURE_INVALID, EDELIVERY_SIGNATURE_XPATH, INVALID_SIGNATURE_FORMAT_DETAIL_MESSAGE)); } - } - private CertificateWrapper findTransferDelegationSignatureCertificate(DiagnosticData data) { - for(String id : data.getSignatureIdList()) { - List signedDocuments = data.getSignerDocuments(id); - if(signedDocuments != null) { - boolean found = signedDocuments.stream().anyMatch(d -> SignatureValidator.ASIC_TRANSFER_DELEGATION_FILENAME.equals(d.getReferencedName())); - if(found) { - SignatureWrapper wrapper = data.getSignatureById(id); - return wrapper.getSigningCertificate(); - } - } + if(result.hasErrors() && logger.isTraceEnabled()) { + logger.trace("Nemhandel e-Delivery signature is not valid"); } - return null; } - private void addSignatureValidationErrors(Reports reports, ValidationResult result, ValidationContext validationContext) { + private void addSignatureValidationErrors(Reports reports, ValidationResult result, SBDMessageContext messageContext) { SimpleReport simpleReport = reports.getSimpleReport(); List signatureIdList = simpleReport.getSignatureIdList(); - if(signatureIdList != null && !signatureIdList.isEmpty()) { + if(signatureIdList == null || signatureIdList.isEmpty()) { + result.addError(ValidationMessage.fromErrorCode(messageContext.isOutbound() ? ErrorCodes.SENDER_EDELIVERY_SIGNATURE_INVALID : ErrorCodes.RECEIVER_EDELIVERY_SIGNATURE_INVALID, EDELIVERY_SIGNATURE_XPATH, INVALID_SIGNATURE_FORMAT_DETAIL_MESSAGE)); + } else { for(String sigId : signatureIdList) { List validationMessages = simpleReport.getAdESValidationErrors(sigId); if(validationMessages != null && !validationMessages.isEmpty()) { for(eu.europa.esig.dss.jaxb.object.Message m : validationMessages) { String dssError = m.getKey() + ": " + m.getValue(); - result.addError(ValidationMessage.fromErrorCode(validationContext.getMessageContext().isOutbound() ? ErrorCodes.SENDER_EDELIVERY_SIGNATURE_INVALID : ErrorCodes.RECEIVER_EDELIVERY_SIGNATURE_INVALID, EDELIVERY_SIGNATURE_XPATH, validationContext.getRecursionLevel(), dssError)); + result.addError(ValidationMessage.fromErrorCode(messageContext.isOutbound() ? ErrorCodes.SENDER_EDELIVERY_SIGNATURE_INVALID : ErrorCodes.RECEIVER_EDELIVERY_SIGNATURE_INVALID, EDELIVERY_SIGNATURE_XPATH, dssError)); } } } } } - private boolean documentMatchesSBDInAsicContainer(Document document, DSSDocument sbd) throws IOException, TransformerException, - XMLSecurityException { - byte[] canonicalizedOuterXml = DocumentUtil.canonicalize(DocumentUtil.toByteArray(document)); - - byte[] canonicalizedXmlFromAsicContainer; - try(InputStream is = sbd.openStream()){ - canonicalizedXmlFromAsicContainer = DocumentUtil.canonicalize(IOUtils.toByteArray(is)); - } - - DSSDocument canonicalizedParsed = new InMemoryDocument(canonicalizedOuterXml); - DSSDocument canonicalizedFromAsicContainer = new InMemoryDocument(canonicalizedXmlFromAsicContainer); - String digestParsed = canonicalizedParsed.getDigest(DigestAlgorithm.SHA256); - String digestFromAsic = canonicalizedFromAsicContainer.getDigest(DigestAlgorithm.SHA256); - return digestParsed.equals(digestFromAsic); - } - private boolean isValid(Reports reports) { boolean validSignatureCountMatches = reports.getSimpleReport().getSignaturesCount() == reports.getSimpleReport().getValidSignaturesCount(); if(!validSignatureCountMatches || reports.getSimpleReport().getValidSignaturesCount() <= 0) @@ -381,62 +256,4 @@ public class SignatureValidatorImpl implements SignatureValidator { } return true; } - - private static class ValidationContext { - private int recursionLevel; - private DocumentBuilder documentBuilder; - private XPath sbdXPath; - private CertificateWrapper previousSigningCertificate; - private String previousSenderCvr; - - private SBDMessageContext messageContext; - - public int getRecursionLevel() { - return recursionLevel; - } - - public void setRecursionLevel(int recursionLevel) { - this.recursionLevel = recursionLevel; - } - - public DocumentBuilder getDocumentBuilder() { - return documentBuilder; - } - - public void setDocumentBuilder(DocumentBuilder documentBuilder) { - this.documentBuilder = documentBuilder; - } - - public CertificateWrapper getPreviousSigningCertificate() { - return previousSigningCertificate; - } - - public void setPreviousSigningCertificate(CertificateWrapper previousSigningCertificate) { - this.previousSigningCertificate = previousSigningCertificate; - } - - public XPath getSbdXPath() { - return sbdXPath; - } - - public void setSbdXPath(XPath sbdXPath) { - this.sbdXPath = sbdXPath; - } - - public String getPreviousSenderCvr() { - return previousSenderCvr; - } - - public void setPreviousSenderCvr(String previousSenderCvr) { - this.previousSenderCvr = previousSenderCvr; - } - - public SBDMessageContext getMessageContext() { - return messageContext; - } - - public void setMessageContext(SBDMessageContext messageContext) { - this.messageContext = messageContext; - } - } } diff --git a/src/main/java/dk/erst/oxalis/as4/validation/version/VersionValidator.java b/src/main/java/dk/erst/oxalis/as4/validation/version/VersionValidator.java index 88da88ab8849284c96f36d4e03a3867636bafa06..b5b998b3bd6cee75be623e13662f538eaef137df 100644 --- a/src/main/java/dk/erst/oxalis/as4/validation/version/VersionValidator.java +++ b/src/main/java/dk/erst/oxalis/as4/validation/version/VersionValidator.java @@ -16,7 +16,7 @@ public interface VersionValidator { *
* The receiver and document type identifier in this message is used to do a SMP lookup (if not already up-to-date versions exist in {@link VersionCache}). * The SMP response contains a {@link ExtensionType} object that contains {@link EdelComponentVersion} objects with - * the receivers running version. The running oxalis & schematron versions of this instance will be validated that they are not outdated before sending the message. + * the receivers running version. The running oxalis and schematron versions of this instance will be validated that they are not outdated before sending the message. * * @param message The message to be sent. * @return The {@link ValidationResult} containing any errors or warnings. diff --git a/src/main/java/dk/erst/oxalis/as4/validation/version/VersionValidatorImpl.java b/src/main/java/dk/erst/oxalis/as4/validation/version/VersionValidatorImpl.java index a268c156cd01ac01292d058ae7ab139a9ae21152..72cc5acfe73d3aee277e901f62f386c089c3e230 100644 --- a/src/main/java/dk/erst/oxalis/as4/validation/version/VersionValidatorImpl.java +++ b/src/main/java/dk/erst/oxalis/as4/validation/version/VersionValidatorImpl.java @@ -15,6 +15,7 @@ import network.oxalis.vefa.peppol.lookup.LookupClient; import network.oxalis.vefa.peppol.lookup.api.LookupException; import network.oxalis.vefa.peppol.security.lang.PeppolSecurityException; import org.joda.time.DateTime; +import org.joda.time.DateTimeZone; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.apache.maven.artifact.versioning.DefaultArtifactVersion; @@ -164,7 +165,7 @@ public class VersionValidatorImpl implements VersionValidator { private static boolean isVersionNumberFormatValid(String currentVersionString) { DefaultArtifactVersion currentVersion = new DefaultArtifactVersion(currentVersionString); //if qualifier is null, the parsing went well. - return currentVersion.getQualifier() == null; + return currentVersion.getQualifier() == null || currentVersion.getQualifier().equals("RC"); } private String getOutOfDateTextMandatory(String component) { @@ -195,10 +196,11 @@ public class VersionValidatorImpl implements VersionValidator { )); } - private String getSoonOutOfDateText(DateTime date, String component) { + protected String getSoonOutOfDateText(DateTime date, String component) { + DateTime localTime = new DateTime(date).withZone(DateTimeZone.forID("Europe/Copenhagen")); return PrintUtil.prettyPrint(component +" is soon to be out of date!", Arrays.asList( - "On " + date.toString("EEEE, MMM dd, yyyy HH:mm:ss", Locale.ENGLISH) + " this " + component, + "On " + localTime.toString("EEEE, MMM dd, yyyy HH:mm:ss", Locale.ENGLISH) + " this " + component, "will be obsolete!", "Please make sure you have upgraded before then", "as by then it can no longer serve to receive or send documents.", diff --git a/src/main/java/dk/erst/oxalis/as4/validation/version/model/JodaTimeAdapter.java b/src/main/java/dk/erst/oxalis/as4/validation/version/model/JodaTimeAdapter.java index 1b2cdef0be23481eb5d65994a6fa740368d423a6..acdda94d26343da53ae4a00bf2cce32052d4e981 100644 --- a/src/main/java/dk/erst/oxalis/as4/validation/version/model/JodaTimeAdapter.java +++ b/src/main/java/dk/erst/oxalis/as4/validation/version/model/JodaTimeAdapter.java @@ -2,7 +2,6 @@ package dk.erst.oxalis.as4.validation.version.model; import org.joda.time.DateTime; -import org.joda.time.DateTimeZone; import org.joda.time.format.DateTimeFormat; import org.joda.time.format.DateTimeFormatter; @@ -17,7 +16,7 @@ public class JodaTimeAdapter extends XmlAdapter { /** * The DateTimeFormatter used to parse and format DateTime objects. */ - private static final DateTimeFormatter DATETIME_FORMATTER = DateTimeFormat.forPattern("yyyy-MM-dd'T'HH:mm:ss").withZone(DateTimeZone.UTC); + private static final DateTimeFormatter DATETIME_FORMATTER = DateTimeFormat.forPattern("yyyy-MM-dd'T'HH:mm:ssZ"); /** * Marshals the provided DateTime object into its XML string representation. @@ -41,4 +40,3 @@ public class JodaTimeAdapter extends XmlAdapter { return DATETIME_FORMATTER.parseDateTime(v); } } - diff --git a/src/main/java/network/oxalis/as4/config/As4Conf.java b/src/main/java/network/oxalis/as4/config/As4Conf.java index ac22d81df0c60e97574b0ba1d6c6567f47e6fb41..29e8f60217bd010c51cc3ebd76cadb790b60436e 100644 --- a/src/main/java/network/oxalis/as4/config/As4Conf.java +++ b/src/main/java/network/oxalis/as4/config/As4Conf.java @@ -42,7 +42,7 @@ public enum As4Conf { TYPE, @Path("oxalis.as4.sbdh.limit") - //Default value 60 MB for now. Should be enough to support 10 MB payload in 3 asic containers. + //Default value 60 MB for now. @DefaultValue("60000000") SBDH_LIMIT } diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/NKSPU_Alphanumeric40LetterNonemptyText.xsd b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/NKSPU_Alphanumeric40LetterNonemptyText.xsd new file mode 100644 index 0000000000000000000000000000000000000000..3715fa862e264f7ccbd4a70219e91e84e12da65f --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/NKSPU_Alphanumeric40LetterNonemptyText.xsd @@ -0,0 +1,10 @@ + + + + + + + + + diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/NKSPU_BICIdentifier.xsd b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/NKSPU_BICIdentifier.xsd new file mode 100644 index 0000000000000000000000000000000000000000..6da88d87f818e846bc99c62fe25acf6cf41f5ccb --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/NKSPU_BICIdentifier.xsd @@ -0,0 +1,11 @@ + + + + + + + + + + diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/NKSPU_BankAccountUnavailable.xsd b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/NKSPU_BankAccountUnavailable.xsd new file mode 100644 index 0000000000000000000000000000000000000000..3f96e1d8ff44b514197d8a6d7fa6c907e241e127 --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/NKSPU_BankAccountUnavailable.xsd @@ -0,0 +1,14 @@ + + + + + + + + + + + + + diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/NKSPU_BankAccountUnavailableCode.xsd b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/NKSPU_BankAccountUnavailableCode.xsd new file mode 100644 index 0000000000000000000000000000000000000000..8ff6b3586c63bfb469f2fafd7e55f6aba113caf2 --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/NKSPU_BankAccountUnavailableCode.xsd @@ -0,0 +1,10 @@ + + + + + + + + + diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/NKSPU_BankAccountUnavailableText.xsd b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/NKSPU_BankAccountUnavailableText.xsd new file mode 100644 index 0000000000000000000000000000000000000000..73acf99dbc8e3d763480b595e9be5cee76eab128 --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/NKSPU_BankAccountUnavailableText.xsd @@ -0,0 +1,5 @@ + + + + diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/NKSPU_ForeignBankAccount.xsd b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/NKSPU_ForeignBankAccount.xsd new file mode 100644 index 0000000000000000000000000000000000000000..e0b0c806737fba59b7a43b315af8632113c89874 --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/NKSPU_ForeignBankAccount.xsd @@ -0,0 +1,13 @@ + + + + + + + + + + + + diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/NKSPU_ForeignBankAccountIdentifier.xsd b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/NKSPU_ForeignBankAccountIdentifier.xsd new file mode 100644 index 0000000000000000000000000000000000000000..639e68a821d5f153838c6b0662f04fced12b02e6 --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/NKSPU_ForeignBankAccountIdentifier.xsd @@ -0,0 +1,10 @@ + + + + + + + + + diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/NKSPU_ForeignBankBranchCode.xsd b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/NKSPU_ForeignBankBranchCode.xsd new file mode 100644 index 0000000000000000000000000000000000000000..edc69462e88892fd4c70f921acbce9fa3e38529a --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/NKSPU_ForeignBankBranchCode.xsd @@ -0,0 +1,11 @@ + + + + + + + + + + diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/NKSPU_ForeignBankInfoStructure.xsd b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/NKSPU_ForeignBankInfoStructure.xsd new file mode 100644 index 0000000000000000000000000000000000000000..b445b40b8b32c09eca914a6317c5f1325e60853a --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/NKSPU_ForeignBankInfoStructure.xsd @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/NKSPU_ForeignPersonInfoStructure.xsd b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/NKSPU_ForeignPersonInfoStructure.xsd new file mode 100644 index 0000000000000000000000000000000000000000..2590341c37d2162438ddcac37bb4b271add11dec --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/NKSPU_ForeignPersonInfoStructure.xsd @@ -0,0 +1,17 @@ + + + + + + + + + + + + diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/NKSPU_IBANIdentifier.xsd b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/NKSPU_IBANIdentifier.xsd new file mode 100644 index 0000000000000000000000000000000000000000..dca26a7911effbb885de7f6231f9161fd0ec30ec --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/NKSPU_IBANIdentifier.xsd @@ -0,0 +1,11 @@ + + + + + + + + + + diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/NKSPU_InvalidMessageFormat.xsd b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/NKSPU_InvalidMessageFormat.xsd new file mode 100644 index 0000000000000000000000000000000000000000..489c95c16a7face385bf7c12b3f4ff34788876d1 --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/NKSPU_InvalidMessageFormat.xsd @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/NKSPU_InvalidMessageFormatCode.xsd b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/NKSPU_InvalidMessageFormatCode.xsd new file mode 100644 index 0000000000000000000000000000000000000000..88c71bd46729fa7e3a3e8bf7b9086ac169316688 --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/NKSPU_InvalidMessageFormatCode.xsd @@ -0,0 +1,10 @@ + + + + + + + + + diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/NKSPU_InvalidMessageFormatText.xsd b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/NKSPU_InvalidMessageFormatText.xsd new file mode 100644 index 0000000000000000000000000000000000000000..42a06a1b9608fb2f7a6a6a358d60c14bf37884ac --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/NKSPU_InvalidMessageFormatText.xsd @@ -0,0 +1,5 @@ + + + + diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/NKSPU_InvalidMessageFormathighestseverityCode.xsd b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/NKSPU_InvalidMessageFormathighestseverityCode.xsd new file mode 100644 index 0000000000000000000000000000000000000000..73e175e28aa024ad8a1e9330a46f0a1d017ff421 --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/NKSPU_InvalidMessageFormathighestseverityCode.xsd @@ -0,0 +1,10 @@ + + + + + + + + + diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/NKSPU_LookupDateTime.xsd b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/NKSPU_LookupDateTime.xsd new file mode 100644 index 0000000000000000000000000000000000000000..084c0c9ccdb65dee84e844ba73641619584c7c33 --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/NKSPU_LookupDateTime.xsd @@ -0,0 +1,5 @@ + + + + diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/NKSPU_NemkontoAccountHolder.xsd b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/NKSPU_NemkontoAccountHolder.xsd new file mode 100644 index 0000000000000000000000000000000000000000..f8c6920d3dbd468276f9331760bad5509e10b35f --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/NKSPU_NemkontoAccountHolder.xsd @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/NKSPU_NemkontoAccountInformation.xsd b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/NKSPU_NemkontoAccountInformation.xsd new file mode 100644 index 0000000000000000000000000000000000000000..2a525f9cf65a8005d2a39260dc2dfa22b478e713 --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/NKSPU_NemkontoAccountInformation.xsd @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/NKSPU_NemkontoCurrencyCode.xsd b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/NKSPU_NemkontoCurrencyCode.xsd new file mode 100644 index 0000000000000000000000000000000000000000..2e3738bcb0f3e8c2c093903223fa4b52ecfdb571 --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/NKSPU_NemkontoCurrencyCode.xsd @@ -0,0 +1,179 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/NKSPU_NemkontoPrivatUdbetalerTransporterRequest.xsd b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/NKSPU_NemkontoPrivatUdbetalerTransporterRequest.xsd new file mode 100644 index 0000000000000000000000000000000000000000..2540fcde0173ecc0b908fc68b65567dc86357965 --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/NKSPU_NemkontoPrivatUdbetalerTransporterRequest.xsd @@ -0,0 +1,14 @@ + + + + + + + + + + + + + diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/NKSPU_NemkontoPrivatUdbetalerTransporterResponse.xsd b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/NKSPU_NemkontoPrivatUdbetalerTransporterResponse.xsd new file mode 100644 index 0000000000000000000000000000000000000000..5e11e99fd302cc4bb3a48b9475cc9da4646cc155 --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/NKSPU_NemkontoPrivatUdbetalerTransporterResponse.xsd @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/NKSPU_NemkontoRequest.xsd b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/NKSPU_NemkontoRequest.xsd new file mode 100644 index 0000000000000000000000000000000000000000..6a8b02bf53414f81b4c355fabe6dbb7538615f25 --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/NKSPU_NemkontoRequest.xsd @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/NKSPU_NemkontoRequestError.xsd b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/NKSPU_NemkontoRequestError.xsd new file mode 100644 index 0000000000000000000000000000000000000000..ee9ec9596845db3a3c51529dd07be7b64ddfc63a --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/NKSPU_NemkontoRequestError.xsd @@ -0,0 +1,13 @@ + + + + + + + + + + + + diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/NKSPU_NemkontoRequestErrorCode.xsd b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/NKSPU_NemkontoRequestErrorCode.xsd new file mode 100644 index 0000000000000000000000000000000000000000..39791c38a0a36f5590267d4ba98319dee5106938 --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/NKSPU_NemkontoRequestErrorCode.xsd @@ -0,0 +1,10 @@ + + + + + + + + + diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/NKSPU_NemkontoRequestErrorText.xsd b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/NKSPU_NemkontoRequestErrorText.xsd new file mode 100644 index 0000000000000000000000000000000000000000..04c3f7546bc855a29db83c91aca31999203d46c2 --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/NKSPU_NemkontoRequestErrorText.xsd @@ -0,0 +1,5 @@ + + + + diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/NKSPU_NemkontoResponse.xsd b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/NKSPU_NemkontoResponse.xsd new file mode 100644 index 0000000000000000000000000000000000000000..107198f092002b40210cd04c5c6f3d7e2fcdbb2e --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/NKSPU_NemkontoResponse.xsd @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/NKSPU_ProductionUnitIdentificationStructure.xsd b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/NKSPU_ProductionUnitIdentificationStructure.xsd new file mode 100644 index 0000000000000000000000000000000000000000..38aae8c5efdb6e679517f052bc6940668735672d --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/NKSPU_ProductionUnitIdentificationStructure.xsd @@ -0,0 +1,16 @@ + + + + + + + + + + + + diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/NKSPU_Requester.xsd b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/NKSPU_Requester.xsd new file mode 100644 index 0000000000000000000000000000000000000000..81c643ee08218b75e6bc01203abd415fe738ddd2 --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/NKSPU_Requester.xsd @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/NKSPU_RequesterPaymentReference.xsd b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/NKSPU_RequesterPaymentReference.xsd new file mode 100644 index 0000000000000000000000000000000000000000..1fd0f8b2d820f0d2c7ef9e6ad8be18fd36757948 --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/NKSPU_RequesterPaymentReference.xsd @@ -0,0 +1,6 @@ + + + + + diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/NKSPU_RequesterPrimaryInternalReference.xsd b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/NKSPU_RequesterPrimaryInternalReference.xsd new file mode 100644 index 0000000000000000000000000000000000000000..68cdc88ad24cf55dec60f65c865a29cda033e2ed --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/NKSPU_RequesterPrimaryInternalReference.xsd @@ -0,0 +1,6 @@ + + + + + diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/NKSPU_RequesterSecondaryInternalReference.xsd b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/NKSPU_RequesterSecondaryInternalReference.xsd new file mode 100644 index 0000000000000000000000000000000000000000..89f735eb2f8868011c10a04e289ba6f2ebde0285 --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/NKSPU_RequesterSecondaryInternalReference.xsd @@ -0,0 +1,6 @@ + + + + + diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/NKSPU_SEnumberIdentificationStructure.xsd b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/NKSPU_SEnumberIdentificationStructure.xsd new file mode 100644 index 0000000000000000000000000000000000000000..6608eefdff30c3711d0eecb3aef3c2453c92b503 --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/NKSPU_SEnumberIdentificationStructure.xsd @@ -0,0 +1,15 @@ + + + + + + + + + + + + diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/NKSPU_SEnumberIdentifier.xsd b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/NKSPU_SEnumberIdentifier.xsd new file mode 100644 index 0000000000000000000000000000000000000000..12fa17472f418dc18d709c44d8599dee3762c3cf --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/NKSPU_SEnumberIdentifier.xsd @@ -0,0 +1,10 @@ + + + + + + + + + diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/NKSPU_TransporterAgreementIdentifier.xsd b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/NKSPU_TransporterAgreementIdentifier.xsd new file mode 100644 index 0000000000000000000000000000000000000000..883857d455f876846c2938aeb2c9999cefe0f155 --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/NKSPU_TransporterAgreementIdentifier.xsd @@ -0,0 +1,11 @@ + + + + + + + + + + diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/NKSPU_TransporterGroupIdentifier.xsd b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/NKSPU_TransporterGroupIdentifier.xsd new file mode 100644 index 0000000000000000000000000000000000000000..e366b5ce03ae845ee6d05991f53bdf8393637121 --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/NKSPU_TransporterGroupIdentifier.xsd @@ -0,0 +1,7 @@ + + + + + + diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/NKSPU_TransporterHeader.xsd b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/NKSPU_TransporterHeader.xsd new file mode 100644 index 0000000000000000000000000000000000000000..67d2035fdab3aabb1a22cdd931cad37332c25462 --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/NKSPU_TransporterHeader.xsd @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/NKSPU_TransporterHeaderError.xsd b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/NKSPU_TransporterHeaderError.xsd new file mode 100644 index 0000000000000000000000000000000000000000..1135c68a7c03b382b8e17533334a2fcac9c09689 --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/NKSPU_TransporterHeaderError.xsd @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/NKSPU_TransporterHeaderErrorCode.xsd b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/NKSPU_TransporterHeaderErrorCode.xsd new file mode 100644 index 0000000000000000000000000000000000000000..716c7ed359b7e12f0f0591214b487585d9c2edb9 --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/NKSPU_TransporterHeaderErrorCode.xsd @@ -0,0 +1,10 @@ + + + + + + + + + diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/NKSPU_TransporterHeaderErrorText.xsd b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/NKSPU_TransporterHeaderErrorText.xsd new file mode 100644 index 0000000000000000000000000000000000000000..8dcb17afdbba9ccc91dbca9a82d32ff6f80a224a --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/NKSPU_TransporterHeaderErrorText.xsd @@ -0,0 +1,5 @@ + + + + diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/NKSPU_TransporterSystemIdentifier.xsd b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/NKSPU_TransporterSystemIdentifier.xsd new file mode 100644 index 0000000000000000000000000000000000000000..dae7b57e3fc5b074aab8b7802337f3869c0e0011 --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/NKSPU_TransporterSystemIdentifier.xsd @@ -0,0 +1,11 @@ + + + + + + + + + + diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/NKSPU_VansHeader.xsd b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/NKSPU_VansHeader.xsd new file mode 100644 index 0000000000000000000000000000000000000000..a8e165a1b5d99549cc1fe080c8dbfa8b218cea97 --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/NKSPU_VansHeader.xsd @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/NKSPU_VansNemkontoEnvironmentCode.xsd b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/NKSPU_VansNemkontoEnvironmentCode.xsd new file mode 100644 index 0000000000000000000000000000000000000000..65b0ca9c485d2c9f4c9aeaecb8cafacbecb22118 --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/NKSPU_VansNemkontoEnvironmentCode.xsd @@ -0,0 +1,11 @@ + + + + + + + + + + diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/NKSPU_VansRecipientAddress.xsd b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/NKSPU_VansRecipientAddress.xsd new file mode 100644 index 0000000000000000000000000000000000000000..b41c53615c3d4fda420e8430113ca772e03dbb79 --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/NKSPU_VansRecipientAddress.xsd @@ -0,0 +1,6 @@ + + + + + diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/NKSPU_VansSenderAddress.xsd b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/NKSPU_VansSenderAddress.xsd new file mode 100644 index 0000000000000000000000000000000000000000..3b5ca919fec3a8764f4fc9bac7461abce490ae5e --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/NKSPU_VansSenderAddress.xsd @@ -0,0 +1,15 @@ + + + + + + + + + + + + diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_alphanumeric40letternonemptytext.xsd.meta.xml b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_alphanumeric40letternonemptytext.xsd.meta.xml new file mode 100644 index 0000000000000000000000000000000000000000..00a674d7ddf455108217bb28f6e9cea745c57936 --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_alphanumeric40letternonemptytext.xsd.meta.xml @@ -0,0 +1,9 @@ + + + Alphanumeric40LetterNonemptyText + + No description + Definition på alfanummerisk tekst – min 1 tegn, max 40 tegn tekst + + Lars Harritshøj + \ No newline at end of file diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_alphanumeric40letternonemptytext.xsd.system.xml b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_alphanumeric40letternonemptytext.xsd.system.xml new file mode 100644 index 0000000000000000000000000000000000000000..6467e3dd0fb8472d0100622b51d1c2d24e1d8107 --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_alphanumeric40letternonemptytext.xsd.system.xml @@ -0,0 +1,6 @@ + + \ No newline at end of file diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_bankaccountunavailable.xsd.meta.xml b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_bankaccountunavailable.xsd.meta.xml new file mode 100644 index 0000000000000000000000000000000000000000..3f8873ad9bed499bca460768938ad334e920db28 --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_bankaccountunavailable.xsd.meta.xml @@ -0,0 +1,16 @@ + + + BankAccountUnavailable + + No description + En struktur bestående af en kode og en tekst der angiver at indehaveren er kendt men at NemKontoen for den efterspurgte indehaver ikke findes eller er lukket. + + + BankAccountUnavailable + BankKontoUtilgængelig + + Lars Harritshøj + + BankAccountUnavailable + + \ No newline at end of file diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_bankaccountunavailable.xsd.system.xml b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_bankaccountunavailable.xsd.system.xml new file mode 100644 index 0000000000000000000000000000000000000000..b6265ab684789966bfb41c5b62aaa6c906a326c7 --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_bankaccountunavailable.xsd.system.xml @@ -0,0 +1,6 @@ + + \ No newline at end of file diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_bankaccountunavailablecode.xsd.meta.xml b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_bankaccountunavailablecode.xsd.meta.xml new file mode 100644 index 0000000000000000000000000000000000000000..4c1cf4ca43a690c3fa3362903d25a72e64801be5 --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_bankaccountunavailablecode.xsd.meta.xml @@ -0,0 +1,15 @@ + + + BankAccountUnavailableCode + + No description + En kode der angiver årsagen til at kontooplysninger ikke kan leveres. + + + BankAccountUnavailableCode + Kode for utilgængelig konto + + + BankAccountUnavailableCode + + \ No newline at end of file diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_bankaccountunavailablecode.xsd.system.xml b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_bankaccountunavailablecode.xsd.system.xml new file mode 100644 index 0000000000000000000000000000000000000000..40d16f22e7009322feed3138194976a1807bdbda --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_bankaccountunavailablecode.xsd.system.xml @@ -0,0 +1,6 @@ + + \ No newline at end of file diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_bankaccountunavailabletext.xsd.meta.xml b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_bankaccountunavailabletext.xsd.meta.xml new file mode 100644 index 0000000000000000000000000000000000000000..35c45dcf0c96a2a224a08e7629fd6e79d6501e31 --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_bankaccountunavailabletext.xsd.meta.xml @@ -0,0 +1,16 @@ + + + BankAccountUnavailableText + + No description + En beskrivende tekst, der angiver årsagen til at kontooplysninger ikke kan leveres. + + + BankAccountUnavailableText + Årsagstekst for utilgængelig konto + + Lars Harritshøj + + BankAccountUnavailableText + + \ No newline at end of file diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_bankaccountunavailabletext.xsd.system.xml b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_bankaccountunavailabletext.xsd.system.xml new file mode 100644 index 0000000000000000000000000000000000000000..2ba49a06337f5fdf831faf53ec0409ad8c202f07 --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_bankaccountunavailabletext.xsd.system.xml @@ -0,0 +1,6 @@ + + \ No newline at end of file diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_bicidentifier.xsd.meta.xml b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_bicidentifier.xsd.meta.xml new file mode 100644 index 0000000000000000000000000000000000000000..8e786c629bd248f18f236a385f972e2e60498c09 --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_bicidentifier.xsd.meta.xml @@ -0,0 +1,23 @@ + + + BICIdentifier + + No description + SWIFT – også kaldet BIC (”Bank Identifier Code”) - er en international bank-identifikationskode på 8 eller 11 bogstaver og tal. + BIC er en struktureret adresse som identifierer parter i finansielle transaktioner. BIC anvendes i hele verden af finansielle systemer især i forbindelse med udveksling af data mellem it-systemer. + + BICs opbygning + De første 4 tegn er altid bogstaver og identificerer banken. Herefter kommer 2 bogstaver som er koden for det land, betalingen skal gå til. De første tegn for Danmarks Nationalbank vil derfor være: DKNBDK + Herefter kommer 2 tal eller bogstaver som identificerer lokationen (byen). For Danmarks Nationalbanks vedkommende: KK. Koden bliver altså samlet: DKNBDKKK + Dette er et pengeinstituts ”hovedadresse”. Skal et beløb sendes til en afdeling af en bank, KAN der i SWIFT-koden være 3 yderligere bogstaver/tal, som kaldes "branchcode" (afdelingskode). + Bemærk mærke til at SWIFT-koden altid skal skrives ud i ét, ingen mellemrum. Og altid 8 eller 11 tegn. + + Mere information kan findes på: http://www.swift.com/ + + + + BICIdentifier + BICIdentifier + + Lars Harritshøj + \ No newline at end of file diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_bicidentifier.xsd.system.xml b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_bicidentifier.xsd.system.xml new file mode 100644 index 0000000000000000000000000000000000000000..47d857c51f976ae9d5404f91fba3a9f257657c5b --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_bicidentifier.xsd.system.xml @@ -0,0 +1,6 @@ + + \ No newline at end of file diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_foreignbankaccount.xsd.meta.xml b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_foreignbankaccount.xsd.meta.xml new file mode 100644 index 0000000000000000000000000000000000000000..26497f267ac0b72c47fbc68e1907a85379fed4e4 --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_foreignbankaccount.xsd.meta.xml @@ -0,0 +1,16 @@ + + + ForeignBankAccount + + No description + En struktur som indeholder to overordnede strukturer ForeignBankInfoStructure og ForeignPersonStructure. + + + ForeignBankAccount + Udenlandsk bankkontostruktur + + Lars Harritshøj + + Udenlandskbankkontostruktur + + \ No newline at end of file diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_foreignbankaccount.xsd.system.xml b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_foreignbankaccount.xsd.system.xml new file mode 100644 index 0000000000000000000000000000000000000000..d99ff922d9ce69f101c5e813bff5053f6b764ec3 --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_foreignbankaccount.xsd.system.xml @@ -0,0 +1,6 @@ + + \ No newline at end of file diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_foreignbankaccountidentifier.xsd.meta.xml b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_foreignbankaccountidentifier.xsd.meta.xml new file mode 100644 index 0000000000000000000000000000000000000000..9d37f50159fa25661910c50d5d3277b60d4faf77 --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_foreignbankaccountidentifier.xsd.meta.xml @@ -0,0 +1,12 @@ + + + ForeignBankAccountIdentifier + + No description + Udenlandsk kontonummer. + + + Udenlandsk kontonummer + + Lars Harritshøj + \ No newline at end of file diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_foreignbankaccountidentifier.xsd.system.xml b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_foreignbankaccountidentifier.xsd.system.xml new file mode 100644 index 0000000000000000000000000000000000000000..b1515ad8d5b7c461f7eab2771bab304f2cad09f2 --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_foreignbankaccountidentifier.xsd.system.xml @@ -0,0 +1,6 @@ + + \ No newline at end of file diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_foreignbankbranchcode.xsd.meta.xml b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_foreignbankbranchcode.xsd.meta.xml new file mode 100644 index 0000000000000000000000000000000000000000..a878ee8424403560d900eb44f0aaae9506f3ba1a --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_foreignbankbranchcode.xsd.meta.xml @@ -0,0 +1,9 @@ + + + ForeignBankBranchCode + + No description + Udenlandsk registreringsnummer. + + Lars Harritshøj + \ No newline at end of file diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_foreignbankbranchcode.xsd.system.xml b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_foreignbankbranchcode.xsd.system.xml new file mode 100644 index 0000000000000000000000000000000000000000..d7118fafc079b615c0fe0619a020dc87c0f15d29 --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_foreignbankbranchcode.xsd.system.xml @@ -0,0 +1,6 @@ + + \ No newline at end of file diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_foreignbankinfostructure.xsd.meta.xml b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_foreignbankinfostructure.xsd.meta.xml new file mode 100644 index 0000000000000000000000000000000000000000..383516a14292de297020f45c386fd276ec2d7917 --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_foreignbankinfostructure.xsd.meta.xml @@ -0,0 +1,13 @@ + + + ForeignBankInfoStructure + + No description + Strukturtype som indeholder en underliggende struktur ForeignBankAccountIdentifier og andre felter + + + ForeignBankInfoText + Udenlandsk bank informationsstruktur. + + Lars Harritshøj + \ No newline at end of file diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_foreignbankinfostructure.xsd.system.xml b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_foreignbankinfostructure.xsd.system.xml new file mode 100644 index 0000000000000000000000000000000000000000..a186e173a520028207dccabdb23677379ef45ac5 --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_foreignbankinfostructure.xsd.system.xml @@ -0,0 +1,6 @@ + + \ No newline at end of file diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_foreignpersoninfostructure.xsd.meta.xml b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_foreignpersoninfostructure.xsd.meta.xml new file mode 100644 index 0000000000000000000000000000000000000000..f2e4dfb9a55a8a4501c1612e71f0a290ec8d5b82 --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_foreignpersoninfostructure.xsd.meta.xml @@ -0,0 +1,13 @@ + + + ForeignPersonInfoStructure + + No description + Strukturtype som indeholder en underliggende struktur ForeignBankAccountIdentifier og andre felter + + + ForeignBankInfoText + Udenlandsk bank informationsstruktur. + + Lars Harritshøj + \ No newline at end of file diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_foreignpersoninfostructure.xsd.system.xml b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_foreignpersoninfostructure.xsd.system.xml new file mode 100644 index 0000000000000000000000000000000000000000..d6dc07efdbfa6bdbe21d309c38d9c1d125fab1f4 --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_foreignpersoninfostructure.xsd.system.xml @@ -0,0 +1,6 @@ + + \ No newline at end of file diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_ibanidentifier.xsd.meta.xml b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_ibanidentifier.xsd.meta.xml new file mode 100644 index 0000000000000000000000000000000000000000..273b7efdfe4c4c49bcb2ec586748ee61347be6ff --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_ibanidentifier.xsd.meta.xml @@ -0,0 +1,27 @@ + + + IBANIdentifier + + No description + IBAN-nummeret (”International Bank Account Number”) er den internationale udgave af et kontonummer. + IBAN er en entydig identifikation af en konto i et pengeinstitut i EU og andre vestlige lande. IBAN afløser ikke det oprindelige kontonummer, men er et supplement der benyttes i international sammenhæng. + Det gør en automatisk behandling af betalinger over landegrænser mulig, da IBAN kontrolleres for gyldighed af afsendende og modtagende pengeinstitut. + Alle pengeinstitutter i EU har indført IBAN. Derudover har flere lande uden for EU indført IBAN, så IBAN kan også kan benyttes til overførsler til fx USA eller Japan. + IBAN-nummeret kan typisk ses på kontoudtog eller oplyses ved henvendelse til sit pengeinstitut. + IBAN’s opbygning + IBAN består af op til 34 tegn, og et dansk IBAN kunne fx se sådan ud: DK9912344231123456. + IBAN-nummeret består af fire elementer: + - landekode – for Danmarks vedkommende DK + - et kontrolnummer på 2 cifre + - bankens registreringsnummer + - det normale kontonummer + IBAN består af op til 34 tegn, og et dansk IBAN kunne fx se sådan ud: DK9912344231123456. + Mere information kan findes på: http://www.ecbs.org/ + + + + IBAN, International Bank Account Number + IBAN, International Bank Account Number fjernes International bankkontonummer + + Lars Harritshøj + \ No newline at end of file diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_ibanidentifier.xsd.system.xml b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_ibanidentifier.xsd.system.xml new file mode 100644 index 0000000000000000000000000000000000000000..4894b61efe393addfb5a5ac9426fb0766cdb6ba9 --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_ibanidentifier.xsd.system.xml @@ -0,0 +1,6 @@ + + \ No newline at end of file diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_invalidmessageformat.xsd.meta.xml b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_invalidmessageformat.xsd.meta.xml new file mode 100644 index 0000000000000000000000000000000000000000..37f269829629751e6deebba2960fabb675f13ad1 --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_invalidmessageformat.xsd.meta.xml @@ -0,0 +1,13 @@ + + + InvalidMessageFormat + + No description + Denne struktur indeholder den alvorligste fejltype, koder og de tilhørende forklarende tekster der beskriver fejlene og sendes hvis betalingsmeddelelsen ikke overholder den gældende version af OIOXML specifikationen + + + InvalidMessageFormat + Fejltekststruktur + + Lars Harritshøj + \ No newline at end of file diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_invalidmessageformat.xsd.system.xml b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_invalidmessageformat.xsd.system.xml new file mode 100644 index 0000000000000000000000000000000000000000..67b4fc3cee31fbbe2130b1e633f19382f7ac27c9 --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_invalidmessageformat.xsd.system.xml @@ -0,0 +1,6 @@ + + \ No newline at end of file diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_invalidmessageformatcode.xsd.meta.xml b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_invalidmessageformatcode.xsd.meta.xml new file mode 100644 index 0000000000000000000000000000000000000000..82fe464601e98550abf3cae7799ec0223e1da034 --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_invalidmessageformatcode.xsd.meta.xml @@ -0,0 +1,13 @@ + + + InvalidMessageFormatCode + + No description + En kode der angiver at input ikke overholder en af de gældende OIOXML specifikationer. + + + InvalidMessageFormatCode + Fejlkode + + Lars Harritshøj + \ No newline at end of file diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_invalidmessageformatcode.xsd.system.xml b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_invalidmessageformatcode.xsd.system.xml new file mode 100644 index 0000000000000000000000000000000000000000..f2c0c0d2103198180d8b430d9ece50569a8a332b --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_invalidmessageformatcode.xsd.system.xml @@ -0,0 +1,6 @@ + + \ No newline at end of file diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_invalidmessageformathighestseveritycode.xsd.meta.xml b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_invalidmessageformathighestseveritycode.xsd.meta.xml new file mode 100644 index 0000000000000000000000000000000000000000..f161ee50567e6f84a7b8d50bf774be0344356786 --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_invalidmessageformathighestseveritycode.xsd.meta.xml @@ -0,0 +1,13 @@ + + + InvalidMessageFormathighestseverityCode + + No description + En kode der angiver den alvorligste fejltype fundet ved læsning af XML. + + + InvalidMessageFormathighestseverityCode + Alvorligste fejltype + + Lars Harritshøj + \ No newline at end of file diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_invalidmessageformathighestseveritycode.xsd.system.xml b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_invalidmessageformathighestseveritycode.xsd.system.xml new file mode 100644 index 0000000000000000000000000000000000000000..d905c379e7ddac9558c34bb2fedecaed1041b58a --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_invalidmessageformathighestseveritycode.xsd.system.xml @@ -0,0 +1,6 @@ + + \ No newline at end of file diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_invalidmessageformattext.xsd.meta.xml b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_invalidmessageformattext.xsd.meta.xml new file mode 100644 index 0000000000000000000000000000000000000000..1330808895eb4c9207d9ddac96ef2c0459c06a43 --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_invalidmessageformattext.xsd.meta.xml @@ -0,0 +1,13 @@ + + + InvalidMessageFormatText + + No description + En tekst der beskriver den modtagne fejlkode + + + InvalidMessageFormatText + Fejltekst + + Lars Harritshøj + \ No newline at end of file diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_invalidmessageformattext.xsd.system.xml b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_invalidmessageformattext.xsd.system.xml new file mode 100644 index 0000000000000000000000000000000000000000..dfbc9f2772ea41a48da7b117fe8053dfb975050c --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_invalidmessageformattext.xsd.system.xml @@ -0,0 +1,6 @@ + + \ No newline at end of file diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_lookupdatetime.xsd.meta.xml b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_lookupdatetime.xsd.meta.xml new file mode 100644 index 0000000000000000000000000000000000000000..56bdd255e0e2fed3fda53d975e9e82cb9f87ff8d --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_lookupdatetime.xsd.meta.xml @@ -0,0 +1,13 @@ + + + LookupDateTime + + No description + Behandlingstidspunktet for en forespørgsel + + + LookupDataTime + Tid + + Lars Harritshøj + \ No newline at end of file diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_lookupdatetime.xsd.system.xml b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_lookupdatetime.xsd.system.xml new file mode 100644 index 0000000000000000000000000000000000000000..55057e7088d55862d629d3b956fd78cde7e067a1 --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_lookupdatetime.xsd.system.xml @@ -0,0 +1,6 @@ + + \ No newline at end of file diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_nemkontoaccountholder.xsd.meta.xml b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_nemkontoaccountholder.xsd.meta.xml new file mode 100644 index 0000000000000000000000000000000000000000..b431521a73c8fd238d85bc77ed75b0c8841e63aa --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_nemkontoaccountholder.xsd.meta.xml @@ -0,0 +1,13 @@ + + + NemkontoAccountHolder + + No description + En struktur med indehaveren af en nemkonto som er identificeret ved et CPR-, et CVR-, SE- eller et P-nummer. + + + NemkontoAccountHolderType + Nemkontoejer + + Lars Harritshøj + \ No newline at end of file diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_nemkontoaccountholder.xsd.system.xml b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_nemkontoaccountholder.xsd.system.xml new file mode 100644 index 0000000000000000000000000000000000000000..f348df0c445cc81b11a49048ecd5fde4ff90cd81 --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_nemkontoaccountholder.xsd.system.xml @@ -0,0 +1,6 @@ + + \ No newline at end of file diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_nemkontoaccountinformation.xsd.meta.xml b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_nemkontoaccountinformation.xsd.meta.xml new file mode 100644 index 0000000000000000000000000000000000000000..257d496176f59486f9e3e15a01a6b63136c0ed31 --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_nemkontoaccountinformation.xsd.meta.xml @@ -0,0 +1,13 @@ + + + NemkontoAccountInformation + + No description + Struktur der angiver indehavers kontonummer i form af en indenlandsk eller udenlandsk konto eller en besked om nemkontoen ikke er tilgængelig. + + + NemkontoAccountInformation. + Nemkontoinformation + + Lars Harritshøj + \ No newline at end of file diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_nemkontoaccountinformation.xsd.system.xml b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_nemkontoaccountinformation.xsd.system.xml new file mode 100644 index 0000000000000000000000000000000000000000..041db4bce70eeb6beb9e95bfd442d2ca62df01a2 --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_nemkontoaccountinformation.xsd.system.xml @@ -0,0 +1,6 @@ + + \ No newline at end of file diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_nemkontocurrencycode.xsd.meta.xml b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_nemkontocurrencycode.xsd.meta.xml new file mode 100644 index 0000000000000000000000000000000000000000..b27182190da6c2c06eefa3185255d801511f5c34 --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_nemkontocurrencycode.xsd.meta.xml @@ -0,0 +1,9 @@ + + + NemkontoCurrencyCode + + No description + Valutakode - Et repræsentativt udsnit af ISO 4217 valutakode standarden + + Lars Harritshøj + \ No newline at end of file diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_nemkontocurrencycode.xsd.system.xml b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_nemkontocurrencycode.xsd.system.xml new file mode 100644 index 0000000000000000000000000000000000000000..916e6948495a0dd9777a1198035b4016f8823d30 --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_nemkontocurrencycode.xsd.system.xml @@ -0,0 +1,6 @@ + + \ No newline at end of file diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_nemkontoprivatudbetalertransporterrequest.xsd.meta.xml b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_nemkontoprivatudbetalertransporterrequest.xsd.meta.xml new file mode 100644 index 0000000000000000000000000000000000000000..8b97f1cdda58477f8f2b4514a0b745d0d4656231 --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_nemkontoprivatudbetalertransporterrequest.xsd.meta.xml @@ -0,0 +1,13 @@ + + + NemkontoPrivatUdbetalerTransporterRequest + + No description + Struktur som indeholder strukturerne TransporterHeader og NemkontoRequest. + + + NemkontoPrivateUdbetalerTransportRequester + Nemkonto Forespørgsel med Header + + Lars Harritshøj + \ No newline at end of file diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_nemkontoprivatudbetalertransporterrequest.xsd.system.xml b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_nemkontoprivatudbetalertransporterrequest.xsd.system.xml new file mode 100644 index 0000000000000000000000000000000000000000..fd0ed91c8f10fca03958a3056c5bb7ae3c1c62d4 --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_nemkontoprivatudbetalertransporterrequest.xsd.system.xml @@ -0,0 +1,6 @@ + + \ No newline at end of file diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_nemkontoprivatudbetalertransporterresponse.xsd.meta.xml b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_nemkontoprivatudbetalertransporterresponse.xsd.meta.xml new file mode 100644 index 0000000000000000000000000000000000000000..ccc840e33089611f113455df2bff5d57021362ec --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_nemkontoprivatudbetalertransporterresponse.xsd.meta.xml @@ -0,0 +1,13 @@ + + + NemkontoPrivatUdbetalerTransporterResponse + + No description + Struktur der indeholder strukturerne: InvalidMessageFormat, TrasporterHeaderError eller TransporterHeader og NemkontoResponse + + + NemkontoPrivateUdbetalerTransportResponse + Nemkonto svar med Header + + Lars Harritshøj + \ No newline at end of file diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_nemkontoprivatudbetalertransporterresponse.xsd.system.xml b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_nemkontoprivatudbetalertransporterresponse.xsd.system.xml new file mode 100644 index 0000000000000000000000000000000000000000..a451924eed04f5f68c7f7aeaedb8b298042b4c1b --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_nemkontoprivatudbetalertransporterresponse.xsd.system.xml @@ -0,0 +1,6 @@ + + \ No newline at end of file diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_nemkontorequest.xsd.meta.xml b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_nemkontorequest.xsd.meta.xml new file mode 100644 index 0000000000000000000000000000000000000000..2d540a73cbc1f3a601c0462cc528314c5005a1da --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_nemkontorequest.xsd.meta.xml @@ -0,0 +1,13 @@ + + + NemkontoRequest + + No description + Strukturen indeholder strukturerne Requester, RequesterPaymentReference, RequesterPrimaryInternalReference, RequesterSecondaryInternalReference og NemkontoAccountHolder. + + + NemkontoRequest + Nemkonto Forespørgsel uden Header + + Lars Harritshøj + \ No newline at end of file diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_nemkontorequest.xsd.system.xml b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_nemkontorequest.xsd.system.xml new file mode 100644 index 0000000000000000000000000000000000000000..ef58795b5ad775953e4500cd73f4017fde33ab59 --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_nemkontorequest.xsd.system.xml @@ -0,0 +1,6 @@ + + \ No newline at end of file diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_nemkontorequesterror.xsd.meta.xml b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_nemkontorequesterror.xsd.meta.xml new file mode 100644 index 0000000000000000000000000000000000000000..c5d3dc7fd923e7840d5a6a33733e79752ac20c01 --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_nemkontorequesterror.xsd.meta.xml @@ -0,0 +1,13 @@ + + + NemkontoRequestError + + No description + En struktur bestående af en kode og en tekst, der beskriver årsagen til at nemkontonummer ikke kan returneres. Fx fordi den forespurgte Nemkonto indehaver er ukendt eller den private udbetaler er registreret i misligholdelsesregisteret. + + + NemkontoRequestError + Valideringsfejl + + Lars Harritshøj + \ No newline at end of file diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_nemkontorequesterror.xsd.system.xml b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_nemkontorequesterror.xsd.system.xml new file mode 100644 index 0000000000000000000000000000000000000000..00510b3c58330a9125cf9495753453708a7c0769 --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_nemkontorequesterror.xsd.system.xml @@ -0,0 +1,6 @@ + + \ No newline at end of file diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_nemkontorequesterrorcode.xsd.meta.xml b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_nemkontorequesterrorcode.xsd.meta.xml new file mode 100644 index 0000000000000000000000000000000000000000..cd6d1e3f6bcbe4ec46387e729343f7efdbe2c121 --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_nemkontorequesterrorcode.xsd.meta.xml @@ -0,0 +1,13 @@ + + + NemkontoRequestErrorCode + + No description + En kode der angiver hvorfor forespørgslen ikke blev behandlet + + + NemkontoRequestErrorCode + Valideringsfejlkode + + Lars Harritshøj + \ No newline at end of file diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_nemkontorequesterrorcode.xsd.system.xml b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_nemkontorequesterrorcode.xsd.system.xml new file mode 100644 index 0000000000000000000000000000000000000000..0f4ee4c48492e11fe4e6ad8fcd627ef731cf8ea8 --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_nemkontorequesterrorcode.xsd.system.xml @@ -0,0 +1,6 @@ + + \ No newline at end of file diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_nemkontorequesterrortext.xsd.meta.xml b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_nemkontorequesterrortext.xsd.meta.xml new file mode 100644 index 0000000000000000000000000000000000000000..82bc20721d85d1f6d01921307cb33cf753531723 --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_nemkontorequesterrortext.xsd.meta.xml @@ -0,0 +1,13 @@ + + + NemkontoRequestErrorText + + No description + En beskrivende tekst, bestemt af koden, der fortæller hvorfor betalingsmeddelelsen ikke blev behandlet + + + NemkontoRequestErrorText + Valideringsfejltekst + + Lars Harritshøj + \ No newline at end of file diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_nemkontorequesterrortext.xsd.system.xml b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_nemkontorequesterrortext.xsd.system.xml new file mode 100644 index 0000000000000000000000000000000000000000..876e89056f2046e7dc4043baf7d6ccccbe055376 --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_nemkontorequesterrortext.xsd.system.xml @@ -0,0 +1,6 @@ + + \ No newline at end of file diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_nemkontoresponse.xsd.meta.xml b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_nemkontoresponse.xsd.meta.xml new file mode 100644 index 0000000000000000000000000000000000000000..826776a6f2988280fb934fc4c23f3e19d6468007 --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_nemkontoresponse.xsd.meta.xml @@ -0,0 +1,13 @@ + + + NemkontoResponse + + No description + Struktur der indeholder strukturerne: NemkontoRequest, LookupDateTime, og enten NemkontoAccountInformation eller NemkontorequestError + + + NemkontoResponse + Nemkonto Forespørgsel uden Header + + Lars Harritshøj + \ No newline at end of file diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_nemkontoresponse.xsd.system.xml b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_nemkontoresponse.xsd.system.xml new file mode 100644 index 0000000000000000000000000000000000000000..13f2769d98f847ba7a5e805587e6fe32c9fd975a --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_nemkontoresponse.xsd.system.xml @@ -0,0 +1,6 @@ + + \ No newline at end of file diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_productionunitidentificationstructure.xsd.meta.xml b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_productionunitidentificationstructure.xsd.meta.xml new file mode 100644 index 0000000000000000000000000000000000000000..8cedb306850adfcabeae5bc60d67c1864e206a1e --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_productionunitidentificationstructure.xsd.meta.xml @@ -0,0 +1,9 @@ + + + ProductionUnitIdentificationStructure + + No description + Strukturen indeholder et CVR-nummer- og et produktionsenhedsnummer element, da det øger sikkerheden for korrekt identifikation og dermed mindsker risikoen for fejludbetalinger. + + Lars Harritshøj + \ No newline at end of file diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_productionunitidentificationstructure.xsd.system.xml b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_productionunitidentificationstructure.xsd.system.xml new file mode 100644 index 0000000000000000000000000000000000000000..266f497b45328ba8ac64a20f6263e7d6a1ea752b --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_productionunitidentificationstructure.xsd.system.xml @@ -0,0 +1,6 @@ + + \ No newline at end of file diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_requester.xsd.meta.xml b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_requester.xsd.meta.xml new file mode 100644 index 0000000000000000000000000000000000000000..e21c17338202d7cfb25bc28d93b32718470b5a7b --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_requester.xsd.meta.xml @@ -0,0 +1,13 @@ + + + Requester + + No description + Strukturen indeholder CVRnumberIdentifier, PersonCivilRegistrationIdentifier eller strukturen SEnumberIdentifier. + + + Requester + Forespørger + + Lars Harritshøj + \ No newline at end of file diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_requester.xsd.system.xml b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_requester.xsd.system.xml new file mode 100644 index 0000000000000000000000000000000000000000..f34164649b07321761038de9112f2e37cf3c2937 --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_requester.xsd.system.xml @@ -0,0 +1,6 @@ + + \ No newline at end of file diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_requesterpaymentreference.xsd.meta.xml b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_requesterpaymentreference.xsd.meta.xml new file mode 100644 index 0000000000000000000000000000000000000000..7f960d838c77f8cdb7429dc3177da67c14224c12 --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_requesterpaymentreference.xsd.meta.xml @@ -0,0 +1,12 @@ + + + RequesterPaymentReference + + No desprintion + En reference til den private udbetalers system som identificerer den udbetaling, som NemKonto forespørgslen skal bruges til at gennemføre. + + + RequesterPaymentReference + Identifikation af forespørgsel + + \ No newline at end of file diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_requesterpaymentreference.xsd.system.xml b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_requesterpaymentreference.xsd.system.xml new file mode 100644 index 0000000000000000000000000000000000000000..c1aa7a22bb131723b68c7eb22d5868d9c987f632 --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_requesterpaymentreference.xsd.system.xml @@ -0,0 +1,6 @@ + + \ No newline at end of file diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_requesterprimaryinternalreference.xsd.meta.xml b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_requesterprimaryinternalreference.xsd.meta.xml new file mode 100644 index 0000000000000000000000000000000000000000..32a71527332f1301d2365183de17ae1cea190844 --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_requesterprimaryinternalreference.xsd.meta.xml @@ -0,0 +1,13 @@ + + + RequesterPrimaryInternalReference + + No description + Dette element er valgfrit og kan indeholde en reference som den private udbetaler kan benytte efter behov. + + + RequesterPrimaryInternalReference + Valgfri reference 1 + + Lars Harritshøj + \ No newline at end of file diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_requesterprimaryinternalreference.xsd.system.xml b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_requesterprimaryinternalreference.xsd.system.xml new file mode 100644 index 0000000000000000000000000000000000000000..9a30d27502bfb17bf080c63912c7e2fe60ffd651 --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_requesterprimaryinternalreference.xsd.system.xml @@ -0,0 +1,6 @@ + + \ No newline at end of file diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_requestersecondaryinternalreference.xsd.meta.xml b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_requestersecondaryinternalreference.xsd.meta.xml new file mode 100644 index 0000000000000000000000000000000000000000..8b7e394f3bd56ac0699fa8d6e2e106c1acee9cfd --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_requestersecondaryinternalreference.xsd.meta.xml @@ -0,0 +1,12 @@ + + + RequesterSecondaryInternalReference + + No description + Dette element er valgfrit og kan indeholde en reference som den private udbetaler kan benytte efter behov. + + + RequesterSecondaryInternalReference + Valgfri reference 2 + + \ No newline at end of file diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_requestersecondaryinternalreference.xsd.system.xml b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_requestersecondaryinternalreference.xsd.system.xml new file mode 100644 index 0000000000000000000000000000000000000000..98345a7bcb3044672cbfa453d1746cbdfbc2cd91 --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_requestersecondaryinternalreference.xsd.system.xml @@ -0,0 +1,6 @@ + + \ No newline at end of file diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_senumberidentificationstructure.xsd.meta.xml b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_senumberidentificationstructure.xsd.meta.xml new file mode 100644 index 0000000000000000000000000000000000000000..f75597f68a806da8104849a12bed199dce6dd8df --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_senumberidentificationstructure.xsd.meta.xml @@ -0,0 +1,9 @@ + + + SEnumberIdentificationStructure + + No description + Struktur som indeholder strukturerne CVRnumberIdentifier og SEnumberIdentifier + + Lars Harritshøj + \ No newline at end of file diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_senumberidentificationstructure.xsd.system.xml b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_senumberidentificationstructure.xsd.system.xml new file mode 100644 index 0000000000000000000000000000000000000000..a78bc88e5ffcc9b26f72bff84b4b7cc83395e829 --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_senumberidentificationstructure.xsd.system.xml @@ -0,0 +1,6 @@ + + \ No newline at end of file diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_senumberidentifier.xsd.meta.xml b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_senumberidentifier.xsd.meta.xml new file mode 100644 index 0000000000000000000000000000000000000000..a09ebe5e94135065f4e482599e27b2519d0aced5 --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_senumberidentifier.xsd.meta.xml @@ -0,0 +1,13 @@ + + + SEnumberIdentifier + + No description + SE-nummer (SE står for Stamregister over erhvervsdrivende) er det nummer man får tildelt af SKAT i forbindelse med registreringen som en virksomhed, der skal trække bidrag og A-skat. Det er det nummer, som virksomheden er registreret under i det register, som SKAT har oprettet til brug for bl.a. opkrævningen af AM-bidrag, SP-bidrag og A-skat. SE-numre bruges af SKAT til regnskabsmæssig opdeling af virksomheden. + + + SEnumberIdentifier + SE nummer + + Lars Harritshøj + \ No newline at end of file diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_senumberidentifier.xsd.system.xml b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_senumberidentifier.xsd.system.xml new file mode 100644 index 0000000000000000000000000000000000000000..282bf1ef2399cb0d4030d340d04cd25826455c79 --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_senumberidentifier.xsd.system.xml @@ -0,0 +1,6 @@ + + \ No newline at end of file diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_transporteragreementidentifier.xsd.meta.xml b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_transporteragreementidentifier.xsd.meta.xml new file mode 100644 index 0000000000000000000000000000000000000000..b4b23614f534a48583421078b92d21f99d15ba1e --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_transporteragreementidentifier.xsd.meta.xml @@ -0,0 +1,13 @@ + + + TransporterAgreementIdentifier + + No description + Aftalenummeret som den private betalingsformidler har med NemKonto. + + + TransporterAgreementIdentifier + Aftalenummer + + Lars Harritshøj + \ No newline at end of file diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_transporteragreementidentifier.xsd.system.xml b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_transporteragreementidentifier.xsd.system.xml new file mode 100644 index 0000000000000000000000000000000000000000..ebb1dbdc7d0246402f76b4d38971dd017bd1b5b9 --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_transporteragreementidentifier.xsd.system.xml @@ -0,0 +1,6 @@ + + \ No newline at end of file diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_transportergroupidentifier.xsd.meta.xml b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_transportergroupidentifier.xsd.meta.xml new file mode 100644 index 0000000000000000000000000000000000000000..2a401bbf58c9d6d5932c08228a3683fd6e94c6c7 --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_transportergroupidentifier.xsd.meta.xml @@ -0,0 +1,13 @@ + + + TransporterGroupIdentifier + + No description + Identificerer bundtet overfor såvel NKS PU som PBF. + + + TransporterGroupIdentifier + BundtId + + Lars Harritshøj + \ No newline at end of file diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_transportergroupidentifier.xsd.system.xml b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_transportergroupidentifier.xsd.system.xml new file mode 100644 index 0000000000000000000000000000000000000000..ffcb88c38e36d5d3f709eb7b3d954a96943d4f75 --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_transportergroupidentifier.xsd.system.xml @@ -0,0 +1,6 @@ + + \ No newline at end of file diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_transporterheader.xsd.meta.xml b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_transporterheader.xsd.meta.xml new file mode 100644 index 0000000000000000000000000000000000000000..67c3f69c76c5d5d9ea3fdd41bdaf430ceef3fe35 --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_transporterheader.xsd.meta.xml @@ -0,0 +1,13 @@ + + + TransporterHeader + + No description + Struktur som indeholder TransporterAgreementIdentifier, TrasporterSystemIdentifier, TransporterGroupIdentifier samt eventuelt VansHeader + + + TransporterHeader + TransporterHeader + + Lars Harritshøj + \ No newline at end of file diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_transporterheader.xsd.system.xml b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_transporterheader.xsd.system.xml new file mode 100644 index 0000000000000000000000000000000000000000..7981963ed0e1a8d7e516be8df759bdb4654a02b7 --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_transporterheader.xsd.system.xml @@ -0,0 +1,6 @@ + + \ No newline at end of file diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_transporterheadererror.xsd.meta.xml b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_transporterheadererror.xsd.meta.xml new file mode 100644 index 0000000000000000000000000000000000000000..a47af42fb56e337cda093d00a011a30f57760cf9 --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_transporterheadererror.xsd.meta.xml @@ -0,0 +1,13 @@ + + + TransporterHeaderError + + No description + Struktur der indeholder TransporterHeader og TransporterheaderErrorCode og TransporterHeaderErrorText + + + TransporterHeaderError + TransporterHeader fejl + + Lars Harritshøj + \ No newline at end of file diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_transporterheadererror.xsd.system.xml b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_transporterheadererror.xsd.system.xml new file mode 100644 index 0000000000000000000000000000000000000000..c369cf6a320e6574d6ef230c4a2ae4706d95be8c --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_transporterheadererror.xsd.system.xml @@ -0,0 +1,6 @@ + + \ No newline at end of file diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_transporterheadererrorcode.xsd.meta.xml b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_transporterheadererrorcode.xsd.meta.xml new file mode 100644 index 0000000000000000000000000000000000000000..c1d8e1aecb88e17a8d04e64793de9a16f55a5263 --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_transporterheadererrorcode.xsd.meta.xml @@ -0,0 +1,13 @@ + + + TransporterHeaderErrorCode + + No description + En kode der optræder når data i TransporterHeader ikke kan accepteres. + + + TransporterHeaderErrorCode + TransporterHeader fejlkode + + Lars Harritshøj + \ No newline at end of file diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_transporterheadererrorcode.xsd.system.xml b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_transporterheadererrorcode.xsd.system.xml new file mode 100644 index 0000000000000000000000000000000000000000..f45acd001ebd6bd79999b70930436728b528ba40 --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_transporterheadererrorcode.xsd.system.xml @@ -0,0 +1,6 @@ + + \ No newline at end of file diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_transporterheadererrortext.xsd.meta.xml b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_transporterheadererrortext.xsd.meta.xml new file mode 100644 index 0000000000000000000000000000000000000000..08a7acba5f60cde166efc849564f6c0ae4bf9732 --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_transporterheadererrortext.xsd.meta.xml @@ -0,0 +1,13 @@ + + + TransporterHeaderErrorText + + No description + En tekst der angiver en begrundelse for at input i TransporterHeader ikke kunne accepteres + + + TransporterHeaderErrorText + TransporterHeader fejltekst + + Lars Harritshøj + \ No newline at end of file diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_transporterheadererrortext.xsd.system.xml b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_transporterheadererrortext.xsd.system.xml new file mode 100644 index 0000000000000000000000000000000000000000..87964e266aadf72ac80c4f350a205e7cd1632c6a --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_transporterheadererrortext.xsd.system.xml @@ -0,0 +1,6 @@ + + \ No newline at end of file diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_transportersystemidentifier.xsd.meta.xml b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_transportersystemidentifier.xsd.meta.xml new file mode 100644 index 0000000000000000000000000000000000000000..e6ba0321ed166d47c07779521bdcdaef8ef3abf8 --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_transportersystemidentifier.xsd.meta.xml @@ -0,0 +1,13 @@ + + + TransporterSystemIdentifier + + No description + Navnet på det system, som er afsender af meddelelsen. + + + TransporterSystemIdentifier + SystemId + + Lars Harritshøj + \ No newline at end of file diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_transportersystemidentifier.xsd.system.xml b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_transportersystemidentifier.xsd.system.xml new file mode 100644 index 0000000000000000000000000000000000000000..8321b2c6a6d057b676e2e4e1938be7c177a2bf92 --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_transportersystemidentifier.xsd.system.xml @@ -0,0 +1,6 @@ + + \ No newline at end of file diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_vansheader.xsd.meta.xml b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_vansheader.xsd.meta.xml new file mode 100644 index 0000000000000000000000000000000000000000..9025d93c63f5608e5d7ca9e467ec1997fd880644 --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_vansheader.xsd.meta.xml @@ -0,0 +1,13 @@ + + + VansHeader + + No description + Struktur der indeholder VansSenderAddress og VansRecipientAddress + + + VansHeader + VansHeader + + Lars Harritshøj + \ No newline at end of file diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_vansheader.xsd.system.xml b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_vansheader.xsd.system.xml new file mode 100644 index 0000000000000000000000000000000000000000..d60d85008a1c6611af069289109af7c252d4d554 --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_vansheader.xsd.system.xml @@ -0,0 +1,6 @@ + + \ No newline at end of file diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_vansnemkontoenvironmentcode.xsd.meta.xml b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_vansnemkontoenvironmentcode.xsd.meta.xml new file mode 100644 index 0000000000000000000000000000000000000000..d6bcf596b796fd4da1674ae159957341696596ce --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_vansnemkontoenvironmentcode.xsd.meta.xml @@ -0,0 +1,13 @@ + + + VansNemkontoEnvironmentCode + + No description + En kode der angiver hvilket miljø meddelelsen skal leveres til + + + VansNemkontoEnviromentCode + Vans miljø kode + + Lars Harritshøj + \ No newline at end of file diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_vansnemkontoenvironmentcode.xsd.system.xml b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_vansnemkontoenvironmentcode.xsd.system.xml new file mode 100644 index 0000000000000000000000000000000000000000..884728af6b61eaca22de72cf7b68a150af8f4153 --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_vansnemkontoenvironmentcode.xsd.system.xml @@ -0,0 +1,6 @@ + + \ No newline at end of file diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_vansrecipientaddress.xsd.meta.xml b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_vansrecipientaddress.xsd.meta.xml new file mode 100644 index 0000000000000000000000000000000000000000..598cb0f6781ca0546adc76a47cb1e193a23fa6ae --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_vansrecipientaddress.xsd.meta.xml @@ -0,0 +1,13 @@ + + + VansRecipientAddress + + No description + Anvendt i en request sendes VansNemkontoEnvironmentCode fra afsender miljøet. Anvendt i et response sendes EAN131Identifier på PBF. + + + VansRecipientAddress + Vans modtager + + Lars Harritshøj + \ No newline at end of file diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_vansrecipientaddress.xsd.system.xml b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_vansrecipientaddress.xsd.system.xml new file mode 100644 index 0000000000000000000000000000000000000000..3a5601bdd303d96484bbf3818a010437c37fa3c0 --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_vansrecipientaddress.xsd.system.xml @@ -0,0 +1,6 @@ + + \ No newline at end of file diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_vanssenderaddress.xsd.meta.xml b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_vanssenderaddress.xsd.meta.xml new file mode 100644 index 0000000000000000000000000000000000000000..8625183123c25c6ce69da1d4cb8cbf5d2702b18c --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_vanssenderaddress.xsd.meta.xml @@ -0,0 +1,13 @@ + + + VansSenderAddress + + No description + Anvendt i en request sendes EAN131Identifier på PBF. Anvendt i et response sendes VansNemkontoEnvironmentCode fra afsender miljøet + + + VansSenderAddress + Vans afsender + + Lars Harritshøj + \ No newline at end of file diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_vanssenderaddress.xsd.system.xml b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_vanssenderaddress.xsd.system.xml new file mode 100644 index 0000000000000000000000000000000000000000..11ef21a5ad09f848f01561410c7c49426bcf386e --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/nkspu_vanssenderaddress.xsd.system.xml @@ -0,0 +1,6 @@ + + \ No newline at end of file diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto.ebms/xml/schemas/2006/05/01/EBMS_Common.xsd b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto.ebms/xml/schemas/2006/05/01/EBMS_Common.xsd new file mode 100644 index 0000000000000000000000000000000000000000..1e40c4435bc07fa53e7847a1a45a8f0f7eb8b615 --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto.ebms/xml/schemas/2006/05/01/EBMS_Common.xsd @@ -0,0 +1,137 @@ + + + + + + + + + + + + + + + + + + + + + + Information om afsender + + + + + + PartyId(1) skal indeholde kortnavn, PartyId(2) skal indeholde EAN-nummer. + + + + + + + + + Information om modtager + + + + + + PartyId(1) skal indeholde kortnavn, PartyId(2) skal indeholde EAN-nummer. + + + + + + + + + Unik reference og timestamp på meddelelsen + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Betalingsmeddelelse C2NKS og NKS2C + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto.swift/xml/schemas/2006/05/01/SWIFT_A_GeneralInformation.xsd b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto.swift/xml/schemas/2006/05/01/SWIFT_A_GeneralInformation.xsd new file mode 100644 index 0000000000000000000000000000000000000000..39af7524385e243f47a321a286d964fd5c86b59c --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto.swift/xml/schemas/2006/05/01/SWIFT_A_GeneralInformation.xsd @@ -0,0 +1,12 @@ + + + + + + + + + + + diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto.swift/xml/schemas/2006/05/01/SWIFT_B_OriginalGroupReferenceInformation.xsd b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto.swift/xml/schemas/2006/05/01/SWIFT_B_OriginalGroupReferenceInformation.xsd new file mode 100644 index 0000000000000000000000000000000000000000..2dee7bb431568c179b9564d106b1078da1bb58ca --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto.swift/xml/schemas/2006/05/01/SWIFT_B_OriginalGroupReferenceInformation.xsd @@ -0,0 +1,39 @@ + + + + + + + + Bundtreference + + + + + + + + + + + + Status på forsendelse + + + + + Fejlkode + + + + + + + Uddybende tekst + + + + + + diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto.swift/xml/schemas/2006/05/01/SWIFT_C_OriginalPaymentInformation.xsd b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto.swift/xml/schemas/2006/05/01/SWIFT_C_OriginalPaymentInformation.xsd new file mode 100644 index 0000000000000000000000000000000000000000..0d1d7550ceabbc6e6219c1f3e7a259a04cb36877 --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto.swift/xml/schemas/2006/05/01/SWIFT_C_OriginalPaymentInformation.xsd @@ -0,0 +1,22 @@ + + + + + + + + + + + + + Debiteringstekst + + + + + + diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto.swift/xml/schemas/2006/05/01/SWIFT_Common.xsd b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto.swift/xml/schemas/2006/05/01/SWIFT_Common.xsd new file mode 100644 index 0000000000000000000000000000000000000000..5618327bf47ded4f71c3059856d6c3eccc665335 --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto.swift/xml/schemas/2006/05/01/SWIFT_Common.xsd @@ -0,0 +1,502 @@ + + + + + + + + + + + + + Modtagers navn for en udenlansk komplet betaling + + + + + Modtagers adresse for en udenlandsk komplet betaling + + + + + + + + + + + + + + + + + + + + + CPR + + + + + + + + + SE nummer + + + + + + + + + + PI aftalenummer + + + + + + + + + + + + + + Modtager PI Swift kode + + + + + Branch Kode + + + + + + Modtager PI navn + + + + + + + + + + + + + + + + + + + + + + + CVR- el. P-nummer + + + + + Kode for CVR- eller P-nummer + + + + + + + + + + + + + + + Branch Kode + + + + + + + + + + + + + + + + + + + + Modtagers reg.nr. og kontonummer + + + + + IBAN nummer + + + + + + + + + + + + + + + + FI-kreditnummer eller Girokontonummer + + + + + + + + + + Debitors betalingsreference + + + + + + BetalingsID (UPR) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Modtagers adresse + + + + + Modtagers postkode + + + + + Modtagers by + + + + + Modtagers landekode + + + + + + + + + Modtager PI adresse incl. postkode og by + + + + + Modtager PI landekode + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Beløb incl. valuta + + + + + + + + + + + Beløb incl. valuta + + + + + Modtagervalutakode + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto.swift/xml/schemas/2006/05/01/SWIFT_D_OriginalTransactionReferenceInformationAndStatus.xsd b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto.swift/xml/schemas/2006/05/01/SWIFT_D_OriginalTransactionReferenceInformationAndStatus.xsd new file mode 100644 index 0000000000000000000000000000000000000000..707d2401b9ac523d050863095512fbe18543649f --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto.swift/xml/schemas/2006/05/01/SWIFT_D_OriginalTransactionReferenceInformationAndStatus.xsd @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto.swift/xml/schemas/2006/05/01/SWIFT_E_OriginalTransactionInformation.xsd b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto.swift/xml/schemas/2006/05/01/SWIFT_E_OriginalTransactionInformation.xsd new file mode 100644 index 0000000000000000000000000000000000000000..fee64504d6fd87adad6e66a8bc6fa6e7d8da1c77 --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto.swift/xml/schemas/2006/05/01/SWIFT_E_OriginalTransactionInformation.xsd @@ -0,0 +1,18 @@ + + + + + + + + + Ikke komplet betaling + + + + + + + + diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto.swift/xml/schemas/2006/05/01/SWIFT_NKSPayment.xsd b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto.swift/xml/schemas/2006/05/01/SWIFT_NKSPayment.xsd new file mode 100644 index 0000000000000000000000000000000000000000..f650f1c52ebf2e060aca6205fe4e0d3f5bf91439 --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto.swift/xml/schemas/2006/05/01/SWIFT_NKSPayment.xsd @@ -0,0 +1,252 @@ + + + + + + + + + + + Bundt referencenummer + + + + + Creation Date Time + + + + + NKS aftalenummer + + + + + Kontrolsum + + + + + + + + + + Antal transaktioner + + + + + True if one debitor + + + + + + + + + + + + + + + Udbetalingsdato + + + + + Credit transfer (TRF) + + + + + + + + + + Betalingstype + + + + + + + + + + + + Debiteringstekst + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Gebyrkode + + + + + + + + + + Ikke komplet betaling + + + + + + Ydelsesart korttekst + + + + + + + + + + + Adviseringstekst + + + + + + + + + + + + + + + + + Faktura nummer + + + + + Læselinie + + + + + + + + + Straksadvisering mk + + + + + + + + + + Instruktion til bogføringscentral + + + + + + + + + + + + + + + Afsenders reg.nr. og kontonummer + + + + + IBAN nummer + + + + + + + + + + + + + + + Tekst til modtagers kontoudtog + + + + + + + + + + Kode for instruktion + + + + + Valg af service + + + + + + + + + + + + + diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto.swift/xml/schemas/2006/05/01/SWIFT_Package.xsd b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto.swift/xml/schemas/2006/05/01/SWIFT_Package.xsd new file mode 100644 index 0000000000000000000000000000000000000000..92f38f78fb5042d2582362a7ffa35ef5a8482100 --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto.swift/xml/schemas/2006/05/01/SWIFT_Package.xsd @@ -0,0 +1,9 @@ + + + + + + + + diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto/xml/schemas/2006/05/01/NKS_NKSPayment.xsd b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto/xml/schemas/2006/05/01/NKS_NKSPayment.xsd new file mode 100644 index 0000000000000000000000000000000000000000..6d48677439c373697156be8ed0fac6f5b6ae053d --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto/xml/schemas/2006/05/01/NKS_NKSPayment.xsd @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto/xml/schemas/2006/05/01/NKS_NKSReceipt0.xsd b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto/xml/schemas/2006/05/01/NKS_NKSReceipt0.xsd new file mode 100644 index 0000000000000000000000000000000000000000..70424f15ea7fca8f3e2adfb1d54788b54a2c6577 --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto/xml/schemas/2006/05/01/NKS_NKSReceipt0.xsd @@ -0,0 +1,15 @@ + + + + + + + + + + + + diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto/xml/schemas/2006/05/01/NKS_NKSReceipt1.xsd b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto/xml/schemas/2006/05/01/NKS_NKSReceipt1.xsd new file mode 100644 index 0000000000000000000000000000000000000000..f3ae69e67efbdc2aafdf4ad869fadf3d7210c707 --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto/xml/schemas/2006/05/01/NKS_NKSReceipt1.xsd @@ -0,0 +1,15 @@ + + + + + + + + + + + + diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto/xml/schemas/2006/05/01/NKS_NKSResponse2.xsd b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto/xml/schemas/2006/05/01/NKS_NKSResponse2.xsd new file mode 100644 index 0000000000000000000000000000000000000000..49b812266451ca382b51e85c79220a8720943801 --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto/xml/schemas/2006/05/01/NKS_NKSResponse2.xsd @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto/xml/schemas/2006/05/01/NKS_NKSResponse5.xsd b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto/xml/schemas/2006/05/01/NKS_NKSResponse5.xsd new file mode 100644 index 0000000000000000000000000000000000000000..d69090682609dd5649b6890ef8efad5dbf4d645a --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto/xml/schemas/2006/05/01/NKS_NKSResponse5.xsd @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto/xml/schemas/2006/05/01/NKS_NKSResponse7.xsd b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto/xml/schemas/2006/05/01/NKS_NKSResponse7.xsd new file mode 100644 index 0000000000000000000000000000000000000000..ea2ea66a28907de0d269b85df857186ad4a99278 --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto/xml/schemas/2006/05/01/NKS_NKSResponse7.xsd @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto/xml/schemas/2006/05/01/NKS_NKSResponse8.xsd b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto/xml/schemas/2006/05/01/NKS_NKSResponse8.xsd new file mode 100644 index 0000000000000000000000000000000000000000..47c3814a0d7e854b2c5f3b942611f50310057f58 --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto/xml/schemas/2006/05/01/NKS_NKSResponse8.xsd @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + diff --git a/src/main/resources/META-INF/Schemas/NemKonto/nemkonto/xml/schemas/2006/05/01/NKS_NKSResponse9.xsd b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto/xml/schemas/2006/05/01/NKS_NKSResponse9.xsd new file mode 100644 index 0000000000000000000000000000000000000000..86311be06fae831782ba2d4268f6d791143af0eb --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/nemkonto/xml/schemas/2006/05/01/NKS_NKSResponse9.xsd @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + diff --git a/src/main/resources/META-INF/Schemas/NemKonto/other/CPR_CompletePostalLabelText.xsd b/src/main/resources/META-INF/Schemas/NemKonto/other/CPR_CompletePostalLabelText.xsd new file mode 100644 index 0000000000000000000000000000000000000000..e9e7d1db331561b73755b9cc8ced1e3159f6bce8 --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/other/CPR_CompletePostalLabelText.xsd @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/main/resources/META-INF/Schemas/NemKonto/other/CPR_PersonCivilRegistrationIdentifier.xsd b/src/main/resources/META-INF/Schemas/NemKonto/other/CPR_PersonCivilRegistrationIdentifier.xsd new file mode 100644 index 0000000000000000000000000000000000000000..bb7f5a0f9b8bdb08843f0eb14dec6fb2698c4a45 --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/other/CPR_PersonCivilRegistrationIdentifier.xsd @@ -0,0 +1,42 @@ + + + + + + CivilRegistrationNumber (PNR) + + Description: + Unique identification of a person + + The Civil Registration System contains: + - Data on persons, who after 1968 April 2nd Danish registry of citizens. + As for Greenland the corresponding date is 1972 may 1st. + - Danish citizens living outside Denmark (who must pay duty and ATP) + has also been given a civil registration number. + - Civil registration numbers are also assigned for other administrative purposes. + + + Value space: + The civil registration number consists of two parts. + The first part is the valid birthday in the form DDMMYY. + The following part is a serial number of four digits. + The civil registration number may also hold the value 0000000000. + This value is used where the civil registration number is required but unknown. + + Lifecycle: + The civil registration number is generated and assigned at birth, entry and change of civil registration number of for administrative reasons. + The civil registration number may be assigned via hospitals. + + The civil registration number is not to be deleted. + + Remarks: + 1994 June 11th the civil registration number was changed according to this description. + + + + + + + + + diff --git a/src/main/resources/META-INF/Schemas/NemKonto/other/CPR_SecondaryPostalLabel.xsd b/src/main/resources/META-INF/Schemas/NemKonto/other/CPR_SecondaryPostalLabel.xsd new file mode 100644 index 0000000000000000000000000000000000000000..ddca114a68e83934326a77fffe2910ef3fe0bf3d --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/other/CPR_SecondaryPostalLabel.xsd @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/META-INF/Schemas/NemKonto/other/CVR_CVRnumberIdentifier.xsd b/src/main/resources/META-INF/Schemas/NemKonto/other/CVR_CVRnumberIdentifier.xsd new file mode 100644 index 0000000000000000000000000000000000000000..93ef4a60e3ac6d03116ebc897b4f901c052f99cd --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/other/CVR_CVRnumberIdentifier.xsd @@ -0,0 +1,14 @@ + + + + + Unique and generally usable identifier for all legal units included i CVR. + + + + + + + + + diff --git a/src/main/resources/META-INF/Schemas/NemKonto/other/CVR_ProductionUnitIdentifier.xsd b/src/main/resources/META-INF/Schemas/NemKonto/other/CVR_ProductionUnitIdentifier.xsd new file mode 100644 index 0000000000000000000000000000000000000000..066e75cee0485696c476491958c61174626ec584 --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/other/CVR_ProductionUnitIdentifier.xsd @@ -0,0 +1,13 @@ + + + + + Unique and generally usable identifier for all production units (“workplaces”). + + + + + + + + diff --git a/src/main/resources/META-INF/Schemas/NemKonto/other/DKCC_CountryIdentificationCode.xsd b/src/main/resources/META-INF/Schemas/NemKonto/other/DKCC_CountryIdentificationCode.xsd new file mode 100644 index 0000000000000000000000000000000000000000..2439f5f2542d9622beb09eb9be6416fa0d03bddd --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/other/DKCC_CountryIdentificationCode.xsd @@ -0,0 +1,59 @@ + + + + + + The country code based on the 4 diffent schemes. + Landeidentifikations kode baseret på de 4 forskellige formater. + + + + + + + + + + + + This is a support type for CountryIdentificationCodeType. The pattern is a choice of 4 different patterns for different schems. ISO 3166 standard, alpha 2: [a-z,A-Z]{2}. Eksample "DK" for Danmark. ISO 3166 standard, alpha 3: [a-z,A-Z]{3}. Eksample "DKN" for Danmark. UN Statistics Divisions country codes: [0-9]{3}. Eksample "208" for Danmark AuthorityCode from the Central Office of Civil Registration: [0-9]{4}. Eksample "5100" for Danmark. + Dette er en støttetype til CountryIdentificationCodeType. Det regulære udtryk er et valg for de 4 forskellige regulære udtryk for de forskellige formater. ISO 3166 standard, alpha 2: [a-z,A-Z]{2}. Eksempel "DK" for Danmark. ISO 3166 standard, alpha 3: [a-z,A-Z]{3}. Eksempel "DKN" for Danmark. UN Statistics Divisions country codes: [0-9]{3}. Eksempel "208" for Danmark AuthorityCode from the Central Office of Civil Registration: [0-9]{4}. Eksempel "5100" for Danmark. + + + + + + + + This is a support type for CountryIdentificationCodeType. + Dette er en støttetype til CountryIdentificationCodeType. + + + + + This scheme follows the ISO 3166 standard, alpha 2. + Dette format følge ISO 3166 standarden, alpha 2. + + + + + This scheme follows the ISO 3166 standard, alpha 3. + Dette format følge ISO 3166 standarden, alpha 3. + + + + + This scheme follows the UN Statistics Divisions country codes. + Dette format følger FNs Statistik Kontor landekoder + + + + + This scheme follows the AuthorityCode from the Central Office of Civil Registration. + Dette format følger MyndighedsKoden fra Det Centrale Personregister + + + + + + diff --git a/src/main/resources/META-INF/Schemas/NemKonto/other/DKCC_PostalAddressFifthLineText.xsd b/src/main/resources/META-INF/Schemas/NemKonto/other/DKCC_PostalAddressFifthLineText.xsd new file mode 100644 index 0000000000000000000000000000000000000000..a3b339839f8884382c09c8c03f136346d621f6ff --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/other/DKCC_PostalAddressFifthLineText.xsd @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/main/resources/META-INF/Schemas/NemKonto/other/DKCC_PostalAddressFirstLineText.xsd b/src/main/resources/META-INF/Schemas/NemKonto/other/DKCC_PostalAddressFirstLineText.xsd new file mode 100644 index 0000000000000000000000000000000000000000..76e0e31f5e2115f51cad91178226ad7bb313ef95 --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/other/DKCC_PostalAddressFirstLineText.xsd @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/main/resources/META-INF/Schemas/NemKonto/other/DKCC_PostalAddressFourthLineText.xsd b/src/main/resources/META-INF/Schemas/NemKonto/other/DKCC_PostalAddressFourthLineText.xsd new file mode 100644 index 0000000000000000000000000000000000000000..d348a041cec617ec66d81a83c7964335fa6acccb --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/other/DKCC_PostalAddressFourthLineText.xsd @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/main/resources/META-INF/Schemas/NemKonto/other/DKCC_PostalAddressSecondLineText.xsd b/src/main/resources/META-INF/Schemas/NemKonto/other/DKCC_PostalAddressSecondLineText.xsd new file mode 100644 index 0000000000000000000000000000000000000000..aa90bb222c90e8240b2e19acc64c0c7e471e35ef --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/other/DKCC_PostalAddressSecondLineText.xsd @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/main/resources/META-INF/Schemas/NemKonto/other/DKCC_PostalAddressSixthLineText.xsd b/src/main/resources/META-INF/Schemas/NemKonto/other/DKCC_PostalAddressSixthLineText.xsd new file mode 100644 index 0000000000000000000000000000000000000000..be43fb58d70a5bf76ed718f67825f85c24011dd1 --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/other/DKCC_PostalAddressSixthLineText.xsd @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/main/resources/META-INF/Schemas/NemKonto/other/DKCC_PostalAddressThirdLineText.xsd b/src/main/resources/META-INF/Schemas/NemKonto/other/DKCC_PostalAddressThirdLineText.xsd new file mode 100644 index 0000000000000000000000000000000000000000..353f9adad4dc65e74baf1692d9185ff15f758300 --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/other/DKCC_PostalAddressThirdLineText.xsd @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/main/resources/META-INF/Schemas/NemKonto/other/EAN_EAN13Identifier.xsd b/src/main/resources/META-INF/Schemas/NemKonto/other/EAN_EAN13Identifier.xsd new file mode 100644 index 0000000000000000000000000000000000000000..775846e5ba3814c61a1af806eb57c702e94656e1 --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/other/EAN_EAN13Identifier.xsd @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/META-INF/Schemas/NemKonto/other/ITST_BankAccountIdentifier.xsd b/src/main/resources/META-INF/Schemas/NemKonto/other/ITST_BankAccountIdentifier.xsd new file mode 100644 index 0000000000000000000000000000000000000000..b04fccef38889df9760f218988f8f9a6f6251292 --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/other/ITST_BankAccountIdentifier.xsd @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/main/resources/META-INF/Schemas/NemKonto/other/ITST_BankAccountStructure.xsd b/src/main/resources/META-INF/Schemas/NemKonto/other/ITST_BankAccountStructure.xsd new file mode 100644 index 0000000000000000000000000000000000000000..7a63122f22041c7f1fd349e351c19fcdd05346f7 --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/other/ITST_BankAccountStructure.xsd @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/META-INF/Schemas/NemKonto/other/ITST_BankBranchIdentifier.xsd b/src/main/resources/META-INF/Schemas/NemKonto/other/ITST_BankBranchIdentifier.xsd new file mode 100644 index 0000000000000000000000000000000000000000..bd424b921ffb143ce07d87afe903ebe1e2760cd8 --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/other/ITST_BankBranchIdentifier.xsd @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/main/resources/META-INF/Schemas/NemKonto/other/cpr_completepostallabeltext.xsd.meta.xml b/src/main/resources/META-INF/Schemas/NemKonto/other/cpr_completepostallabeltext.xsd.meta.xml new file mode 100644 index 0000000000000000000000000000000000000000..d1ec1402c7b15ca0e734e3d8d1b9a16cafb39ebd --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/other/cpr_completepostallabeltext.xsd.meta.xml @@ -0,0 +1 @@ +CompletePostalLabelTextLine of text for postal labelsTekstlinie til adresselabelsLabel textLabeltekstCompletePostalLabelText1.0.0http://www.oio.dk/files/Dokumentationsguide_for_adresse.pdf8bd99501-1842-4427-b6f4-0e9d105ff3b3http://www.oio.dk/files/Dokumentationsguide_for_adresse.pdf \ No newline at end of file diff --git a/src/main/resources/META-INF/Schemas/NemKonto/other/cpr_completepostallabeltext.xsd.system.xml b/src/main/resources/META-INF/Schemas/NemKonto/other/cpr_completepostallabeltext.xsd.system.xml new file mode 100644 index 0000000000000000000000000000000000000000..6417efbdf143cab278f6d74a6619b2aefe563a96 --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/other/cpr_completepostallabeltext.xsd.system.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/src/main/resources/META-INF/Schemas/NemKonto/other/cpr_personcivilregistrationidentifier.xsd.meta.xml b/src/main/resources/META-INF/Schemas/NemKonto/other/cpr_personcivilregistrationidentifier.xsd.meta.xml new file mode 100644 index 0000000000000000000000000000000000000000..a3013d0c6901a79c678455750872d05b50e4bcea --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/other/cpr_personcivilregistrationidentifier.xsd.meta.xml @@ -0,0 +1 @@ +PersonCivilRegistrationIdentifierCivilRegistrationNumber (PNR) Description: Unique identification of a person The Civil Registration System contains: - Data on persons, who after 1968 April 2nd Danish registry of citizens. As for Greenland the corresponding date is 1972 may 1st. - Danish citizens living outside Denmark (who must pay duty and ATP) has also been given a civil registration number. - Civil registration numbers are also assigned for other administrative purposes. Value space: The civil registration number consists of two parts. The first part is the valid birthday in the form DDMMYY. The following part is a serial number of four digits. The civil registration number may also hold the value 0000000000. This value is used where the civil registration number is required but unknown. Lifecycle: The civil registration number is generated and assigned at birth, entry and change of civil registration number of for administrative reasons. The civil registration number may be assigned via hospitals. The civil registration number is not to be deleted. Remarks: 1994 June 11th the civil registration number was changed according to this description.Personnummer (PNR): Unik identifikation af en person i Det Centrale Personregister indeholder: - Data om personer, der efter 2. april 1968 har været tilmeldt dansk folkeregister – for Grønlands vedkommende dog efter 1. maj 1972. –Personer, der er bosat uden for Danmark, men som i kraft af medlemskab af ATP eller pligt til at svare skat, har fået tildelt et personnummer. Der tildeles desuden personnumre til andet administrativt behov(administrative personnumre). Værdimængde: Personnummeret består af to dele. Første del er en gyldig fødselsdato på formen DDMMÅÅ. Anden del er et serienummer på fire cifre. Personnummeret kan også have værdien 0000000000. Denne værdi bruges hvor personnummeret er påkrævet men ukendt. Livscyklus: Personnummeret genereres og tildeles ved fødsel, optagelse og ændring af personnummer af administrative grunde. Personnummeret kan tildeles via hospitaler. Personnummeret må ikke slettes. Bemærkninger: D. 11. juni 1994 blev personnummeret ændret i henhold til denne beskrivelse.Civil registration numberPersonnummermv@cpr.dkPersonCivilRegistrationIdentifier2.0.0http://rep.oio.dk/cpr.dk/xml/schemas/core/2002/06/28/CPR_CivilRegistrationNumber.xsdCivilRegistrationNumber;CivilRegistration;CivilRegistrationNumberIdentifier;personnummerd530421f-71be-4f8c-8c80-00860e5b55a7668e1d9c-51b1-4d11-8098-c612392f216c \ No newline at end of file diff --git a/src/main/resources/META-INF/Schemas/NemKonto/other/cpr_personcivilregistrationidentifier.xsd.system.xml b/src/main/resources/META-INF/Schemas/NemKonto/other/cpr_personcivilregistrationidentifier.xsd.system.xml new file mode 100644 index 0000000000000000000000000000000000000000..9b84a25ccb0bae4230e6492cef13ee65a5a801c0 --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/other/cpr_personcivilregistrationidentifier.xsd.system.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/src/main/resources/META-INF/Schemas/NemKonto/other/cpr_secondarypostallabel.xsd.meta.xml b/src/main/resources/META-INF/Schemas/NemKonto/other/cpr_secondarypostallabel.xsd.meta.xml new file mode 100644 index 0000000000000000000000000000000000000000..7f5ab363192033aae2943c18e2346b45aa2035b8 --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/other/cpr_secondarypostallabel.xsd.meta.xml @@ -0,0 +1 @@ +SecondaryPostalLabelLabel information containing addressee and address information for print on forms, labels and letters. Standardized print of name and address data on forms og mailing items and the like based upon the standards of CPR. Intentionally to contain additional information on adresses associated with a "non-active" person. E.g where such information as contact adress, additional address, election address is needed. New in this version: only the name has been changed.Overførsel af adresseoplysninger på 6 tekstlinjer uden fast indhold eller bindinger, til brug på labels, rudekuverter og lign. Print af adresseoplysninger på formularer og brevforsendelser og lign. baseret på CPRs standarder. Skal anvendes i adressesammenhænge, hvor der ikke er tale om en aktiv person i CPR-kontekst. Dvs. i de sammenhænge, hvor der er behov for at indsætte en af følgende adresser: 1. Kontaktadresse, 2. Supplerende adresse, 3. Valgadresse, 4. Værgeadresse eller 5. Udlandsadresse. Nyt i denne version: kun navnet er ændret.Postal labelAdresselabel (Sekundær adresselabel)SecondaryPostalLabel2.0.0http://rep.oio.dk/cpr.dk/xml/schemas/core/2005/05/19/CPR_SecondaryAddressLabel.xsdhttp://www.oio.dk/files/Dokumentationsguide_for_adresse.pdfrudekuvert;AddressLabel;Sekundær adresselabel;Adresselabel;printaddress8bd99501-1842-4427-b6f4-0e9d105ff3b3http://www.oio.dk/files/Dokumentationsguide_for_adresse.pdf \ No newline at end of file diff --git a/src/main/resources/META-INF/Schemas/NemKonto/other/cpr_secondarypostallabel.xsd.system.xml b/src/main/resources/META-INF/Schemas/NemKonto/other/cpr_secondarypostallabel.xsd.system.xml new file mode 100644 index 0000000000000000000000000000000000000000..44b5fdb57cffc5ca26aebe70ecafb32ca2c1992a --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/other/cpr_secondarypostallabel.xsd.system.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/src/main/resources/META-INF/Schemas/NemKonto/other/cvr_cvrnumberidentifier.xsd.meta.xml b/src/main/resources/META-INF/Schemas/NemKonto/other/cvr_cvrnumberidentifier.xsd.meta.xml new file mode 100644 index 0000000000000000000000000000000000000000..4d61f2c021fb296a94fd34a61c7d8dbd6794b896 --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/other/cvr_cvrnumberidentifier.xsd.meta.xml @@ -0,0 +1 @@ +CVRnumberIdentifierUnique and generally usable identifier for all legal units included i CVR.Unik og generelt brugbar identifikator for alle juridiske enheder under CVRCVR numberCVR nummerpnj@itst.dkCVRnumberIdentifier2.0.0http://rep.oio.dk/cvr.dk/xml/schemas/2002/06/28/CVR_CVRNumber.xsdcvr;cvrnumber;cvrnummerd077d9d3-dcc1-4b7e-baf9-0b318d141ec7 \ No newline at end of file diff --git a/src/main/resources/META-INF/Schemas/NemKonto/other/cvr_cvrnumberidentifier.xsd.system.xml b/src/main/resources/META-INF/Schemas/NemKonto/other/cvr_cvrnumberidentifier.xsd.system.xml new file mode 100644 index 0000000000000000000000000000000000000000..1a542540cca2fe6a9bf5af348d0c280410b9e194 --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/other/cvr_cvrnumberidentifier.xsd.system.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/src/main/resources/META-INF/Schemas/NemKonto/other/cvr_productionunitidentifier.xsd.meta.xml b/src/main/resources/META-INF/Schemas/NemKonto/other/cvr_productionunitidentifier.xsd.meta.xml new file mode 100644 index 0000000000000000000000000000000000000000..da100b40db85fc5bad54b9e23bdc3cd6e9db5950 --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/other/cvr_productionunitidentifier.xsd.meta.xml @@ -0,0 +1 @@ +ProductionUnitIdentifierUnique and generally usable identifier for all production units (“workplaces”).Unik og generelt brugbar identifikator for alle produktionsenheder ("arbejdspladser").Production unitProduktionsenhedpnj@itst.dkProductionUnitIdentifier2.0.0http://rep.oio.dk/cvr.dk/xml/schemas/2002/06/28/CVR_ProductionUnitNumber.xsdproduction unit;produktionsenhed;arbejdsplads;workplaced077d9d3-dcc1-4b7e-baf9-0b318d141ec7 \ No newline at end of file diff --git a/src/main/resources/META-INF/Schemas/NemKonto/other/cvr_productionunitidentifier.xsd.system.xml b/src/main/resources/META-INF/Schemas/NemKonto/other/cvr_productionunitidentifier.xsd.system.xml new file mode 100644 index 0000000000000000000000000000000000000000..92d0cde59c2d50214fd5bcfd33565ded8c821740 --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/other/cvr_productionunitidentifier.xsd.system.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/src/main/resources/META-INF/Schemas/NemKonto/other/dkcc_countryidentificationcode.xsd.meta.xml b/src/main/resources/META-INF/Schemas/NemKonto/other/dkcc_countryidentificationcode.xsd.meta.xml new file mode 100644 index 0000000000000000000000000000000000000000..bd773cf47cb06602217ad8fed8694c41f884f91e --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/other/dkcc_countryidentificationcode.xsd.meta.xml @@ -0,0 +1 @@ +CountryIdentificationCodeThe country code - 2 or 3 letters or 3 digits - as described in the ISO 3166 standards or 4 digits as described in the AuthorityCode from the Central Office of Civil Registration. E.g.'DK', 'DNK', '208' is the codes for Denmark in the ISO 3166 standards and '5100' is the code for Denmark in the AuthorityCode from the Central Office of Civil Registration.Landeidentifikations kode - 2 eller 3 karaktere eller 3 cifre - som beskrevet i ISO 3166 standarden eller 4 cifre som beskrevet i MyndighedsKode fra Det Centrale Personregister.E.k.s. 'DK', 'DNK', '208' er koderne for Danmark i ISO 3166 standarden og '5100' er koden for Danmark i MyndighedsKode fra Det Centrale Personregister.CountryIdentificationCodehttp://www.itst.dk/arkitektur-og-standarder/Standardisering/datastandardisering/om/kernekomponenter/dokumentationsguides/Dokumentationsguide%20for%20Adresse.pdfCountry8bd99501-1842-4427-b6f4-0e9d105ff3b3http://www.itst.dk/arkitektur-og-standarder/Standardisering/datastandardisering/om/kernekomponenter/dokumentationsguides/Dokumentationsguide%20for%20Adresse.pdf \ No newline at end of file diff --git a/src/main/resources/META-INF/Schemas/NemKonto/other/dkcc_countryidentificationcode.xsd.system.xml b/src/main/resources/META-INF/Schemas/NemKonto/other/dkcc_countryidentificationcode.xsd.system.xml new file mode 100644 index 0000000000000000000000000000000000000000..30f4ad6cd782f9c9b25843f753933ea7d185429a --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/other/dkcc_countryidentificationcode.xsd.system.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/src/main/resources/META-INF/Schemas/NemKonto/other/dkcc_postaladdressfifthlinetext.xsd.meta.xml b/src/main/resources/META-INF/Schemas/NemKonto/other/dkcc_postaladdressfifthlinetext.xsd.meta.xml new file mode 100644 index 0000000000000000000000000000000000000000..42911eefec0de098657182fdca24c1fbc42ac389 --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/other/dkcc_postaladdressfifthlinetext.xsd.meta.xml @@ -0,0 +1,2 @@ +PostalAddressFifthLineTextFree Address line 5. When used in CompletePostalLabel: Postal code and postal district. Line of text which contains Postal code and postal district. In the non-structured schema SecondaryPostalLabel this element can be used freely.Fri Adresselinje 5. Ved brug i CompletePostalLabel: Postnr. og postdistrikt. Tekstlinje som indeholder +postnummer og postdistrikt. I det ikke-strukturerede skema SecondaryPostalLabel kan dette element anvendes frit.Fifth address lineFemte adresse liniePostalAddressFifthLineText1.0.0http://www.oio.dk/files/Dokumentationsguide_for_adresse.pdf8bd99501-1842-4427-b6f4-0e9d105ff3b3http://www.oio.dk/files/Dokumentationsguide_for_adresse.pdf \ No newline at end of file diff --git a/src/main/resources/META-INF/Schemas/NemKonto/other/dkcc_postaladdressfifthlinetext.xsd.system.xml b/src/main/resources/META-INF/Schemas/NemKonto/other/dkcc_postaladdressfifthlinetext.xsd.system.xml new file mode 100644 index 0000000000000000000000000000000000000000..b042d0ee38745fab34af4078383410eaab679ed4 --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/other/dkcc_postaladdressfifthlinetext.xsd.system.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/src/main/resources/META-INF/Schemas/NemKonto/other/dkcc_postaladdressfirstlinetext.xsd.meta.xml b/src/main/resources/META-INF/Schemas/NemKonto/other/dkcc_postaladdressfirstlinetext.xsd.meta.xml new file mode 100644 index 0000000000000000000000000000000000000000..08f49f9c54130cd83c0f75581a5aa8c68b231b7c --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/other/dkcc_postaladdressfirstlinetext.xsd.meta.xml @@ -0,0 +1,2 @@ +PostalAddressFirstLineTextFree address line 1. When used in CompletePostalLabel: C/O name, Care-of name, i.e. "living at", typically person, family, college, nursing home or the like. In the non-structured schema SecondaryPostalLabel this element can be used freely.Fri adresselinje 1. Ved brug i CompletePostalLabel: C/O-navn, Care-of navn, dvs. "boende hos", typisk person, familie, kollegie, plejehjem eller lign. I det ikke-strukturerede +skema SecondaryPostalLabel kan dette element anvendes frit.First address lineFørste adresseliniePostalAddressFirstLineText1.0.0http://www.oio.dk/files/Dokumentationsguide_for_adresse.pdf8bd99501-1842-4427-b6f4-0e9d105ff3b3http://www.oio.dk/files/Dokumentationsguide_for_adresse.pdf \ No newline at end of file diff --git a/src/main/resources/META-INF/Schemas/NemKonto/other/dkcc_postaladdressfirstlinetext.xsd.system.xml b/src/main/resources/META-INF/Schemas/NemKonto/other/dkcc_postaladdressfirstlinetext.xsd.system.xml new file mode 100644 index 0000000000000000000000000000000000000000..b2d13df78711c436c66ab33904b96599914d8efd --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/other/dkcc_postaladdressfirstlinetext.xsd.system.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/src/main/resources/META-INF/Schemas/NemKonto/other/dkcc_postaladdressfourthlinetext.xsd.meta.xml b/src/main/resources/META-INF/Schemas/NemKonto/other/dkcc_postaladdressfourthlinetext.xsd.meta.xml new file mode 100644 index 0000000000000000000000000000000000000000..cf662175d6343bd3fdfcfe700c3d8403feb1701d --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/other/dkcc_postaladdressfourthlinetext.xsd.meta.xml @@ -0,0 +1,4 @@ +PostalAddressFourthLineTextFree Address line 4. When used in CompletePostalLabel: Name of city (place name). In this schema equivalent to DistrictSubdivisionIdentifier, i.e. the city name that is specified as a part of the official address spcification for a certain street or specific parts of a street. In the non-structured schema SecondaryPostalLabel this element can be used freely.Fri Adresselinje 4. Ved brug i CompletePostalLabel: Bynavn (stednavn): +Svarer i dette skema til elementet DistrictSubdivisionIdentifier, dvs. det +bynavn der er fastsat som en del af den officielle adressebetegnelse for en +vej eller for nærmere bestemte dele af en vej. I det ikke-strukturerede skema SecondaryPostalLabel kan dette element anvendes frit.Fourth address lineFørste adresse liniePostalAddressFourthLineText1.0.0http://www.oio.dk/files/Dokumentationsguide_for_adresse.pdf8bd99501-1842-4427-b6f4-0e9d105ff3b3http://www.oio.dk/files/Dokumentationsguide_for_adresse.pdf \ No newline at end of file diff --git a/src/main/resources/META-INF/Schemas/NemKonto/other/dkcc_postaladdressfourthlinetext.xsd.system.xml b/src/main/resources/META-INF/Schemas/NemKonto/other/dkcc_postaladdressfourthlinetext.xsd.system.xml new file mode 100644 index 0000000000000000000000000000000000000000..9d2c985fabe619473ca3e1d8c1fa730ed8d63f10 --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/other/dkcc_postaladdressfourthlinetext.xsd.system.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/src/main/resources/META-INF/Schemas/NemKonto/other/dkcc_postaladdresssecondlinetext.xsd.meta.xml b/src/main/resources/META-INF/Schemas/NemKonto/other/dkcc_postaladdresssecondlinetext.xsd.meta.xml new file mode 100644 index 0000000000000000000000000000000000000000..b95a3dca1e452746d112e82800b2bbfb81eb706c --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/other/dkcc_postaladdresssecondlinetext.xsd.meta.xml @@ -0,0 +1,3 @@ +PostalAddressSecondLineTextFree Address line 2. When used in CompletePostalLabel: Locality (building name): Equivalent to the element MailDeliverySublocationIdentifier, i.e. name of a farm, estate, building or dwelling, which is used as an additional postal address identifier. In the non-structured schema SecondaryPostalLabel this element can be used freely.Fri Adresselinje 2. Ved brug i CompletePostalLabel: Lokalitet (bygningsnavn): Svarer til elementet MailDeliverySublocationIdentifier, dvs. gårdnavn, navn på ejendom, bygning +eller bolig eller lign., som anvendes som supplerende postadressebetegnelse. I det ikke-strukturerede skema SecondaryPostalLabel kan dette element +anvendes frit.Second address lineAnden adresse liniePostalAddressSecondLineText1.0.0http://www.oio.dk/files/Dokumentationsguide_for_adresse.pdf8bd99501-1842-4427-b6f4-0e9d105ff3b3http://www.oio.dk/files/Dokumentationsguide_for_adresse.pdf \ No newline at end of file diff --git a/src/main/resources/META-INF/Schemas/NemKonto/other/dkcc_postaladdresssecondlinetext.xsd.system.xml b/src/main/resources/META-INF/Schemas/NemKonto/other/dkcc_postaladdresssecondlinetext.xsd.system.xml new file mode 100644 index 0000000000000000000000000000000000000000..b8364bc7686683a734eb8cc5194cf106c815498c --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/other/dkcc_postaladdresssecondlinetext.xsd.system.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/src/main/resources/META-INF/Schemas/NemKonto/other/dkcc_postaladdresssixthlinetext.xsd.meta.xml b/src/main/resources/META-INF/Schemas/NemKonto/other/dkcc_postaladdresssixthlinetext.xsd.meta.xml new file mode 100644 index 0000000000000000000000000000000000000000..1e65d658e0aae4ab63345a74b33d66469221fc21 --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/other/dkcc_postaladdresssixthlinetext.xsd.meta.xml @@ -0,0 +1 @@ +PostalAddressSixthLineTextFree Address line 6. When used in CompletePostalLabel: Free line of text. CPR uses the line for customer service with a key constant. In the non-structured schema SecondaryPostalLabel this element can be used freely.Fri Adresselinje 6. Ved brug i CompletePostalLabel: (Fri tekstlinje) CPR anvender linjen til kundeservicering med en nøglekonstant. I det ikke-strukturerede skema SecondaryPostalLabel kan dette element anvendes frit.Sixth address linePostalAddressSixthLineText1.0.0http://www.oio.dk/files/Dokumentationsguide_for_adresse.pdf8bd99501-1842-4427-b6f4-0e9d105ff3b3http://www.oio.dk/files/Dokumentationsguide_for_adresse.pdf \ No newline at end of file diff --git a/src/main/resources/META-INF/Schemas/NemKonto/other/dkcc_postaladdresssixthlinetext.xsd.system.xml b/src/main/resources/META-INF/Schemas/NemKonto/other/dkcc_postaladdresssixthlinetext.xsd.system.xml new file mode 100644 index 0000000000000000000000000000000000000000..82aa79110a8dfa15cd60266250d950923ebe02e1 --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/other/dkcc_postaladdresssixthlinetext.xsd.system.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/src/main/resources/META-INF/Schemas/NemKonto/other/dkcc_postaladdressthirdlinetext.xsd.meta.xml b/src/main/resources/META-INF/Schemas/NemKonto/other/dkcc_postaladdressthirdlinetext.xsd.meta.xml new file mode 100644 index 0000000000000000000000000000000000000000..05da3684085c1dd17ccfa1d7ee5833c3199598ba --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/other/dkcc_postaladdressthirdlinetext.xsd.meta.xml @@ -0,0 +1,2 @@ +PostalAddressThirdLineTextFree Address line 3. When used in CompletePostalLabel: Standard street address. In this schema equivalent to an aggregation of the elements StreetNameForAddressingName, StreetBuildingIdenfier, FloorIdentifier and SuiteIdentifier, i.e. a line of text which contains street name, house number including letter, floor and door if any. In the non-structured schema SecondaryPostalLabel this element can be used freely.Fri Adresselinje 3. Ved brug i CompletePostalLabel: Standard vejadresse: Svarer i dette skema til en +aggregering af elementerne StreetNameForAddressingName, StreetBuildingIdenfier, FloorIdentifier og SuiteIdentifier, dvs. en tekstlinje som indeholder vejnavn, husnummer inkl. evt. bogstav, etage og dørbetegnelse. I det ikke-strukturerede skema SecondaryPostalLabel kan dette element anvendes frit.Third address lineTredje adresse liniePostalAddressThirdLineText1.0.0http://www.oio.dk/files/Dokumentationsguide_for_adresse.pdf8bd99501-1842-4427-b6f4-0e9d105ff3b3http://www.oio.dk/files/Dokumentationsguide_for_adresse.pdf \ No newline at end of file diff --git a/src/main/resources/META-INF/Schemas/NemKonto/other/dkcc_postaladdressthirdlinetext.xsd.system.xml b/src/main/resources/META-INF/Schemas/NemKonto/other/dkcc_postaladdressthirdlinetext.xsd.system.xml new file mode 100644 index 0000000000000000000000000000000000000000..33ecdc7ac6b7f47f18c0768e0877423b71d3e235 --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/other/dkcc_postaladdressthirdlinetext.xsd.system.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/src/main/resources/META-INF/Schemas/NemKonto/other/ean_ean13identifier.xsd.meta.xml b/src/main/resources/META-INF/Schemas/NemKonto/other/ean_ean13identifier.xsd.meta.xml new file mode 100644 index 0000000000000000000000000000000000000000..ec04391f71d05f0b5cf8771d1cbbb6f931d5811f --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/other/ean_ean13identifier.xsd.meta.xml @@ -0,0 +1,3 @@ +EAN13IdentifierIt is expected that schemas will, contrary to general reccomendations of NDR 3.0, reuse the type rather than the element of this schema. The element is included in order to ensure full NDR 3.0 compliance. + +An EAN 13 digits identifications type algorithm is the 1st digit multiplied by 1, + the second digit multiplied by 3, + the third digit multiplied by 1, and so forth until the 12th digit, if the result of all these numbers added together does not end in a 0 then the result is rounded up, for example 56 is rounded up to 60. Then the result is subtracted from the rounded up result to give the 13 digit. in the case of the result ending in zero then the 13 digit must also end in zero.Jan Brown, IT- og TelestyrelsenEAN13Identifier1.0.0 \ No newline at end of file diff --git a/src/main/resources/META-INF/Schemas/NemKonto/other/ean_ean13identifier.xsd.system.xml b/src/main/resources/META-INF/Schemas/NemKonto/other/ean_ean13identifier.xsd.system.xml new file mode 100644 index 0000000000000000000000000000000000000000..d70d34fc65df19462796ce28a5141f3fd035ba30 --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/other/ean_ean13identifier.xsd.system.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/src/main/resources/META-INF/Schemas/NemKonto/other/itst_bankaccountidentifier.xsd.meta.xml b/src/main/resources/META-INF/Schemas/NemKonto/other/itst_bankaccountidentifier.xsd.meta.xml new file mode 100644 index 0000000000000000000000000000000000000000..eeb8e228dd1d243a0429c8a881e41de7355eeb5e --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/other/itst_bankaccountidentifier.xsd.meta.xml @@ -0,0 +1 @@ +BankAccountIdentifierUnique specification of an account in a given commercial bank.Entydig angivelse af en konto i et givent pengeinstitut.Bank Account NumberBankkontonummerpnj@itst.dkBankAccountIdentifier2.0.0http://rep.oio.dk/itst.dk/xml/schemas/2005/06/24/ITST_BankAccountIdentifier.xsdhttp://oio.dk/files/dokumentationsguide_for_dansk_bankkonto.pdfbankkonto;kontonr;kontonummer;bank account;account number;account;bank;http://oio.dk/files/dokumentationsguide_for_dansk_bankkonto.pdf \ No newline at end of file diff --git a/src/main/resources/META-INF/Schemas/NemKonto/other/itst_bankaccountidentifier.xsd.system.xml b/src/main/resources/META-INF/Schemas/NemKonto/other/itst_bankaccountidentifier.xsd.system.xml new file mode 100644 index 0000000000000000000000000000000000000000..f8eaf03d78aeba52c5a9a80501cdf00d53993bda --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/other/itst_bankaccountidentifier.xsd.system.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/src/main/resources/META-INF/Schemas/NemKonto/other/itst_bankaccountstructure.xsd.meta.xml b/src/main/resources/META-INF/Schemas/NemKonto/other/itst_bankaccountstructure.xsd.meta.xml new file mode 100644 index 0000000000000000000000000000000000000000..c1e5336a24cbbb08b676868fedd8733585e230cd --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/other/itst_bankaccountstructure.xsd.meta.xml @@ -0,0 +1 @@ +BankAccountStructureUnique specification of a Danish bank account.Entydig angivelse af en dansk bankkonto.Bank accountBankkontopnj@itst.dkBankAccountStructure1.0.1http://rep.oio.dk/itst.dk/xml/schemas/2005/06/24/ITST_BankAccountStructure.xsdhttp://www.itst.dk/arkitektur-og-standarder/Standardisering/datastandardisering/om/kernekomponenter/dokumentationsguides/Dokumentationsguide%20for%20Dansk%20Bankkonto.pdfbankkonto;kontonr;kontonummer;bank account;account number;account;bank;http://www.itst.dk/arkitektur-og-standarder/Standardisering/datastandardisering/om/kernekomponenter/dokumentationsguides/Dokumentationsguide%20for%20Dansk%20Bankkonto.pdf \ No newline at end of file diff --git a/src/main/resources/META-INF/Schemas/NemKonto/other/itst_bankaccountstructure.xsd.system.xml b/src/main/resources/META-INF/Schemas/NemKonto/other/itst_bankaccountstructure.xsd.system.xml new file mode 100644 index 0000000000000000000000000000000000000000..5627524ea5bfa772c75b7ee8bdaeb14c2981f370 --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/other/itst_bankaccountstructure.xsd.system.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/src/main/resources/META-INF/Schemas/NemKonto/other/itst_bankbranchidentifier.xsd.meta.xml b/src/main/resources/META-INF/Schemas/NemKonto/other/itst_bankbranchidentifier.xsd.meta.xml new file mode 100644 index 0000000000000000000000000000000000000000..f878bbd1fa22226ce5bcd245f585c97912d141b7 --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/other/itst_bankbranchidentifier.xsd.meta.xml @@ -0,0 +1 @@ +BankBranchIdentifierUnique specification of a Danish commercial bank.Entydig angivelse af et dansk pengeinstitut.Branch codeRegistreringsnummerpnj@itst.dkBankBranchIdentifier2.0.0http://rep.oio.dk/itst.dk/xml/schemas/2005/06/24/ITST_BankRegistrationIdentifier.xsdhttp://oio.dk/files/dokumentationsguide_for_dansk_bankkonto.pdfBankBranch;bank branch;bank;bank account;bankkonto;registreringsnummer;bankregistreringsnummer;kontonummer;kontoaccounthttp://oio.dk/files/dokumentationsguide_for_dansk_bankkonto.pdf \ No newline at end of file diff --git a/src/main/resources/META-INF/Schemas/NemKonto/other/itst_bankbranchidentifier.xsd.system.xml b/src/main/resources/META-INF/Schemas/NemKonto/other/itst_bankbranchidentifier.xsd.system.xml new file mode 100644 index 0000000000000000000000000000000000000000..99cb392f6c0c739689764072ad0dba7009b1eac4 --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/other/itst_bankbranchidentifier.xsd.system.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/src/main/resources/META-INF/Schemas/NemKonto/other/nemhandel/CVR_full.xsd b/src/main/resources/META-INF/Schemas/NemKonto/other/nemhandel/CVR_full.xsd new file mode 100644 index 0000000000000000000000000000000000000000..b0a3c6a18aeb5ef2377b08c1f9f9614f9974b2ac --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/other/nemhandel/CVR_full.xsd @@ -0,0 +1,7 @@ + + + + + diff --git a/src/main/resources/META-INF/Schemas/NemKonto/other/nemhandel/DKCC_full.xsd b/src/main/resources/META-INF/Schemas/NemKonto/other/nemhandel/DKCC_full.xsd new file mode 100644 index 0000000000000000000000000000000000000000..7b1dc3c50deb4fde4336e5f74ef636c7914db1c2 --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/other/nemhandel/DKCC_full.xsd @@ -0,0 +1,12 @@ + + + + + + + + + diff --git a/src/main/resources/META-INF/Schemas/NemKonto/other/nemhandel/README.txt b/src/main/resources/META-INF/Schemas/NemKonto/other/nemhandel/README.txt new file mode 100644 index 0000000000000000000000000000000000000000..fb778b75681c1f7728aff5330aef775e5ef63357 --- /dev/null +++ b/src/main/resources/META-INF/Schemas/NemKonto/other/nemhandel/README.txt @@ -0,0 +1,9 @@ +The files located in this folder are a workaround to be able to resolve multiple imports with the same namespace. + +For example, it's problematic in NKSPU_ProductionUnitIdentificationStructure.xsd that there are two imports with the same namespace. + + + +NOTE: The files in this folder are created manually by the Nemhandel team. diff --git a/src/main/resources/META-INF/Schemas/catalog.xml b/src/main/resources/META-INF/Schemas/catalog.xml new file mode 100644 index 0000000000000000000000000000000000000000..d979a3f2454cb57b08655b9f6fe3556ae54d046f --- /dev/null +++ b/src/main/resources/META-INF/Schemas/catalog.xml @@ -0,0 +1,128 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/META-INF/Schemas/transfer-delegation/transferDelegation.xsd b/src/main/resources/META-INF/Schemas/transfer-delegation/transferDelegation.xsd deleted file mode 100644 index 9df1f9eea77f17a3ec592e4e751c6702f770e37c..0000000000000000000000000000000000000000 --- a/src/main/resources/META-INF/Schemas/transfer-delegation/transferDelegation.xsd +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - - - - - - - - - - Sender identification - - - - - - - - - - - - - - Receiver identification - - - - - - - - - - diff --git a/src/main/resources/META-INF/Schematron/NemKonto/NemKonto.xsl b/src/main/resources/META-INF/Schematron/NemKonto/NemKonto.xsl new file mode 100644 index 0000000000000000000000000000000000000000..629059eaeea7a20a3dc538b9e33f41ee5f073bb6 Binary files /dev/null and b/src/main/resources/META-INF/Schematron/NemKonto/NemKonto.xsl differ diff --git a/src/main/resources/db/oxalis-as4-db-changelog.xml b/src/main/resources/db/oxalis-as4-db-changelog.xml index ecd3269af66b60b269608a32c6cf3c897f61983a..059db0843b5ed1bf4be1fc965529c7c96889a3f0 100644 --- a/src/main/resources/db/oxalis-as4-db-changelog.xml +++ b/src/main/resources/db/oxalis-as4-db-changelog.xml @@ -446,10 +446,7 @@ - - - - + @@ -526,4 +523,21 @@ + + + + + + + + + + + + + + diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml deleted file mode 100644 index fa2ccbec9cde3be5beafabc15cfbcda2238a4b4b..0000000000000000000000000000000000000000 --- a/src/main/resources/logback.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - ${catalina.base}/logs/myapp.log - - %d{yyyy-MM-dd'T'HH:mm:ss.SSS'Z'} [%c] [%level] [%thread] [%X{messageUuid}] [%X{transmissionId}] %msg%n - - - - - - - diff --git a/src/main/resources/reference.conf b/src/main/resources/reference.conf index bcc39c29e53f522105abe1945cfe99cd0b505c28..ec91b2192b9ade7c275b4e0a7daac68db47f54b2 100644 --- a/src/main/resources/reference.conf +++ b/src/main/resources/reference.conf @@ -54,7 +54,7 @@ document.type = { schemaPath: "META-INF/Schemas/UBL_v2.1/maindoc/UBL-Reminder-2.1.xsd" identifierDiscriminators = [{ xpathExpression: "/sbd:StandardBusinessDocument/root:Reminder/cbc:CustomizationID" - xpathExpectedResult: "OIOUBL-2\\.(01|02|1)" + xpathExpectedResult: "OIOUBL-2\\.1" }] namespaces = [ { namespace: "http://www.unece.org/cefact/namespaces/StandardBusinessDocumentHeader", prefix: "sbd" }, @@ -101,7 +101,9 @@ document.type = { errorLocationXPath: "Xpath" } ] + preloadSchema: true preloadSchematron: true + messageLevelResponseType: "OIOUBL_APPLICATION_RESPONSE" } @@ -112,7 +114,7 @@ document.type = { schemaPath: "META-INF/Schemas/UBL_v2.1/maindoc/UBL-Invoice-2.1.xsd" identifierDiscriminators = [{ xpathExpression: "/sbd:StandardBusinessDocument/root:Invoice/cbc:CustomizationID" - xpathExpectedResult: "OIOUBL-2\\.(01|02|1)" + xpathExpectedResult: "OIOUBL-2\\.1" }] namespaces = [ { namespace: "http://www.unece.org/cefact/namespaces/StandardBusinessDocumentHeader", prefix: "sbd" }, @@ -131,6 +133,7 @@ document.type = { errorLocationXPath: "Xpath" } ] + preloadSchema: true preloadSchematron: true messageLevelResponseType: "OIOUBL_APPLICATION_RESPONSE" } @@ -142,7 +145,7 @@ document.type = { schemaPath: "META-INF/Schemas/UBL_v2.1/maindoc/UBL-CreditNote-2.1.xsd" identifierDiscriminators = [{ xpathExpression: "/sbd:StandardBusinessDocument/root:CreditNote/cbc:CustomizationID" - xpathExpectedResult: "OIOUBL-2\\.(01|02|1)" + xpathExpectedResult: "OIOUBL-2\\.1" }] namespaces = [ { namespace: "http://www.unece.org/cefact/namespaces/StandardBusinessDocumentHeader", prefix: "sbd" }, @@ -161,6 +164,7 @@ document.type = { errorLocationXPath: "Xpath" } ] + preloadSchema: true preloadSchematron: true messageLevelResponseType: "OIOUBL_APPLICATION_RESPONSE" } @@ -172,7 +176,7 @@ document.type = { schemaPath: "META-INF/Schemas/UBL_v2.1/maindoc/UBL-ApplicationResponse-2.1.xsd" identifierDiscriminators = [{ xpathExpression: "/sbd:StandardBusinessDocument/root:ApplicationResponse/cbc:CustomizationID" - xpathExpectedResult: "OIOUBL-2\\.(01|02|1)" + xpathExpectedResult: "OIOUBL-2\\.1" }] responseType: true messageLevelResponse: { @@ -198,6 +202,7 @@ document.type = { errorLocationXPath: "Xpath" } ] + preloadSchema: true preloadSchematron: true messageLevelResponseType: "OIOUBL_APPLICATION_RESPONSE" } @@ -209,7 +214,7 @@ document.type = { schemaPath: "META-INF/Schemas/UBL_v2.1/maindoc/UBL-Statement-2.1.xsd" identifierDiscriminators = [{ xpathExpression: "/sbd:StandardBusinessDocument/root:Statement/cbc:CustomizationID" - xpathExpectedResult: "OIOUBL-2\\.(01|02|1)" + xpathExpectedResult: "OIOUBL-2\\.1" }] namespaces = [ { namespace: "http://www.unece.org/cefact/namespaces/StandardBusinessDocumentHeader", prefix: "sbd" }, @@ -238,7 +243,7 @@ document.type = { schemaPath: "META-INF/Schemas/UBL_v2.1/maindoc/UBL-Order-2.1.xsd" identifierDiscriminators = [{ xpathExpression: "/sbd:StandardBusinessDocument/root:Order/cbc:CustomizationID" - xpathExpectedResult: "OIOUBL-2\\.(01|02|1)" + xpathExpectedResult: "OIOUBL-2\\.1" }] namespaces = [ { namespace: "http://www.unece.org/cefact/namespaces/StandardBusinessDocumentHeader", prefix: "sbd" }, @@ -267,7 +272,7 @@ document.type = { schemaPath: "META-INF/Schemas/UBL_v2.1/maindoc/UBL-OrderResponseSimple-2.1.xsd" identifierDiscriminators = [{ xpathExpression: "/sbd:StandardBusinessDocument/root:OrderResponseSimple/cbc:CustomizationID" - xpathExpectedResult: "OIOUBL-2\\.(01|02|1)" + xpathExpectedResult: "OIOUBL-2\\.1" }] namespaces = [ { namespace: "http://www.unece.org/cefact/namespaces/StandardBusinessDocumentHeader", prefix: "sbd" }, @@ -296,7 +301,7 @@ document.type = { schemaPath: "META-INF/Schemas/UBL_v2.1/maindoc/UBL-OrderResponse-2.1.xsd" identifierDiscriminators = [{ xpathExpression: "/sbd:StandardBusinessDocument/root:OrderResponse/cbc:CustomizationID" - xpathExpectedResult: "OIOUBL-2\\.(01|02|1)" + xpathExpectedResult: "OIOUBL-2\\.1" }] namespaces = [ { namespace: "http://www.unece.org/cefact/namespaces/StandardBusinessDocumentHeader", prefix: "sbd" }, @@ -325,7 +330,7 @@ document.type = { schemaPath: "META-INF/Schemas/UBL_v2.1/maindoc/UBL-OrderCancellation-2.1.xsd" identifierDiscriminators = [{ xpathExpression: "/sbd:StandardBusinessDocument/root:OrderCancellation/cbc:CustomizationID" - xpathExpectedResult: "OIOUBL-2\\.(01|02|1)" + xpathExpectedResult: "OIOUBL-2\\.1" }] namespaces = [ { namespace: "http://www.unece.org/cefact/namespaces/StandardBusinessDocumentHeader", prefix: "sbd" }, @@ -354,7 +359,7 @@ document.type = { schemaPath: "META-INF/Schemas/UBL_v2.1/maindoc/UBL-OrderChange-2.1.xsd" identifierDiscriminators = [{ xpathExpression: "/sbd:StandardBusinessDocument/root:OrderChange/cbc:CustomizationID" - xpathExpectedResult: "OIOUBL-2\\.(01|02|1)" + xpathExpectedResult: "OIOUBL-2\\.1" }] namespaces = [ { namespace: "http://www.unece.org/cefact/namespaces/StandardBusinessDocumentHeader", prefix: "sbd" }, @@ -383,7 +388,7 @@ document.type = { schemaPath: "META-INF/Schemas/UBL_v2.1/maindoc/UBL-Catalogue-2.1.xsd" identifierDiscriminators = [{ xpathExpression: "/sbd:StandardBusinessDocument/root:Catalogue/cbc:CustomizationID" - xpathExpectedResult: "OIOUBL-2\\.(01|02|1)" + xpathExpectedResult: "OIOUBL-2\\.1" }] namespaces = [ { namespace: "http://www.unece.org/cefact/namespaces/StandardBusinessDocumentHeader", prefix: "sbd" }, @@ -412,7 +417,7 @@ document.type = { schemaPath: "META-INF/Schemas/UBL_v2.1/maindoc/UBL-CatalogueDeletion-2.1.xsd" identifierDiscriminators = [{ xpathExpression: "/sbd:StandardBusinessDocument/root:CatalogueDeletion/cbc:CustomizationID" - xpathExpectedResult: "OIOUBL-2\\.(01|02|1)" + xpathExpectedResult: "OIOUBL-2\\.1" }] namespaces = [ { namespace: "http://www.unece.org/cefact/namespaces/StandardBusinessDocumentHeader", prefix: "sbd" }, @@ -441,7 +446,7 @@ document.type = { schemaPath: "META-INF/Schemas/UBL_v2.1/maindoc/UBL-CatalogueRequest-2.1.xsd" identifierDiscriminators = [{ xpathExpression: "/sbd:StandardBusinessDocument/root:CatalogueRequest/cbc:CustomizationID" - xpathExpectedResult: "OIOUBL-2\\.(01|02|1)" + xpathExpectedResult: "OIOUBL-2\\.1" }] namespaces = [ { namespace: "http://www.unece.org/cefact/namespaces/StandardBusinessDocumentHeader", prefix: "sbd" }, @@ -470,7 +475,7 @@ document.type = { schemaPath: "META-INF/Schemas/UBL_v2.1/maindoc/UBL-CatalogueItemSpecificationUpdate-2.1.xsd" identifierDiscriminators = [{ xpathExpression: "/sbd:StandardBusinessDocument/root:CatalogueItemSpecificationUpdate/cbc:CustomizationID" - xpathExpectedResult: "OIOUBL-2\\.(01|02|1)" + xpathExpectedResult: "OIOUBL-2\\.1" }] namespaces = [ { namespace: "http://www.unece.org/cefact/namespaces/StandardBusinessDocumentHeader", prefix: "sbd" }, @@ -499,7 +504,7 @@ document.type = { schemaPath: "META-INF/Schemas/UBL_v2.1/maindoc/UBL-CataloguePricingUpdate-2.1.xsd" identifierDiscriminators = [{ xpathExpression: "/sbd:StandardBusinessDocument/root:CataloguePricingUpdate/cbc:CustomizationID" - xpathExpectedResult: "OIOUBL-2\\.(01|02|1)" + xpathExpectedResult: "OIOUBL-2\\.1" }] namespaces = [ { namespace: "http://www.unece.org/cefact/namespaces/StandardBusinessDocumentHeader", prefix: "sbd" }, @@ -1159,4 +1164,258 @@ document.type = { ] messageLevelResponseType: "PEPPOL_MESSAGE_LEVEL_RESPONSE" } + + # Nemkonto document types + nemkontoNKSPayment = { + friendlyName: "NemKonto - NKSPayment" + payloadRootLocalName: "NKSPayment" + payloadRootNamespace: "http://rep.oio.dk/oes.dk/nemkonto/xml/schemas/2006/05/01/" + schemaPath: "META-INF/Schemas/NemKonto/nemkonto/xml/schemas/2006/05/01/NKS_NKSPayment.xsd" + identifierDiscriminators = [] + namespaces = [ + { namespace: "http://www.unece.org/cefact/namespaces/StandardBusinessDocumentHeader", prefix: "sbd" }, + { namespace: "http://rep.oio.dk/oes.dk/nemkonto.swift/xml/schemas/2006/05/01/", prefix: "swift" }, + { namespace: "http://rep.oio.dk/oes.dk/nemkonto.ebms/xml/schemas/2006/05/01/", prefix: "ebms" }, + { namespace: "http://rep.oio.dk/oes.dk/nemkonto/xml/schemas/2006/05/01/", prefix: "root"} + ] + schematronDocuments = [ + { + schematronDocumentPath: "META-INF/Schematron/NemKonto/NemKonto.xsl" + errorXPath: "/Schematron/Error" + errorMessageXPath: "Description" + errorLocationXPath: "Xpath" + } + ] + preloadSchema: false + preloadSchematron: false + messageLevelResponseType: "OIOUBL_APPLICATION_RESPONSE" + } + + nemkontoNKSReceipt0 = { + friendlyName: "NemKonto - NKSReceipt0" + payloadRootLocalName: "NKSReceipt0" + payloadRootNamespace: "http://rep.oio.dk/oes.dk/nemkonto/xml/schemas/2006/05/01/" + schemaPath: "META-INF/Schemas/NemKonto/nemkonto/xml/schemas/2006/05/01/NKS_NKSReceipt0.xsd" + identifierDiscriminators = [] + namespaces = [ + { namespace: "http://www.unece.org/cefact/namespaces/StandardBusinessDocumentHeader", prefix: "sbd" }, + { namespace: "http://rep.oio.dk/oes.dk/nemkonto.swift/xml/schemas/2006/05/01/", prefix: "swift" }, + { namespace: "http://rep.oio.dk/oes.dk/nemkonto.ebms/xml/schemas/2006/05/01/", prefix: "ebms" }, + { namespace: "http://rep.oio.dk/oes.dk/nemkonto/xml/schemas/2006/05/01/", prefix: "root"} + ] + schematronDocuments = [ + { + schematronDocumentPath: "META-INF/Schematron/NemKonto/NemKonto.xsl" + errorXPath: "/Schematron/Error" + errorMessageXPath: "Description" + errorLocationXPath: "Xpath" + } + ] + preloadSchema: false + preloadSchematron: false + messageLevelResponseType: "OIOUBL_APPLICATION_RESPONSE" + } + + nemkontoNKSReceipt1 = { + friendlyName: "NemKonto - NKSReceipt1" + payloadRootLocalName: "NKSReceipt1" + payloadRootNamespace: "http://rep.oio.dk/oes.dk/nemkonto/xml/schemas/2006/05/01/" + schemaPath: "META-INF/Schemas/NemKonto/nemkonto/xml/schemas/2006/05/01/NKS_NKSReceipt1.xsd" + identifierDiscriminators = [] + namespaces = [ + { namespace: "http://www.unece.org/cefact/namespaces/StandardBusinessDocumentHeader", prefix: "sbd" }, + { namespace: "http://rep.oio.dk/oes.dk/nemkonto.swift/xml/schemas/2006/05/01/", prefix: "swift" }, + { namespace: "http://rep.oio.dk/oes.dk/nemkonto.ebms/xml/schemas/2006/05/01/", prefix: "ebms" }, + { namespace: "http://rep.oio.dk/oes.dk/nemkonto/xml/schemas/2006/05/01/", prefix: "root"} + ] + schematronDocuments = [ + { + schematronDocumentPath: "META-INF/Schematron/NemKonto/NemKonto.xsl" + errorXPath: "/Schematron/Error" + errorMessageXPath: "Description" + errorLocationXPath: "Xpath" + } + ] + preloadSchema: false + preloadSchematron: false + messageLevelResponseType: "OIOUBL_APPLICATION_RESPONSE" + } + + nemkontoNKSResponse2 = { + friendlyName: "NemKonto - NKSResponse2" + payloadRootLocalName: "NKSResponse2" + payloadRootNamespace: "http://rep.oio.dk/oes.dk/nemkonto/xml/schemas/2006/05/01/" + schemaPath: "META-INF/Schemas/NemKonto/nemkonto/xml/schemas/2006/05/01/NKS_NKSResponse2.xsd" + identifierDiscriminators = [] + namespaces = [ + { namespace: "http://www.unece.org/cefact/namespaces/StandardBusinessDocumentHeader", prefix: "sbd" }, + { namespace: "http://rep.oio.dk/oes.dk/nemkonto.swift/xml/schemas/2006/05/01/", prefix: "swift" }, + { namespace: "http://rep.oio.dk/oes.dk/nemkonto.ebms/xml/schemas/2006/05/01/", prefix: "ebms" }, + { namespace: "http://rep.oio.dk/oes.dk/nemkonto/xml/schemas/2006/05/01/", prefix: "root"} + ] + schematronDocuments = [ + { + schematronDocumentPath: "META-INF/Schematron/NemKonto/NemKonto.xsl" + errorXPath: "/Schematron/Error" + errorMessageXPath: "Description" + errorLocationXPath: "Xpath" + } + ] + preloadSchema: false + preloadSchematron: false + messageLevelResponseType: "OIOUBL_APPLICATION_RESPONSE" + } + + nemkontoNKSResponse5 = { + friendlyName: "NemKonto - NKSResponse5" + payloadRootLocalName: "NKSResponse5" + payloadRootNamespace: "http://rep.oio.dk/oes.dk/nemkonto/xml/schemas/2006/05/01/" + schemaPath: "META-INF/Schemas/NemKonto/nemkonto/xml/schemas/2006/05/01/NKS_NKSResponse5.xsd" + identifierDiscriminators = [] + namespaces = [ + { namespace: "http://www.unece.org/cefact/namespaces/StandardBusinessDocumentHeader", prefix: "sbd" }, + { namespace: "http://rep.oio.dk/oes.dk/nemkonto.swift/xml/schemas/2006/05/01/", prefix: "swift" }, + { namespace: "http://rep.oio.dk/oes.dk/nemkonto.ebms/xml/schemas/2006/05/01/", prefix: "ebms" }, + { namespace: "http://rep.oio.dk/oes.dk/nemkonto/xml/schemas/2006/05/01/", prefix: "root"} + ] + schematronDocuments = [ + { + schematronDocumentPath: "META-INF/Schematron/NemKonto/NemKonto.xsl" + errorXPath: "/Schematron/Error" + errorMessageXPath: "Description" + errorLocationXPath: "Xpath" + } + ] + preloadSchema: false + preloadSchematron: false + messageLevelResponseType: "OIOUBL_APPLICATION_RESPONSE" + } + + nemkontoNKSResponse7 = { + friendlyName: "NemKonto - NKSResponse7" + payloadRootLocalName: "NKSResponse7" + payloadRootNamespace: "http://rep.oio.dk/oes.dk/nemkonto/xml/schemas/2006/05/01/" + schemaPath: "META-INF/Schemas/NemKonto/nemkonto/xml/schemas/2006/05/01/NKS_NKSResponse7.xsd" + identifierDiscriminators = [] + namespaces = [ + { namespace: "http://www.unece.org/cefact/namespaces/StandardBusinessDocumentHeader", prefix: "sbd" }, + { namespace: "http://rep.oio.dk/oes.dk/nemkonto.swift/xml/schemas/2006/05/01/", prefix: "swift" }, + { namespace: "http://rep.oio.dk/oes.dk/nemkonto.ebms/xml/schemas/2006/05/01/", prefix: "ebms" }, + { namespace: "http://rep.oio.dk/oes.dk/nemkonto/xml/schemas/2006/05/01/", prefix: "root"} + ] + schematronDocuments = [ + { + schematronDocumentPath: "META-INF/Schematron/NemKonto/NemKonto.xsl" + errorXPath: "/Schematron/Error" + errorMessageXPath: "Description" + errorLocationXPath: "Xpath" + } + ] + preloadSchema: false + preloadSchematron: false + messageLevelResponseType: "OIOUBL_APPLICATION_RESPONSE" + } + + nemkontoNKSResponse8 = { + friendlyName: "NemKonto - NKSResponse8" + payloadRootLocalName: "NKSResponse8" + payloadRootNamespace: "http://rep.oio.dk/oes.dk/nemkonto/xml/schemas/2006/05/01/" + schemaPath: "META-INF/Schemas/NemKonto/nemkonto/xml/schemas/2006/05/01/NKS_NKSResponse8.xsd" + identifierDiscriminators = [] + namespaces = [ + { namespace: "http://www.unece.org/cefact/namespaces/StandardBusinessDocumentHeader", prefix: "sbd" }, + { namespace: "http://rep.oio.dk/oes.dk/nemkonto.swift/xml/schemas/2006/05/01/", prefix: "swift" }, + { namespace: "http://rep.oio.dk/oes.dk/nemkonto.ebms/xml/schemas/2006/05/01/", prefix: "ebms" }, + { namespace: "http://rep.oio.dk/oes.dk/nemkonto/xml/schemas/2006/05/01/", prefix: "root"} + ] + schematronDocuments = [ + { + schematronDocumentPath: "META-INF/Schematron/NemKonto/NemKonto.xsl" + errorXPath: "/Schematron/Error" + errorMessageXPath: "Description" + errorLocationXPath: "Xpath" + } + ] + preloadSchema: false + preloadSchematron: false + messageLevelResponseType: "OIOUBL_APPLICATION_RESPONSE" + } + + nemkontoNKSResponse9 = { + friendlyName: "NemKonto - NKSResponse9" + payloadRootLocalName: "NKSResponse9" + payloadRootNamespace: "http://rep.oio.dk/oes.dk/nemkonto/xml/schemas/2006/05/01/" + schemaPath: "META-INF/Schemas/NemKonto/nemkonto/xml/schemas/2006/05/01/NKS_NKSResponse9.xsd" + identifierDiscriminators = [] + namespaces = [ + { namespace: "http://www.unece.org/cefact/namespaces/StandardBusinessDocumentHeader", prefix: "sbd" }, + { namespace: "http://rep.oio.dk/oes.dk/nemkonto.swift/xml/schemas/2006/05/01/", prefix: "swift" }, + { namespace: "http://rep.oio.dk/oes.dk/nemkonto.ebms/xml/schemas/2006/05/01/", prefix: "ebms" }, + { namespace: "http://rep.oio.dk/oes.dk/nemkonto/xml/schemas/2006/05/01/", prefix: "root"} + ] + schematronDocuments = [ + { + schematronDocumentPath: "META-INF/Schematron/NemKonto/NemKonto.xsl" + errorXPath: "/Schematron/Error" + errorMessageXPath: "Description" + errorLocationXPath: "Xpath" + } + ] + preloadSchema: false + preloadSchematron: false + messageLevelResponseType: "OIOUBL_APPLICATION_RESPONSE" + } + + # NemKonto PU + nemkontoPURequest = { + friendlyName: "NemKonto PU - NemkontoPrivatUdbetalerTransporterRequest" + payloadRootLocalName: "NemkontoPrivatUdbetalerTransporterRequest" + payloadRootNamespace: "http://rep.oio.dk/oes.dk/nemkonto/xml/schemas/2007/10/01/" + schemaPath: "META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/NKSPU_NemkontoPrivatUdbetalerTransporterRequest.xsd" + identifierDiscriminators = [] + namespaces = [ + { namespace: "http://www.unece.org/cefact/namespaces/StandardBusinessDocumentHeader", prefix: "sbd" }, + { namespace: "http://rep.oio.dk/ean/xml/schemas/2005/01/10/", prefix: "ean" }, + { namespace: "http://rep.oio.dk/cvr.dk/xml/schemas/2005/03/22/", prefix: "cvr" }, + { namespace: "http://rep.oio.dk/cpr.dk/xml/schemas/core/2005/03/18/", prefix: "cpr" }, + { namespace: "http://rep.oio.dk/oes.dk/nemkonto/xml/schemas/2007/10/01/", prefix: "root"} + ] + schematronDocuments = [ + { + schematronDocumentPath: "META-INF/Schematron/NemKonto/NemKonto.xsl" + errorXPath: "/Schematron/Error" + errorMessageXPath: "Description" + errorLocationXPath: "Xpath" + } + ] + preloadSchema: false + preloadSchematron: false + messageLevelResponseType: "OIOUBL_APPLICATION_RESPONSE" + } + + nemkontoPUResponse = { + friendlyName: "NemKonto PU - NemkontoPrivatUdbetalerTransporterResponse" + payloadRootLocalName: "NemkontoPrivatUdbetalerTransporterResponse" + payloadRootNamespace: "http://rep.oio.dk/oes.dk/nemkonto/xml/schemas/2007/10/01/" + schemaPath: "META-INF/Schemas/NemKonto/nemkonto-pu/xml/schemas/2007/10/01/NKSPU_NemkontoPrivatUdbetalerTransporterResponse.xsd" + identifierDiscriminators = [] + namespaces = [ + { namespace: "http://www.unece.org/cefact/namespaces/StandardBusinessDocumentHeader", prefix: "sbd" }, + { namespace: "http://rep.oio.dk/ean/xml/schemas/2005/01/10/", prefix: "ean" }, + { namespace: "http://rep.oio.dk/cvr.dk/xml/schemas/2005/03/22/", prefix: "cvr" }, + { namespace: "http://rep.oio.dk/cpr.dk/xml/schemas/core/2005/03/18/", prefix: "cpr" }, + { namespace: "http://rep.oio.dk/oes.dk/nemkonto/xml/schemas/2007/10/01/", prefix: "root"} + ] + schematronDocuments = [ + { + schematronDocumentPath: "META-INF/Schematron/NemKonto/NemKonto.xsl" + errorXPath: "/Schematron/Error" + errorMessageXPath: "Description" + errorLocationXPath: "Xpath" + } + ] + preloadSchema: false + preloadSchematron: false + messageLevelResponseType: "OIOUBL_APPLICATION_RESPONSE" + } } diff --git a/src/test/java/dk/erst/oxalis/as4/AbstractXmlTest.java b/src/test/java/dk/erst/oxalis/as4/AbstractXmlTest.java index f5e576c4c990d66f6b8ea049e85f2bd3fa401d70..dd8dbb57955b3a8f1d53a0d9a28a6bc4a4c1dc02 100644 --- a/src/test/java/dk/erst/oxalis/as4/AbstractXmlTest.java +++ b/src/test/java/dk/erst/oxalis/as4/AbstractXmlTest.java @@ -1,10 +1,5 @@ package dk.erst.oxalis.as4; -import dk.erst.oxalis.as4.persistence.model.Account; -import dk.erst.oxalis.as4.persistence.model.Message; -import dk.erst.oxalis.as4.persistence.model.MessageContent; -import dk.erst.oxalis.as4.persistence.model.MessageDirection; -import dk.erst.oxalis.as4.persistence.model.MessageStatus; import dk.erst.oxalis.as4.util.DocumentBuilderProvider; import dk.erst.oxalis.as4.util.DocumentUtil; import dk.erst.oxalis.as4.util.XPathUtil; @@ -22,24 +17,16 @@ import org.w3c.dom.Node; import org.xml.sax.SAXException; import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; -import javax.xml.parsers.ParserConfigurationException; -import javax.xml.transform.Transformer; import javax.xml.transform.TransformerException; -import javax.xml.transform.TransformerFactory; -import javax.xml.transform.dom.DOMSource; -import javax.xml.transform.stream.StreamResult; import javax.xml.xpath.XPath; import javax.xml.xpath.XPathConstants; import javax.xml.xpath.XPathExpressionException; import javax.xml.xpath.XPathFactory; import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.Base64; import java.util.Date; -import java.util.UUID; public abstract class AbstractXmlTest { @@ -52,6 +39,15 @@ public abstract class AbstractXmlTest { } } + protected Document wrapInSBDAsDocument(byte[] payload) throws Exception { + DocumentBuilder builder = new DocumentBuilderProvider().get(); + + try(ByteArrayInputStream bis = new ByteArrayInputStream(wrapInSbd(payload))) { + Document doc = builder.parse(bis); + return doc; + } + } + protected Document loadAndWrapInSBD(String xmlPath, Header header) throws Exception { DocumentBuilder builder = new DocumentBuilderProvider().get(); @@ -80,15 +76,18 @@ public abstract class AbstractXmlTest { private byte[] wrapInSbd(String xmlPath) throws Exception { try(InputStream in = this.getClass().getClassLoader().getResourceAsStream(xmlPath)) { - SbdhWrapper wrapper = new SbdhWrapper(); - DocumentBuilder builder = new DocumentBuilderProvider().get(); - byte[] xmlFile = IOUtils.toByteArray(in); - Header dummyHeader = getDummyHeader(builder, xmlFile); - try(ByteArrayInputStream wrapperInput = new ByteArrayInputStream(xmlFile)) { - return wrapper.wrap(wrapperInput, dummyHeader); - } + return wrapInSbd(xmlFile); + } + } + + protected byte[] wrapInSbd(byte[] payload) throws Exception { + DocumentBuilder builder = new DocumentBuilderProvider().get(); + Header dummyHeader = getDummyHeader(builder, payload); + SbdhWrapper wrapper = new SbdhWrapper(); + try(ByteArrayInputStream wrapperInput = new ByteArrayInputStream(payload)) { + return wrapper.wrap(wrapperInput, dummyHeader); } } @@ -183,7 +182,11 @@ public abstract class AbstractXmlTest { } } - protected void appendNemhandelSignatureScopeNode(Document doc, byte[] content) throws XPathExpressionException { + protected void appendNemhandelSignatureScopeNode(Document doc, byte[] content, EDeliverySpecification specVersion) throws XPathExpressionException { + appendNemhandelSignatureScopeNode(doc, content, specVersion != null ? specVersion.value() : null); + } + + protected void appendNemhandelSignatureScopeNode(Document doc, byte[] content, String specVersion) throws XPathExpressionException { Element scope = doc.createElementNS(Ns.SBDH, "Scope"); Element type = doc.createElementNS(Ns.SBDH, "Type"); @@ -194,6 +197,12 @@ public abstract class AbstractXmlTest { instanceIdent.setTextContent(Base64.getEncoder().encodeToString(content)); scope.appendChild(instanceIdent); + if(specVersion != null) { + Element identifier = doc.createElementNS(Ns.SBDH, "Identifier"); + identifier.setTextContent(specVersion); + scope.appendChild(identifier); + } + XPathFactory xPathFactory = XPathFactory.newInstance(); XPath xpath = xPathFactory.newXPath(); MapNamespaceContext nsCtx = new MapNamespaceContext(); diff --git a/src/test/java/dk/erst/oxalis/as4/NemhandelModuleTests.java b/src/test/java/dk/erst/oxalis/as4/NemhandelModuleTests.java new file mode 100644 index 0000000000000000000000000000000000000000..5b136e4796acb57e3032542590939d071ee36383 --- /dev/null +++ b/src/test/java/dk/erst/oxalis/as4/NemhandelModuleTests.java @@ -0,0 +1,100 @@ +package dk.erst.oxalis.as4; + +import com.google.inject.AbstractModule; +import com.google.inject.Guice; +import com.google.inject.Injector; +import com.google.inject.util.Modules; +import dk.erst.oxalis.as4.rest.resources.AbstractJaxRsTest; +import network.oxalis.api.evidence.EvidenceFactory; +import network.oxalis.api.outbound.Transmitter; +import network.oxalis.as4.common.As4CommonModule; +import network.oxalis.as4.common.DummyHeaderParser; +import network.oxalis.commons.certvalidator.api.CrlFetcher; +import network.oxalis.commons.config.ConfigModule; +import network.oxalis.commons.filesystem.FileSystemModule; +import network.oxalis.commons.guice.GuiceModuleLoader; +import network.oxalis.commons.header.HeaderModule; +import network.oxalis.commons.header.SbdhHeaderParser; +import network.oxalis.commons.mode.ModeModule; +import network.oxalis.commons.mode.OxalisCrlFetcher; +import network.oxalis.commons.mode.OxalisOcspFetcher; +import network.oxalis.commons.security.KeyStoreConf; +import network.oxalis.commons.settings.SettingsBuilder; +import network.oxalis.commons.tracing.TracingModule; +import network.oxalis.outbound.lookup.LookupModule; +import network.oxalis.outbound.transmission.TransmissionModule; +import network.oxalis.pkix.ocsp.api.OcspFetcher; +import network.oxalis.test.dummy.DummyPkiModule; +import network.oxalis.vefa.peppol.lookup.LookupClient; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.wss4j.common.ext.WSSecurityException; +import org.hamcrest.CoreMatchers; +import org.hamcrest.MatcherAssert; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; +import org.xml.sax.SAXException; + +import javax.xml.bind.JAXBException; +import javax.xml.stream.XMLStreamException; +import java.io.IOException; + +public class NemhandelModuleTests { + + @Mock + private OxalisCrlFetcher mockCrlFetcher; + + @Mock + private OxalisOcspFetcher mockOcspFetcher; + + @Mock + private CloseableHttpClient mockHttpClient; + + @Mock + private Transmitter transmitterMock; + + @Mock + private LookupClient lookupClientMock; + + @Mock + private EvidenceFactory evidenceFactoryMock; + + private Injector injector; + + + @BeforeMethod + public void setup() throws IOException, XMLStreamException, JAXBException, SAXException { + MockitoAnnotations.initMocks(this); + + injector = Guice.createInjector( + Modules.override( + new GuiceModuleLoader() + ).with( + new DummyPkiModule(), + new AbstractModule() { + @Override + protected void configure() { + SettingsBuilder.with(binder(), KeyStoreConf.class); + bind(CrlFetcher.class).toInstance(mockCrlFetcher); + bind(OcspFetcher.class).toInstance(mockOcspFetcher); + bind(CloseableHttpClient.class).toInstance(mockHttpClient); + bind(Transmitter.class).toInstance(transmitterMock); + bind(LookupClient.class).toInstance(lookupClientMock); + bind(EvidenceFactory.class).toInstance(evidenceFactoryMock); + } + } + ) + ); + } + + @Test + public void testAs4SignatureErrorsCanFindWSS4jResourceBundleAfterNemhandelExtensionsAreLoaded() { + try { + throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "certpath", new Object[] {"No trusted certs found"} + ); + } catch (WSSecurityException e) { + MatcherAssert.assertThat(e.getMessage(), CoreMatchers.equalTo("Error during certificate path validation: No trusted certs found")); + } + } +} diff --git a/src/test/java/dk/erst/oxalis/as4/async/AsyncValidationRunnableImplTest.java b/src/test/java/dk/erst/oxalis/as4/async/AsyncValidationRunnableImplTest.java index 484fbde7418d8cef9297559c2bf5c1d9655effbc..aecf1bb706618389afda5b8e4ea0260b3287708f 100644 --- a/src/test/java/dk/erst/oxalis/as4/async/AsyncValidationRunnableImplTest.java +++ b/src/test/java/dk/erst/oxalis/as4/async/AsyncValidationRunnableImplTest.java @@ -12,15 +12,12 @@ import dk.erst.oxalis.as4.async.response.MessageLevelResponseCreationException; import dk.erst.oxalis.as4.async.response.MessageLevelResponseFactory; import dk.erst.oxalis.as4.config.documenttype.*; import dk.erst.oxalis.as4.error.ErrorCodes; -import dk.erst.oxalis.as4.handlers.NemhandelPersister; import dk.erst.oxalis.as4.handlers.OutboundException; import dk.erst.oxalis.as4.handlers.SendSbdHandler; import dk.erst.oxalis.as4.handlers.dto.ValidationMessage; import dk.erst.oxalis.as4.handlers.dto.ValidationResult; import dk.erst.oxalis.as4.jdbc.JdbcModule; -import dk.erst.oxalis.as4.mode.Mode; import dk.erst.oxalis.as4.persistence.model.*; -import dk.erst.oxalis.as4.signature.TransferDelegationException; import dk.erst.oxalis.as4.util.*; import dk.erst.oxalis.as4.validation.MessageValidator; import dk.erst.oxalis.as4.validation.ValidationException; @@ -39,14 +36,11 @@ import org.mockito.MockitoAnnotations; import org.testng.Assert; import org.testng.annotations.*; import org.w3c.dom.Document; -import org.xml.sax.SAXException; import javax.persistence.EntityManager; import javax.persistence.EntityTransaction; -import javax.xml.bind.JAXBException; import javax.xml.transform.TransformerException; import javax.xml.xpath.XPath; -import javax.xml.xpath.XPathExpressionException; import javax.xml.xpath.XPathFactory; import java.io.IOException; import java.nio.charset.StandardCharsets; @@ -67,12 +61,9 @@ public class AsyncValidationRunnableImplTest extends AbstractXmlTest { private Provider entityManagerProviderMock; private MessageValidator messageValidatorMock; - private DocumentSender documentSenderMock; - private NemhandelPersister nemhandelPersisterMock; private ExecutorService executorService; private Key executorServiceBindingKey = Key.get(ExecutorService.class, NemhandelExecutorService.class); private Account account; - private Mode modeMock; private MessageLevelResponseFactory messageLevelResponseFactoryMock; private MessageLevelResponseConverter messageLevelResponseConverterMock; private DocumentTypeConfigResolver documentTypeConfigResolverMock; @@ -82,26 +73,20 @@ public class AsyncValidationRunnableImplTest extends AbstractXmlTest { private DocumentTypeConfig dummyConfig; @BeforeClass - public void setupBeforeClass() throws OutboundException, MessageLevelResponseCreationException, XPathExpressionException, IOException, TransformerException, SAXException, JAXBException, TransferDelegationException, MessageLevelResponseConverterException, DocumentTypeConfigResolverException { + public void setupBeforeClass() { Security.addProvider(new BouncyCastleProvider()); MockitoAnnotations.initMocks(this); messageValidatorMock = Mockito.mock(MessageValidator.class); - documentSenderMock = Mockito.mock(DocumentSender.class); - nemhandelPersisterMock = Mockito.mock(NemhandelPersister.class); messageLevelResponseFactoryMock = Mockito.mock(MessageLevelResponseFactory.class); sendSbdHandlerMock = Mockito.mock(SendSbdHandler.class); messageLevelResponseConverterMock = Mockito.mock(MessageLevelResponseConverter.class); - modeMock = Mockito.mock(Mode.class); documentTypeConfigResolverMock = Mockito.mock(DocumentTypeConfigResolver.class); injector = createInjector(new AbstractModule() { @Override protected void configure() { bind(MessageValidator.class).toInstance(messageValidatorMock); - bind(DocumentSender.class).toInstance(documentSenderMock); - bind(NemhandelPersister.class).toInstance(nemhandelPersisterMock); - bind(Mode.class).toInstance(modeMock); bind(MessageLevelResponseFactory.class).toInstance(messageLevelResponseFactoryMock); bind(SendSbdHandler.class).toInstance(sendSbdHandlerMock); bind(MessageLevelResponseConverter.class).toInstance(messageLevelResponseConverterMock); @@ -134,9 +119,6 @@ public class AsyncValidationRunnableImplTest extends AbstractXmlTest { em.persist(account); }); - doReturn("87654321").when(nemhandelPersisterMock).getReceiverCvrNumber(any()); - doReturn("signed content".getBytes(StandardCharsets.UTF_8)).when(documentSenderMock).signMessage(any(), any(), any(), any()); - when(modeMock.isNemhandelEdeliveryMode()).thenReturn(true); dummyMLR = load("sbd-examples/SBD_MessageLevelResponse.xml"); doReturn(dummyMLR).when(messageLevelResponseFactoryMock).createMessageLevelResponse(any()); dummyOIOUBLApplicationResponse = load("sbd-examples/SBD_OIOUBL_ApplicationResponse.xml"); @@ -166,12 +148,9 @@ public class AsyncValidationRunnableImplTest extends AbstractXmlTest { Mockito.reset(entityManagerProviderMock); Mockito.reset(messageValidatorMock); - Mockito.reset(documentSenderMock); - Mockito.reset(nemhandelPersisterMock); Mockito.reset(messageLevelResponseFactoryMock); Mockito.reset(sendSbdHandlerMock); Mockito.reset(messageLevelResponseConverterMock); - Mockito.reset(modeMock); Mockito.reset(documentTypeConfigResolverMock); } @@ -211,96 +190,6 @@ public class AsyncValidationRunnableImplTest extends AbstractXmlTest { verify(unitOfWorkMock).end(); } - @Test - public void testSignsMessage() throws ValidationException, InterruptedException, ExecutionException, TimeoutException { - when(modeMock.isNemhandelEdeliveryMode()).thenReturn(true); - final String messageUuid = UUID.randomUUID().toString(); - doInTx(em -> { - Message message = new Message( - account, - MessageDirection.IN, - "iso6523-actorid-upis::sender", - "iso6523-actorid-upis::receiver", - "channel", - messageUuid, - "busdox-docid-qns::urn:oasis:names:specification:ubl:schema:xsd:Invoice-2::Invoice##OIOUBL-2.1::2.1", - new MessageContent("".getBytes(StandardCharsets.UTF_8))); - message.setPeppolProcessId("cenbii-procid-ubl::proces"); - message.setStatus(MessageStatus.CREATED); - em.persist(account); - em.persist(message); - }); - - ValidationResult result = new ValidationResult(); - List empty = new LinkedList<>(); - doReturn(new DataPairs.Tuple2<>(empty, result)).when(messageValidatorMock).validate(argThat(msg -> messageUuid.equals(msg.getMessageUuid())), eq(ValidationType.ASYNCHRONOUS), any()); - - Runnable runnable = new AsyncProcessingRunnable(injector, messageUuid); - Future future = executorService.submit(runnable); - future.get(60, TimeUnit.SECONDS); // wait for async processing to complete - - doInTx(em -> { - em.clear(); // clear the entitymanager to force reload of entities which may have been changed by a separate thread - Message message = em.createQuery("from Message m where m.messageUuid = :messageUuid", Message.class) - .setParameter("messageUuid", messageUuid) - .getSingleResult(); - MatcherAssert.assertThat(message, CoreMatchers.notNullValue()); - MatcherAssert.assertThat(message.getStatus(), CoreMatchers.equalTo(MessageStatus.RECEIVED)); - MatcherAssert.assertThat(message.getMessageContent().getData(), CoreMatchers.equalTo("signed content".getBytes(StandardCharsets.UTF_8))); - - List logs = em.createQuery("from MessageLog l where l.message = :message order by l.id") - .setParameter("message", message) - .getResultList(); - MatcherAssert.assertThat(logs, CoreMatchers.notNullValue()); - MatcherAssert.assertThat(logs.size(), CoreMatchers.equalTo(4)); // Signing init, overdragelses forhold init, overdragelses forhold done, signing done - }); - } - - @Test - public void testDoesNotSignMessage() throws ValidationException, InterruptedException, ExecutionException, TimeoutException { - when(modeMock.isNemhandelEdeliveryMode()).thenReturn(false); - final String messageUuid = UUID.randomUUID().toString(); - doInTx(em -> { - Message message = new Message( - account, - MessageDirection.IN, - "iso6523-actorid-upis::sender", - "iso6523-actorid-upis::receiver", - "channel", - messageUuid, - "busdox-docid-qns::urn:oasis:names:specification:ubl:schema:xsd:Invoice-2::Invoice##OIOUBL-2.1::2.1", - new MessageContent("".getBytes(StandardCharsets.UTF_8))); - message.setPeppolProcessId("cenbii-procid-ubl::proces"); - message.setStatus(MessageStatus.CREATED); - em.persist(account); - em.persist(message); - }); - - ValidationResult result = new ValidationResult(); - List empty = new LinkedList<>(); - doReturn(new DataPairs.Tuple2<>(empty, result)).when(messageValidatorMock).validate(argThat(msg -> messageUuid.equals(msg.getMessageUuid())), eq(ValidationType.ASYNCHRONOUS), any()); - - Runnable runnable = new AsyncProcessingRunnable(injector, messageUuid); - Future future = executorService.submit(runnable); - future.get(60, TimeUnit.SECONDS); // wait for async processing to complete - - doInTx(em -> { - em.clear(); // clear the entitymanager to force reload of entities which may have been changed by a separate thread - Message message = em.createQuery("from Message m where m.messageUuid = :messageUuid", Message.class) - .setParameter("messageUuid", messageUuid) - .getSingleResult(); - MatcherAssert.assertThat(message, CoreMatchers.notNullValue()); - MatcherAssert.assertThat(message.getStatus(), CoreMatchers.equalTo(MessageStatus.RECEIVED)); - MatcherAssert.assertThat(message.getMessageContent().getData(), CoreMatchers.equalTo("".getBytes(StandardCharsets.UTF_8))); - - List logs = em.createQuery("from MessageLog l where l.message = :message order by l.id") - .setParameter("message", message) - .getResultList(); - MatcherAssert.assertThat(logs, CoreMatchers.notNullValue()); - MatcherAssert.assertThat(logs.size(), CoreMatchers.equalTo(0)); - }); - } - @Test public void testSavesValidationResultForMessage() throws ValidationException, ExecutionException, InterruptedException, TimeoutException { final String messageUuid = UUID.randomUUID().toString(); @@ -452,7 +341,7 @@ public class AsyncValidationRunnableImplTest extends AbstractXmlTest { } @Test - private void testHandlesUnknownMessageIds() throws ValidationException { + public void testHandlesUnknownMessageIds() throws ValidationException { final String messageUuid = UUID.randomUUID().toString(); doReturn(null).when(messageValidatorMock).validate(eq(null), eq(ValidationType.ASYNCHRONOUS), any()); @@ -467,7 +356,7 @@ public class AsyncValidationRunnableImplTest extends AbstractXmlTest { } @Test - private void testDoesNotProcessOutboundMessages() throws ValidationException, ExecutionException, InterruptedException, TimeoutException { + public void testDoesNotProcessOutboundMessages() throws ValidationException, ExecutionException, InterruptedException, TimeoutException { final String messageUuid = UUID.randomUUID().toString(); doInTx(em -> { Message message = new Message( @@ -502,7 +391,7 @@ public class AsyncValidationRunnableImplTest extends AbstractXmlTest { } @Test - public void testGeneratesMessageLevelResponseForValidationErrors() throws ValidationException, ExecutionException, InterruptedException, TimeoutException, MessageLevelResponseCreationException, JAXBException, TransferDelegationException, IOException, TransformerException, XPathExpressionException, SAXException, OutboundException, MessageLevelResponseConverterException { + public void testGeneratesMessageLevelResponseForValidationErrors() throws ValidationException, ExecutionException, InterruptedException, TimeoutException, MessageLevelResponseCreationException, IOException, TransformerException, OutboundException, MessageLevelResponseConverterException { final String messageUuid = UUID.randomUUID().toString(); doInTx(em -> { Message message = new Message( @@ -610,7 +499,7 @@ public class AsyncValidationRunnableImplTest extends AbstractXmlTest { } @Test - public void testConvertsMessageLevelResponseToOIOUBLApplicationResponseIfOriginalDocumentIsOIOUBL() throws Exception, TransferDelegationException { + public void testConvertsMessageLevelResponseToOIOUBLApplicationResponseIfOriginalDocumentIsOIOUBL() throws Exception { final String messageUuid = UUID.randomUUID().toString(); final byte[] originalDocumentContent = loadAndWrapInSBDAsByteArray("ubl-examples/OIOUBL_Invoice_v2p2.xml"); doInTx(em -> { @@ -1055,6 +944,101 @@ public class AsyncValidationRunnableImplTest extends AbstractXmlTest { }); } + @Test + public void testSavesStacktraceToMessageIfProcessingThrowsException() throws ValidationException, ExecutionException, InterruptedException, TimeoutException { + final String messageUuid = UUID.randomUUID().toString(); + doInTx(em -> { + Message message = new Message( + account, + MessageDirection.IN, + "iso6523-actorid-upis::sender", + "iso6523-actorid-upis::receiver", + "channel", + messageUuid, + "busdox-docid-qns::urn:oasis:names:specification:ubl:schema:xsd:Invoice-2::Invoice##OIOUBL-2.1::2.1", + new MessageContent("".getBytes(StandardCharsets.UTF_8))); + message.setPeppolProcessId("cenbii-procid-ubl::proces"); + message.setStatus(MessageStatus.CREATED); + em.persist(account); + em.persist(message); + }); + + doThrow(ValidationException.class).when(messageValidatorMock).validate(argThat(msg -> messageUuid.equals(msg.getMessageUuid())), eq(ValidationType.ASYNCHRONOUS), any()); + + Runnable runnable = new AsyncProcessingRunnable(injector, messageUuid); + Future future = executorService.submit(runnable); + future.get(60, TimeUnit.SECONDS); // wait for async processing to complete + + doInTx(em -> { + em.clear(); // clear the entitymanager to force reload of entities which may have been changed by a separate thread + Message message = em.createQuery("from Message m where m.messageUuid = :messageUuid", Message.class) + .setParameter("messageUuid", messageUuid) + .getSingleResult(); + MatcherAssert.assertThat(message, CoreMatchers.notNullValue()); + MatcherAssert.assertThat(message.getStacktrace(), CoreMatchers.notNullValue()); + MatcherAssert.assertThat(message.getStacktrace(), CoreMatchers.containsString(ValidationException.class.getName())); + }); + } + + @Test + public void testSetsReferenceToOriginalDocument() throws ValidationException, ExecutionException, InterruptedException, TimeoutException { + final byte[] xml = loadAsByteArray("sbd-examples/SBD_OIOUBL_ApplicationResponse_BusinessReject.xml"); + final String messageUuid = UUID.randomUUID().toString(); + final Message[] messages = new Message[1]; + doInTx(em -> { + messages[0] = new Message( + account, + MessageDirection.IN, + "iso6523-actorid-upis::sender", + "iso6523-actorid-upis::receiver", + "channel", + messageUuid, + "busdox-docid-qns::urn:oasis:names:specification:ubl:schema:xsd:ApplicationResponse-2::ApplicationResponse##OIOUBL-2.1::2.1", + new MessageContent(xml)); + messages[0].setPeppolProcessId("urn:fdc:peppol.eu:poacc:bis:mlr:3"); + messages[0].setStatus(MessageStatus.CREATED); + em.persist(account); + em.persist(messages[0]); + + Message original = new Message( + account, + MessageDirection.OUT, + "iso6523-actorid-upis::sender", + "iso6523-actorid-upis::receiver", + "channel", + "44abcdc4-0258-4c99-a229-601ca8e79759", // original UUID from sbd-examples/SBD_MessageLevelResponse.xml + "busdox-docid-qns::urn:oasis:names:specification:ubl:schema:xsd:Invoice-2::Invoice##OIOUBL-2.1::2.1", + new MessageContent("".getBytes(StandardCharsets.UTF_8)) + ); + original.setStatus(MessageStatus.SENT); + em.persist(original); + }); + + doReturn(emptyValidationResult()).when(messageValidatorMock).validate(argThat(msg -> messageUuid.equals(msg.getMessageUuid())), eq(ValidationType.ASYNCHRONOUS), any()); + + // make sure conversion is needed + dummyConfig.setResponseType(true); + dummyConfig.setMessageLevelResponse(oioublARConfig()); + dummyConfig.setNamespaces(mlrNamespaces()); + + Runnable runnable = new AsyncProcessingRunnable(injector, messageUuid); + Future future = executorService.submit(runnable); + future.get(60, TimeUnit.SECONDS); + + doInTx(em -> { + em.clear(); + Message mlr = em.createQuery("from Message where messageUuid = :messageUuid", Message.class) + .setParameter("messageUuid", messageUuid) + .getResultStream() + .findFirst() + .orElse(null); + + MatcherAssert.assertThat(mlr, CoreMatchers.notNullValue()); + MatcherAssert.assertThat(mlr.getOriginalMessage(), CoreMatchers.notNullValue()); + MatcherAssert.assertThat(mlr.getOriginalMessage().getMessageUuid(), CoreMatchers.equalTo("44abcdc4-0258-4c99-a229-601ca8e79759")); + }); + } + private void doInTx(Consumer action){ Provider provider = injector.getProvider(EntityManager.class); TxUtil.doInTx(provider, action); diff --git a/src/test/java/dk/erst/oxalis/as4/async/response/MessageLevelResponseFactoryTest.java b/src/test/java/dk/erst/oxalis/as4/async/response/MessageLevelResponseFactoryTest.java index 0e8ecf2d69ad2d8f931a17898b2d2449f4e6e27c..82d4efebea792a172b730dafcbabf6da67d4d4de 100644 --- a/src/test/java/dk/erst/oxalis/as4/async/response/MessageLevelResponseFactoryTest.java +++ b/src/test/java/dk/erst/oxalis/as4/async/response/MessageLevelResponseFactoryTest.java @@ -113,13 +113,13 @@ public class MessageLevelResponseFactoryTest extends AbstractXmlTest { Document doc = loadAndWrapInSBD("ubl-examples/OIOUBL_Invoice_v2p2.xml"); byte[] xml = toByteArray(doc); Message message = createTestInvoiceMessage(xml, MessageStatus.ERROR); - MessageLog error1 = NemhandelLog.createMessageLog(message, new ValidationMessage(ValidationMessageType.ERROR, "err-1", "error message 1", "line-1")); + MessageLog error1 = NemhandelLog.createMessageLog(message, new ValidationMessage(ValidationMessageType.ERROR, "err-1", "err-1: error message 1", "line-1")); message.addDocumentLog(error1); - MessageLog error2 = NemhandelLog.createMessageLog(message, new ValidationMessage(ValidationMessageType.ERROR, "err-2", "error message 2", "line-2")); + MessageLog error2 = NemhandelLog.createMessageLog(message, new ValidationMessage(ValidationMessageType.ERROR, "err-2", "err-2: error message 2", "line-2")); message.addDocumentLog(error2); - MessageLog warning1 = NemhandelLog.createMessageLog(message, new ValidationMessage(ValidationMessageType.WARNING, "warn-1", "warning message 1", "line-3")); + MessageLog warning1 = NemhandelLog.createMessageLog(message, new ValidationMessage(ValidationMessageType.WARNING, "warn-1", "warn-1: warning message 1", "line-3")); message.addDocumentLog(warning1); - MessageLog warning2 = NemhandelLog.createMessageLog(message, new ValidationMessage(ValidationMessageType.WARNING, "warn-2", "warning message 2", "line-4")); + MessageLog warning2 = NemhandelLog.createMessageLog(message, new ValidationMessage(ValidationMessageType.WARNING, "warn-2", "warn-2: warning message 2", "line-4")); message.addDocumentLog(warning2); Document mlr = factory.createMessageLevelResponse(message); @@ -240,6 +240,7 @@ public class MessageLevelResponseFactoryTest extends AbstractXmlTest { MatcherAssert.assertThat(mlr, hasXpath(lineResponseXpath + "/cac:LineReference/cbc:LineID", CoreMatchers.equalTo(resultLog.getValidationLineReference()))); MatcherAssert.assertThat(mlr, hasXpath(lineResponseXpath + "/cac:Response/cbc:ResponseCode", CoreMatchers.equalTo(MessageLevelResponseFactory.RESPONSE_CODE_REJECTED))); MatcherAssert.assertThat(mlr, hasXpath(lineResponseXpath + "/cac:Response/cac:Status/cbc:StatusReasonCode", CoreMatchers.equalTo(ValidationMessageType.WARNING.equals(resultLog.getType()) ? MessageLevelResponseFactory.RESPONSE_STATUS_BUSINESS_VIOLATION_WARNING : MessageLevelResponseFactory.RESPONSE_STATUS_BUSINESS_VIOLATION_FATAL))); + MatcherAssert.assertThat(mlr, hasXpath(lineResponseXpath + "/cac:Response/cbc:Description", CoreMatchers.equalTo(resultLog.getDescription()))); } private Matcher hasXpath(String xpath, Matcher valueMatcher) { diff --git a/src/test/java/dk/erst/oxalis/as4/config/documenttype/DocumentTypeModuleTest.java b/src/test/java/dk/erst/oxalis/as4/config/documenttype/DocumentTypeModuleTest.java index 4acb52b876c7d7af39487b497e3b5bac1c2e802c..be25d6d18c484b1415bc3d2d74ad0df2c43f75c6 100644 --- a/src/test/java/dk/erst/oxalis/as4/config/documenttype/DocumentTypeModuleTest.java +++ b/src/test/java/dk/erst/oxalis/as4/config/documenttype/DocumentTypeModuleTest.java @@ -23,7 +23,9 @@ import java.util.Set; public class DocumentTypeModuleTest { private static final String UBL_PATH_PREFIX = "META-INF/Schemas/UBL_v2.1/maindoc"; + private static final String NEMKONTO_XSD_PATH_PREFIX = "META-INF/Schemas/NemKonto"; private static final String SCHEMATRON_UBL_PATH_PREFIX = "META-INF/Schematron/OIOUBL_v2.1"; + private static final String SCHEMATRON_NEMKONTO_PATH_PREFIX = "META-INF/Schematron/NemKonto"; private static final String SCHEMATRON_PEPPOL_PATH_PREFIX = "META-INF/Schematron/PEPPOL"; private static final String SCHEMATRON_ERROR_XPATH = "/Schematron/Error"; private static final String SCHEMATRON_ERROR_DESCRIPTION_XPATH = "Description"; @@ -69,7 +71,7 @@ public class DocumentTypeModuleTest { config.setPayloadRootLocalName("Reminder"); config.setPayloadRootNamespace("urn:oasis:names:specification:ubl:schema:xsd:Reminder-2"); config.setSchemaPath(UBL_PATH_PREFIX + "/UBL-Reminder-2.1.xsd"); - config.setIdentifierDiscriminators(Collections.singletonList(new XpathDiscriminatorConfig("/sbd:StandardBusinessDocument/root:Reminder/cbc:CustomizationID", "OIOUBL-2\\.(01|02|1)"))); + config.setIdentifierDiscriminators(Collections.singletonList(new XpathDiscriminatorConfig("/sbd:StandardBusinessDocument/root:Reminder/cbc:CustomizationID", "OIOUBL-2\\.1"))); config.setNamespaces(Arrays.asList( standardBusinessDocumentNamespace(), commonAggregateComponentsNamespace(), @@ -119,7 +121,7 @@ public class DocumentTypeModuleTest { config.setPayloadRootLocalName("Invoice"); config.setPayloadRootNamespace("urn:oasis:names:specification:ubl:schema:xsd:Invoice-2"); config.setSchemaPath(UBL_PATH_PREFIX + "/UBL-Invoice-2.1.xsd"); - config.setIdentifierDiscriminators(Collections.singletonList(new XpathDiscriminatorConfig("/sbd:StandardBusinessDocument/root:Invoice/cbc:CustomizationID", "OIOUBL-2\\.(01|02|1)"))); + config.setIdentifierDiscriminators(Collections.singletonList(new XpathDiscriminatorConfig("/sbd:StandardBusinessDocument/root:Invoice/cbc:CustomizationID", "OIOUBL-2\\.1"))); config.setNamespaces(Arrays.asList( standardBusinessDocumentNamespace(), commonAggregateComponentsNamespace(), @@ -144,7 +146,7 @@ public class DocumentTypeModuleTest { config.setPayloadRootLocalName("CreditNote"); config.setPayloadRootNamespace("urn:oasis:names:specification:ubl:schema:xsd:CreditNote-2"); config.setSchemaPath(UBL_PATH_PREFIX + "/UBL-CreditNote-2.1.xsd"); - config.setIdentifierDiscriminators(Collections.singletonList(new XpathDiscriminatorConfig("/sbd:StandardBusinessDocument/root:CreditNote/cbc:CustomizationID", "OIOUBL-2\\.(01|02|1)"))); + config.setIdentifierDiscriminators(Collections.singletonList(new XpathDiscriminatorConfig("/sbd:StandardBusinessDocument/root:CreditNote/cbc:CustomizationID", "OIOUBL-2\\.1"))); config.setNamespaces(Arrays.asList( standardBusinessDocumentNamespace(), commonAggregateComponentsNamespace(), @@ -169,7 +171,7 @@ public class DocumentTypeModuleTest { config.setPayloadRootLocalName("ApplicationResponse"); config.setPayloadRootNamespace("urn:oasis:names:specification:ubl:schema:xsd:ApplicationResponse-2"); config.setSchemaPath(UBL_PATH_PREFIX + "/UBL-ApplicationResponse-2.1.xsd"); - config.setIdentifierDiscriminators(Collections.singletonList(new XpathDiscriminatorConfig("/sbd:StandardBusinessDocument/root:ApplicationResponse/cbc:CustomizationID", "OIOUBL-2\\.(01|02|1)"))); + config.setIdentifierDiscriminators(Collections.singletonList(new XpathDiscriminatorConfig("/sbd:StandardBusinessDocument/root:ApplicationResponse/cbc:CustomizationID", "OIOUBL-2\\.1"))); config.setNamespaces(Arrays.asList( standardBusinessDocumentNamespace(), commonAggregateComponentsNamespace(), @@ -200,7 +202,7 @@ public class DocumentTypeModuleTest { config.setPayloadRootLocalName("Statement"); config.setPayloadRootNamespace("urn:oasis:names:specification:ubl:schema:xsd:Statement-2"); config.setSchemaPath(UBL_PATH_PREFIX + "/UBL-Statement-2.1.xsd"); - config.setIdentifierDiscriminators(Collections.singletonList(new XpathDiscriminatorConfig("/sbd:StandardBusinessDocument/root:Statement/cbc:CustomizationID", "OIOUBL-2\\.(01|02|1)"))); + config.setIdentifierDiscriminators(Collections.singletonList(new XpathDiscriminatorConfig("/sbd:StandardBusinessDocument/root:Statement/cbc:CustomizationID", "OIOUBL-2\\.1"))); config.setNamespaces(Arrays.asList( standardBusinessDocumentNamespace(), commonAggregateComponentsNamespace(), @@ -225,7 +227,7 @@ public class DocumentTypeModuleTest { config.setPayloadRootLocalName("Order"); config.setPayloadRootNamespace("urn:oasis:names:specification:ubl:schema:xsd:Order-2"); config.setSchemaPath(UBL_PATH_PREFIX + "/UBL-Order-2.1.xsd"); - config.setIdentifierDiscriminators(Collections.singletonList(new XpathDiscriminatorConfig("/sbd:StandardBusinessDocument/root:Order/cbc:CustomizationID", "OIOUBL-2\\.(01|02|1)"))); + config.setIdentifierDiscriminators(Collections.singletonList(new XpathDiscriminatorConfig("/sbd:StandardBusinessDocument/root:Order/cbc:CustomizationID", "OIOUBL-2\\.1"))); config.setNamespaces(Arrays.asList( standardBusinessDocumentNamespace(), commonAggregateComponentsNamespace(), @@ -250,7 +252,7 @@ public class DocumentTypeModuleTest { config.setPayloadRootLocalName("OrderResponseSimple"); config.setPayloadRootNamespace("urn:oasis:names:specification:ubl:schema:xsd:OrderResponseSimple-2"); config.setSchemaPath(UBL_PATH_PREFIX + "/UBL-OrderResponseSimple-2.1.xsd"); - config.setIdentifierDiscriminators(Collections.singletonList(new XpathDiscriminatorConfig("/sbd:StandardBusinessDocument/root:OrderResponseSimple/cbc:CustomizationID", "OIOUBL-2\\.(01|02|1)"))); + config.setIdentifierDiscriminators(Collections.singletonList(new XpathDiscriminatorConfig("/sbd:StandardBusinessDocument/root:OrderResponseSimple/cbc:CustomizationID", "OIOUBL-2\\.1"))); config.setNamespaces(Arrays.asList( standardBusinessDocumentNamespace(), commonAggregateComponentsNamespace(), @@ -275,7 +277,7 @@ public class DocumentTypeModuleTest { config.setPayloadRootLocalName("OrderResponse"); config.setPayloadRootNamespace("urn:oasis:names:specification:ubl:schema:xsd:OrderResponse-2"); config.setSchemaPath(UBL_PATH_PREFIX + "/UBL-OrderResponse-2.1.xsd"); - config.setIdentifierDiscriminators(Collections.singletonList(new XpathDiscriminatorConfig("/sbd:StandardBusinessDocument/root:OrderResponse/cbc:CustomizationID", "OIOUBL-2\\.(01|02|1)"))); + config.setIdentifierDiscriminators(Collections.singletonList(new XpathDiscriminatorConfig("/sbd:StandardBusinessDocument/root:OrderResponse/cbc:CustomizationID", "OIOUBL-2\\.1"))); config.setNamespaces(Arrays.asList( standardBusinessDocumentNamespace(), commonAggregateComponentsNamespace(), @@ -300,7 +302,7 @@ public class DocumentTypeModuleTest { config.setPayloadRootLocalName("OrderCancellation"); config.setPayloadRootNamespace("urn:oasis:names:specification:ubl:schema:xsd:OrderCancellation-2"); config.setSchemaPath(UBL_PATH_PREFIX + "/UBL-OrderCancellation-2.1.xsd"); - config.setIdentifierDiscriminators(Collections.singletonList(new XpathDiscriminatorConfig("/sbd:StandardBusinessDocument/root:OrderCancellation/cbc:CustomizationID", "OIOUBL-2\\.(01|02|1)"))); + config.setIdentifierDiscriminators(Collections.singletonList(new XpathDiscriminatorConfig("/sbd:StandardBusinessDocument/root:OrderCancellation/cbc:CustomizationID", "OIOUBL-2\\.1"))); config.setNamespaces(Arrays.asList( standardBusinessDocumentNamespace(), commonAggregateComponentsNamespace(), @@ -325,7 +327,7 @@ public class DocumentTypeModuleTest { config.setPayloadRootLocalName("OrderChange"); config.setPayloadRootNamespace("urn:oasis:names:specification:ubl:schema:xsd:OrderChange-2"); config.setSchemaPath(UBL_PATH_PREFIX + "/UBL-OrderChange-2.1.xsd"); - config.setIdentifierDiscriminators(Collections.singletonList(new XpathDiscriminatorConfig("/sbd:StandardBusinessDocument/root:OrderChange/cbc:CustomizationID", "OIOUBL-2\\.(01|02|1)"))); + config.setIdentifierDiscriminators(Collections.singletonList(new XpathDiscriminatorConfig("/sbd:StandardBusinessDocument/root:OrderChange/cbc:CustomizationID", "OIOUBL-2\\.1"))); config.setNamespaces(Arrays.asList( standardBusinessDocumentNamespace(), commonAggregateComponentsNamespace(), @@ -350,7 +352,7 @@ public class DocumentTypeModuleTest { config.setPayloadRootLocalName("Catalogue"); config.setPayloadRootNamespace("urn:oasis:names:specification:ubl:schema:xsd:Catalogue-2"); config.setSchemaPath(UBL_PATH_PREFIX + "/UBL-Catalogue-2.1.xsd"); - config.setIdentifierDiscriminators(Collections.singletonList(new XpathDiscriminatorConfig("/sbd:StandardBusinessDocument/root:Catalogue/cbc:CustomizationID", "OIOUBL-2\\.(01|02|1)"))); + config.setIdentifierDiscriminators(Collections.singletonList(new XpathDiscriminatorConfig("/sbd:StandardBusinessDocument/root:Catalogue/cbc:CustomizationID", "OIOUBL-2\\.1"))); config.setNamespaces(Arrays.asList( standardBusinessDocumentNamespace(), commonAggregateComponentsNamespace(), @@ -375,7 +377,7 @@ public class DocumentTypeModuleTest { config.setPayloadRootLocalName("CatalogueDeletion"); config.setPayloadRootNamespace("urn:oasis:names:specification:ubl:schema:xsd:CatalogueDeletion-2"); config.setSchemaPath(UBL_PATH_PREFIX + "/UBL-CatalogueDeletion-2.1.xsd"); - config.setIdentifierDiscriminators(Collections.singletonList(new XpathDiscriminatorConfig("/sbd:StandardBusinessDocument/root:CatalogueDeletion/cbc:CustomizationID", "OIOUBL-2\\.(01|02|1)"))); + config.setIdentifierDiscriminators(Collections.singletonList(new XpathDiscriminatorConfig("/sbd:StandardBusinessDocument/root:CatalogueDeletion/cbc:CustomizationID", "OIOUBL-2\\.1"))); config.setNamespaces(Arrays.asList( standardBusinessDocumentNamespace(), commonAggregateComponentsNamespace(), @@ -400,7 +402,7 @@ public class DocumentTypeModuleTest { config.setPayloadRootLocalName("CatalogueRequest"); config.setPayloadRootNamespace("urn:oasis:names:specification:ubl:schema:xsd:CatalogueRequest-2"); config.setSchemaPath(UBL_PATH_PREFIX + "/UBL-CatalogueRequest-2.1.xsd"); - config.setIdentifierDiscriminators(Collections.singletonList(new XpathDiscriminatorConfig("/sbd:StandardBusinessDocument/root:CatalogueRequest/cbc:CustomizationID", "OIOUBL-2\\.(01|02|1)"))); + config.setIdentifierDiscriminators(Collections.singletonList(new XpathDiscriminatorConfig("/sbd:StandardBusinessDocument/root:CatalogueRequest/cbc:CustomizationID", "OIOUBL-2\\.1"))); config.setNamespaces(Arrays.asList( standardBusinessDocumentNamespace(), commonAggregateComponentsNamespace(), @@ -425,7 +427,7 @@ public class DocumentTypeModuleTest { config.setPayloadRootLocalName("CatalogueItemSpecificationUpdate"); config.setPayloadRootNamespace("urn:oasis:names:specification:ubl:schema:xsd:CatalogueItemSpecificationUpdate-2"); config.setSchemaPath(UBL_PATH_PREFIX + "/UBL-CatalogueItemSpecificationUpdate-2.1.xsd"); - config.setIdentifierDiscriminators(Collections.singletonList(new XpathDiscriminatorConfig("/sbd:StandardBusinessDocument/root:CatalogueItemSpecificationUpdate/cbc:CustomizationID", "OIOUBL-2\\.(01|02|1)"))); + config.setIdentifierDiscriminators(Collections.singletonList(new XpathDiscriminatorConfig("/sbd:StandardBusinessDocument/root:CatalogueItemSpecificationUpdate/cbc:CustomizationID", "OIOUBL-2\\.1"))); config.setNamespaces(Arrays.asList( standardBusinessDocumentNamespace(), commonAggregateComponentsNamespace(), @@ -450,7 +452,7 @@ public class DocumentTypeModuleTest { config.setPayloadRootLocalName("CataloguePricingUpdate"); config.setPayloadRootNamespace("urn:oasis:names:specification:ubl:schema:xsd:CataloguePricingUpdate-2"); config.setSchemaPath(UBL_PATH_PREFIX + "/UBL-CataloguePricingUpdate-2.1.xsd"); - config.setIdentifierDiscriminators(Collections.singletonList(new XpathDiscriminatorConfig("/sbd:StandardBusinessDocument/root:CataloguePricingUpdate/cbc:CustomizationID", "OIOUBL-2\\.(01|02|1)"))); + config.setIdentifierDiscriminators(Collections.singletonList(new XpathDiscriminatorConfig("/sbd:StandardBusinessDocument/root:CataloguePricingUpdate/cbc:CustomizationID", "OIOUBL-2\\.1"))); config.setNamespaces(Arrays.asList( standardBusinessDocumentNamespace(), commonAggregateComponentsNamespace(), @@ -978,6 +980,7 @@ public class DocumentTypeModuleTest { new SchematronValidationConfig(SCHEMATRON_UBL_PATH_PREFIX + "/OIOUBL_Invoice_Schematron.xsl", SCHEMATRON_ERROR_XPATH, SCHEMATRON_ERROR_DESCRIPTION_XPATH, SCHEMATRON_ERROR_LOCATION_XPATH) )); additionalDocumentType.setPreloadSchematron(false); + additionalDocumentType.setPreloadSchema(false); additionalDocumentType.setMessageLevelResponseType(MessageLevelResponseType.OIOUBL_APPLICATION_RESPONSE); testDocumentTypeContainsCorrectValues(additionalDocumentType); @@ -998,6 +1001,248 @@ public class DocumentTypeModuleTest { }); } + @Test + public void testProvidesNemkontoNKSPaymentDocumentTypeConfig() { + DocumentTypeConfig config = new DocumentTypeConfig(); + config.setFriendlyName("NemKonto - NKSPayment"); + config.setPayloadRootLocalName("NKSPayment"); + config.setPayloadRootNamespace("http://rep.oio.dk/oes.dk/nemkonto/xml/schemas/2006/05/01/"); + config.setSchemaPath(NEMKONTO_XSD_PATH_PREFIX + "/nemkonto/xml/schemas/2006/05/01/NKS_NKSPayment.xsd"); + config.setIdentifierDiscriminators(Collections.emptyList()); + config.setNamespaces(Arrays.asList( + standardBusinessDocumentNamespace(), + nemKontoSwiftNamespace(), + nemKontoEbmsNamespace(), + new PrefixedNamespace("http://rep.oio.dk/oes.dk/nemkonto/xml/schemas/2006/05/01/", "root") + )); + config.setSchematronDocuments(Collections.singletonList( + new SchematronValidationConfig(SCHEMATRON_NEMKONTO_PATH_PREFIX + "/NemKonto.xsl", SCHEMATRON_ERROR_XPATH, SCHEMATRON_ERROR_DESCRIPTION_XPATH, SCHEMATRON_ERROR_LOCATION_XPATH) + )); + config.setMessageLevelResponseType(MessageLevelResponseType.OIOUBL_APPLICATION_RESPONSE); + + testDocumentTypeContainsCorrectValues(config); + } + + @Test + public void testProvidesNemkontoNKSReceipt0DocumentTypeConfig() { + DocumentTypeConfig config = new DocumentTypeConfig(); + config.setFriendlyName("NemKonto - NKSReceipt0"); + config.setPayloadRootLocalName("NKSReceipt0"); + config.setPayloadRootNamespace("http://rep.oio.dk/oes.dk/nemkonto/xml/schemas/2006/05/01/"); + config.setSchemaPath(NEMKONTO_XSD_PATH_PREFIX + "/nemkonto/xml/schemas/2006/05/01/NKS_NKSReceipt0.xsd"); + config.setIdentifierDiscriminators(Collections.emptyList()); + config.setNamespaces(Arrays.asList( + standardBusinessDocumentNamespace(), + nemKontoSwiftNamespace(), + nemKontoEbmsNamespace(), + new PrefixedNamespace("http://rep.oio.dk/oes.dk/nemkonto/xml/schemas/2006/05/01/", "root") + )); + config.setSchematronDocuments(Collections.singletonList( + new SchematronValidationConfig(SCHEMATRON_NEMKONTO_PATH_PREFIX + "/NemKonto.xsl", SCHEMATRON_ERROR_XPATH, SCHEMATRON_ERROR_DESCRIPTION_XPATH, SCHEMATRON_ERROR_LOCATION_XPATH) + )); + config.setMessageLevelResponseType(MessageLevelResponseType.OIOUBL_APPLICATION_RESPONSE); + + testDocumentTypeContainsCorrectValues(config); + } + + @Test + public void testProvidesNemkontoNKSReceipt1DocumentTypeConfig() { + DocumentTypeConfig config = new DocumentTypeConfig(); + config.setFriendlyName("NemKonto - NKSReceipt1"); + config.setPayloadRootLocalName("NKSReceipt1"); + config.setPayloadRootNamespace("http://rep.oio.dk/oes.dk/nemkonto/xml/schemas/2006/05/01/"); + config.setSchemaPath(NEMKONTO_XSD_PATH_PREFIX + "/nemkonto/xml/schemas/2006/05/01/NKS_NKSReceipt1.xsd"); + config.setIdentifierDiscriminators(Collections.emptyList()); + config.setNamespaces(Arrays.asList( + standardBusinessDocumentNamespace(), + nemKontoSwiftNamespace(), + nemKontoEbmsNamespace(), + new PrefixedNamespace("http://rep.oio.dk/oes.dk/nemkonto/xml/schemas/2006/05/01/", "root") + )); + config.setSchematronDocuments(Collections.singletonList( + new SchematronValidationConfig(SCHEMATRON_NEMKONTO_PATH_PREFIX + "/NemKonto.xsl", SCHEMATRON_ERROR_XPATH, SCHEMATRON_ERROR_DESCRIPTION_XPATH, SCHEMATRON_ERROR_LOCATION_XPATH) + )); + config.setMessageLevelResponseType(MessageLevelResponseType.OIOUBL_APPLICATION_RESPONSE); + + testDocumentTypeContainsCorrectValues(config); + } + + @Test + public void testProvidesNemkontoNKSResponse2DocumentTypeConfig() { + DocumentTypeConfig config = new DocumentTypeConfig(); + config.setFriendlyName("NemKonto - NKSResponse2"); + config.setPayloadRootLocalName("NKSResponse2"); + config.setPayloadRootNamespace("http://rep.oio.dk/oes.dk/nemkonto/xml/schemas/2006/05/01/"); + config.setSchemaPath(NEMKONTO_XSD_PATH_PREFIX + "/nemkonto/xml/schemas/2006/05/01/NKS_NKSResponse2.xsd"); + config.setIdentifierDiscriminators(Collections.emptyList()); + config.setNamespaces(Arrays.asList( + standardBusinessDocumentNamespace(), + nemKontoSwiftNamespace(), + nemKontoEbmsNamespace(), + new PrefixedNamespace("http://rep.oio.dk/oes.dk/nemkonto/xml/schemas/2006/05/01/", "root") + )); + config.setSchematronDocuments(Collections.singletonList( + new SchematronValidationConfig(SCHEMATRON_NEMKONTO_PATH_PREFIX + "/NemKonto.xsl", SCHEMATRON_ERROR_XPATH, SCHEMATRON_ERROR_DESCRIPTION_XPATH, SCHEMATRON_ERROR_LOCATION_XPATH) + )); + config.setMessageLevelResponseType(MessageLevelResponseType.OIOUBL_APPLICATION_RESPONSE); + + testDocumentTypeContainsCorrectValues(config); + } + + @Test + public void testProvidesNemkontoNKSResponse5DocumentTypeConfig() { + DocumentTypeConfig config = new DocumentTypeConfig(); + config.setFriendlyName("NemKonto - NKSResponse5"); + config.setPayloadRootLocalName("NKSResponse5"); + config.setPayloadRootNamespace("http://rep.oio.dk/oes.dk/nemkonto/xml/schemas/2006/05/01/"); + config.setSchemaPath(NEMKONTO_XSD_PATH_PREFIX + "/nemkonto/xml/schemas/2006/05/01/NKS_NKSResponse5.xsd"); + config.setIdentifierDiscriminators(Collections.emptyList()); + config.setNamespaces(Arrays.asList( + standardBusinessDocumentNamespace(), + nemKontoSwiftNamespace(), + nemKontoEbmsNamespace(), + new PrefixedNamespace("http://rep.oio.dk/oes.dk/nemkonto/xml/schemas/2006/05/01/", "root") + )); + config.setSchematronDocuments(Collections.singletonList( + new SchematronValidationConfig(SCHEMATRON_NEMKONTO_PATH_PREFIX + "/NemKonto.xsl", SCHEMATRON_ERROR_XPATH, SCHEMATRON_ERROR_DESCRIPTION_XPATH, SCHEMATRON_ERROR_LOCATION_XPATH) + )); + config.setMessageLevelResponseType(MessageLevelResponseType.OIOUBL_APPLICATION_RESPONSE); + + testDocumentTypeContainsCorrectValues(config); + } + + @Test + public void testProvidesNemkontoNKSResponse7DocumentTypeConfig() { + DocumentTypeConfig config = new DocumentTypeConfig(); + config.setFriendlyName("NemKonto - NKSResponse7"); + config.setPayloadRootLocalName("NKSResponse7"); + config.setPayloadRootNamespace("http://rep.oio.dk/oes.dk/nemkonto/xml/schemas/2006/05/01/"); + config.setSchemaPath(NEMKONTO_XSD_PATH_PREFIX + "/nemkonto/xml/schemas/2006/05/01/NKS_NKSResponse7.xsd"); + config.setIdentifierDiscriminators(Collections.emptyList()); + config.setNamespaces(Arrays.asList( + standardBusinessDocumentNamespace(), + nemKontoSwiftNamespace(), + nemKontoEbmsNamespace(), + new PrefixedNamespace("http://rep.oio.dk/oes.dk/nemkonto/xml/schemas/2006/05/01/", "root") + )); + config.setSchematronDocuments(Collections.singletonList( + new SchematronValidationConfig(SCHEMATRON_NEMKONTO_PATH_PREFIX + "/NemKonto.xsl", SCHEMATRON_ERROR_XPATH, SCHEMATRON_ERROR_DESCRIPTION_XPATH, SCHEMATRON_ERROR_LOCATION_XPATH) + )); + config.setMessageLevelResponseType(MessageLevelResponseType.OIOUBL_APPLICATION_RESPONSE); + + testDocumentTypeContainsCorrectValues(config); + } + + @Test + public void testProvidesNemkontoNKSResponse8DocumentTypeConfig() { + DocumentTypeConfig config = new DocumentTypeConfig(); + config.setFriendlyName("NemKonto - NKSResponse8"); + config.setPayloadRootLocalName("NKSResponse8"); + config.setPayloadRootNamespace("http://rep.oio.dk/oes.dk/nemkonto/xml/schemas/2006/05/01/"); + config.setSchemaPath(NEMKONTO_XSD_PATH_PREFIX + "/nemkonto/xml/schemas/2006/05/01/NKS_NKSResponse8.xsd"); + config.setIdentifierDiscriminators(Collections.emptyList()); + config.setNamespaces(Arrays.asList( + standardBusinessDocumentNamespace(), + nemKontoSwiftNamespace(), + nemKontoEbmsNamespace(), + new PrefixedNamespace("http://rep.oio.dk/oes.dk/nemkonto/xml/schemas/2006/05/01/", "root") + )); + config.setSchematronDocuments(Collections.singletonList( + new SchematronValidationConfig(SCHEMATRON_NEMKONTO_PATH_PREFIX + "/NemKonto.xsl", SCHEMATRON_ERROR_XPATH, SCHEMATRON_ERROR_DESCRIPTION_XPATH, SCHEMATRON_ERROR_LOCATION_XPATH) + )); + config.setMessageLevelResponseType(MessageLevelResponseType.OIOUBL_APPLICATION_RESPONSE); + + testDocumentTypeContainsCorrectValues(config); + } + + @Test + public void testProvidesNemkontoNKSResponse9DocumentTypeConfig() { + DocumentTypeConfig config = new DocumentTypeConfig(); + config.setFriendlyName("NemKonto - NKSResponse9"); + config.setPayloadRootLocalName("NKSResponse9"); + config.setPayloadRootNamespace("http://rep.oio.dk/oes.dk/nemkonto/xml/schemas/2006/05/01/"); + config.setSchemaPath(NEMKONTO_XSD_PATH_PREFIX + "/nemkonto/xml/schemas/2006/05/01/NKS_NKSResponse9.xsd"); + config.setIdentifierDiscriminators(Collections.emptyList()); + config.setNamespaces(Arrays.asList( + standardBusinessDocumentNamespace(), + nemKontoSwiftNamespace(), + nemKontoEbmsNamespace(), + new PrefixedNamespace("http://rep.oio.dk/oes.dk/nemkonto/xml/schemas/2006/05/01/", "root") + )); + config.setSchematronDocuments(Collections.singletonList( + new SchematronValidationConfig(SCHEMATRON_NEMKONTO_PATH_PREFIX + "/NemKonto.xsl", SCHEMATRON_ERROR_XPATH, SCHEMATRON_ERROR_DESCRIPTION_XPATH, SCHEMATRON_ERROR_LOCATION_XPATH) + )); + config.setMessageLevelResponseType(MessageLevelResponseType.OIOUBL_APPLICATION_RESPONSE); + + testDocumentTypeContainsCorrectValues(config); + } + + @Test + public void testProvidesNemkontPrivatUdbetalerTransporterRequestDocumentTypeConfig() { + DocumentTypeConfig config = new DocumentTypeConfig(); + config.setFriendlyName("NemKonto PU - NemkontoPrivatUdbetalerTransporterRequest"); + config.setPayloadRootLocalName("NemkontoPrivatUdbetalerTransporterRequest"); + config.setPayloadRootNamespace("http://rep.oio.dk/oes.dk/nemkonto/xml/schemas/2007/10/01/"); + config.setSchemaPath(NEMKONTO_XSD_PATH_PREFIX + "/nemkonto-pu/xml/schemas/2007/10/01/NKSPU_NemkontoPrivatUdbetalerTransporterRequest.xsd"); + config.setIdentifierDiscriminators(Collections.emptyList()); + config.setNamespaces(Arrays.asList( + standardBusinessDocumentNamespace(), + nemkontoEANNamespace(), + nemkontoCVRNamespace(), + nemkontoCPRNamespace(), + new PrefixedNamespace("http://rep.oio.dk/oes.dk/nemkonto/xml/schemas/2007/10/01/", "root") + )); + config.setSchematronDocuments(Collections.singletonList( + new SchematronValidationConfig(SCHEMATRON_NEMKONTO_PATH_PREFIX + "/NemKonto.xsl", SCHEMATRON_ERROR_XPATH, SCHEMATRON_ERROR_DESCRIPTION_XPATH, SCHEMATRON_ERROR_LOCATION_XPATH) + )); + config.setMessageLevelResponseType(MessageLevelResponseType.OIOUBL_APPLICATION_RESPONSE); + + testDocumentTypeContainsCorrectValues(config); + } + + private static PrefixedNamespace nemkontoCPRNamespace() { + return new PrefixedNamespace("http://rep.oio.dk/cpr.dk/xml/schemas/core/2005/03/18/", "cpr"); + } + + private static PrefixedNamespace nemkontoCVRNamespace() { + return new PrefixedNamespace("http://rep.oio.dk/cvr.dk/xml/schemas/2005/03/22/", "cvr"); + } + + private static PrefixedNamespace nemkontoEANNamespace() { + return new PrefixedNamespace("http://rep.oio.dk/ean/xml/schemas/2005/01/10/", "ean"); + } + + @Test + public void testProvidesNemkontPrivatUdbetalerTransporterResponseDocumentTypeConfig() { + DocumentTypeConfig config = new DocumentTypeConfig(); + config.setFriendlyName("NemKonto PU - NemkontoPrivatUdbetalerTransporterResponse"); + config.setPayloadRootLocalName("NemkontoPrivatUdbetalerTransporterResponse"); + config.setPayloadRootNamespace("http://rep.oio.dk/oes.dk/nemkonto/xml/schemas/2007/10/01/"); + config.setSchemaPath(NEMKONTO_XSD_PATH_PREFIX + "/nemkonto-pu/xml/schemas/2007/10/01/NKSPU_NemkontoPrivatUdbetalerTransporterResponse.xsd"); + config.setIdentifierDiscriminators(Collections.emptyList()); + config.setNamespaces(Arrays.asList( + standardBusinessDocumentNamespace(), + nemkontoEANNamespace(), + nemkontoCVRNamespace(), + nemkontoCPRNamespace(), + new PrefixedNamespace("http://rep.oio.dk/oes.dk/nemkonto/xml/schemas/2007/10/01/", "root") + )); + config.setSchematronDocuments(Collections.singletonList( + new SchematronValidationConfig(SCHEMATRON_NEMKONTO_PATH_PREFIX + "/NemKonto.xsl", SCHEMATRON_ERROR_XPATH, SCHEMATRON_ERROR_DESCRIPTION_XPATH, SCHEMATRON_ERROR_LOCATION_XPATH) + )); + config.setMessageLevelResponseType(MessageLevelResponseType.OIOUBL_APPLICATION_RESPONSE); + + testDocumentTypeContainsCorrectValues(config); + } + + private static PrefixedNamespace nemKontoEbmsNamespace() { + return new PrefixedNamespace("http://rep.oio.dk/oes.dk/nemkonto.ebms/xml/schemas/2006/05/01/", "ebms"); + } + + private static PrefixedNamespace nemKontoSwiftNamespace() { + return new PrefixedNamespace("http://rep.oio.dk/oes.dk/nemkonto.swift/xml/schemas/2006/05/01/", "swift"); + } + private static PrefixedNamespace unqualifiedDatatypesNamespace() { return new PrefixedNamespace("urn:un:unece:uncefact:data:specification:UnqualifiedDataTypesSchemaModule:2", "udt"); } diff --git a/src/test/java/dk/erst/oxalis/as4/error/ErrorCodesTest.java b/src/test/java/dk/erst/oxalis/as4/error/ErrorCodesTest.java index 2f4aab290139b4df47733508556ff65afab4b999..013b2b708e5edd3f7c72458991ebd1ece1254ed1 100644 --- a/src/test/java/dk/erst/oxalis/as4/error/ErrorCodesTest.java +++ b/src/test/java/dk/erst/oxalis/as4/error/ErrorCodesTest.java @@ -19,19 +19,19 @@ public class ErrorCodesTest { @Test public void testFormatsErrorCodeCorrectly() { String error = ErrorCodes.SENDER_EDELIVERY_SIGNATURE_MISSING.getErrorCode(); - MatcherAssert.assertThat(error, CoreMatchers.equalTo("E-APS21001")); + MatcherAssert.assertThat(error, CoreMatchers.equalTo("E-APS21020")); } @Test public void testGetErrorCodeByCodeCorrectly() { ErrorCodes error = ErrorCodes.getByCode(ErrorCodes.SENDER_EDELIVERY_SIGNATURE_MISSING.getErrorCode()); - MatcherAssert.assertThat(error.getErrorCode(), CoreMatchers.equalTo("E-APS21001")); + MatcherAssert.assertThat(error.getErrorCode(), CoreMatchers.equalTo("E-APS21020")); } @Test public void testCanFormatErrorMessageWithParameters() { - String error = ErrorCodes.SENDER_EDELIVERY_SIGNATURE_INVALID.formatErrorMessage(1, "foo"); - MatcherAssert.assertThat(error, CoreMatchers.equalTo("E-APS21002: Invalid Nemhandel e-Delivery document signature at level 1: foo")); + String error = ErrorCodes.SENDER_EDELIVERY_SIGNATURE_INVALID.formatErrorMessage("foo"); + MatcherAssert.assertThat(error, CoreMatchers.equalTo("E-APS21021: Invalid Nemhandel e-Delivery document signature: foo")); } @Test diff --git a/src/test/java/dk/erst/oxalis/as4/handlers/GetOutboxMessageHandlerTest.java b/src/test/java/dk/erst/oxalis/as4/handlers/GetOutboxMessageHandlerTest.java index 879ba2cf4d27e5ae2e7f4bd4a9b9b1315f73767a..02b4a39a5183c03948a46c9e52410ebd1847d2ab 100644 --- a/src/test/java/dk/erst/oxalis/as4/handlers/GetOutboxMessageHandlerTest.java +++ b/src/test/java/dk/erst/oxalis/as4/handlers/GetOutboxMessageHandlerTest.java @@ -5,6 +5,7 @@ import com.google.inject.Guice; import com.google.inject.Injector; import com.google.inject.persist.PersistService; import com.google.inject.util.Modules; +import dk.erst.oxalis.as4.handlers.dto.MessageAndResponses; import dk.erst.oxalis.as4.handlers.dto.MessageModel; import dk.erst.oxalis.as4.jdbc.JdbcModule; import dk.erst.oxalis.as4.persistence.model.Account; @@ -17,7 +18,10 @@ import dk.erst.oxalis.as4.util.TxUtil; import dk.erst.oxalis.as4.util.UtilityModule; import network.oxalis.commons.config.ConfigModule; import network.oxalis.commons.filesystem.FileSystemModule; +import org.apache.commons.io.IOUtils; import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.hamcrest.CoreMatchers; +import org.hamcrest.MatcherAssert; import org.testng.annotations.AfterClass; import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeClass; @@ -26,9 +30,13 @@ import org.testng.annotations.Test; import javax.persistence.EntityManager; import javax.ws.rs.NotFoundException; import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; import java.security.Security; import java.time.LocalDateTime; import java.time.temporal.ChronoUnit; +import java.util.UUID; import java.util.function.Consumer; import static org.testng.Assert.assertEquals; @@ -39,6 +47,7 @@ public class GetOutboxMessageHandlerTest { private Injector injector; private Account account; + private Account account2; @BeforeClass public void setupBeforeClass() { @@ -62,8 +71,10 @@ public class GetOutboxMessageHandlerTest { persistService.start(); account = new Account("testuser", new PasswordHasher().hashPassword("testpassword"), "DK"); + account2 = new Account("testuser-2", new PasswordHasher().hashPassword("testpassword"), "DK"); doInTx(em -> { em.persist(account); + em.persist(account2); }); } @@ -138,6 +149,87 @@ public class GetOutboxMessageHandlerTest { handler.getDocumentStatus("abc", account); } + @Test + public void testGetMessageAndAllResponsesOnlyReturnsResponsesTiedToUsersAccount() { + + + String originalUuid = UUID.randomUUID().toString(); + String response1Uuid = UUID.randomUUID().toString(); + String response2Uuid = UUID.randomUUID().toString(); + + doInTx(em -> { + Message m = createDummyMessage(account, originalUuid, "test"); + em.persist(m); + Message resp1 = createDummyResponseMessage(account, response1Uuid, "response 1", m); + em.persist(resp1); + + // response with different account + Message resp2 = createDummyResponseMessage(account2, response2Uuid, "response 2", m); + em.persist(resp2); + }); + + GetOutboxMessagesHandler handler = injector.getInstance(GetOutboxMessagesHandler.class); + MessageAndResponses responses = handler.getMessageAndAllResponses(account, originalUuid); + MatcherAssert.assertThat(responses, CoreMatchers.notNullValue()); + MatcherAssert.assertThat(responses.getOriginalDocument(), CoreMatchers.notNullValue()); + MatcherAssert.assertThat(responses.getOriginalDocument().getMessageUuid(), CoreMatchers.equalTo(originalUuid)); + + MatcherAssert.assertThat(responses.getResponses(), CoreMatchers.notNullValue()); + MatcherAssert.assertThat(responses.getResponses().size(), CoreMatchers.equalTo(1)); + MatcherAssert.assertThat(responses.getResponses().iterator().next().getMessageUuid(), CoreMatchers.equalTo(response1Uuid)); + } + + @Test + void testGetMessageContentReturnsTheExpectedContent() throws IOException { + GetOutboxMessagesHandler handler = injector.getInstance(GetOutboxMessagesHandler.class); + + Message dummyMessage1 = createDummyMessage(account, "abc1", "test"); + dummyMessage1.setReceived(LocalDateTime.now()); + dummyMessage1.setStatus(MessageStatus.RECEIVED); + + doInTx(em -> { + em.persist(dummyMessage1); + }); + try(InputStream is = handler.getMessageContent("abc1", account)) { + MatcherAssert.assertThat(IOUtils.toString(is, StandardCharsets.UTF_8), CoreMatchers.equalTo("test")); + } + } + + @Test(expectedExceptions = NotFoundException.class) + void testGetMessageContentThrowsNotFoundExceptionForUnknownMessage() throws IOException { + GetOutboxMessagesHandler handler = injector.getInstance(GetOutboxMessagesHandler.class); + handler.getMessageContent(UUID.randomUUID().toString(), account); + } + + @Test(expectedExceptions = NotFoundException.class) + void testGetMessageContentThrowsNotFoundExceptionForMessageTiedToAnotherAccount() throws IOException { + Message dummyMessage1 = createDummyMessage(account2, UUID.randomUUID().toString(), "test"); + dummyMessage1.setReceived(LocalDateTime.now()); + dummyMessage1.setStatus(MessageStatus.RECEIVED); + + doInTx(em -> { + em.persist(dummyMessage1); + }); + + GetOutboxMessagesHandler handler = injector.getInstance(GetOutboxMessagesHandler.class); + handler.getMessageContent(dummyMessage1.getMessageUuid(), account); + } + + @Test(expectedExceptions = NotFoundException.class) + void testGetMessageContentDoesNotLookInInbox() throws IOException { + Message dummyMessage1 = createDummyMessage(account, UUID.randomUUID().toString(), "test"); + dummyMessage1.setReceived(LocalDateTime.now()); + dummyMessage1.setStatus(MessageStatus.RECEIVED); + dummyMessage1.setDirection(MessageDirection.IN); // inbox + + doInTx(em -> { + em.persist(dummyMessage1); + }); + + GetOutboxMessagesHandler handler = injector.getInstance(GetOutboxMessagesHandler.class); + handler.getMessageContent(dummyMessage1.getMessageUuid(), account); + } + private Message createDummyMessage(Account account, String uuid, String content) { Message message = new Message(); message.setAccount(account); @@ -151,4 +243,19 @@ public class GetOutboxMessageHandlerTest { message.setMessageContent(new MessageContent(content.getBytes())); return message; } + + private Message createDummyResponseMessage(Account account, String uuid, String content, Message originalMessage) { + Message message = new Message(); + message.setAccount(account); + message.setSender("sender"); + message.setReceiver("receiver"); + message.setMessageUuid(uuid); + message.setDirection(MessageDirection.IN); + message.setDocumentTypeId("urn:oasis:names:specification:ubl:schema:xsd:ApplicationResponse-2::ApplicationResponse##OIOUBL-2.1::2.1"); + message.setChannel("as4"); + message.setStatus(MessageStatus.RECEIVED); + message.setMessageContent(new MessageContent(content.getBytes())); + message.setOriginalMessage(originalMessage); + return message; + } } diff --git a/src/test/java/dk/erst/oxalis/as4/handlers/NemhandelPersisterHandlerTest.java b/src/test/java/dk/erst/oxalis/as4/handlers/NemhandelPersisterHandlerTest.java index 3ecda0a824da702866c3e6329d90bea2fa864d49..785801194526312eb738341a224f4230e39d5da8 100644 --- a/src/test/java/dk/erst/oxalis/as4/handlers/NemhandelPersisterHandlerTest.java +++ b/src/test/java/dk/erst/oxalis/as4/handlers/NemhandelPersisterHandlerTest.java @@ -165,16 +165,51 @@ public class NemhandelPersisterHandlerTest { assertThat(message.getAccount(), equalTo(account)); assertThat(message.getSender(), equalTo("iso6523-actorid-upis::urn:oasis:names:tc:ebcore:partyid-type:unregistered:c1")); assertThat(message.getReceiver(), equalTo("iso6523-actorid-upis::urn:oasis:names:tc:ebcore:partyid-type:unregistered:c4")); - assertThat(message.getDocumentTypeId(), equalTo("submitMessage")); + assertThat(message.getDocumentTypeId(), equalTo("busdox-docid-qns::submitMessage")); assertThat(message.getDirection(), equalTo(MessageDirection.IN)); - assertThat(message.getPeppolProcessId(), equalTo("http://ec.europa.eu/edelivery/services/connectivity-service")); + assertThat(message.getPeppolProcessId(), equalTo("e-delivery::http://ec.europa.eu/edelivery/services/connectivity-service")); assertThat(message.getStatus(), equalTo(MessageStatus.PROCESSING)); assertThat(message.getMessageContent().getDataAsString(), equalTo(xml)); + assertThat(message.getTransmissionId(), equalTo("1")); + }); + } + + @Test + public void testSavesDocumentToDbWithIdentifiers() throws IOException { + String xml = load("/cef-sbd-with-identifiers.xml"); + persistDocument(xml); + + doInTx(em -> { + Message message = em.createQuery("from Message", Message.class) + .getSingleResult(); + + //should use e-delivery prefix from sbdh + assertThat(message.getDocumentTypeId(), equalTo("e-delivery::submitMessage")); + //should use e-delivery prefix from sbdh + assertThat(message.getPeppolProcessId(), equalTo("e-delivery::http://ec.europa.eu/edelivery/services/connectivity-service")); + + }); + } + + @Test + public void testSavesDocumentToDbWithDefaultIdentifiers() throws IOException { + String xml = load("/cef-sbd-without-identifiers.xml"); + persistDocument(xml); + + doInTx(em -> { + Message message = em.createQuery("from Message", Message.class) + .getSingleResult(); + + //should use default prefix + assertThat(message.getDocumentTypeId(), equalTo("busdox-docid-qns::submitMessage")); + //should use default prefix + assertThat(message.getPeppolProcessId(), equalTo("cenbii-procid-ubl::http://ec.europa.eu/edelivery/services/connectivity-service")); + }); } private void persistDocument(String xml) throws IOException { - accountReceiver = new AccountReceiver(account, "urn:oasis:names:tc:ebcore:partyid-type:unregistered:C4", "87654321"); + accountReceiver = new AccountReceiver(account, "urn:oasis:names:tc:ebcore:partyid-type:unregistered:C4"); // add some accountreceiver entry: doInTx(em -> { em.persist(accountReceiver); @@ -196,7 +231,7 @@ public class NemhandelPersisterHandlerTest { expectedExceptionsMessageRegExp = "E-APR24123: Receiver urn:oasis:names:tc:ebcore:partyid-type:unregistered:C4 is not recognized by the receiving endpoint. The document was rejected by the receiving endpoint" ) public void testExceptionOnNonMatchingAccountReceiver() throws IOException { - accountReceiver = new AccountReceiver(account, "NonMatchingParticipantID", "00000000"); + accountReceiver = new AccountReceiver(account, "NonMatchingParticipantID"); // add some accountreceiver entry: doInTx(em -> { em.persist(accountReceiver); @@ -219,7 +254,7 @@ public class NemhandelPersisterHandlerTest { expectedExceptionsMessageRegExp = "Xml is not a StandardBusinessDocument, or is missing" ) public void testThrowsOnMissingSender() throws IOException { - accountReceiver = new AccountReceiver(account, "urn:oasis:names:tc:ebcore:partyid-type:unregistered:C4", "00000000"); + accountReceiver = new AccountReceiver(account, "urn:oasis:names:tc:ebcore:partyid-type:unregistered:C4"); // add some accountreceiver entry: doInTx(em -> { em.persist(accountReceiver); @@ -244,7 +279,7 @@ public class NemhandelPersisterHandlerTest { expectedExceptionsMessageRegExp = "Xml is not a StandardBusinessDocument, or is missing" ) public void testThrowsOnMissingReceiver() throws IOException { - accountReceiver = new AccountReceiver(account, "urn:oasis:names:tc:ebcore:partyid-type:unregistered:C4", "00000000"); + accountReceiver = new AccountReceiver(account, "urn:oasis:names:tc:ebcore:partyid-type:unregistered:C4"); // add some accountreceiver entry: doInTx(em -> { em.persist(accountReceiver); @@ -268,7 +303,7 @@ public class NemhandelPersisterHandlerTest { expectedExceptionsMessageRegExp = "Xml is not a StandardBusinessDocument, or a with DOCUMENTID is missing" ) public void testThrowsOnMissingDocumentTypeId() throws IOException { - accountReceiver = new AccountReceiver(account, "urn:oasis:names:tc:ebcore:partyid-type:unregistered:C4", "00000000"); + accountReceiver = new AccountReceiver(account, "urn:oasis:names:tc:ebcore:partyid-type:unregistered:C4"); // add some accountreceiver entry: doInTx(em -> { em.persist(accountReceiver); @@ -464,7 +499,7 @@ public class NemhandelPersisterHandlerTest { Assert.fail("Should throw"); }catch(IOException e) { assertThat(e.getMessage(), Matchers.startsWith("Error while performing validation")); - assertThat(e.getMessage(), Matchers.containsString("E-APS21001:")); + assertThat(e.getMessage(), Matchers.containsString("E-APS21020:")); verify(executorServiceMock, never()).execute(any(AsyncProcessingRunnable.class)); } @@ -488,7 +523,7 @@ public class NemhandelPersisterHandlerTest { @Test public void testCreatesMLRMessageLogsForMLRWhereNoKnownOriginalMessage() throws IOException { String xml = load("/sbd-examples/SBD_MessageLevelResponse.xml"); - accountReceiver = new AccountReceiver(account, "0184:12345678", "87654321"); + accountReceiver = new AccountReceiver(account, "0184:12345678"); doInTx(em -> { em.persist(accountReceiver); }); @@ -518,7 +553,7 @@ public class NemhandelPersisterHandlerTest { mesg.setReceiver("0184:12345678"); mesg.setSender("0184:12345678"); - accountReceiver = new AccountReceiver(account, "0184:12345678", "87654321"); + accountReceiver = new AccountReceiver(account, "0184:12345678"); String xml = load("/sbd-examples/SBD_MessageLevelResponse.xml"); doInTx(em -> { @@ -537,6 +572,7 @@ public class NemhandelPersisterHandlerTest { MatcherAssert.assertThat(logs.stream().anyMatch(m -> ErrorCodes.C2_RECEIVING_OF_MLR_DONE.getErrorCode().equals(m.getCode())), CoreMatchers.is(true)); }); } + @Test public void testCreatesARMessageLogsForOriginalMessage() throws IOException { @@ -550,7 +586,7 @@ public class NemhandelPersisterHandlerTest { mesg.setReceiver("0184:12345678"); mesg.setSender("0184:12345678"); - accountReceiver = new AccountReceiver(account, "0184:12345678", "87654321"); + accountReceiver = new AccountReceiver(account, "0184:12345678"); String xml = load("/sbd-examples/SBD_OIOUBL_ApplicationResponse.xml"); doInTx(em -> { diff --git a/src/test/java/dk/erst/oxalis/as4/handlers/SendSbdHandlerTest.java b/src/test/java/dk/erst/oxalis/as4/handlers/SendSbdHandlerTest.java index d733ee307c96dd7e5460c5be4f3fc14bc216b627..e1ed409d45cf40f336ed5044d243afca87a32726 100644 --- a/src/test/java/dk/erst/oxalis/as4/handlers/SendSbdHandlerTest.java +++ b/src/test/java/dk/erst/oxalis/as4/handlers/SendSbdHandlerTest.java @@ -556,10 +556,10 @@ public class SendSbdHandlerTest { assertThat(message.getStatus(), equalTo(MessageStatus.ERROR)); assertThat(messageLog.size(), equalTo(4)); - assertThat(messageLog.get(0).getDescription(), equalTo("E-APS21001: No Nemhandel e-Delivery document signature found at level 0")); + assertThat(messageLog.get(0).getDescription(), equalTo("E-APS21020: No Nemhandel e-Delivery document signature found")); assertThat(messageLog.get(0).getMessageStatus(), equalTo(MessageStatus.ERROR)); assertThat(messageLog.get(0).getType(), equalTo(ValidationMessageType.ERROR)); - assertThat(messageLog.get(0).getCode(), equalTo("E-APS21001")); + assertThat(messageLog.get(0).getCode(), equalTo("E-APS21020")); assertThat(messageLog.get(0).getValidationLineReference(), equalTo("line-1")); assertThat(messageLog.get(1).getDescription(), equalTo("E-REF23001: Standard Business Document is null or empty")); assertThat(messageLog.get(1).getMessageStatus(), equalTo(MessageStatus.ERROR)); @@ -579,6 +579,33 @@ public class SendSbdHandlerTest { } } + @Test + public void testUpdatesStatusToErrorWhenOutboundValidationExceptionFromDocumentSender() throws OutboundException { + String xml = load("/cef-sbd.xml"); + ValidationResult result = new ValidationResult(); + result.addError(new ValidationMessage(ValidationMessageType.ERROR, "error-1", "dummy error", "/foo")); + + doThrow(new OutboundValidationException(result, null)).when(mockSender).send(Mockito.any(Message.class), Mockito.any(SBDMessageContext.class)); + + SendSbdHandler handler = injector.getInstance(SendSbdHandler.class); + try { + handler.handle(account, xml, "test", new SBDMessageContext()); + } catch(Exception e) { + MatcherAssert.assertThat(e, CoreMatchers.instanceOf(OutboundValidationException.class)); + + doInTx(em -> { + Message m = em.createQuery("from Message where messageUuid = :uuid", Message.class) + .setParameter("uuid", "555bcb4c-940b-4694-9b90-d9b0ae1e937b") // message id from cef-sdb.xml + .getResultStream() + .findFirst() + .orElse(null); + + MatcherAssert.assertThat(m, CoreMatchers.notNullValue()); + MatcherAssert.assertThat(m.getStatus(), CoreMatchers.equalTo(MessageStatus.ERROR)); + }); + } + } + private static As4TransmissionResponse fakeResponse() throws URISyntaxException { return fakeResponse(null); } diff --git a/src/test/java/dk/erst/oxalis/as4/handlers/dto/ValidationResultTest.java b/src/test/java/dk/erst/oxalis/as4/handlers/dto/ValidationResultTest.java new file mode 100644 index 0000000000000000000000000000000000000000..1822262bb56554f27087a25753eea77a3c40c0c1 --- /dev/null +++ b/src/test/java/dk/erst/oxalis/as4/handlers/dto/ValidationResultTest.java @@ -0,0 +1,56 @@ +package dk.erst.oxalis.as4.handlers.dto; + +import dk.erst.oxalis.as4.validation.ValidationType; +import org.testng.annotations.Test; + +import static org.testng.Assert.assertEquals; + +public class ValidationResultTest { + + @Test + void validationResultFormatTest() { + ValidationResult result = new ValidationResult(); + result.setValidationType(ValidationType.ASYNCHRONOUS); + result.addError("E-APS00001","dummy error"); + result.addError("E-APS00002","dummy error 2"); + result.addWarning("W-APS00001","some warning"); + String formatted = result.toString(); + assertEquals(formatted, "Validation result (validation type: ASYNCHRONOUS): 2 errors: [E-APS00001: dummy error, E-APS00002: dummy error 2], 1 warnings: [W-APS00001: some warning]"); + } + + @Test + void validationResultFormatTestOnlyError() { + ValidationResult result = new ValidationResult(); + result.setValidationType(ValidationType.ASYNCHRONOUS); + result.addError("E-APS00001","dummy error"); + result.addError("E-APS00002","dummy error 2"); + String formatted = result.toString(); + assertEquals(formatted, "Validation result (validation type: ASYNCHRONOUS): 2 errors: [E-APS00001: dummy error, E-APS00002: dummy error 2], 0 warnings"); + } + + @Test + void validationResultFormatTestOnlyWarnings() { + ValidationResult result = new ValidationResult(); + result.setValidationType(ValidationType.ASYNCHRONOUS); + result.addWarning("W-APS00001","some warning"); + result.addWarning("W-APS00002","some warning 2"); + String formatted = result.toString(); + assertEquals(formatted, "Validation result (validation type: ASYNCHRONOUS): 0 errors, 2 warnings: [W-APS00001: some warning, W-APS00002: some warning 2]"); + } + + @Test + void validationResultFormatEmpty() { + ValidationResult result = new ValidationResult(); + result.setValidationType(ValidationType.ASYNCHRONOUS); + String formatted = result.toString(); + assertEquals(formatted, "Validation result (validation type: ASYNCHRONOUS): 0 errors, 0 warnings"); + } + + + @Test + void validationResultFormatEmptyWithoutType() { + ValidationResult result = new ValidationResult(); + String formatted = result.toString(); + assertEquals(formatted, "Validation result: 0 errors, 0 warnings"); + } +} diff --git a/src/test/java/dk/erst/oxalis/as4/jdbc/LiquibaseTest.java b/src/test/java/dk/erst/oxalis/as4/jdbc/LiquibaseTest.java new file mode 100644 index 0000000000000000000000000000000000000000..169f705b7daa43f4029fe2648fb33110dd12ed48 --- /dev/null +++ b/src/test/java/dk/erst/oxalis/as4/jdbc/LiquibaseTest.java @@ -0,0 +1,59 @@ +package dk.erst.oxalis.as4.jdbc; + +import com.google.inject.Guice; +import com.google.inject.Injector; +import com.google.inject.persist.PersistService; +import com.typesafe.config.ConfigFactory; +import dk.erst.oxalis.as4.util.UtilityModule; +import network.oxalis.commons.config.ConfigModule; +import network.oxalis.commons.filesystem.FileSystemModule; +import org.hamcrest.MatcherAssert; +import org.hamcrest.Matchers; +import org.testng.annotations.Test; + +import javax.persistence.EntityManager; +import java.math.BigInteger; + +public class LiquibaseTest { + @Test + public void testCanConfigureLiquibaseChangelogFile(){ + try { + System.setProperty("oxalis.path.conf", "configure-liquibase-changelog-conf"); + // invalidate cached system properties to force reload! + ConfigFactory.invalidateCaches(); + + Injector injector = Guice.createInjector( + new FileSystemModule(), + new ConfigModule(), + new UtilityModule(), + new JdbcModule() + ); + + // Must manually start Jpa here + PersistService persistService = injector.getInstance(PersistService.class); + persistService.start(); + + + EntityManager em = injector.getInstance(EntityManager.class); + + // Make sure liquibase has run + BigInteger n = (BigInteger)em.createNativeQuery("SELECT count(1) from databasechangelog") + .getSingleResult(); + MatcherAssert.assertThat(n.longValue(), Matchers.greaterThanOrEqualTo(1L)); + + // Make sure liquibase has the included changesets from the real changelog (which is included in dummy-db-changelog.xml) + n = (BigInteger)em.createNativeQuery("SELECT count(1) from Account") + .getSingleResult(); + MatcherAssert.assertThat(n.longValue(), Matchers.greaterThanOrEqualTo(0L)); + + // make sure test changeset from configured changelog file has run and created dummy table + n = (BigInteger)em.createNativeQuery("SELECT count(1) from Dummy_Table") + .getSingleResult(); + MatcherAssert.assertThat(n.longValue(), Matchers.greaterThanOrEqualTo(0L)); + + } finally { + System.clearProperty("oxalis.path.conf"); + ConfigFactory.invalidateCaches(); + } + } +} diff --git a/src/test/java/dk/erst/oxalis/as4/rest/resources/AbstractJaxRsTest.java b/src/test/java/dk/erst/oxalis/as4/rest/resources/AbstractJaxRsTest.java index eeec95641467a8ba2d1fb2ddaffd6bec6a1ae5f3..03e91b9ff412dfc427bee3120380e24bc15d0ac2 100644 --- a/src/test/java/dk/erst/oxalis/as4/rest/resources/AbstractJaxRsTest.java +++ b/src/test/java/dk/erst/oxalis/as4/rest/resources/AbstractJaxRsTest.java @@ -123,11 +123,6 @@ public class AbstractJaxRsTest extends AbstractJettyServerTest { protected static class DummyDocumentSender implements DocumentSender { - @Override - public byte[] signMessage(byte[] sbd, byte[] originalDocument, String receiverCVR, SBDMessageContext messageContext) throws OutboundException { - return "hello".getBytes(StandardCharsets.UTF_8); - } - @Override public TransmissionResponse send(Message message, SBDMessageContext messageContext) throws OutboundException { URI uri = null; diff --git a/src/test/java/dk/erst/oxalis/as4/rest/resources/ForwardedHeaderFilterTest.java b/src/test/java/dk/erst/oxalis/as4/rest/resources/ForwardedHeaderFilterTest.java index 81c2648cafb8e5f8bafe3642e1a159102a02b99b..c8b9c15ec051eb87cb7341a10f7781a70df47f23 100644 --- a/src/test/java/dk/erst/oxalis/as4/rest/resources/ForwardedHeaderFilterTest.java +++ b/src/test/java/dk/erst/oxalis/as4/rest/resources/ForwardedHeaderFilterTest.java @@ -22,7 +22,6 @@ import network.oxalis.pkix.ocsp.api.OcspFetcher; import network.oxalis.test.dummy.DummyPkiModule; import org.apache.http.impl.client.CloseableHttpClient; import org.bouncycastle.jce.provider.BouncyCastleProvider; -import org.jetbrains.annotations.NotNull; import org.testng.annotations.AfterClass; import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeClass; @@ -284,7 +283,6 @@ public class ForwardedHeaderFilterTest extends AbstractJaxRsTest { } - @NotNull private static String getUrlFromRequest(HttpServletRequest modifiedRequest) { String scheme = modifiedRequest.getScheme(); String host = modifiedRequest.getServerName(); diff --git a/src/test/java/dk/erst/oxalis/as4/rest/resources/InboxResourceTest.java b/src/test/java/dk/erst/oxalis/as4/rest/resources/InboxResourceTest.java index fdef4336a0fe2547885c6a9400d54fcba2e1134d..8f2b164d4475ee42cc4b21388938be094aee40fd 100644 --- a/src/test/java/dk/erst/oxalis/as4/rest/resources/InboxResourceTest.java +++ b/src/test/java/dk/erst/oxalis/as4/rest/resources/InboxResourceTest.java @@ -1,12 +1,7 @@ package dk.erst.oxalis.as4.rest.resources; import dk.erst.oxalis.as4.error.ErrorCodes; -import dk.erst.oxalis.as4.handlers.dto.ErrorResponse; -import dk.erst.oxalis.as4.handlers.dto.MessageModel; -import dk.erst.oxalis.as4.handlers.dto.MessageModelResponse; -import dk.erst.oxalis.as4.handlers.dto.UpdateStatusRequest; -import dk.erst.oxalis.as4.handlers.dto.ValidationMessageType; -import dk.erst.oxalis.as4.handlers.dto.MessageLogResponse; +import dk.erst.oxalis.as4.handlers.dto.*; import dk.erst.oxalis.as4.persistence.model.*; import dk.erst.oxalis.as4.util.PasswordHasher; import org.apache.http.HttpStatus; @@ -25,9 +20,11 @@ import javax.ws.rs.core.Response; import java.nio.charset.StandardCharsets; import java.security.Security; import java.sql.SQLException; +import java.util.UUID; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.notNullValue; import static org.testng.Assert.assertEquals; public class InboxResourceTest extends AbstractJaxRsTest { @@ -136,11 +133,35 @@ public class InboxResourceTest extends AbstractJaxRsTest { MatcherAssert.assertThat(resp.getEntries().get(0).getSender(), CoreMatchers.equalTo("sender1")); } + @Test + public void getStatusAndResponsesForMessageWithUUIDWrongUser() { + doInTx(em -> { + Message m1 = new Message(account, MessageDirection.IN, "sender1", "receiver1", "test", "messageUUID2", + "documentType", new MessageContent("foo".getBytes(StandardCharsets.UTF_8))); + m1.setStatus(MessageStatus.PROCESSING); + em.persist(m1); + + Message response1 = new Message(account, MessageDirection.IN, "sender1", "receiver1", "test", "messageUUID", + "documentType", new MessageContent("foo".getBytes(StandardCharsets.UTF_8))); + response1.setOriginalMessage(m1); + response1.setStatus(MessageStatus.ERROR); + em.persist(response1); + }); + Client client = ResteasyClientBuilder.newClient(); + Response response = client.target("http://localhost:8080/api/inbox/messageUUID/status") + .register(new BasicAuthentication("wronguser", "testpassword")) + .request(MediaType.APPLICATION_XML_TYPE) + .get(); + assertThat(response, notNullValue()); + assertThat(response.getStatus(), equalTo(401)); + } + @Test public void testGetInboxMessageStatusFromMessageUUID() { doInTx(em -> { Message m1 = new Message(account, MessageDirection.IN, "sender1", "receiver1", "test", "messageUUID", "documentType", new MessageContent("foo".getBytes(StandardCharsets.UTF_8))); + m1.setMessageLevelResponseUuid("mlrMessageUUID"); em.persist(m1); }); @@ -165,8 +186,10 @@ public class InboxResourceTest extends AbstractJaxRsTest { MatcherAssert.assertThat(m.getChannel(), CoreMatchers.equalTo("test")); MatcherAssert.assertThat(m.getMessageUuid(), CoreMatchers.equalTo("messageUUID")); MatcherAssert.assertThat(m.getDocumentTypeId(), CoreMatchers.equalTo("documentType")); + MatcherAssert.assertThat(m.getMessageLevelResponseUuid(), CoreMatchers.equalTo("mlrMessageUUID")); } + @Test public void testGetInboxMessageLog400BadRequestOnWrongMessageUUID() { doInTx(em -> { diff --git a/src/test/java/dk/erst/oxalis/as4/rest/resources/OutboxResourceTest.java b/src/test/java/dk/erst/oxalis/as4/rest/resources/OutboxResourceTest.java index 88e5d21aabbf22f34c97da0cef31a2c876241df5..684bbfae7d3f7e401adf9fd270ff41998cd0bfd8 100644 --- a/src/test/java/dk/erst/oxalis/as4/rest/resources/OutboxResourceTest.java +++ b/src/test/java/dk/erst/oxalis/as4/rest/resources/OutboxResourceTest.java @@ -32,18 +32,22 @@ import java.io.UnsupportedEncodingException; import java.nio.charset.StandardCharsets; import java.security.Security; import java.sql.SQLException; +import java.time.LocalDateTime; import java.util.UUID; import java.util.regex.Pattern; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.notNullValue; import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.doReturn; +import static org.testng.Assert.assertEquals; public class OutboxResourceTest extends AbstractJaxRsTest { private Account account; private EntityManager entityManager; + private static final String OUTBOX_URL = "http://localhost:8080/api/outbox"; @BeforeClass public void setup() { @@ -350,6 +354,99 @@ public class OutboxResourceTest extends AbstractJaxRsTest { assertThat(response.getStatus(), equalTo(HttpStatus.SC_UNAUTHORIZED)); } + + @Test + public void testGetSimpleStatusForDocumentUnauthorized() { + doInTx(em -> { + Message m1 = new Message(account, MessageDirection.OUT, "sender1", "receiver1", "test", "messageUUID", + "documentType", new MessageContent("".getBytes())); + m1.setStatus(MessageStatus.CREATED); + + em.persist(m1); + Message resp = new Message(account, MessageDirection.IN, "receiver1", "sender1", "test", "messageUUID2", + "documentType", new MessageContent("".getBytes())); + resp.setStatus(MessageStatus.ERROR); + resp.setOriginalMessage(m1); + em.persist(resp); + }); + + Client client = ResteasyClientBuilder.newClient(); + Response response = client.target("http://localhost:8080/api/outbox/messageUUID") + .register(new BasicAuthentication("wronguser", "testpassword")) + .request().get(); + assertThat(response, notNullValue()); + assertThat(response.getStatus(), equalTo(401)); + } + @Test + public void testGetMessageAndAllResponses() { + doInTx(em -> { + Message m1 = new Message(account, MessageDirection.OUT, "sender1", "receiver1", "test", "messageUUID", + "documentType", new MessageContent("".getBytes())); + m1.setStatus(MessageStatus.CREATED); + + em.persist(m1); + Message resp = new Message(account, MessageDirection.IN, "receiver1", "sender1", "test", "messageUUID2", + "documentType", new MessageContent("".getBytes())); + resp.setStatus(MessageStatus.ERROR); + resp.setOriginalMessage(m1); + em.persist(resp); + }); + + Client client = ResteasyClientBuilder.newClient(); + Response response = client.target("http://localhost:8080/api/outbox/messageUUID/with-responses") + .register(new BasicAuthentication("testuser", "testpassword")) + .request().get(); + assertThat(response, notNullValue()); + assertThat(response.getStatus(), equalTo(200)); + MessageAndResponses resp = response.readEntity(MessageAndResponses.class); + + assertThat(resp, notNullValue()); + assertThat(resp.getOriginalDocument().getMessageUuid(), equalTo("messageUUID")); + + assertThat(resp.getResponses().stream().anyMatch(x -> x.getMessageUuid().equals("messageUUID2")), CoreMatchers.equalTo(true)); + } + @Test + public void testGetMessageAndAllResponsesMultipleResponses() { + doInTx(em -> { + Message m1 = new Message(account, MessageDirection.OUT, "sender1", "receiver1", "test", "messageUUID", + "documentType", new MessageContent("".getBytes())); + m1.setStatus(MessageStatus.CREATED); + + em.persist(m1); + Message resp1 = new Message(account, MessageDirection.IN, "receiver1", "sender1", "test", "messageUUID2", + "documentType", new MessageContent("".getBytes())); + resp1.setStatus(MessageStatus.ERROR); + resp1.setOriginalMessage(m1); + em.persist(resp1); + Message resp2 = new Message(account, MessageDirection.IN, "receiver1", "sender1", "test", "messageUUID3", + "documentType", new MessageContent("".getBytes())); + resp2.setStatus(MessageStatus.ERROR); + resp2.setOriginalMessage(m1); + em.persist(resp2); + Message resp3 = new Message(account, MessageDirection.IN, "receiver1", "sender1", "test", "messageUUID4", + "documentType", new MessageContent("".getBytes())); + resp3.setStatus(MessageStatus.ERROR); + resp3.setOriginalMessage(m1); + em.persist(resp3); + }); + + Client client = ResteasyClientBuilder.newClient(); + Response response = client.target("http://localhost:8080/api/outbox/messageUUID/with-responses") + .register(new BasicAuthentication("testuser", "testpassword")) + .request().get(); + assertThat(response, notNullValue()); + assertThat(response.getStatus(), equalTo(200)); + MessageAndResponses resp = response.readEntity(MessageAndResponses.class); + + assertThat(resp, notNullValue()); + assertThat(resp.getOriginalDocument().getMessageUuid(), equalTo("messageUUID")); + + assertThat(resp.getResponses().stream().anyMatch(x -> x.getMessageUuid().equals("messageUUID2")), CoreMatchers.equalTo(true)); + assertThat(resp.getResponses().stream().anyMatch(x -> x.getMessageUuid().equals("messageUUID3")), CoreMatchers.equalTo(true)); + assertThat(resp.getResponses().stream().anyMatch(x -> x.getMessageUuid().equals("messageUUID4")), CoreMatchers.equalTo(true)); + assertThat(resp.getResponses().size(), equalTo(3)); + } + @Test public void testDocumentSignatureIsOptionalForC2() throws OutboundException { SendSbdHandler sbdHandlerMock = Mockito.mock(SendSbdHandler.class); @@ -367,6 +464,44 @@ public class OutboxResourceTest extends AbstractJaxRsTest { } } + @Test + public void testGetMessageContentReturnsCorrectResult() { + doInTx(em -> { + Message m1 = new Message(account, MessageDirection.OUT, "sender1", "receiver1", "test", "uuid-1", + "documentType", new MessageContent("".getBytes())); + m1.setStatus(MessageStatus.CREATED); + em.persist(m1); + }); + + Client client = ResteasyClientBuilder.newClient(); + Response response = client.target(OUTBOX_URL + "/uuid-1") + .register(new BasicAuthentication(account.getUsername(), "testpassword")) + .request().get(); + + MatcherAssert.assertThat(response.getStatus(), CoreMatchers.equalTo(HttpStatus.SC_OK)); + MatcherAssert.assertThat(response.readEntity(String.class), CoreMatchers.equalTo("")); + } + + @Test + public void testGetMessageContentReturns404NotFoundIfMessageIsNotFound() { + Client client = ResteasyClientBuilder.newClient(); + Response response = client.target(OUTBOX_URL + "/" + UUID.randomUUID().toString()) + .register(new BasicAuthentication(account.getUsername(), "testpassword")) + .request().get(); + + MatcherAssert.assertThat(response.getStatus(), CoreMatchers.equalTo(HttpStatus.SC_NOT_FOUND)); + } + + @Test + public void testGetMessageContentReturns401UnauthorizedIfWrongAccount() { + Client client = ResteasyClientBuilder.newClient(); + Response response = client.target(OUTBOX_URL + "/" + UUID.randomUUID().toString()) + .register(new BasicAuthentication("wrongaccount", "wrongpassword")) + .request().get(); + + MatcherAssert.assertThat(response.getStatus(), CoreMatchers.equalTo(HttpStatus.SC_UNAUTHORIZED)); + } + private String load(String path) { try (InputStream in = SendSbdHandlerTest.class.getResourceAsStream(path)) { return IOUtils.toString(in, StandardCharsets.UTF_8); diff --git a/src/test/java/dk/erst/oxalis/as4/util/OxalisDocumentSenderTest.java b/src/test/java/dk/erst/oxalis/as4/util/OxalisDocumentSenderTest.java index 7504cec02aaf3db14505e5a96c110f3855631793..ea49414e18ca6d107b0584582eaebcfd7c839e00 100644 --- a/src/test/java/dk/erst/oxalis/as4/util/OxalisDocumentSenderTest.java +++ b/src/test/java/dk/erst/oxalis/as4/util/OxalisDocumentSenderTest.java @@ -3,21 +3,18 @@ package dk.erst.oxalis.as4.util; import com.google.inject.AbstractModule; import com.google.inject.Guice; import com.google.inject.Injector; -import com.google.inject.Provider; import com.google.inject.persist.PersistService; import com.google.inject.util.Modules; -import com.typesafe.config.Config; -import com.typesafe.config.ConfigFactory; import dk.erst.oxalis.as4.AbstractXmlTest; +import dk.erst.oxalis.as4.EDeliverySpecification; import dk.erst.oxalis.as4.error.ErrorCodes; import dk.erst.oxalis.as4.handlers.OutboundException; import dk.erst.oxalis.as4.handlers.OutboundValidationException; import dk.erst.oxalis.as4.jdbc.JdbcModule; import dk.erst.oxalis.as4.mode.Mode; import dk.erst.oxalis.as4.persistence.model.*; +import dk.erst.oxalis.as4.signature.SignatureException; import dk.erst.oxalis.as4.signature.SignatureFactory; -import dk.erst.oxalis.as4.signature.TransferDelegationException; -import dk.erst.oxalis.as4.signature.TransferDelegationFactoryImpl; import dk.erst.oxalis.as4.validation.MessageValidator; import dk.erst.oxalis.as4.validation.ValidationException; import dk.erst.oxalis.as4.handlers.dto.ValidationResult; @@ -25,7 +22,6 @@ import dk.erst.oxalis.as4.validation.ValidationType; import io.opentracing.Span; import network.oxalis.api.lang.OxalisContentException; import network.oxalis.api.lang.OxalisTransmissionException; -import network.oxalis.api.lookup.LookupService; import network.oxalis.api.model.Direction; import network.oxalis.api.model.TransmissionIdentifier; import network.oxalis.api.outbound.TransmissionMessage; @@ -33,74 +29,49 @@ import network.oxalis.api.outbound.TransmissionRequest; import network.oxalis.api.outbound.TransmissionResponse; import network.oxalis.api.outbound.Transmitter; import network.oxalis.api.timestamp.Timestamp; -import network.oxalis.as4.lang.OxalisAs4Exception; import network.oxalis.as4.lang.OxalisAs4TransmissionException; import network.oxalis.as4.outbound.As4TransmissionResponse; import network.oxalis.commons.config.ConfigModule; import network.oxalis.commons.filesystem.FileSystemModule; -import network.oxalis.commons.header.SbdhHeaderParser; import network.oxalis.outbound.transmission.TransmissionRequestBuilder; -import network.oxalis.sniffer.PeppolStandardBusinessHeader; import network.oxalis.vefa.peppol.common.code.DigestMethod; import network.oxalis.vefa.peppol.common.lang.PeppolParsingException; import network.oxalis.vefa.peppol.common.model.*; import network.oxalis.vefa.peppol.lookup.api.LookupException; import network.oxalis.vefa.peppol.lookup.api.NotFoundException; import org.apache.commons.io.IOUtils; -import org.apache.cxf.helpers.MapNamespaceContext; -import org.apache.xml.security.exceptions.XMLSecurityException; -import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.hamcrest.CoreMatchers; import org.hamcrest.MatcherAssert; -import org.hamcrest.Matchers; import org.mockito.ArgumentCaptor; -import org.mockito.Mock; import org.mockito.Mockito; import org.testng.Assert; import org.testng.annotations.*; import org.w3c.dom.Document; -import org.w3c.dom.Node; -import org.xml.sax.InputSource; import org.xml.sax.SAXException; import javax.persistence.EntityManager; -import javax.xml.bind.JAXBException; import javax.xml.parsers.DocumentBuilder; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerException; import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; -import javax.xml.xpath.XPath; import javax.xml.xpath.XPathExpressionException; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.net.URI; import java.net.URISyntaxException; -import java.net.URL; import java.nio.charset.StandardCharsets; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.security.KeyStore; -import java.security.KeyStoreException; -import java.security.NoSuchAlgorithmException; -import java.security.Security; -import java.security.cert.CertificateException; -import java.security.cert.X509Certificate; import java.util.*; import java.util.function.Consumer; import java.util.regex.Pattern; import java.util.stream.Collectors; -import static dk.erst.oxalis.as4.util.TxUtil.doInTx; -import static dk.erst.oxalis.as4.util.XPathUtil.evaluateXPath; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.*; -import static org.hamcrest.Matchers.nullValue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; -import static org.testng.Assert.assertEquals; public class OxalisDocumentSenderTest extends AbstractXmlTest { private Injector injector; @@ -110,15 +81,11 @@ public class OxalisDocumentSenderTest extends AbstractXmlTest { private TransmissionRequest transmissionRequestMock; private MessageValidator messageValidatorMock; private SignatureFactory signatureFactoryMock; - private TransferDelegationFactoryImpl transferDelegationFactoryMock; - private LookupService lookupServiceMock; - private X509Certificate accessPointCertificate; - private SbdhHeaderParser headerParserMock; private Mode modeMock; - private DocumentBuilderProvider documentBuilderProviderMock; private DocumentBuilder documentBuilderMock; private Account account; private EntityManager entityManager; + private Document exampleSBD; @BeforeClass public void setupBeforeClass() { @@ -162,42 +129,14 @@ public class OxalisDocumentSenderTest extends AbstractXmlTest { transmissionRequestMock = Mockito.mock(TransmissionRequest.class); messageValidatorMock = Mockito.mock(MessageValidator.class); signatureFactoryMock = Mockito.mock(SignatureFactory.class); - transferDelegationFactoryMock = Mockito.mock(TransferDelegationFactoryImpl.class); - lookupServiceMock = Mockito.mock(LookupService.class); - headerParserMock = Mockito.mock(SbdhHeaderParser.class); modeMock = Mockito.mock(Mode.class); - documentBuilderProviderMock = Mockito.mock(DocumentBuilderProvider.class); + DocumentBuilderProvider documentBuilderProviderMock = Mockito.mock(DocumentBuilderProvider.class); documentBuilderMock = Mockito.mock(DocumentBuilder.class); - try(InputStream is = this.getClass().getResourceAsStream("/oxalis_home/Test_systemcertifikat.p12")) { - URL url = this.getClass().getResource("/oxalis_home/nemhandel-e-delivery-oxalis.conf"); - Path confPath = Paths.get(url.toURI()); - - Config cfg = ConfigFactory.parseFile(confPath.toFile()).resolve(); - - KeyStore ks = KeyStore.getInstance("PKCS12"); - ks.load(is, cfg.getString("oxalis.keystore.password").toCharArray()); - - accessPointCertificate = (X509Certificate) ks.getCertificate(cfg.getString("oxalis.keystore.key.alias")); - } - Endpoint endpoint = Mockito.mock(Endpoint.class); - doReturn(accessPointCertificate).when(endpoint).getCertificate(); - doReturn(endpoint).when(lookupServiceMock).lookup(any(Header.class)); when(modeMock.isNemhandelEdeliveryMode()).thenReturn(true); byte[] signedDoc = "SignedDocument".getBytes(StandardCharsets.UTF_8); - doReturn(signedDoc).when(signatureFactoryMock).signDocument(any(), any(), any(), any()); - - PeppolStandardBusinessHeader psbh = Mockito.mock(PeppolStandardBusinessHeader.class); - Header head = Header.of( - ParticipantIdentifier.of("0184:12345678"), - ParticipantIdentifier.of("0184:12345678"), - ProcessIdentifier.of("urn:www.nesubl.eu:profiles:profile5:ver2.0"), - DocumentTypeIdentifier.of("urn:oasis:names:specification:ubl:schema:xsd:Invoice-2::Invoice##OIOUBL-2.1::2.1"), - InstanceIdentifier.of("foo"), - InstanceType.of("foo", "InstanceType", "1.0"), - new Date()); - doReturn(head).when(headerParserMock).parse(any(InputStream.class)); + doReturn(signedDoc).when(signatureFactoryMock).signDocument(any(), any()); when(requestBuilderMock.sender(any(ParticipantIdentifier.class))).thenReturn(requestBuilderMock); when(requestBuilderMock.receiver(any(ParticipantIdentifier.class))).thenReturn(requestBuilderMock); @@ -215,14 +154,13 @@ public class OxalisDocumentSenderTest extends AbstractXmlTest { when(documentBuilderProviderMock.get()).thenReturn(documentBuilderMock); - Document doc = loadDocument("sbd-examples/SBD_OIOUBL_Invoice.xml"); - when(documentBuilderMock.parse(any(InputStream.class))).thenReturn(doc); + exampleSBD = loadDocument("sbd-examples/SBD_OIOUBL_Invoice.xml"); + when(documentBuilderMock.parse(any(InputStream.class))).thenReturn(exampleSBD); sender = new OxalisDocumentSender( () -> requestBuilderMock, transmitterMock, messageValidatorMock, signatureFactoryMock, - transferDelegationFactoryMock, lookupServiceMock, accessPointCertificate, headerParserMock, modeMock, documentBuilderProviderMock, () -> entityManager); } @@ -380,6 +318,33 @@ public class OxalisDocumentSenderTest extends AbstractXmlTest { } } + @Test + public void testDocumentTypeNotSupportByReceiver() throws OxalisTransmissionException { + when(transmitterMock.transmit(any(TransmissionMessage.class))).thenThrow(new OxalisTransmissionException("test", new LookupException("Combination of receiver (iso6523-actorid-upis::0184:31674255) and document type identifier (busdox-docid-qns::urn:oasis:names:specification:ubl:schema:xsd:Invoice-2::Invoice##OIOUBL-2.1::2.1) is not supported."))); + String messageUUID = UUID.randomUUID().toString(); + Message message = new Message( + account, + MessageDirection.OUT, + "iso6523-actorid-upis::sender", + "iso6523-actorid-upis::receiver", + "channel", + messageUUID, + "busdox-docid-qns::urn:oasis:names:specification:ubl:schema:xsd:Invoice-2::Invoice##OIOUBL-2.1::2.1", + new MessageContent("test".getBytes(StandardCharsets.UTF_8))); + doInTx(em -> { + em.persist(message); + }); + try { + sender.send(message, new SBDMessageContext()); + Assert.fail("should throw"); + }catch(OutboundException e){ + assertThat(e.getCause(), instanceOf(OxalisTransmissionException.class)); + MatcherAssert.assertThat(e.getErrorCode(), CoreMatchers.equalTo(ErrorCodes.OUTBOUND_RECEIVER_DOES_NOT_EXIST_ERROR.getErrorCode())); + MatcherAssert.assertThat(e.getMessage(), CoreMatchers.equalTo("E-APS24006: The receiver 'iso6523-actorid-upis::0184:31674255' does not support document type 'busdox-docid-qns::urn:oasis:names:specification:ubl:schema:xsd:Invoice-2::Invoice##OIOUBL-2.1::2.1' according to the SMP lookup.")); + MatcherAssert.assertThat(e.getMessageUuid(), CoreMatchers.equalTo(messageUUID)); + } + } + @Test public void testNotFoundExceptionErrorDuringSMP() throws OxalisTransmissionException { when(transmitterMock.transmit(any(TransmissionMessage.class))).thenThrow(new OxalisTransmissionException("test", new NotFoundException(""))); @@ -524,33 +489,12 @@ public class OxalisDocumentSenderTest extends AbstractXmlTest { } @Test - public void testCreatesTransferDelegation() throws URISyntaxException, OxalisTransmissionException, OutboundException, IOException, KeyStoreException, CertificateException, NoSuchAlgorithmException, JAXBException, TransferDelegationException { + public void testDoesNotSignDocumentWhenNotInNemhandelMode() throws URISyntaxException, OxalisTransmissionException, OutboundException, SignatureException, IOException, TransformerException { TransmissionResponse fakeResponse = fakeResponse(); when(transmitterMock.transmit(any(TransmissionMessage.class))).thenReturn(fakeResponse); - when(modeMock.isNemhandelEdeliveryMode()).thenReturn(true); - - Message message = new Message( - account, - MessageDirection.OUT, - "iso6523-actorid-upis::sender", - "iso6523-actorid-upis::receiver", - "channel", - UUID.randomUUID().toString(), - "busdox-docid-qns::urn:oasis:names:specification:ubl:schema:xsd:Invoice-2::Invoice##OIOUBL-2.1::2.1", - new MessageContent("test".getBytes(StandardCharsets.UTF_8))); - message.setPeppolProcessId("cenbii-procid-ubl::proces::oioubl-procid-ubl::urn:www.nesubl.eu:profiles:profile5:ver2.0"); - doInTx(em -> { - em.persist(message); - }); - sender.send(message, new SBDMessageContext()); - verify(transferDelegationFactoryMock).createTransferDelegation("97281536", "97281536"); - } - - @Test - public void testLooksUpEndpointFromSMPWithCorrectInput() throws URISyntaxException, OxalisTransmissionException, OutboundException, IOException, KeyStoreException, CertificateException, NoSuchAlgorithmException, JAXBException, PeppolParsingException, XPathExpressionException, TransformerException, SAXException { - TransmissionResponse fakeResponse = fakeResponse(); - when(transmitterMock.transmit(any(TransmissionMessage.class))).thenReturn(fakeResponse); - + when(modeMock.isNemhandelEdeliveryMode()).thenReturn(false); + byte[] testContent = "test".getBytes(StandardCharsets.UTF_8); + // Account account = new Account("test", "password"); Message message = new Message( account, MessageDirection.OUT, @@ -559,34 +503,23 @@ public class OxalisDocumentSenderTest extends AbstractXmlTest { "channel", UUID.randomUUID().toString(), "busdox-docid-qns::urn:oasis:names:specification:ubl:schema:xsd:Invoice-2::Invoice##OIOUBL-2.1::2.1", - new MessageContent("test".getBytes(StandardCharsets.UTF_8))); + new MessageContent(testContent)); message.setPeppolProcessId("cenbii-procid-ubl::proces::oioubl-procid-ubl::urn:www.nesubl.eu:profiles:profile5:ver2.0"); doInTx(em -> { em.persist(message); }); sender.send(message, new SBDMessageContext()); - ArgumentCaptor
headerArgumentCaptor = ArgumentCaptor.forClass(Header.class); - verify(lookupServiceMock).lookup(headerArgumentCaptor.capture()); - - Header header = headerArgumentCaptor.getValue(); - MatcherAssert.assertThat(header, CoreMatchers.notNullValue()); - MatcherAssert.assertThat(header.getSender(), CoreMatchers.equalTo(ParticipantIdentifier.parse(message.getSender()))); - MatcherAssert.assertThat(header.getReceiver(), CoreMatchers.equalTo(ParticipantIdentifier.parse(message.getReceiver()))); - MatcherAssert.assertThat(header.getDocumentType(), CoreMatchers.equalTo(DocumentTypeIdentifier.parse(message.getDocumentTypeId()))); - MatcherAssert.assertThat(header.getProcess(), CoreMatchers.equalTo(ProcessIdentifier.parse(message.getPeppolProcessId()))); + MatcherAssert.assertThat(message.getMessageContent().getData(), equalTo(DocumentUtil.toByteArray(exampleSBD))); + verify(signatureFactoryMock, never()).signDocument(any(), any()); } @Test - public void testSignsDocument() throws URISyntaxException, OxalisTransmissionException, OutboundException, IOException, KeyStoreException, CertificateException, NoSuchAlgorithmException, JAXBException, SAXException, TransformerException, XPathExpressionException, TransferDelegationException, XMLSecurityException { + public void testIfMessageUUIDIsSetItStays() throws Exception { + Pattern UUID_REGEX = Pattern.compile("^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$"); TransmissionResponse fakeResponse = fakeResponse(); when(transmitterMock.transmit(any(TransmissionMessage.class))).thenReturn(fakeResponse); - when(modeMock.isNemhandelEdeliveryMode()).thenReturn(true); - - byte[] transferDelegation = "foo".getBytes(StandardCharsets.UTF_8); - doReturn(transferDelegation).when(transferDelegationFactoryMock).createTransferDelegation(anyString(), anyString()); - - byte[] signedDoc = "ThisIsASignedDocument".getBytes(StandardCharsets.UTF_8); - doReturn(signedDoc).when(signatureFactoryMock).signDocument(any(), any(), any(), any()); + String messageUUID = UUID.randomUUID().toString(); + Document doc = loadDocument("sbd-examples/SBD_OIOUBL_Invoice-NoMessageUUID.xml"); Message message = new Message( account, @@ -594,49 +527,25 @@ public class OxalisDocumentSenderTest extends AbstractXmlTest { "iso6523-actorid-upis::sender", "iso6523-actorid-upis::receiver", "channel", - UUID.randomUUID().toString(), + messageUUID, "busdox-docid-qns::urn:oasis:names:specification:ubl:schema:xsd:Invoice-2::Invoice##OIOUBL-2.1::2.1", - new MessageContent("test".getBytes(StandardCharsets.UTF_8))); + new MessageContent(toByteArray(doc))); + message.setPeppolProcessId("cenbii-procid-ubl::proces"); doInTx(em -> { em.persist(message); }); - message.setPeppolProcessId("cenbii-procid-ubl::proces::oioubl-procid-ubl::urn:www.nesubl.eu:profiles:profile5:ver2.0"); - - sender.send(message, new SBDMessageContext()); - MatcherAssert.assertThat(message.getMessageContent().getData(), equalTo(signedDoc)); - } - - @Test - public void testDoesNotSignDocument() throws URISyntaxException, OxalisTransmissionException, OutboundException, IOException, KeyStoreException, CertificateException, NoSuchAlgorithmException, JAXBException, SAXException, TransformerException, XPathExpressionException, TransferDelegationException { - TransmissionResponse fakeResponse = fakeResponse(); - when(transmitterMock.transmit(any(TransmissionMessage.class))).thenReturn(fakeResponse); - when(modeMock.isNemhandelEdeliveryMode()).thenReturn(false); - byte[] testContent = "test".getBytes(StandardCharsets.UTF_8); + TransmissionResponse response = sender.send(message, new SBDMessageContext()); - // Account account = new Account("test", "password"); - Message message = new Message( - account, - MessageDirection.OUT, - "iso6523-actorid-upis::sender", - "iso6523-actorid-upis::receiver", - "channel", - UUID.randomUUID().toString(), - "busdox-docid-qns::urn:oasis:names:specification:ubl:schema:xsd:Invoice-2::Invoice##OIOUBL-2.1::2.1", - new MessageContent(testContent)); - message.setPeppolProcessId("cenbii-procid-ubl::proces::oioubl-procid-ubl::urn:www.nesubl.eu:profiles:profile5:ver2.0"); - doInTx(em -> { - em.persist(message); - }); - sender.send(message, new SBDMessageContext()); - MatcherAssert.assertThat(message.getMessageContent().getData(), equalTo(testContent)); + assertThat(message.getMessageUuid(), CoreMatchers.equalTo(messageUUID)); + assertThat(UUID_REGEX.matcher(message.getMessageUuid()).matches(), equalTo(Boolean.TRUE)); } @Test - public void testSendsResignedSbd() throws URISyntaxException, OxalisTransmissionException, JAXBException, OutboundException, IOException, SAXException, TransformerException, XPathExpressionException, XMLSecurityException { + public void testHandlesTransactionsCorrectlyForInternalMessageLevelResponses() throws URISyntaxException, OxalisTransmissionException, OutboundException, SignatureException { TransmissionResponse fakeResponse = fakeResponse(); when(transmitterMock.transmit(any(TransmissionMessage.class))).thenReturn(fakeResponse); byte[] signedDoc = "SignedDocument".getBytes(StandardCharsets.UTF_8); - doReturn(signedDoc).when(signatureFactoryMock).signDocument(any(), any(), any(), any()); + doReturn(signedDoc).when(signatureFactoryMock).signDocument(any(), any()); when(modeMock.isNemhandelEdeliveryMode()).thenReturn(true); Message message = new Message( @@ -653,22 +562,21 @@ public class OxalisDocumentSenderTest extends AbstractXmlTest { }); message.setPeppolProcessId("cenbii-procid-ubl::proces::oioubl-procid-ubl::urn:www.nesubl.eu:profiles:profile5:ver2.0"); - sender.send(message, new SBDMessageContext()); + EntityManager em = injector.getInstance(EntityManager.class); + TxUtil.doInTxThrows(() -> em, entityManager -> { + // will fail if OxalisDocumentSender attempts to start new transaction + sender.send(message, new SBDMessageContext(true, Direction.OUT)); + }); ArgumentCaptor inputStreamArgumentCaptor = ArgumentCaptor.forClass(InputStream.class); verify(requestBuilderMock).payLoad(inputStreamArgumentCaptor.capture()); - byte[] bytes = IOUtils.toByteArray(inputStreamArgumentCaptor.getValue()); - MatcherAssert.assertThat(bytes, equalTo(signedDoc)); } @Test - public void testIfMessageUUIDIsSetItStays() throws Exception { - Pattern UUID_REGEX = Pattern.compile("^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$"); + public void testCorrectlyFormatsDocumentLogs() throws OutboundException, OxalisContentException, OxalisTransmissionException, URISyntaxException, PeppolParsingException, XPathExpressionException, IOException, TransformerException, SAXException { TransmissionResponse fakeResponse = fakeResponse(); when(transmitterMock.transmit(any(TransmissionMessage.class))).thenReturn(fakeResponse); - String messageUUID = UUID.randomUUID().toString(); - Document doc = loadDocument("sbd-examples/SBD_OIOUBL_Invoice-NoMessageUUID.xml"); Message message = new Message( account, @@ -676,25 +584,33 @@ public class OxalisDocumentSenderTest extends AbstractXmlTest { "iso6523-actorid-upis::sender", "iso6523-actorid-upis::receiver", "channel", - messageUUID, + UUID.randomUUID().toString(), "busdox-docid-qns::urn:oasis:names:specification:ubl:schema:xsd:Invoice-2::Invoice##OIOUBL-2.1::2.1", - new MessageContent(toByteArray(doc))); + new MessageContent("test".getBytes(StandardCharsets.UTF_8))); + message.setPeppolProcessId("cenbii-procid-ubl::proces"); doInTx(em -> { em.persist(message); }); - TransmissionResponse response = sender.send(message, new SBDMessageContext()); + sender.send(message, new SBDMessageContext()); - assertThat(message.getMessageUuid(), CoreMatchers.equalTo(messageUUID)); - assertThat(UUID_REGEX.matcher(message.getMessageUuid()).matches(), equalTo(Boolean.TRUE)); + doInTx(em -> { + List logs = em.createQuery("from MessageLog l where l.message = :message", MessageLog.class) + .setParameter("message", message) + .getResultStream() + .filter(l -> l.getDescription() != null && l.getDescription().contains("%")) + .collect(Collectors.toList()); + + MatcherAssert.assertThat(logs.size(), CoreMatchers.equalTo(0)); + }); } @Test - public void testHandlesTransactionsCorrectlyForInternalMessageLevelResponses() throws URISyntaxException, OxalisTransmissionException, JAXBException, OutboundException, IOException, SAXException, TransformerException, XPathExpressionException, XMLSecurityException { + public void testUnsignedDocumentIsSignedBeforeSending() throws URISyntaxException, OxalisTransmissionException, OutboundException, IOException, SignatureException { TransmissionResponse fakeResponse = fakeResponse(); when(transmitterMock.transmit(any(TransmissionMessage.class))).thenReturn(fakeResponse); byte[] signedDoc = "SignedDocument".getBytes(StandardCharsets.UTF_8); - doReturn(signedDoc).when(signatureFactoryMock).signDocument(any(), any(), any(), any()); + doReturn(signedDoc).when(signatureFactoryMock).signDocument(any(), any()); when(modeMock.isNemhandelEdeliveryMode()).thenReturn(true); Message message = new Message( @@ -711,28 +627,28 @@ public class OxalisDocumentSenderTest extends AbstractXmlTest { }); message.setPeppolProcessId("cenbii-procid-ubl::proces::oioubl-procid-ubl::urn:www.nesubl.eu:profiles:profile5:ver2.0"); - EntityManager em = injector.getInstance(EntityManager.class); - TxUtil.doInTxThrows(() -> em, entityManager -> { - // will fail if OxalisDocumentSender attempts to start new transaction - sender.send(message, new SBDMessageContext(true, Direction.OUT)); - }); + sender.send(message, new SBDMessageContext()); ArgumentCaptor inputStreamArgumentCaptor = ArgumentCaptor.forClass(InputStream.class); verify(requestBuilderMock).payLoad(inputStreamArgumentCaptor.capture()); + byte[] bytes = IOUtils.toByteArray(inputStreamArgumentCaptor.getValue()); + MatcherAssert.assertThat(bytes, equalTo(signedDoc)); } @Test - public void testDoesNotIncludeOriginalStandardBusinessDocumentWhenSigningInternalMessageLevelResponse() throws URISyntaxException, OxalisTransmissionException, JAXBException, OutboundException, IOException, SAXException, TransformerException, XPathExpressionException, TransferDelegationException, XMLSecurityException { + public void testDoesNotResignDocumentBeforeSendingIfDocumentIsAlreadySigned() throws Exception { TransmissionResponse fakeResponse = fakeResponse(); when(transmitterMock.transmit(any(TransmissionMessage.class))).thenReturn(fakeResponse); + byte[] signedDoc = "SignedDocument".getBytes(StandardCharsets.UTF_8); + doReturn(signedDoc).when(signatureFactoryMock).signDocument(any(), any()); when(modeMock.isNemhandelEdeliveryMode()).thenReturn(true); - byte[] transferDelegation = "foo".getBytes(StandardCharsets.UTF_8); - doReturn(transferDelegation).when(transferDelegationFactoryMock).createTransferDelegation(anyString(), anyString()); - - byte[] signedDoc = "ThisIsASignedDocument".getBytes(StandardCharsets.UTF_8); - doReturn(signedDoc).when(signatureFactoryMock).signDocument(any(), any(), any(), any()); + Document doc = loadAndWrapInSBD("ubl-examples/OIOUBL_Invoice_v2p2.xml"); + // add dummy signature + appendNemhandelSignatureScopeNode(doc, "dummy-signature".getBytes(StandardCharsets.UTF_8), EDeliverySpecification.NEMHANDEL_EDELIVERY_1_2); + doReturn(doc).when(documentBuilderMock).parse(any(InputStream.class)); + final byte[] original = DocumentUtil.toByteArray(doc); Message message = new Message( account, @@ -742,54 +658,25 @@ public class OxalisDocumentSenderTest extends AbstractXmlTest { "channel", UUID.randomUUID().toString(), "busdox-docid-qns::urn:oasis:names:specification:ubl:schema:xsd:Invoice-2::Invoice##OIOUBL-2.1::2.1", - new MessageContent("test".getBytes(StandardCharsets.UTF_8))); - message.setPeppolProcessId("cenbii-procid-ubl::proces::oioubl-procid-ubl::urn:www.nesubl.eu:profiles:profile5:ver2.0"); + new MessageContent(original)); doInTx(em -> { em.persist(message); }); - sender.send(message, new SBDMessageContext(true, Direction.OUT)); - - verify(signatureFactoryMock).signDocument(any(), Mockito.isNull(), eq(transferDelegation), argThat(ctx -> ctx.isInternalMessageLevelResponse())); - - } - - @Test - public void testSignatureDoesNotIncludeOriginalSBDIfOriginalSBDIsUnsigned() throws Exception, TransferDelegationException { - Document sbd = loadAndWrapInSBD("ubl-examples/OIOUBL_Invoice_v2p2.xml"); - appendNemhandelSignatureScopeNode(sbd, "asic-container".getBytes(StandardCharsets.UTF_8)); - byte[] xml = DocumentUtil.toByteArray(sbd); - - TransmissionResponse fakeResponse = fakeResponse(); - when(transmitterMock.transmit(any(TransmissionMessage.class))).thenReturn(fakeResponse); - when(modeMock.isNemhandelEdeliveryMode()).thenReturn(true); + message.setPeppolProcessId("cenbii-procid-ubl::proces::oioubl-procid-ubl::urn:www.nesubl.eu:profiles:profile5:ver2.0"); - byte[] transferDelegation = "foo".getBytes(StandardCharsets.UTF_8); - doReturn(transferDelegation).when(transferDelegationFactoryMock).createTransferDelegation(anyString(), anyString()); + sender.send(message, new SBDMessageContext()); - byte[] signedDoc = "ThisIsASignedDocument".getBytes(StandardCharsets.UTF_8); - doReturn(signedDoc).when(signatureFactoryMock).signDocument(any(), any(), any(), any()); + ArgumentCaptor inputStreamArgumentCaptor = ArgumentCaptor.forClass(InputStream.class); + verify(requestBuilderMock).payLoad(inputStreamArgumentCaptor.capture()); - Message message = new Message( - account, - MessageDirection.OUT, - "iso6523-actorid-upis::sender", - "iso6523-actorid-upis::receiver", - "channel", - UUID.randomUUID().toString(), - "busdox-docid-qns::urn:oasis:names:specification:ubl:schema:xsd:Invoice-2::Invoice##OIOUBL-2.1::2.1", - new MessageContent(xml)); - message.setPeppolProcessId("cenbii-procid-ubl::proces::oioubl-procid-ubl::urn:www.nesubl.eu:profiles:profile5:ver2.0"); - doInTx(em -> { - em.persist(message); - }); - sender.send(message, new SBDMessageContext(false, Direction.OUT, false)); - verify(signatureFactoryMock).signDocument(any(), Mockito.isNull(), eq(transferDelegation), any()); + byte[] bytes = IOUtils.toByteArray(inputStreamArgumentCaptor.getValue()); + MatcherAssert.assertThat(bytes, equalTo(original)); + verify(signatureFactoryMock, never()).signDocument(any(), any()); } @Test - public void testCorrectlyFormatsDocumentLogs() throws OutboundException, OxalisContentException, OxalisTransmissionException, URISyntaxException, PeppolParsingException, XPathExpressionException, IOException, TransformerException, SAXException { - TransmissionResponse fakeResponse = fakeResponse(); - when(transmitterMock.transmit(any(TransmissionMessage.class))).thenReturn(fakeResponse); + public void testHandleSignatureExceptionWhenSigningFails() throws SignatureException { + doThrow(SignatureException.class).when(signatureFactoryMock).signDocument(any(), any()); Message message = new Message( account, @@ -801,21 +688,14 @@ public class OxalisDocumentSenderTest extends AbstractXmlTest { "busdox-docid-qns::urn:oasis:names:specification:ubl:schema:xsd:Invoice-2::Invoice##OIOUBL-2.1::2.1", new MessageContent("test".getBytes(StandardCharsets.UTF_8))); - message.setPeppolProcessId("cenbii-procid-ubl::proces"); - doInTx(em -> { - em.persist(message); - }); - sender.send(message, new SBDMessageContext()); - - doInTx(em -> { - List logs = em.createQuery("from MessageLog l where l.message = :message", MessageLog.class) - .setParameter("message", message) - .getResultStream() - .filter(l -> l.getDescription() != null && l.getDescription().contains("%")) - .collect(Collectors.toList()); - - MatcherAssert.assertThat(logs.size(), CoreMatchers.equalTo(0)); - }); + try { + sender.send(message, new SBDMessageContext()); + Assert.fail("should throw"); + } catch (OutboundException e) { + assertThat(e.getCause(), instanceOf(SignatureException.class)); + assertThat(e.getErrorCode(), CoreMatchers.equalTo(ErrorCodes.OUTBOUND_EXCEPTION.getErrorCode())); + assertThat(e.getMessage(), CoreMatchers.equalTo("Error while signing document")); + } } private Document loadDocument(String xmlPath) throws Exception { diff --git a/src/test/java/dk/erst/oxalis/as4/validation/MessageValidatorTest.java b/src/test/java/dk/erst/oxalis/as4/validation/MessageValidatorTest.java index 6a9336a24230625265849368727815b051d1320e..18dabccd1fc17e53a24e1750ec96b2776b661b3f 100644 --- a/src/test/java/dk/erst/oxalis/as4/validation/MessageValidatorTest.java +++ b/src/test/java/dk/erst/oxalis/as4/validation/MessageValidatorTest.java @@ -16,6 +16,7 @@ import dk.erst.oxalis.as4.persistence.model.MessageLog; import dk.erst.oxalis.as4.util.DataPairs; import dk.erst.oxalis.as4.util.SBDMessageContext; import dk.erst.oxalis.as4.util.UtilityModule; +import dk.erst.oxalis.as4.validation.registration.RegistrationValidator; import dk.erst.oxalis.as4.validation.schema.SchemaValidator; import dk.erst.oxalis.as4.validation.schema.SchemaValidationException; import dk.erst.oxalis.as4.validation.schematron.SchematronValidator; @@ -69,6 +70,9 @@ public class MessageValidatorTest extends AbstractXmlTest { @Mock private VersionValidator versionValidatorMock; + @Mock + private RegistrationValidator registrationValidatorMock; + private Set documentTypeConfigs; @BeforeMethod @@ -90,7 +94,8 @@ public class MessageValidatorTest extends AbstractXmlTest { schematronValidatorMock, documentBuilderProvider, signatureValidatorMock, - versionValidatorMock); + versionValidatorMock, + registrationValidatorMock); } @Test diff --git a/src/test/java/dk/erst/oxalis/as4/validation/ValidationModuleTest.java b/src/test/java/dk/erst/oxalis/as4/validation/ValidationModuleTest.java index 75d843c43225f84fb4f40f2f8c180478570b7e13..86453fe2118f4b72b371bbea691f78c57252aa62 100644 --- a/src/test/java/dk/erst/oxalis/as4/validation/ValidationModuleTest.java +++ b/src/test/java/dk/erst/oxalis/as4/validation/ValidationModuleTest.java @@ -2,17 +2,22 @@ package dk.erst.oxalis.as4.validation; import com.google.common.cache.LoadingCache; import com.google.inject.*; +import com.google.inject.name.Names; import com.typesafe.config.ConfigFactory; import dk.erst.oxalis.as4.config.documenttype.DocumentTypeConfig; import dk.erst.oxalis.as4.config.documenttype.DocumentTypeModule; import dk.erst.oxalis.as4.util.UtilityModule; +import dk.erst.oxalis.as4.validation.schema.SchemaCache; +import dk.erst.oxalis.as4.validation.schema.SchemaCacheLoader; import dk.erst.oxalis.as4.validation.schema.SchemaResolver; import dk.erst.oxalis.as4.validation.schema.SchemaValidator; import dk.erst.oxalis.as4.validation.schematron.XSLTCache; import dk.erst.oxalis.as4.validation.schematron.SchematronTransformerFactory; import dk.erst.oxalis.as4.validation.schematron.SchematronValidator; +import dk.erst.oxalis.as4.validation.schematron.XSLTCacheLoader; import dk.erst.oxalis.as4.validation.signature.SignatureValidator; import dk.erst.oxalis.as4.validation.version.VersionValidator; +import network.oxalis.api.lookup.LookupService; import network.oxalis.commons.config.ConfigModule; import network.oxalis.commons.filesystem.FileSystemModule; import network.oxalis.vefa.peppol.lookup.LookupClient; @@ -25,6 +30,7 @@ import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; import javax.xml.transform.Templates; +import javax.xml.validation.Schema; import java.util.List; import java.util.Set; import java.util.stream.Collectors; @@ -42,12 +48,11 @@ public class ValidationModuleTest { @Mock private LookupClient lookupClientMock; + @Mock + private LookupService lookupServiceMock; + @BeforeMethod public void setup() { - System.setProperty("oxalis.path.conf", "schematron-preload-conf"); - // invalidate cached system properties to force reload! - ConfigFactory.invalidateCaches(); - MockitoAnnotations.initMocks(this); injector = Guice.createInjector( new FileSystemModule(), @@ -60,18 +65,12 @@ public class ValidationModuleTest { protected void configure() { bind(SignatureValidator.class).toInstance(signatureValidatorMock); bind(LookupClient.class).toInstance(lookupClientMock); + bind(LookupService.class).toInstance(lookupServiceMock); } } ); } - @AfterMethod - public void cleanup() { - System.clearProperty("oxalis.path.conf"); - // invalidate cached system properties to force reload! - ConfigFactory.invalidateCaches(); - } - @Test public void testModuleProvidesExpectedSingletons() { testProvidesSingleton(SchemaResolver.class); @@ -80,6 +79,10 @@ public class ValidationModuleTest { testProvidesSingleton(SchematronValidator.class); testProvidesSingleton(MessageValidator.class); testProvidesSingleton(VersionValidator.class); + testProvidesSingleton(SchemaCacheLoader.class); + testProvidesSingleton(XSLTCacheLoader.class); + testProvidesSingleton(Key.get(new TypeLiteral>() {}, SchemaCache.class)); + testProvidesSingleton(Key.get(new TypeLiteral>() {}, XSLTCache.class)); } private void testProvidesSingleton(Class clazz) { @@ -90,6 +93,14 @@ public class ValidationModuleTest { MatcherAssert.assertThat(singleton2, CoreMatchers.sameInstance(singleton)); } + private void testProvidesSingleton(Key key) { + T singleton = injector.getInstance(key); + MatcherAssert.assertThat(singleton, CoreMatchers.notNullValue()); + + T singleton2 = injector.getInstance(key); + MatcherAssert.assertThat(singleton2, CoreMatchers.sameInstance(singleton)); + } + @Test public void testInitializesSchematronCacheAndPreloadsSpecificSchematron() { @@ -140,4 +151,54 @@ public class ValidationModuleTest { } } + @Test + public void testInitializesSchemaCacheAndPreloadsSpecificXSDSchemas() { + + try { + System.setProperty("oxalis.path.conf", "schematron-preload-conf"); + // invalidate cached system properties to force reload! + ConfigFactory.invalidateCaches(); + + MockitoAnnotations.initMocks(this); + injector = Guice.createInjector( + new FileSystemModule(), + new ConfigModule(), + new UtilityModule(), + new DocumentTypeModule(), + new ValidationModule(), + new AbstractModule() { + @Override + protected void configure() { + bind(SignatureValidator.class).toInstance(signatureValidatorMock); + bind(LookupClient.class).toInstance(lookupClientMock); + } + } + ); + + LoadingCache schemaCache = injector.getInstance(Key.get(new TypeLiteral>() {}, SchemaCache.class)); + MatcherAssert.assertThat(schemaCache, CoreMatchers.notNullValue()); + + Set configs = injector.getInstance(Key.get(new TypeLiteral>() {})); + + List preloaded = configs.stream().filter(DocumentTypeConfig::isPreloadSchema).collect(Collectors.toList()); + MatcherAssert.assertThat(preloaded.size(), CoreMatchers.equalTo(2)); + MatcherAssert.assertThat(preloaded.stream().anyMatch(p -> p.getFriendlyName().equals("Faktura")), CoreMatchers.is(true)); + MatcherAssert.assertThat(preloaded.stream().anyMatch(p -> p.getFriendlyName().equals("Applikationsmeddelse")), CoreMatchers.is(true)); + preloaded.stream().forEach(s -> { + MatcherAssert.assertThat("XSD schema " + s.getSchemaPath() + " should be preloaded", schemaCache.getIfPresent(s.getSchemaPath()), CoreMatchers.notNullValue()); + }); + + List notPreloaded = configs.stream() + .filter(c -> !c.isPreloadSchema()) + .filter(c -> preloaded.stream().noneMatch(d -> d.getSchemaPath().equals(c.getSchemaPath()))) // some document types use the same XSD so we have to exclude all these if one of them has been preloaded + .collect(Collectors.toList()); + notPreloaded.stream().forEach(s -> { + MatcherAssert.assertThat("XSD schema " + s.getSchemaPath() + " should not be preloaded", schemaCache.getIfPresent(s.getSchemaPath()), CoreMatchers.nullValue()); + }); + } finally { + System.clearProperty("oxalis.path.conf"); + ConfigFactory.invalidateCaches(); + } + } + } diff --git a/src/test/java/dk/erst/oxalis/as4/validation/registration/RegistrationValidatorTest.java b/src/test/java/dk/erst/oxalis/as4/validation/registration/RegistrationValidatorTest.java new file mode 100644 index 0000000000000000000000000000000000000000..0c85bff28515c7929016d967b6aa22385723f2b9 --- /dev/null +++ b/src/test/java/dk/erst/oxalis/as4/validation/registration/RegistrationValidatorTest.java @@ -0,0 +1,127 @@ +package dk.erst.oxalis.as4.validation.registration; + +import com.google.inject.AbstractModule; +import com.google.inject.Guice; +import com.google.inject.Injector; +import com.google.inject.name.Names; +import dk.erst.oxalis.as4.AbstractXmlTest; +import dk.erst.oxalis.as4.config.documenttype.DocumentTypeConfig; +import dk.erst.oxalis.as4.config.documenttype.MessageLevelResponseType; +import dk.erst.oxalis.as4.handlers.dto.ValidationResult; +import dk.erst.oxalis.as4.persistence.model.Message; +import network.oxalis.api.lang.OxalisTransmissionException; +import network.oxalis.api.lookup.LookupService; +import network.oxalis.as4.lang.OxalisAs4TransmissionException; +import network.oxalis.vefa.peppol.common.lang.EndpointNotFoundException; +import network.oxalis.vefa.peppol.common.model.Endpoint; +import network.oxalis.vefa.peppol.lookup.api.LookupException; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.testng.Assert; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import static dk.erst.oxalis.as4.error.ErrorCodes.OUTBOUND_SENDER_DOES_NOT_EXIST_ERROR; +import static dk.erst.oxalis.as4.error.ErrorCodes.OUTBOUND_SMP_ERROR; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.doThrow; + +public class RegistrationValidatorTest extends AbstractXmlTest{ + + @Mock + private LookupService mockLookupService; + private Injector injector; + private RegistrationValidator registrationValidator; + + @BeforeMethod + public void setup() throws Exception { + MockitoAnnotations.initMocks(this); + injector = Guice.createInjector( + new AbstractModule() { + @Override + protected void configure() { + bind(LookupService.class).toInstance(mockLookupService); + } + } + ); + registrationValidator = new RegistrationValidatorImpl(injector); + } + + @AfterMethod + public void cleanup() { + } + + @Test + public void testLookupOIOUBL() throws Exception { + Message message = new Message(); + message.setSender("iso6523-actorid-upis::0088:5798912345672"); + DocumentTypeConfig documentTypeConfig = new DocumentTypeConfig(); + documentTypeConfig.setMessageLevelResponseType(MessageLevelResponseType.OIOUBL_APPLICATION_RESPONSE); + doReturn(new Endpoint(null, null, null, null)) + .when(mockLookupService).lookup(any()); + ValidationResult result = registrationValidator.validate(message, documentTypeConfig); + Assert.assertEquals(result.getErrors().size(), 0); + + doThrow(new OxalisAs4TransmissionException("test", new LookupException("test"))) + .when(mockLookupService).lookup(any()); + result = registrationValidator.validate(message, documentTypeConfig); + Assert.assertEquals(result.getErrors().size(), 1); + Assert.assertEquals(result.getErrors().get(0).getCode(), OUTBOUND_SENDER_DOES_NOT_EXIST_ERROR.getErrorCode()); + + doThrow(new OxalisAs4TransmissionException("test", new EndpointNotFoundException("test"))) + .when(mockLookupService).lookup(any()); + result = registrationValidator.validate(message, documentTypeConfig); + Assert.assertEquals(result.getErrors().size(), 1); + Assert.assertEquals(result.getErrors().get(0).getCode(), OUTBOUND_SENDER_DOES_NOT_EXIST_ERROR.getErrorCode()); + + doThrow(new OxalisAs4TransmissionException("test")).when(mockLookupService).lookup(any()); + try { + registrationValidator.validate(message, documentTypeConfig); + Assert.fail(); + } + catch(Exception e) { + Assert.assertTrue(e instanceof OxalisTransmissionException); + } + } + + + + @Test + public void testLookupPEPPOL() throws Exception { + Message message = new Message(); + message.setSender("iso6523-actorid-upis::0088:5798912345672"); + DocumentTypeConfig documentTypeConfig = new DocumentTypeConfig(); + documentTypeConfig.setMessageLevelResponseType(MessageLevelResponseType.PEPPOL_MESSAGE_LEVEL_RESPONSE); + doReturn(new Endpoint(null, null, null, null)) + .when(mockLookupService).lookup(any()); + ValidationResult result = registrationValidator.validate(message, documentTypeConfig); + Assert.assertEquals(result.getErrors().size(), 0); + + doThrow(new OxalisAs4TransmissionException("test", new LookupException("test"))) + .when(mockLookupService).lookup(any()); + result = registrationValidator.validate(message, documentTypeConfig); + Assert.assertEquals(result.getErrors().size(), 1); + Assert.assertEquals(result.getErrors().get(0).getCode(), OUTBOUND_SENDER_DOES_NOT_EXIST_ERROR.getErrorCode()); + + doThrow(new OxalisAs4TransmissionException("test", new EndpointNotFoundException("test"))) + .when(mockLookupService).lookup(any()); + result = registrationValidator.validate(message, documentTypeConfig); + Assert.assertEquals(result.getErrors().size(), 1); + Assert.assertEquals(result.getErrors().get(0).getCode(), OUTBOUND_SENDER_DOES_NOT_EXIST_ERROR.getErrorCode()); + + doThrow(new OxalisAs4TransmissionException("test")).when(mockLookupService).lookup(any()); + try { + registrationValidator.validate(message, documentTypeConfig); + Assert.fail(); + } + catch(Exception e) { + Assert.assertTrue(e instanceof OxalisTransmissionException); + } + } + + + + +} diff --git a/src/test/java/dk/erst/oxalis/as4/validation/schema/SchemaCacheLoaderTest.java b/src/test/java/dk/erst/oxalis/as4/validation/schema/SchemaCacheLoaderTest.java new file mode 100644 index 0000000000000000000000000000000000000000..fdd9d84f45dec63c8dd8f370382f4a1d80bbdf0f --- /dev/null +++ b/src/test/java/dk/erst/oxalis/as4/validation/schema/SchemaCacheLoaderTest.java @@ -0,0 +1,69 @@ +package dk.erst.oxalis.as4.validation.schema; + +import org.hamcrest.CoreMatchers; +import org.hamcrest.MatcherAssert; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import javax.xml.validation.Schema; +import java.io.File; +import java.io.FileNotFoundException; +import java.net.URL; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.Map; + +public class SchemaCacheLoaderTest { + + private SchemaCacheLoader cacheLoader; + + @BeforeMethod + public void setup() { + cacheLoader = new SchemaCacheLoader(); + } + + + @Test(expectedExceptions = IllegalArgumentException.class) + public void testThrowsExceptionIfXsltPathIsNull() throws Exception { + cacheLoader.load(null); + } + + @Test(expectedExceptions = FileNotFoundException.class) + public void testThrowsExceptionIfXSLTDoesNotExist() throws Exception { + String xsdPath = "foo.xsd"; + cacheLoader.load(xsdPath); + } + + @Test + public void testCanLoadFromResources() throws Exception { + Schema schema = cacheLoader.load("META-INF/Schemas/UBL_v2.1/maindoc/UBL-Invoice-2.1.xsd"); + MatcherAssert.assertThat(schema, CoreMatchers.notNullValue()); + } + + @Test + public void testCanCreateSchemaUsingAbsolutePath() throws Exception { + URL res = this.getClass().getClassLoader().getResource("META-INF/Schemas/UBL_v2.1/maindoc/UBL-Invoice-2.1.xsd"); + File file = Paths.get(res.toURI()).toFile(); + + Schema schema = cacheLoader.load(file.getAbsolutePath()); + MatcherAssert.assertThat(schema, CoreMatchers.notNullValue()); + } + + @Test + public void testBulkLoadReturnsEmptyCollectionIfNoSchemaProvided() throws Exception { + Map schemaMap = cacheLoader.loadAll(null); + MatcherAssert.assertThat(schemaMap, CoreMatchers.notNullValue()); + MatcherAssert.assertThat(schemaMap.size(), CoreMatchers.equalTo(0)); + } + + @Test + public void testCanBulkLoadSchemas() throws Exception { + String invoicePath = "META-INF/Schemas/UBL_v2.1/maindoc/UBL-Invoice-2.1.xsd"; + String arPath = "META-INF/Schemas/UBL_v2.1/maindoc/UBL-ApplicationResponse-2.1.xsd"; + Map schemaMap = cacheLoader.loadAll(Arrays.asList(invoicePath, arPath)); + MatcherAssert.assertThat(schemaMap, CoreMatchers.notNullValue()); + MatcherAssert.assertThat(schemaMap.size(), CoreMatchers.equalTo(2)); + MatcherAssert.assertThat(schemaMap.get(invoicePath), CoreMatchers.notNullValue()); + MatcherAssert.assertThat(schemaMap.get(arPath), CoreMatchers.notNullValue()); + } +} diff --git a/src/test/java/dk/erst/oxalis/as4/validation/schema/SchemaResolverTest.java b/src/test/java/dk/erst/oxalis/as4/validation/schema/SchemaResolverTest.java index 2afcf610fc306ad1c5a3f2ae907e7b13086b9e09..6334dd7da59fda207c8e0e5e979f876bcd20119c 100644 --- a/src/test/java/dk/erst/oxalis/as4/validation/schema/SchemaResolverTest.java +++ b/src/test/java/dk/erst/oxalis/as4/validation/schema/SchemaResolverTest.java @@ -1,8 +1,8 @@ package dk.erst.oxalis.as4.validation.schema; -import com.google.inject.AbstractModule; -import com.google.inject.Guice; -import com.google.inject.Injector; +import com.google.common.cache.LoadingCache; +import com.google.inject.*; +import com.google.inject.util.Modules; import dk.erst.oxalis.as4.AbstractXmlTest; import dk.erst.oxalis.as4.config.documenttype.DocumentTypeConfig; import dk.erst.oxalis.as4.config.documenttype.DocumentTypeConfigResolver; @@ -13,12 +13,15 @@ import dk.erst.oxalis.as4.util.DocumentBuilderProvider; import dk.erst.oxalis.as4.util.UtilityModule; import dk.erst.oxalis.as4.validation.ValidationException; import dk.erst.oxalis.as4.validation.ValidationModule; +import dk.erst.oxalis.as4.validation.schematron.SchematronTransformerFactory; +import dk.erst.oxalis.as4.validation.schematron.XSLTCache; import dk.erst.oxalis.as4.validation.signature.SignatureValidator; import network.oxalis.commons.config.ConfigModule; import network.oxalis.commons.filesystem.FileSystemModule; import org.hamcrest.CoreMatchers; import org.hamcrest.MatcherAssert; import org.mockito.Mock; +import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; @@ -28,8 +31,12 @@ import org.xml.sax.SAXException; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.Templates; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerConfigurationException; import javax.xml.validation.Schema; import java.io.IOException; +import java.util.concurrent.ExecutionException; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doReturn; @@ -42,6 +49,11 @@ public class SchemaResolverTest extends AbstractXmlTest { @Mock private SignatureValidator signatureValidatorMock; + private LoadingCache schemaCache; + + @Mock + private LoadingCache cacheMock; + @BeforeMethod public void setup() throws ValidationException { MockitoAnnotations.initMocks(this); @@ -62,17 +74,12 @@ public class SchemaResolverTest extends AbstractXmlTest { schemaResolver = injector.getInstance(SchemaResolver.class); documentTypeConfigResolver = injector.getInstance(DocumentTypeConfigResolver.class); + schemaCache = injector.getInstance(Key.get(new TypeLiteral>() {}, SchemaCache.class)); } @Test - public void testReturnsNullIfDocumentIsNull() throws Exception { - MatcherAssert.assertThat(schemaResolver.resolveSchema(null, new DocumentTypeConfig()), CoreMatchers.nullValue()); - } - - @Test - public void testReturnsNullIfDocumentTypeConfigIsNull() throws Exception { - Document doc = new DocumentBuilderProvider().get().newDocument(); - MatcherAssert.assertThat(schemaResolver.resolveSchema(doc, null), CoreMatchers.nullValue()); + public void testReturnsNullIfSchemaPathIsNull() throws Exception { + MatcherAssert.assertThat(schemaResolver.resolveSchema(null), CoreMatchers.nullValue()); } @Test @@ -207,8 +214,47 @@ public class SchemaResolverTest extends AbstractXmlTest { private void assertCanLocateSchema(String xmlPath) throws Exception { Document doc = loadAndWrapInSBD(xmlPath); DocumentTypeConfig config = documentTypeConfigResolver.getDocumentTypeConfig(doc); - Schema schema = schemaResolver.resolveSchema(doc, config); + Schema schema = schemaResolver.resolveSchema(config.getSchemaPath()); MatcherAssert.assertThat(schema, CoreMatchers.notNullValue()); } + @Test + public void testLoadsCompiledSchemaThroughCache() throws Exception { + Injector injector = Guice.createInjector(Modules.override( + new FileSystemModule(), + new ConfigModule(), + new UtilityModule(), + new DocumentTypeModule(), + new ValidationModule()) + .with( + new AbstractModule() { + @Override + protected void configure() { + bind(SignatureValidator.class).toInstance(signatureValidatorMock); + bind(Key.get(new TypeLiteral>() {}, SchemaCache.class)).toInstance(cacheMock); + } + }) + ); + + Schema schemaMock = Mockito.mock(Schema.class); + doReturn(schemaMock).when(cacheMock).get(any()); + + SchemaResolver schemaResolver = injector.getInstance(SchemaResolver.class); + Schema t = schemaResolver.resolveSchema("META-INF/Schemas/UBL_v2.1/maindoc/UBL-Invoice-2.1.xsd"); + + MatcherAssert.assertThat(t, CoreMatchers.equalTo(schemaMock)); + } + + @Test + public void testCachesCompiledSchemaWhichIsNotAlreadyCached() throws TransformerConfigurationException, IOException, ExecutionException { + String schemaPath = "META-INF/Schemas/UBL_v2.1/common/UBL-CommonAggregateComponents-2.1.xsd"; + + MatcherAssert.assertThat(schemaCache.getIfPresent(schemaPath), CoreMatchers.nullValue()); + Schema t = schemaResolver.resolveSchema(schemaPath); + MatcherAssert.assertThat(t, CoreMatchers.notNullValue()); + + // should be cached + MatcherAssert.assertThat(schemaCache.getIfPresent(schemaPath), CoreMatchers.notNullValue()); + } + } diff --git a/src/test/java/dk/erst/oxalis/as4/validation/schema/SchemaValidatorTest.java b/src/test/java/dk/erst/oxalis/as4/validation/schema/SchemaValidatorTest.java index b629b88822f7a9baee749234d229e2396b79c35a..b9e0de55d351cef534a879a5d529c82af766036e 100644 --- a/src/test/java/dk/erst/oxalis/as4/validation/schema/SchemaValidatorTest.java +++ b/src/test/java/dk/erst/oxalis/as4/validation/schema/SchemaValidatorTest.java @@ -5,7 +5,6 @@ import com.google.inject.Guice; import com.google.inject.Injector; import dk.erst.oxalis.as4.config.documenttype.DocumentTypeConfig; import dk.erst.oxalis.as4.config.documenttype.DocumentTypeConfigResolver; -import dk.erst.oxalis.as4.config.documenttype.DocumentTypeConfigResolverException; import dk.erst.oxalis.as4.config.documenttype.DocumentTypeModule; import dk.erst.oxalis.as4.AbstractXmlTest; import dk.erst.oxalis.as4.error.ErrorCodes; @@ -14,7 +13,6 @@ import dk.erst.oxalis.as4.util.SBDMessageContext; import dk.erst.oxalis.as4.util.UtilityModule; import dk.erst.oxalis.as4.validation.ValidationModule; import dk.erst.oxalis.as4.handlers.dto.ValidationResult; -import dk.erst.oxalis.as4.validation.schematron.SchematronValidationException; import dk.erst.oxalis.as4.validation.signature.SignatureValidator; import network.oxalis.api.model.Direction; import network.oxalis.commons.config.ConfigModule; @@ -28,17 +26,10 @@ import org.mockito.MockitoAnnotations; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; import org.w3c.dom.Document; -import org.xml.sax.SAXException; import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; import java.io.ByteArrayInputStream; -import java.io.File; -import java.io.IOException; import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; public class SchemaValidatorTest extends AbstractXmlTest { private Injector injector; @@ -309,6 +300,56 @@ public class SchemaValidatorTest extends AbstractXmlTest { } } + @Test + public void testCanValidateNemkontoNKSPayment() throws Exception { + assertValidateValidXml("nemkonto-examples/Payment_GLN_5798009811578.xml"); + } + + @Test + public void testCanValidateNemkontoNKSReceipt0() throws Exception { + assertValidateValidXml("nemkonto-examples/Kvittering 0.xml"); + } + + @Test + public void testCanValidateNemkontoNKSReceipt1() throws Exception { + assertValidateValidXml("nemkonto-examples/Kvittering 1.xml"); + } + + @Test + public void testCanValidateNemkontoNKSResponse2() throws Exception { + assertValidateValidXml("nemkonto-examples/Retursvar 2 med ACPT.xml"); + } + + @Test + public void testCanValidateNemkontoNKSResponse5() throws Exception { + assertValidateValidXml("nemkonto-examples/Retursvar 5 med PART.xml"); + } + + @Test + public void testCanValidateNemkontoNKSResponse7() throws Exception { + assertValidateValidXml("nemkonto-examples/Retursvar 7.xml"); + } + + @Test + public void testCanValidateNemkontoNKSResponse8() throws Exception { + assertValidateValidXml("nemkonto-examples/Retursvar 8.xml"); + } + + @Test + public void testCanValidateNemkontoNKSResponse9() throws Exception { + assertValidateValidXml("nemkonto-examples/Retursvar 9.xml"); + } + + @Test + public void testCanValidateNemkontoNemkontoPrivatUdbetalerTransporterRequest() throws Exception { + assertValidateValidXml("nemkonto-examples/Request - CVR-nr SE-nr P-nr.xml"); + } + + @Test + public void testCanValidateNemkontoNemkontoPrivatUdbetalerTransporterResponse() throws Exception { + assertValidateValidXml("nemkonto-examples/Reply - CPR-nr - DK konto og ingen kontonummer.XML"); + } + private void assertValidateValidXml(String xmlPath) throws Exception { Document doc = loadAndWrapInSBD(xmlPath); diff --git a/src/test/java/dk/erst/oxalis/as4/validation/schematron/SchematronValidatorTest.java b/src/test/java/dk/erst/oxalis/as4/validation/schematron/SchematronValidatorTest.java index 310708ed758b51bef32d1ba4b4df73e5b2c49f9c..c872116c78ce82871837bc82800d4a060f9ddaa8 100644 --- a/src/test/java/dk/erst/oxalis/as4/validation/schematron/SchematronValidatorTest.java +++ b/src/test/java/dk/erst/oxalis/as4/validation/schematron/SchematronValidatorTest.java @@ -291,6 +291,56 @@ public class SchematronValidatorTest extends AbstractXmlTest { MatcherAssert.assertThat(result.getErrors().get(0).getLineReference(), CoreMatchers.equalTo("/*:CreditNote[namespace-uri()='urn:oasis:names:specification:ubl:schema:xsd:CreditNote-2'][1]/*:LegalMonetaryTotal[namespace-uri()='urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2'][1]")); } + @Test + public void testCanValidateNemkontoNKSPayment() throws Exception { + assertValidateSchematronOk("nemkonto-examples/Payment_GLN_5798009811578.xml"); + } + + @Test + public void testCanValidateNemkontoNKSReceipt0() throws Exception { + assertValidateSchematronOk("nemkonto-examples/Kvittering 0.xml"); + } + + @Test + public void testCanValidateNemkontoNKSReceipt1() throws Exception { + assertValidateSchematronOk("nemkonto-examples/Kvittering 1.xml"); + } + + @Test + public void testCanValidateNemkontoNKSResponse2() throws Exception { + assertValidateSchematronOk("nemkonto-examples/Retursvar 2 med ACPT.xml"); + } + + @Test + public void testCanValidateNemkontoNKSResponse5() throws Exception { + assertValidateSchematronOk("nemkonto-examples/Retursvar 5 med PART.xml"); + } + + @Test + public void testCanValidateNemkontoNKSResponse7() throws Exception { + assertValidateSchematronOk("nemkonto-examples/Retursvar 7.xml"); + } + + @Test + public void testCanValidateNemkontoNKSResponse8() throws Exception { + assertValidateSchematronOk("nemkonto-examples/Retursvar 8.xml"); + } + + @Test + public void testCanValidateNemkontoNKSResponse9() throws Exception { + assertValidateSchematronOk("nemkonto-examples/Retursvar 9.xml"); + } + + @Test + public void testCanValidateNemkontoNemkontoPrivatUdbetalerTransporterRequest() throws Exception { + assertValidateSchematronOk("nemkonto-examples/Request - CVR-nr SE-nr P-nr.xml"); + } + + @Test + public void testCanValidateNemkontoNemkontoPrivatUdbetalerTransporterResponse() throws Exception { + assertValidateSchematronOk("nemkonto-examples/Reply - CPR-nr - DK konto og ingen kontonummer.XML"); + } + private void assertValidateSchematronOk(String xmlPath) throws Exception { Document doc = loadAndWrapInSBD(xmlPath); diff --git a/src/test/java/dk/erst/oxalis/as4/validation/signature/DSSModuleTest.java b/src/test/java/dk/erst/oxalis/as4/validation/signature/DSSModuleTest.java index 89a4d4ac3d3f189d2e643cc5318abd2bee0554ef..ba2177fd0fe8f2128f9665103e50b55fa8943885 100644 --- a/src/test/java/dk/erst/oxalis/as4/validation/signature/DSSModuleTest.java +++ b/src/test/java/dk/erst/oxalis/as4/validation/signature/DSSModuleTest.java @@ -6,11 +6,12 @@ import com.google.inject.util.Modules; import dk.erst.oxalis.as4.config.documenttype.DocumentTypeModule; import dk.erst.oxalis.as4.util.UtilityModule; import dk.erst.oxalis.as4.validation.ValidationModule; -import eu.europa.esig.dss.asic.xades.signature.ASiCWithXAdESService; import eu.europa.esig.dss.policy.ValidationPolicy; import eu.europa.esig.dss.spi.x509.CertificateSource; import eu.europa.esig.dss.spi.x509.revocation.crl.CRLSource; import eu.europa.esig.dss.validation.CertificateVerifier; +import eu.europa.esig.dss.xades.XAdESSignatureParameters; +import eu.europa.esig.dss.xades.signature.XAdESService; import network.oxalis.api.settings.Settings; import network.oxalis.commons.certvalidator.api.CrlFetcher; import network.oxalis.commons.config.ConfigModule; @@ -102,8 +103,9 @@ public class DSSModuleTest { } @Test - public void testProvidesAsicWithXAdESService() { - testProvidesSingleton(ASiCWithXAdESService.class); + public void testProvidesXAdESService() { + Provider prov = injector.getProvider(XAdESService.class); + MatcherAssert.assertThat(prov, CoreMatchers.notNullValue()); } @Test @@ -131,4 +133,10 @@ public class DSSModuleTest { T singleton2 = injector.getInstance(clazz); MatcherAssert.assertThat(singleton2, CoreMatchers.sameInstance(singleton)); } + + @Test + public void testProvidesXAdESSignatureParameters() { + Provider prov = injector.getProvider(XAdESSignatureParameters.class); + MatcherAssert.assertThat(prov, CoreMatchers.notNullValue()); + } } diff --git a/src/test/java/dk/erst/oxalis/as4/validation/signature/SignatureFactoryTest.java b/src/test/java/dk/erst/oxalis/as4/validation/signature/SignatureFactoryTest.java index 3058bbd307a65aa5b0e31f1a007bba43ad98803f..2d0939198c7ffc2038524dfc876cfd57add7a5d8 100644 --- a/src/test/java/dk/erst/oxalis/as4/validation/signature/SignatureFactoryTest.java +++ b/src/test/java/dk/erst/oxalis/as4/validation/signature/SignatureFactoryTest.java @@ -1,37 +1,30 @@ package dk.erst.oxalis.as4.validation.signature; + import com.google.inject.*; -import com.google.inject.name.Named; -import com.google.inject.name.Names; import com.google.inject.util.Modules; -import com.typesafe.config.Config; import com.typesafe.config.ConfigFactory; import dk.erst.oxalis.as4.AbstractXmlTest; +import dk.erst.oxalis.as4.EDeliverySpecification; import dk.erst.oxalis.as4.config.documenttype.DocumentTypeModule; import dk.erst.oxalis.as4.error.ErrorCodes; -import dk.erst.oxalis.as4.handlers.dto.ValidationMessage; -import dk.erst.oxalis.as4.signature.*; +import dk.erst.oxalis.as4.handlers.dto.ValidationResult; +import dk.erst.oxalis.as4.persistence.model.Account; +import dk.erst.oxalis.as4.persistence.model.Message; +import dk.erst.oxalis.as4.persistence.model.MessageContent; +import dk.erst.oxalis.as4.persistence.model.MessageDirection; +import dk.erst.oxalis.as4.signature.SignatureException; +import dk.erst.oxalis.as4.signature.SignatureFactory; import dk.erst.oxalis.as4.util.*; -import dk.erst.oxalis.as4.validation.ValidationException; import dk.erst.oxalis.as4.validation.ValidationModule; -import eu.europa.esig.dss.asic.common.ASiCContent; -import eu.europa.esig.dss.asic.xades.ASiCWithXAdESContainerExtractor; -import eu.europa.esig.dss.asic.xades.ASiCWithXAdESSignatureParameters; -import eu.europa.esig.dss.asic.xades.signature.ASiCWithXAdESService; -import eu.europa.esig.dss.diagnostic.CertificateWrapper; -import eu.europa.esig.dss.enumerations.ASiCContainerType; -import eu.europa.esig.dss.enumerations.SignatureLevel; +import eu.europa.esig.dss.enumerations.DigestAlgorithm; import eu.europa.esig.dss.model.DSSDocument; import eu.europa.esig.dss.model.InMemoryDocument; -import eu.europa.esig.dss.model.x509.CertificateToken; import eu.europa.esig.dss.policy.ValidationPolicy; import eu.europa.esig.dss.policy.ValidationPolicyFacade; import eu.europa.esig.dss.policy.jaxb.Level; -import eu.europa.esig.dss.token.AbstractKeyStoreTokenConnection; -import eu.europa.esig.dss.token.DSSPrivateKeyEntry; -import eu.europa.esig.dss.token.KSPrivateKeyEntry; -import network.oxalis.api.lang.OxalisLoadingException; +import eu.europa.esig.dss.spi.x509.revocation.crl.CRLSource; +import eu.europa.esig.dss.spi.x509.revocation.ocsp.OCSPSource; import network.oxalis.api.model.Direction; -import network.oxalis.api.settings.Settings; import network.oxalis.commons.certvalidator.api.CrlFetcher; import network.oxalis.commons.config.ConfigModule; import network.oxalis.commons.filesystem.FileSystemModule; @@ -39,25 +32,18 @@ import network.oxalis.commons.mode.ModeModule; import network.oxalis.commons.mode.OxalisCrlFetcher; import network.oxalis.commons.mode.OxalisOcspFetcher; import network.oxalis.commons.security.CertificateModule; -import network.oxalis.commons.security.KeyStoreConf; import network.oxalis.commons.tracing.TracingModule; import network.oxalis.pkix.ocsp.api.OcspFetcher; -import network.oxalis.vefa.peppol.common.lang.PeppolLoadingException; -import network.oxalis.vefa.peppol.mode.Mode; import network.oxalis.vefa.peppol.sbdh.Ns; -import network.oxalis.vefa.peppol.security.ModeDetector; -import org.apache.commons.io.IOUtils; -import org.apache.commons.lang3.StringUtils; import org.apache.cxf.helpers.MapNamespaceContext; -import org.apache.hc.core5.http.nio.ssl.BasicServerTlsStrategy; import org.apache.http.impl.client.CloseableHttpClient; -import org.h2.value.Transfer; import org.hamcrest.CoreMatchers; import org.hamcrest.MatcherAssert; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import org.testng.Assert; +import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; import org.w3c.dom.Document; @@ -65,39 +51,26 @@ import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.SAXException; -import javax.xml.XMLConstants; import javax.xml.bind.JAXBException; import javax.xml.parsers.DocumentBuilder; import javax.xml.stream.XMLStreamException; -import javax.xml.transform.Transformer; -import javax.xml.transform.TransformerException; -import javax.xml.transform.TransformerFactory; -import javax.xml.transform.dom.DOMSource; -import javax.xml.transform.stream.StreamResult; -import javax.xml.xpath.*; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; +import javax.xml.xpath.XPath; +import javax.xml.xpath.XPathFactory; import java.io.IOException; -import java.io.InputStream; import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; -import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.cert.CertificateException; -import java.security.cert.X509Certificate; -import java.util.*; -import java.util.regex.Matcher; -import java.util.regex.Pattern; +import java.util.Base64; +import java.util.UUID; import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.doThrow; public class SignatureFactoryTest extends AbstractXmlTest{ private Injector injector; private SignatureFactory signatureFactory; - private ASiCWithXAdESService service; @Mock private OxalisCrlFetcher mockCrlFetcher; @@ -111,15 +84,13 @@ public class SignatureFactoryTest extends AbstractXmlTest{ @Mock private SignatureValidationPolicyProvider mockValidationPolicyProvider; - private KeyStore keystore; - private KeystoreSignatureTokenConnectionWrapper signatureToken; - private ASiCWithXAdESSignatureParameters signatureParameters; - private KSPrivateKeyEntry signaturePrivateKey; - private KeystoreSignatureTokenConnectionWrapper innerSignatureKeystoreWrapper; - private ASiCWithXAdESSignatureParameters innerSignatureParameters; - private KSPrivateKeyEntry innerSignaturePrivateKey; - private String cvr_Receiver; - private String cvr_Sender; + @Mock + private CRLSource crlSourceMock; + + @Mock + private OCSPSource ocspSourceMock; + + private XPath sbdXpath; @BeforeMethod public void setup() throws IOException, XMLStreamException, JAXBException, SAXException, KeyStoreException, CertificateException, NoSuchAlgorithmException { @@ -130,6 +101,10 @@ public class SignatureFactoryTest extends AbstractXmlTest{ noRevocationCheckPolicy.getSignatureConstraints().getBasicSignatureConstraints().getSigningCertificate().getRevocationDataAvailable().setLevel(Level.IGNORE); doReturn(noRevocationCheckPolicy).when(mockValidationPolicyProvider).get(); + System.setProperty("oxalis.path.conf", "MitID-dummy-CVR-97386837-conf"); + // invalidate cached system properties to force reload! + ConfigFactory.invalidateCaches(); + injector = Guice.createInjector( Modules.override( new FileSystemModule(), @@ -150,264 +125,132 @@ public class SignatureFactoryTest extends AbstractXmlTest{ bind(CloseableHttpClient.class).toInstance(mockHttpClient); bind(ValidationPolicy.class).toProvider(mockValidationPolicyProvider); + bind(CRLSource.class).toInstance(crlSourceMock); + bind(OCSPSource.class).toInstance(ocspSourceMock); } - // run with MitID cert - @Provides - @Singleton - @Named("file") - protected Config loadConfigurationFile(@Named("conf") Path homePath) { - Path configPath = homePath.resolve("nemhandel-e-delivery-oxalis.conf"); - - return Files.exists(configPath) && Files.isReadable(configPath) ? - ConfigFactory.parseFile(configPath.toFile()).resolve() : - ConfigFactory.empty(); - } - - // reload mode after config path has been overriden @Provides @Singleton - Mode provideMode(Config config, CrlFetcher crlFetcher, OcspFetcher ocspFetcher, X509Certificate certificate) { - Map objectStorage = new HashMap<>(); - objectStorage.put("ocsp_fetcher", ocspFetcher); - objectStorage.put("crlFetcher", crlFetcher); - - try { - Mode mode = ModeDetector.detect(certificate, config, objectStorage); - mode = Mockito.spy(mode); - return mode; - } catch (PeppolLoadingException e) { - throw new OxalisLoadingException("Unable to detect mode.", e); - } + SBDPayloadExtractor payloadExtractor(Provider documentBuilderProvider) { + // make payload extractor a spy to more easily simulate errors during signing process + SBDPayloadExtractor extractor = Mockito.spy(new SBDPayloadExtractorImpl(documentBuilderProvider)); + return extractor; } } )); signatureFactory = injector.getInstance(SignatureFactory.class); - service = injector.getInstance(ASiCWithXAdESService.class); - - keystore = injector.getInstance(KeyStore.class); - signatureToken = injector.getInstance(KeystoreSignatureTokenConnectionWrapper.class); - Settings settings = injector.getInstance(new Key>() {}); - - signaturePrivateKey = (KSPrivateKeyEntry) signatureToken.getKey(settings.getString(KeyStoreConf.KEY_ALIAS)); - CertificateToken signingCertificate = signaturePrivateKey.getCertificate(); - CertificateToken[] certificateChain = signaturePrivateKey.getCertificateChain(); - - - signatureParameters = new ASiCWithXAdESSignatureParameters(); - signatureParameters.bLevel().setSigningDate(new Date()); - signatureParameters.setSigningCertificate(signingCertificate); - signatureParameters.setCertificateChain(certificateChain); - signatureParameters.setSignatureLevel(SignatureLevel.XAdES_BASELINE_B); - signatureParameters.aSiC().setContainerType(ASiCContainerType.ASiC_E); - - // note: this keystore is just a contains a sample MitID FOCES3 certificate which is not used for anything other than - // unit tests - Path homePath = injector.getInstance(Key.get(Path.class, Names.named("home"))); - Path cvr97386837KeystorePath = homePath.resolve("MitID-dummy-CVR-97386837-conf/Dummy_Systemcertifikat_1_CVR-97386837.p12"); - String secondKeystorePassword = "7H+_sRI5r1jN"; - try(InputStream is = Files.newInputStream(cvr97386837KeystorePath)) { - KeyStore keystore = KeyStore.getInstance("PKCS12"); - keystore.load(is, secondKeystorePassword.toCharArray()); - innerSignatureKeystoreWrapper = new KeystoreSignatureTokenConnectionWrapper(keystore, new KeyStore.PasswordProtection(secondKeystorePassword.toCharArray())); - } - - // parameters for inner signatures for testing nested ASiC containers - innerSignaturePrivateKey = (KSPrivateKeyEntry) innerSignatureKeystoreWrapper.getKey("dummy systemcertifikat 1"); - - innerSignatureParameters = new ASiCWithXAdESSignatureParameters(); - innerSignatureParameters.bLevel().setSigningDate(new Date()); - innerSignatureParameters.setSigningCertificate(innerSignaturePrivateKey.getCertificate()); - innerSignatureParameters.setCertificateChain(innerSignaturePrivateKey.getCertificateChain()); - innerSignatureParameters.setSignatureLevel(SignatureLevel.XAdES_BASELINE_B); - innerSignatureParameters.aSiC().setContainerType(ASiCContainerType.ASiC_E); + sbdXpath = XPathFactory.newInstance().newXPath(); + MapNamespaceContext namespaceContext = new MapNamespaceContext(); + namespaceContext.addNamespace("sbd", Ns.SBDH); + sbdXpath.setNamespaceContext(namespaceContext); + } - cvr_Receiver = CertificateUtil.extractCVR(innerSignatureParameters.getSigningCertificate().getCertificate()); - cvr_Sender = CertificateUtil.extractCVR(signatureParameters.getSigningCertificate().getCertificate()); + @AfterMethod + public void cleanup() { + System.clearProperty("oxalis.path.conf"); + // invalidate cached system properties to force reload! + ConfigFactory.invalidateCaches(); } @Test - public void testErrorSigningDocumentsWhenNullOrEmptySbd() throws Exception, TransferDelegationException { - Document doc = loadAndWrapInSBD("ubl-examples/OIOUBL_Invoice_v2p2.xml"); - byte[] originalDoc = DocumentUtil.toByteArray(doc); - - TransferDelegationFactoryImpl tdf = new TransferDelegationFactoryImpl(); - byte[] transferDel = tdf.createTransferDelegation("12345678", "99999999"); - + public void testThrowsExceptionIfSBDToSignIsNullOrEmpty() throws Exception { try { - signatureFactory.signDocument("".getBytes(StandardCharsets.UTF_8), originalDoc, transferDel, new SBDMessageContext()); + signatureFactory.signDocument(null, new SBDMessageContext()); Assert.fail("Should not get here."); } catch(IllegalArgumentException ex) { Assert.assertEquals(ex.getMessage(), ErrorCodes.SIGNING_EDELIVERY_SBD_EMPTY_OR_NULL.formatErrorMessage()); } - } - - @Test - public void testErrorSigningDocumentsWhenNullOrEmptyOriginalDocument() throws Exception, TransferDelegationException { - Document doc = loadAndWrapInSBD("ubl-examples/OIOUBL_Invoice_v2p2.xml"); - byte[] sbd = DocumentUtil.toByteArray(doc); - - TransferDelegationFactoryImpl tdf = new TransferDelegationFactoryImpl(); - byte[] transferDel = tdf.createTransferDelegation("12345678", "99999999"); try { - signatureFactory.signDocument(sbd, "".getBytes(StandardCharsets.UTF_8), transferDel, new SBDMessageContext()); + signatureFactory.signDocument(new byte[0], new SBDMessageContext()); Assert.fail("Should not get here."); } catch(IllegalArgumentException ex) { - Assert.assertEquals(ex.getMessage(), ErrorCodes.SIGNING_EDELIVERY_ORIGINAL_SBD_EMPTY_OR_NULL.formatErrorMessage()); + Assert.assertEquals(ex.getMessage(), ErrorCodes.SIGNING_EDELIVERY_SBD_EMPTY_OR_NULL.formatErrorMessage()); } } - @Test - public void testOriginalDocumentCanBeNullForInternalMessageLevelResponse() throws Exception, TransferDelegationException { - Document doc = loadAndWrapInSBD("ubl-examples/OIOUBL_Invoice_v2p2.xml"); - byte[] sbd = DocumentUtil.toByteArray(doc); - - TransferDelegationFactoryImpl tdf = new TransferDelegationFactoryImpl(); - byte[] transferDel = tdf.createTransferDelegation("12345678", "99999999"); - - signatureFactory.signDocument(sbd, null, transferDel, new SBDMessageContext(true, Direction.OUT)); - } - - @Test - public void testErrorSigningDocumentsWhenNullOrEmptyTransferDelegation() throws Exception, TransferDelegationException { - Document doc = loadAndWrapInSBD("ubl-examples/OIOUBL_Invoice_v2p2.xml"); - byte[] originalDoc = DocumentUtil.toByteArray(doc); - byte[] sbd = DocumentUtil.toByteArray(doc); - - try { - signatureFactory.signDocument(sbd, originalDoc, "".getBytes(StandardCharsets.UTF_8), new SBDMessageContext()); - Assert.fail("Should not get here."); - } catch(IllegalArgumentException ex) { - Assert.assertEquals(ex.getMessage(), ErrorCodes.SIGNING_EDELIVERY_TRANSFER_DELEGATION_EMPTY_OR_NULL.formatErrorMessage()); - } + @Test(expectedExceptions = SignatureException.class) + public void testThrowsSignatureExceptionIfUnexpectedErrorOccursWhileSigning() throws Exception { + byte[] xml = loadAndWrapInSBDAsByteArray("ubl-examples/OIOUBL_Invoice_v2p2.xml"); + SBDPayloadExtractor extractor = injector.getInstance(SBDPayloadExtractor.class); // this is a Mockit Spy! + doThrow(RuntimeException.class).when(extractor).extractPayload(Mockito.any()); + signatureFactory.signDocument(xml, new SBDMessageContext()); } @Test - public void testSignDocumentCorrectlyUpdateDocumentWithAsicContainer() throws Exception, TransferDelegationException { - Document doc = loadAndWrapInSBD("ubl-examples/OIOUBL_Invoice_v2p2.xml"); - byte[] originalDoc = DocumentUtil.toByteArray(doc); - byte[] sbd = DocumentUtil.toByteArray(doc); - byte[] canonicalizedSbd = DocumentUtil.canonicalize(sbd); - - TransferDelegationFactoryImpl tdf = new TransferDelegationFactoryImpl(); - byte[] tranferDel = tdf.createTransferDelegation(cvr_Sender, cvr_Receiver); - byte[] signed = signatureFactory.signDocument(sbd, originalDoc, tranferDel, new SBDMessageContext()); - - Assert.assertTrue(signed != null || signed.length > 0); - Document signedDoc = parseDocument(signed); - - MapNamespaceContext namespaceContext = new MapNamespaceContext(); - namespaceContext.addNamespace("sbd", Ns.SBDH); - - XPath xpath = XPathFactory.newInstance().newXPath(); - xpath.setNamespaceContext(namespaceContext); - - // Checks signature count - NodeList signatures = getEdeliverySignatureNodes(signedDoc, xpath); - - MatcherAssert.assertThat(signatures, CoreMatchers.notNullValue()); - MatcherAssert.assertThat(signatures.getLength(), CoreMatchers.equalTo(1)); - - - Node signatureNode = signatures.item(0); - String txt = signatureNode.getTextContent(); - byte[] decoded = Base64.getDecoder().decode(txt); - InMemoryDocument memDoc = new InMemoryDocument(decoded); - - ASiCWithXAdESContainerExtractor extractor = new ASiCWithXAdESContainerExtractor(memDoc); - ASiCContent content = extractor.extract(); - - List signedDocuments = content.getSignedDocuments(); - - // Checks name and content for ASiC container - DSSDocument sbdDoc = signedDocuments.stream().filter(d -> d.getName().equals(SignatureValidator.ASIC_SBD_FILENAME)).findFirst().orElse(null); - MatcherAssert.assertThat(sbdDoc, CoreMatchers.notNullValue()); - MatcherAssert.assertThat(new String(IOUtils.toByteArray(sbdDoc.openStream()), StandardCharsets.UTF_8), CoreMatchers.equalTo(new String(canonicalizedSbd, StandardCharsets.UTF_8))); - - DSSDocument td = signedDocuments.stream().filter(d -> d.getName().equals(SignatureValidator.ASIC_TRANSFER_DELEGATION_FILENAME)).findFirst().orElse(null); - MatcherAssert.assertThat(td , CoreMatchers.notNullValue()); - MatcherAssert.assertThat(new String(IOUtils.toByteArray(td.openStream()), StandardCharsets.UTF_8), CoreMatchers.equalTo(new String(tranferDel, StandardCharsets.UTF_8))); - - DSSDocument orgSbd = signedDocuments.stream().filter(d -> d.getName().equals(SignatureValidator.ASIC_ORIGINAL_SBD_FILENAME)).findFirst().orElse(null); - MatcherAssert.assertThat(orgSbd, CoreMatchers.notNullValue()); - MatcherAssert.assertThat(new String(IOUtils.toByteArray(orgSbd.openStream()), StandardCharsets.UTF_8), CoreMatchers.equalTo(new String(originalDoc, StandardCharsets.UTF_8))); + public void testAddsSignatureInSBDHeaderAsABusinessScope() throws Exception { + byte[] xml = loadAndWrapInSBDAsByteArray("ubl-examples/OIOUBL_Invoice_v2p2.xml"); + byte[] signed = signatureFactory.signDocument(xml, new SBDMessageContext()); + + Document sbd = parseDocument(signed); + NodeList documentSignatures = XPathUtil.evaluateXPathNodeList(sbd, + sbdXpath, + "/sbd:StandardBusinessDocument/sbd:StandardBusinessDocumentHeader/sbd:BusinessScope/sbd:Scope[sbd:Type='NEMHANDEL_EDELIVERY_SIGNATURE']"); + MatcherAssert.assertThat(documentSignatures, CoreMatchers.notNullValue()); + MatcherAssert.assertThat(documentSignatures.getLength(), CoreMatchers.equalTo(1)); + + Node scope = documentSignatures.item(0); + String signature = XPathUtil.evaluateXPath(scope, sbdXpath, "sbd:InstanceIdentifier", null); + MatcherAssert.assertThat(signature, CoreMatchers.notNullValue()); + MatcherAssert.assertThat(signature.isEmpty(), CoreMatchers.is(false)); + + String specificationVersion = XPathUtil.evaluateXPath(scope, sbdXpath, "sbd:Identifier", null); + MatcherAssert.assertThat(specificationVersion, CoreMatchers.notNullValue()); + MatcherAssert.assertThat(specificationVersion, CoreMatchers.equalTo(EDeliverySpecification.NEMHANDEL_EDELIVERY_1_2.value())); } @Test - public void testSignDocumentCorrectlyCanonicalizesBeforeSigning() throws Exception, TransferDelegationException { - DocumentBuilder builder = new DocumentBuilderProvider().get(); - Document doc; - // Non canonicalized uses different line endings in this case! - try(InputStream is = getClass().getClassLoader().getResourceAsStream("sbd-examples/SBD_OIOUBL_Invoice_unsigned_non_canonicalized.xml")) { - doc = builder.parse(is); - } - - byte[] sbd = DocumentUtil.toByteArray(doc); - byte[] originalDoc = DocumentUtil.toByteArray(doc); - - TransferDelegationFactoryImpl tdf = new TransferDelegationFactoryImpl(); - byte[] tranferDel = tdf.createTransferDelegation(cvr_Sender, cvr_Receiver); - byte[] signed = signatureFactory.signDocument(sbd, originalDoc, tranferDel, new SBDMessageContext()); - - Assert.assertTrue(signed != null || signed.length > 0); - Document signedDoc = parseDocument(signed); - - MapNamespaceContext namespaceContext = new MapNamespaceContext(); - namespaceContext.addNamespace("sbd", Ns.SBDH); - - XPath xpath = XPathFactory.newInstance().newXPath(); - xpath.setNamespaceContext(namespaceContext); - - // Checks signature count - NodeList signatures = getEdeliverySignatureNodes(signedDoc, xpath); - - MatcherAssert.assertThat(signatures, CoreMatchers.notNullValue()); - MatcherAssert.assertThat(signatures.getLength(), CoreMatchers.equalTo(1)); - - - Node signatureNode = signatures.item(0); - String txt = signatureNode.getTextContent(); - byte[] decoded = Base64.getDecoder().decode(txt); - InMemoryDocument memDoc = new InMemoryDocument(decoded); - - ASiCWithXAdESContainerExtractor extractor = new ASiCWithXAdESContainerExtractor(memDoc); - ASiCContent content = extractor.extract(); - - List signedDocuments = content.getSignedDocuments(); - - byte[] canonicalizedOriginalSbd = DocumentUtil.canonicalize(sbd); - - MatcherAssert.assertThat("original document is not canonicalized", canonicalizedOriginalSbd, CoreMatchers.not(CoreMatchers.equalTo(sbd))); - - // Checks name and content for ASiC container - DSSDocument sbdDoc = signedDocuments.stream().filter(d -> d.getName().equals(SignatureValidator.ASIC_SBD_FILENAME)).findFirst().orElse(null); - MatcherAssert.assertThat(sbdDoc, CoreMatchers.notNullValue()); - - byte[] sbdFromAsic; - try(InputStream is = sbdDoc.openStream()){ - sbdFromAsic = IOUtils.toByteArray(is); - } - - MatcherAssert.assertThat("sbd from asic container is canonicalized", sbdFromAsic, CoreMatchers.equalTo(canonicalizedOriginalSbd)); + public void testOnlySignsCanonicalizedPayload() throws Exception { + byte[] payload = loadAsByteArray("ubl-examples/OIOUBL_Invoice_v2p2.xml"); + byte[] xml = wrapInSbd(payload); + byte[] signed = signatureFactory.signDocument(xml, new SBDMessageContext()); + + Document sbd = parseDocument(signed); + String documentSignature = XPathUtil.evaluateXPath(sbd, + sbdXpath, + "/sbd:StandardBusinessDocument/sbd:StandardBusinessDocumentHeader/sbd:BusinessScope/sbd:Scope[sbd:Type='NEMHANDEL_EDELIVERY_SIGNATURE']/sbd:InstanceIdentifier", null); + MatcherAssert.assertThat(documentSignature, CoreMatchers.notNullValue()); + + Document signature = parseDocument(Base64.getDecoder().decode(documentSignature.getBytes(StandardCharsets.UTF_8))); + XPath xadesXpath = XPathFactory.newInstance().newXPath(); + MapNamespaceContext nsCtx = new MapNamespaceContext(); + nsCtx.addNamespace("ds", "http://www.w3.org/2000/09/xmldsig#"); + xadesXpath.setNamespaceContext(nsCtx); + + String digestMethod = XPathUtil.evaluateXPath(signature, xadesXpath, "/ds:Signature/ds:SignedInfo/ds:Reference[@URI='" + SignatureValidator.XAdES_SIGNATURE_PAYLOAD_NAME +"']/ds:DigestMethod/@Algorithm", null); + String digest = XPathUtil.evaluateXPath(signature, xadesXpath, "/ds:Signature/ds:SignedInfo/ds:Reference[@URI='" + SignatureValidator.XAdES_SIGNATURE_PAYLOAD_NAME +"']/ds:DigestValue", null); + + // verify that digest matches canonicalized payload + DSSDocument canonicalizedPayload = new InMemoryDocument(DocumentUtil.canonicalize(payload)); + MatcherAssert.assertThat(digest, CoreMatchers.equalTo(canonicalizedPayload.getDigest(DigestAlgorithm.forXML(digestMethod)))); } @Test - public void testOriginalDocumentCanBeNullIfDocumentSignatureIsOptionalAndC1SendsUnsignedSBD() throws Exception, TransferDelegationException { - Document doc = loadAndWrapInSBD("ubl-examples/OIOUBL_Invoice_v2p2.xml"); - byte[] sbd = DocumentUtil.toByteArray(doc); - - TransferDelegationFactoryImpl tdf = new TransferDelegationFactoryImpl(); - byte[] transferDel = tdf.createTransferDelegation("12345678", "99999999"); - - signatureFactory.signDocument(sbd, null, transferDel, new SBDMessageContext(false, Direction.OUT, false)); + public void testCreatesAValidNemhandelEDelivery12Signature() throws Exception { + byte[] sbd = loadAndWrapInSBDAsByteArray("ubl-examples/OIOUBL_Invoice_v2p2.xml"); + byte[] signed = signatureFactory.signDocument(sbd, new SBDMessageContext()); + + Message message = createTestMessage(signed); + SignatureValidator validator = injector.getInstance(SignatureValidator.class); + ValidationResult result = validator.validate(message, new SBDMessageContext(false, Direction.IN)); + MatcherAssert.assertThat(result, CoreMatchers.notNullValue()); + MatcherAssert.assertThat(result.getErrors(), CoreMatchers.notNullValue()); + MatcherAssert.assertThat(result.getErrors().size(), CoreMatchers.equalTo(0)); } - private NodeList getEdeliverySignatureNodes(Document document, XPath xpath) throws XPathExpressionException { - NodeList documentSignatures = XPathUtil.evaluateXPathNodeList(document, - xpath, - "/sbd:StandardBusinessDocument/sbd:StandardBusinessDocumentHeader/sbd:BusinessScope/sbd:Scope[sbd:Type='NEMHANDEL_EDELIVERY_SIGNATURE']/sbd:InstanceIdentifier"); - return documentSignatures; + private Message createTestMessage(byte[] data) { + Account account = new Account("test-user", "test", "DK"); + + MessageContent content = new MessageContent(data); + Message message = new Message(account, + MessageDirection.OUT, + "0184:12345678", + "0184:22334455", + "api", + UUID.randomUUID().toString(), + "urn:oasis:names:specification:ubl:schema:xsd:Invoice-2::Invoice##OIOUBL-2.1::2.1", + content); + return message; } } diff --git a/src/test/java/dk/erst/oxalis/as4/validation/signature/SignatureValidationPolicyProviderTest.java b/src/test/java/dk/erst/oxalis/as4/validation/signature/SignatureValidationPolicyProviderTest.java new file mode 100644 index 0000000000000000000000000000000000000000..995491f98cf183624978ee34a9c94a407cf4c6ca --- /dev/null +++ b/src/test/java/dk/erst/oxalis/as4/validation/signature/SignatureValidationPolicyProviderTest.java @@ -0,0 +1,23 @@ +package dk.erst.oxalis.as4.validation.signature; + +import eu.europa.esig.dss.enumerations.SignatureLevel; +import eu.europa.esig.dss.policy.ValidationPolicy; +import eu.europa.esig.dss.policy.jaxb.Level; +import org.hamcrest.CoreMatchers; +import org.hamcrest.MatcherAssert; +import org.testng.annotations.Test; + +import java.util.Arrays; + +public class SignatureValidationPolicyProviderTest { + + @Test + public void testPolicyRequiresXAdESBaselineB() { + ValidationPolicy policy = new SignatureValidationPolicyProvider().get(); + MatcherAssert.assertThat(policy, CoreMatchers.notNullValue()); + MatcherAssert.assertThat(policy.getSignatureConstraints(), CoreMatchers.notNullValue()); + MatcherAssert.assertThat(policy.getSignatureConstraints().getAcceptableFormats(), CoreMatchers.notNullValue()); + MatcherAssert.assertThat(policy.getSignatureConstraints().getAcceptableFormats().getId(), CoreMatchers.equalTo(Arrays.asList(SignatureLevel.XAdES_BASELINE_B.toString()))); + MatcherAssert.assertThat(policy.getSignatureConstraints().getAcceptableFormats().getLevel(), CoreMatchers.equalTo(Level.FAIL)); + } +} diff --git a/src/test/java/dk/erst/oxalis/as4/validation/signature/SignatureValidatorTest.java b/src/test/java/dk/erst/oxalis/as4/validation/signature/SignatureValidatorTest.java index 6c6efcd618a6b740d4882204007d02251e00f5e9..3228b4874c69bea2004a8a71aec7c3217aa8f414 100644 --- a/src/test/java/dk/erst/oxalis/as4/validation/signature/SignatureValidatorTest.java +++ b/src/test/java/dk/erst/oxalis/as4/validation/signature/SignatureValidatorTest.java @@ -1,32 +1,39 @@ package dk.erst.oxalis.as4.validation.signature; import com.google.inject.*; -import com.google.inject.name.Named; -import com.google.inject.name.Names; import com.google.inject.util.Modules; -import com.typesafe.config.Config; import com.typesafe.config.ConfigFactory; import dk.erst.oxalis.as4.AbstractXmlTest; +import dk.erst.oxalis.as4.EDeliverySpecification; import dk.erst.oxalis.as4.config.documenttype.DocumentTypeModule; import dk.erst.oxalis.as4.error.ErrorCodes; +import dk.erst.oxalis.as4.handlers.dto.ValidationMessage; import dk.erst.oxalis.as4.handlers.dto.ValidationResult; +import dk.erst.oxalis.as4.mode.Mode; import dk.erst.oxalis.as4.persistence.model.Account; import dk.erst.oxalis.as4.persistence.model.Message; import dk.erst.oxalis.as4.persistence.model.MessageContent; import dk.erst.oxalis.as4.persistence.model.MessageDirection; -import dk.erst.oxalis.as4.util.*; +import dk.erst.oxalis.as4.util.DocumentUtil; +import dk.erst.oxalis.as4.util.SBDMessageContext; +import dk.erst.oxalis.as4.util.UtilityModule; import dk.erst.oxalis.as4.validation.ValidationModule; import eu.europa.esig.dss.asic.xades.ASiCWithXAdESSignatureParameters; import eu.europa.esig.dss.asic.xades.signature.ASiCWithXAdESService; import eu.europa.esig.dss.enumerations.ASiCContainerType; +import eu.europa.esig.dss.enumerations.MimeTypeEnum; import eu.europa.esig.dss.enumerations.SignatureLevel; -import eu.europa.esig.dss.model.*; -import eu.europa.esig.dss.model.x509.CertificateToken; +import eu.europa.esig.dss.model.DSSDocument; +import eu.europa.esig.dss.model.InMemoryDocument; +import eu.europa.esig.dss.model.SignatureValue; +import eu.europa.esig.dss.model.ToBeSigned; import eu.europa.esig.dss.policy.ValidationPolicy; -import eu.europa.esig.dss.policy.ValidationPolicyFacade; import eu.europa.esig.dss.policy.jaxb.Level; +import eu.europa.esig.dss.spi.x509.revocation.crl.CRLSource; import eu.europa.esig.dss.token.KSPrivateKeyEntry; -import network.oxalis.api.lang.OxalisLoadingException; +import eu.europa.esig.dss.validation.CertificateVerifier; +import eu.europa.esig.dss.xades.XAdESSignatureParameters; +import eu.europa.esig.dss.xades.signature.XAdESService; import network.oxalis.api.model.Direction; import network.oxalis.api.settings.Settings; import network.oxalis.commons.certvalidator.api.CrlFetcher; @@ -39,48 +46,39 @@ import network.oxalis.commons.security.CertificateModule; import network.oxalis.commons.security.KeyStoreConf; import network.oxalis.commons.tracing.TracingModule; import network.oxalis.pkix.ocsp.api.OcspFetcher; -import network.oxalis.vefa.peppol.common.lang.PeppolLoadingException; -import network.oxalis.vefa.peppol.mode.Mode; -import network.oxalis.vefa.peppol.sbdh.Ns; -import network.oxalis.vefa.peppol.security.ModeDetector; -import org.apache.cxf.helpers.MapNamespaceContext; import org.apache.http.impl.client.CloseableHttpClient; import org.hamcrest.CoreMatchers; import org.hamcrest.MatcherAssert; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; +import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; import org.w3c.dom.Document; -import org.w3c.dom.Node; import org.xml.sax.SAXException; import javax.xml.bind.JAXBException; -import javax.xml.parsers.DocumentBuilder; import javax.xml.stream.XMLStreamException; -import javax.xml.xpath.XPath; import javax.xml.xpath.XPathExpressionException; -import javax.xml.xpath.XPathFactory; import java.io.ByteArrayOutputStream; import java.io.IOException; -import java.io.InputStream; import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.cert.CertificateException; -import java.security.cert.X509Certificate; -import java.util.*; +import java.util.Arrays; +import java.util.Date; +import java.util.List; +import java.util.UUID; import static org.mockito.Mockito.doReturn; public class SignatureValidatorTest extends AbstractXmlTest { private Injector injector; private SignatureValidator signatureValidator; - private ASiCWithXAdESService service; + private XAdESService service; @Mock private OxalisCrlFetcher mockCrlFetcher; @@ -95,28 +93,26 @@ public class SignatureValidatorTest extends AbstractXmlTest { private SignatureValidationPolicyProvider mockValidationPolicyProvider; @Mock - private CertificateVerifierProvider mockCertificateVerifierProvider; + private CRLSource mockCrlSouce; private KeyStore keystore; private KeystoreSignatureTokenConnectionWrapper signatureToken; - private ASiCWithXAdESSignatureParameters signatureParameters; + private XAdESSignatureParameters signatureParameters; private KSPrivateKeyEntry signaturePrivateKey; - private X509Certificate accessPointCertificate; - private KeystoreSignatureTokenConnectionWrapper innerSignatureKeystoreWrapper; - private ASiCWithXAdESSignatureParameters innerSignatureParameters; - private KSPrivateKeyEntry innerSignaturePrivateKey; - private String cvr_C1; - private String cvr_C2; @BeforeMethod public void setup() throws IOException, XMLStreamException, JAXBException, SAXException, KeyStoreException, CertificateException, NoSuchAlgorithmException { MockitoAnnotations.initMocks(this); // disable revocation check for our test certificates - ValidationPolicy noRevocationCheckPolicy = ValidationPolicyFacade.newFacade().getDefaultValidationPolicy(); + ValidationPolicy noRevocationCheckPolicy = new SignatureValidationPolicyProvider().get(); noRevocationCheckPolicy.getSignatureConstraints().getBasicSignatureConstraints().getSigningCertificate().getRevocationDataAvailable().setLevel(Level.IGNORE); doReturn(noRevocationCheckPolicy).when(mockValidationPolicyProvider).get(); + System.setProperty("oxalis.path.conf", "MitID-dummy-CVR-97386837-conf"); + // invalidate cached system properties to force reload! + ConfigFactory.invalidateCaches(); + injector = Guice.createInjector( Modules.override( new FileSystemModule(), @@ -135,85 +131,36 @@ public class SignatureValidatorTest extends AbstractXmlTest { bind(CrlFetcher.class).toInstance(mockCrlFetcher); bind(OcspFetcher.class).toInstance(mockOcspFetcher); bind(CloseableHttpClient.class).toInstance(mockHttpClient); + bind(CRLSource.class).toInstance(mockCrlSouce); bind(ValidationPolicy.class).toProvider(mockValidationPolicyProvider); } - // run with MitID cert @Provides @Singleton - @Named("file") - protected Config loadConfigurationFile(@Named("conf") Path homePath) { - Path configPath = homePath.resolve("nemhandel-e-delivery-oxalis.conf"); - - return Files.exists(configPath) && Files.isReadable(configPath) ? - ConfigFactory.parseFile(configPath.toFile()).resolve() : - ConfigFactory.empty(); - } - - // reload mode after config path has been overriden - @Provides - @Singleton - Mode provideMode(Config config, CrlFetcher crlFetcher, OcspFetcher ocspFetcher, X509Certificate certificate) { - Map objectStorage = new HashMap<>(); - objectStorage.put("ocsp_fetcher", ocspFetcher); - objectStorage.put("crlFetcher", crlFetcher); - - try { - Mode mode = ModeDetector.detect(certificate, config, objectStorage); - mode = Mockito.spy(mode); - return mode; - } catch (PeppolLoadingException e) { - throw new OxalisLoadingException("Unable to detect mode.", e); - } + Mode provideMode(network.oxalis.vefa.peppol.mode.Mode oxalisMode) { + Mode spy = Mockito.spy(new Mode(oxalisMode)); // override mode with a spy to easily simulated running in non-Nemhandel mode in some tests + return spy; } } )); signatureValidator = injector.getInstance(SignatureValidator.class); - service = injector.getInstance(ASiCWithXAdESService.class); + service = injector.getInstance(XAdESService.class); keystore = injector.getInstance(KeyStore.class); signatureToken = injector.getInstance(KeystoreSignatureTokenConnectionWrapper.class); Settings settings = injector.getInstance(new Key>() {}); signaturePrivateKey = (KSPrivateKeyEntry) signatureToken.getKey(settings.getString(KeyStoreConf.KEY_ALIAS)); - CertificateToken signingCertificate = signaturePrivateKey.getCertificate(); - CertificateToken[] certificateChain = signaturePrivateKey.getCertificateChain(); - - - signatureParameters = new ASiCWithXAdESSignatureParameters(); - signatureParameters.bLevel().setSigningDate(new Date()); - signatureParameters.setSigningCertificate(signingCertificate); - signatureParameters.setCertificateChain(certificateChain); - signatureParameters.setSignatureLevel(SignatureLevel.XAdES_BASELINE_B); - signatureParameters.aSiC().setContainerType(ASiCContainerType.ASiC_E); - - accessPointCertificate = injector.getInstance(X509Certificate.class); - - // note: this keystore is just a contains a sample MitID FOCES3 certificate which is not used for anything other than - // unit tests - Path homePath = injector.getInstance(Key.get(Path.class, Names.named("home"))); - Path cvr97386837KeystorePath = homePath.resolve("MitID-dummy-CVR-97386837-conf/Dummy_Systemcertifikat_1_CVR-97386837.p12"); - String secondKeystorePassword = "7H+_sRI5r1jN"; - try(InputStream is = Files.newInputStream(cvr97386837KeystorePath)) { - KeyStore keystore = KeyStore.getInstance("PKCS12"); - keystore.load(is, secondKeystorePassword.toCharArray()); - innerSignatureKeystoreWrapper = new KeystoreSignatureTokenConnectionWrapper(keystore, new KeyStore.PasswordProtection(secondKeystorePassword.toCharArray())); - } + signatureParameters = injector.getProvider(XAdESSignatureParameters.class).get(); + } - // parameters for inner signatures for testing nested ASiC containers - innerSignaturePrivateKey = (KSPrivateKeyEntry) innerSignatureKeystoreWrapper.getKey("dummy systemcertifikat 1"); - - innerSignatureParameters = new ASiCWithXAdESSignatureParameters(); - innerSignatureParameters.bLevel().setSigningDate(new Date()); - innerSignatureParameters.setSigningCertificate(innerSignaturePrivateKey.getCertificate()); - innerSignatureParameters.setCertificateChain(innerSignaturePrivateKey.getCertificateChain()); - innerSignatureParameters.setSignatureLevel(SignatureLevel.XAdES_BASELINE_B); - innerSignatureParameters.aSiC().setContainerType(ASiCContainerType.ASiC_E); - - cvr_C1 = CertificateUtil.extractCVR(innerSignatureParameters.getSigningCertificate().getCertificate()); - cvr_C2 = CertificateUtil.extractCVR(signatureParameters.getSigningCertificate().getCertificate()); + @AfterMethod + public void cleanup() { + System.clearProperty("oxalis.path.conf"); + // invalidate cached system properties to force reload! + ConfigFactory.invalidateCaches(); } @Test @@ -221,312 +168,206 @@ public class SignatureValidatorTest extends AbstractXmlTest { Message message = createTestInvoiceMessage(loadAndWrapInSBDAsByteArray("ubl-examples/OIOUBL_Invoice_v2p2.xml")); SBDMessageContext msgCtx = new SBDMessageContext(false, Direction.OUT); ValidationResult result = signatureValidator.validate(message, msgCtx); - MatcherAssert.assertThat(result, CoreMatchers.notNullValue()); - MatcherAssert.assertThat(result.getErrors(), CoreMatchers.notNullValue()); - MatcherAssert.assertThat(result.getErrors().size(), CoreMatchers.equalTo(1)); - MatcherAssert.assertThat(result.getErrors().get(0).getMessage(), CoreMatchers.containsString(ErrorCodes.SENDER_EDELIVERY_SIGNATURE_MISSING.formatErrorMessage(0))); - MatcherAssert.assertThat(result.getErrors().get(0).getLineReference(), CoreMatchers.containsString(SignatureValidatorImpl.EDELIVERY_SIGNATURE_XPATH)); + assertValidationResultContainsError(result, + ErrorCodes.SENDER_EDELIVERY_SIGNATURE_MISSING.getErrorCode(), + ErrorCodes.SENDER_EDELIVERY_SIGNATURE_MISSING.formatErrorMessage(0), + SignatureValidatorImpl.EDELIVERY_SIGNATURE_XPATH); msgCtx.setDirection(Direction.IN); result = signatureValidator.validate(message, msgCtx); - MatcherAssert.assertThat(result, CoreMatchers.notNullValue()); - MatcherAssert.assertThat(result.getErrors(), CoreMatchers.notNullValue()); - MatcherAssert.assertThat(result.getErrors().size(), CoreMatchers.equalTo(1)); - MatcherAssert.assertThat(result.getErrors().get(0).getMessage(), CoreMatchers.containsString(ErrorCodes.RECEIVER_EDELIVERY_SIGNATURE_MISSING.formatErrorMessage(0))); - MatcherAssert.assertThat(result.getErrors().get(0).getLineReference(), CoreMatchers.containsString(SignatureValidatorImpl.EDELIVERY_SIGNATURE_XPATH)); + assertValidationResultContainsError(result, + ErrorCodes.RECEIVER_EDELIVERY_SIGNATURE_MISSING.getErrorCode(), + ErrorCodes.RECEIVER_EDELIVERY_SIGNATURE_MISSING.formatErrorMessage(0), + SignatureValidatorImpl.EDELIVERY_SIGNATURE_XPATH); } @Test - public void testValidationFailsIfSignatureIsNotValidASiCContainer_invalidSignature() throws Exception { - Document doc = loadAndWrapInSBD("ubl-examples/OIOUBL_Invoice_v2p2.xml"); - byte[] xml = toByteArray(doc); - byte[] delegation = createValidTransferDelegation().getBytes(StandardCharsets.UTF_8); + public void testValidationFailsIfTooManyBusinessScopeWithSignatureIsPresent() throws Exception { + byte[] payload = loadAsByteArray("ubl-examples/OIOUBL_Invoice_v2p2.xml"); + Document sbd = wrapInSBDAsDocument(payload); - InMemoryDocument sbd = new InMemoryDocument(xml, SignatureValidator.ASIC_SBD_FILENAME, MimeType.XML); - InMemoryDocument td = new InMemoryDocument(delegation, SignatureValidator.ASIC_TRANSFER_DELEGATION_FILENAME, MimeType.XML); + InMemoryDocument unsigned = new InMemoryDocument(DocumentUtil.canonicalize(payload), SignatureValidator.XAdES_SIGNATURE_PAYLOAD_NAME, MimeTypeEnum.XML); + DSSDocument signature = createXAdESSignature(unsigned, signatureParameters, signatureToken, signaturePrivateKey); + addSignatureToSBD(sbd, signature, EDeliverySpecification.NEMHANDEL_EDELIVERY_1_2); - List documentsToSign = Arrays.asList(sbd, td); - ToBeSigned dataToSign = service.getDataToSign(documentsToSign, signatureParameters); - SignatureValue signatureValue = signatureToken.sign(dataToSign, signatureParameters.getDigestAlgorithm(), signaturePrivateKey); - signatureValue.setValue("foo".getBytes(StandardCharsets.UTF_8)); // invalid signature value - DSSDocument asicContainer = service.signDocument(documentsToSign, signatureParameters, signatureValue); - addASiCContainerToDocument(doc, asicContainer); + unsigned = new InMemoryDocument(DocumentUtil.canonicalize(payload), SignatureValidator.XAdES_SIGNATURE_PAYLOAD_NAME, MimeTypeEnum.XML); + signature = createXAdESSignature(unsigned, signatureParameters, signatureToken, signaturePrivateKey); + addSignatureToSBD(sbd, signature, EDeliverySpecification.NEMHANDEL_EDELIVERY_1_2); - Message message = createTestInvoiceMessage(toByteArray(doc)); + Message message = createTestInvoiceMessage(DocumentUtil.toByteArray(sbd)); SBDMessageContext msgCtx = new SBDMessageContext(false, Direction.OUT); ValidationResult result = signatureValidator.validate(message, msgCtx); - MatcherAssert.assertThat(result, CoreMatchers.notNullValue()); - MatcherAssert.assertThat(result.getErrors(), CoreMatchers.notNullValue()); - MatcherAssert.assertThat(result.getErrors().size(), CoreMatchers.equalTo(1)); - MatcherAssert.assertThat(result.getErrors().get(0).getMessage(), CoreMatchers.containsString(ErrorCodes.SENDER_EDELIVERY_SIGNATURE_INVALID.getErrorCode())); - MatcherAssert.assertThat(result.getErrors().get(0).getLineReference(), CoreMatchers.containsString(SignatureValidatorImpl.EDELIVERY_SIGNATURE_XPATH)); + assertValidationResultContainsError(result, + ErrorCodes.SENDER_EDELIVERY_TOO_MANY_SIGNATURES.getErrorCode(), + ErrorCodes.SENDER_EDELIVERY_TOO_MANY_SIGNATURES.formatErrorMessage(2), + SignatureValidatorImpl.EDELIVERY_SIGNATURE_XPATH); msgCtx.setDirection(Direction.IN); result = signatureValidator.validate(message, msgCtx); - MatcherAssert.assertThat(result, CoreMatchers.notNullValue()); - MatcherAssert.assertThat(result.getErrors(), CoreMatchers.notNullValue()); - MatcherAssert.assertThat(result.getErrors().size(), CoreMatchers.equalTo(1)); - MatcherAssert.assertThat(result.getErrors().get(0).getMessage(), CoreMatchers.containsString(ErrorCodes.RECEIVER_EDELIVERY_SIGNATURE_INVALID.getErrorCode())); - MatcherAssert.assertThat(result.getErrors().get(0).getLineReference(), CoreMatchers.containsString(SignatureValidatorImpl.EDELIVERY_SIGNATURE_XPATH)); + assertValidationResultContainsError(result, + ErrorCodes.RECEIVER_EDELIVERY_TOO_MANY_SIGNATURES.getErrorCode(), + ErrorCodes.RECEIVER_EDELIVERY_TOO_MANY_SIGNATURES.formatErrorMessage(2), + SignatureValidatorImpl.EDELIVERY_SIGNATURE_XPATH); } @Test - public void testValidationFailsIfTransferDelegationIsNotPresent() throws Exception { - Document doc = loadAndWrapInSBD("ubl-examples/OIOUBL_Invoice_v2p2.xml"); - byte[] xml = toByteArray(doc); - - InMemoryDocument sbd = new InMemoryDocument(xml, SignatureValidator.ASIC_SBD_FILENAME, MimeType.XML); - DSSDocument asicContainer = createAsicContainer(Arrays.asList(sbd), signatureParameters, signatureToken, signaturePrivateKey); - addASiCContainerToDocument(doc, asicContainer); + public void testAcceptsValidSignature() throws Exception { + byte[] payload = loadAsByteArray("ubl-examples/OIOUBL_Invoice_v2p2.xml"); + Document doc = wrapInSBDAsDocument(payload); + InMemoryDocument unsigned = new InMemoryDocument(DocumentUtil.canonicalize(payload), SignatureValidator.XAdES_SIGNATURE_PAYLOAD_NAME, MimeTypeEnum.XML); + DSSDocument signature = createXAdESSignature(unsigned, signatureParameters, signatureToken, signaturePrivateKey); + addSignatureToSBD(doc, signature, EDeliverySpecification.NEMHANDEL_EDELIVERY_1_2); Message message = createTestInvoiceMessage(toByteArray(doc)); SBDMessageContext msgCtx = new SBDMessageContext(false, Direction.OUT); ValidationResult result = signatureValidator.validate(message, msgCtx); MatcherAssert.assertThat(result, CoreMatchers.notNullValue()); MatcherAssert.assertThat(result.getErrors(), CoreMatchers.notNullValue()); - MatcherAssert.assertThat(result.getErrors().size(), CoreMatchers.equalTo(1)); - MatcherAssert.assertThat(result.getErrors().get(0).getMessage(), CoreMatchers.containsString(ErrorCodes.SENDER_EDELIVERY_MISSING_TRANSFER_DELEGATION.formatErrorMessage(0))); - MatcherAssert.assertThat(result.getErrors().get(0).getLineReference(), CoreMatchers.containsString(SignatureValidatorImpl.EDELIVERY_SIGNATURE_XPATH)); - - msgCtx.setDirection(Direction.IN); - result = signatureValidator.validate(message, msgCtx); - MatcherAssert.assertThat(result, CoreMatchers.notNullValue()); - MatcherAssert.assertThat(result.getErrors(), CoreMatchers.notNullValue()); - MatcherAssert.assertThat(result.getErrors().size(), CoreMatchers.equalTo(1)); - MatcherAssert.assertThat(result.getErrors().get(0).getMessage(), CoreMatchers.containsString(ErrorCodes.RECEIVER_EDELIVERY_MISSING_TRANSFER_DELEGATION.formatErrorMessage(0))); - MatcherAssert.assertThat(result.getErrors().get(0).getLineReference(), CoreMatchers.containsString(SignatureValidatorImpl.EDELIVERY_SIGNATURE_XPATH)); + MatcherAssert.assertThat(result.getErrors().size(), CoreMatchers.equalTo(0)); } @Test - public void testValidationFailsIfSBDIsNotPartOfSignature() throws Exception { - Document doc = loadAndWrapInSBD("ubl-examples/OIOUBL_Invoice_v2p2.xml"); - byte[] delegation = createValidTransferDelegation().getBytes(StandardCharsets.UTF_8); + public void testValidationFailsIfSignatureIsNotValidXAdESSignature_invalidSignature() throws Exception { + byte[] payload = loadAsByteArray("ubl-examples/OIOUBL_Invoice_v2p2.xml"); + Document sbd = wrapInSBDAsDocument(payload); - InMemoryDocument td = new InMemoryDocument(delegation, SignatureValidator.ASIC_TRANSFER_DELEGATION_FILENAME, MimeType.XML); - DSSDocument asicContainer = createAsicContainer(Arrays.asList(td), signatureParameters, signatureToken, signaturePrivateKey); - addASiCContainerToDocument(doc, asicContainer); + InMemoryDocument unsigned = new InMemoryDocument(DocumentUtil.canonicalize(payload), SignatureValidator.XAdES_SIGNATURE_PAYLOAD_NAME, MimeTypeEnum.XML); + ToBeSigned dataToSign = service.getDataToSign(unsigned, signatureParameters); + SignatureValue signatureValue = signatureToken.sign(dataToSign, signatureParameters.getDigestAlgorithm(), signaturePrivateKey); + signatureValue.setValue("foo".getBytes(StandardCharsets.UTF_8)); // invalid signature value + DSSDocument signature = service.signDocument(unsigned, signatureParameters, signatureValue); + addSignatureToSBD(sbd, signature, EDeliverySpecification.NEMHANDEL_EDELIVERY_1_2); - Message message = createTestInvoiceMessage(toByteArray(doc)); + Message message = createTestInvoiceMessage(toByteArray(sbd)); SBDMessageContext msgCtx = new SBDMessageContext(false, Direction.OUT); ValidationResult result = signatureValidator.validate(message, msgCtx); - MatcherAssert.assertThat(result, CoreMatchers.notNullValue()); - MatcherAssert.assertThat(result.getErrors(), CoreMatchers.notNullValue()); - MatcherAssert.assertThat(result.getErrors().size(), CoreMatchers.equalTo(1)); - MatcherAssert.assertThat(result.getErrors().get(0).getMessage(), CoreMatchers.containsString(ErrorCodes.SENDER_EDELIVERY_MISSING_SBD_IN_ASIC.formatErrorMessage(0))); - MatcherAssert.assertThat(result.getErrors().get(0).getLineReference(), CoreMatchers.containsString(SignatureValidatorImpl.EDELIVERY_SIGNATURE_XPATH)); + assertValidationResultContainsError(result, + ErrorCodes.SENDER_EDELIVERY_SIGNATURE_INVALID.getErrorCode(), + ErrorCodes.SENDER_EDELIVERY_SIGNATURE_INVALID.formatErrorMessage("BBB_CV_ISI_ANS: The signature is not intact!"), + SignatureValidatorImpl.EDELIVERY_SIGNATURE_XPATH); msgCtx.setDirection(Direction.IN); result = signatureValidator.validate(message, msgCtx); - MatcherAssert.assertThat(result, CoreMatchers.notNullValue()); - MatcherAssert.assertThat(result.getErrors(), CoreMatchers.notNullValue()); - MatcherAssert.assertThat(result.getErrors().size(), CoreMatchers.equalTo(1)); - MatcherAssert.assertThat(result.getErrors().get(0).getMessage(), CoreMatchers.containsString(ErrorCodes.RECEIVER_EDELIVERY_MISSING_SBD_IN_ASIC.formatErrorMessage(0))); - MatcherAssert.assertThat(result.getErrors().get(0).getLineReference(), CoreMatchers.containsString(SignatureValidatorImpl.EDELIVERY_SIGNATURE_XPATH)); + assertValidationResultContainsError(result, + ErrorCodes.RECEIVER_EDELIVERY_SIGNATURE_INVALID.getErrorCode(), + ErrorCodes.RECEIVER_EDELIVERY_SIGNATURE_INVALID.formatErrorMessage("BBB_CV_ISI_ANS: The signature is not intact!"), + SignatureValidatorImpl.EDELIVERY_SIGNATURE_XPATH); } @Test - public void testValidationFailsIfOriginalSbdInAsicContainerIsNotSBD() throws Exception { - Document doc = loadAndWrapInSBD("ubl-examples/OIOUBL_Invoice_v2p2.xml"); - byte[] xml = toByteArray(doc); - byte[] delegation = createValidTransferDelegation().getBytes(StandardCharsets.UTF_8); + public void testValidationFailsIfSignatureIsNotAXAdESSignature() throws Exception { + byte[] payload = loadAsByteArray("ubl-examples/OIOUBL_Invoice_v2p2.xml"); + Document sbd = wrapInSBDAsDocument(payload); - InMemoryDocument sbd = new InMemoryDocument(xml, SignatureValidator.ASIC_SBD_FILENAME, MimeType.XML); - InMemoryDocument original = new InMemoryDocument("".getBytes(StandardCharsets.UTF_8), SignatureValidator.ASIC_ORIGINAL_SBD_FILENAME, MimeType.XML); - InMemoryDocument td = new InMemoryDocument(delegation, SignatureValidator.ASIC_TRANSFER_DELEGATION_FILENAME, MimeType.XML); - DSSDocument asicContainer = createAsicContainer(Arrays.asList(sbd, original, td), signatureParameters, signatureToken, signaturePrivateKey); - addASiCContainerToDocument(doc, asicContainer); + appendNemhandelSignatureScopeNode(sbd, "".getBytes(StandardCharsets.UTF_8), EDeliverySpecification.NEMHANDEL_EDELIVERY_1_2); - Message message = createTestInvoiceMessage(toByteArray(doc)); + Message message = createTestInvoiceMessage(toByteArray(sbd)); SBDMessageContext msgCtx = new SBDMessageContext(false, Direction.OUT); ValidationResult result = signatureValidator.validate(message, msgCtx); - MatcherAssert.assertThat(result, CoreMatchers.notNullValue()); - MatcherAssert.assertThat(result.getErrors(), CoreMatchers.notNullValue()); - MatcherAssert.assertThat(result.getErrors().size(), CoreMatchers.equalTo(1)); - MatcherAssert.assertThat(result.getErrors().get(0).getMessage(), CoreMatchers.containsString(ErrorCodes.SENDER_EDELIVERY_ORIGINAL_SBD_IN_ASIC_IS_NOT_SBD.formatErrorMessage(0))); - MatcherAssert.assertThat(result.getErrors().get(0).getLineReference(), CoreMatchers.containsString(SignatureValidatorImpl.EDELIVERY_SIGNATURE_XPATH)); + assertValidationResultContainsError(result, + ErrorCodes.SENDER_EDELIVERY_SIGNATURE_INVALID.getErrorCode(), + ErrorCodes.SENDER_EDELIVERY_SIGNATURE_INVALID.formatErrorMessage("Signature is not in XAdES format or contains no signatures"), + SignatureValidatorImpl.EDELIVERY_SIGNATURE_XPATH); msgCtx.setDirection(Direction.IN); result = signatureValidator.validate(message, msgCtx); - MatcherAssert.assertThat(result, CoreMatchers.notNullValue()); - MatcherAssert.assertThat(result.getErrors(), CoreMatchers.notNullValue()); - MatcherAssert.assertThat(result.getErrors().size(), CoreMatchers.equalTo(1)); - MatcherAssert.assertThat(result.getErrors().get(0).getMessage(), CoreMatchers.containsString(ErrorCodes.RECEIVER_EDELIVERY_ORIGINAL_SBD_IN_ASIC_IS_NOT_SBD.formatErrorMessage(0))); - MatcherAssert.assertThat(result.getErrors().get(0).getLineReference(), CoreMatchers.containsString(SignatureValidatorImpl.EDELIVERY_SIGNATURE_XPATH)); + assertValidationResultContainsError(result, + ErrorCodes.RECEIVER_EDELIVERY_SIGNATURE_INVALID.getErrorCode(), + ErrorCodes.RECEIVER_EDELIVERY_SIGNATURE_INVALID.formatErrorMessage("Signature is not in XAdES format or contains no signatures"), + SignatureValidatorImpl.EDELIVERY_SIGNATURE_XPATH); } @Test - public void testAcceptsValidSignature() throws Exception { - Document doc = loadAndWrapInSBD("ubl-examples/OIOUBL_Invoice_v2p2.xml"); - byte[] xml = toByteArray(doc); - byte[] delegation = createValidTransferDelegation().getBytes(StandardCharsets.UTF_8); + public void testValidationFailsIfSignatureIsNotXML() throws Exception { + byte[] payload = loadAsByteArray("ubl-examples/OIOUBL_Invoice_v2p2.xml"); + Document sbd = wrapInSBDAsDocument(payload); - InMemoryDocument sbd = new InMemoryDocument(xml, SignatureValidator.ASIC_SBD_FILENAME, MimeType.XML); - InMemoryDocument td = new InMemoryDocument(delegation, SignatureValidator.ASIC_TRANSFER_DELEGATION_FILENAME, MimeType.XML); - DSSDocument asicContainer = createAsicContainer(Arrays.asList(sbd, td), signatureParameters, signatureToken, signaturePrivateKey); - addASiCContainerToDocument(doc, asicContainer); + appendNemhandelSignatureScopeNode(sbd, "foo".getBytes(StandardCharsets.UTF_8), EDeliverySpecification.NEMHANDEL_EDELIVERY_1_2); - Message message = createTestInvoiceMessage(toByteArray(doc)); + Message message = createTestInvoiceMessage(toByteArray(sbd)); SBDMessageContext msgCtx = new SBDMessageContext(false, Direction.OUT); ValidationResult result = signatureValidator.validate(message, msgCtx); - MatcherAssert.assertThat(result, CoreMatchers.notNullValue()); - MatcherAssert.assertThat(result.getErrors(), CoreMatchers.notNullValue()); - MatcherAssert.assertThat(result.getErrors().size(), CoreMatchers.equalTo(0)); - } - - @Test - public void testAcceptsValidRecursivelyNestedAsicContainers() throws Exception { - Document doc = loadAndWrapInSBD("ubl-examples/OIOUBL_Invoice_v2p2.xml"); - byte[] xml = toByteArray(doc); - byte[] delegation = createTransferDelegation(cvr_C1, cvr_C2).getBytes(StandardCharsets.UTF_8); - - - InMemoryDocument sbd = new InMemoryDocument(xml, SignatureValidator.ASIC_SBD_FILENAME, MimeType.XML); - InMemoryDocument td = new InMemoryDocument(delegation, SignatureValidator.ASIC_TRANSFER_DELEGATION_FILENAME, MimeType.XML); - DSSDocument asicContainer = createAsicContainer(Arrays.asList(sbd, td), innerSignatureParameters, innerSignatureKeystoreWrapper, innerSignaturePrivateKey); - addASiCContainerToDocument(doc, asicContainer); + assertValidationResultContainsError(result, + ErrorCodes.SENDER_EDELIVERY_SIGNATURE_INVALID.getErrorCode(), + ErrorCodes.SENDER_EDELIVERY_SIGNATURE_INVALID.formatErrorMessage("Signature is not in XAdES format or contains no signatures"), + SignatureValidatorImpl.EDELIVERY_SIGNATURE_XPATH); - byte[] document1 = toByteArray(doc); - - // Cx translates the document and resigns - byte[] translatedXml = new String(xml, StandardCharsets.UTF_8).replace("A00095678", "" + UUID.randomUUID().toString() + "").getBytes(StandardCharsets.UTF_8); - byte[] delegation2 = createTransferDelegation(cvr_C2, cvr_C2).getBytes(StandardCharsets.UTF_8); // we assume here that C2 and C3 have the same CVR so we can settle for using two keystores - doc = parseDocument(translatedXml); - - sbd = new InMemoryDocument(translatedXml, SignatureValidator.ASIC_SBD_FILENAME, MimeType.XML); - DSSDocument original = new InMemoryDocument(document1, SignatureValidator.ASIC_ORIGINAL_SBD_FILENAME, MimeType.XML); - td = new InMemoryDocument(delegation2, SignatureValidator.ASIC_TRANSFER_DELEGATION_FILENAME, MimeType.XML); - DSSDocument asicContainer2 = createAsicContainer(Arrays.asList(sbd, original, td), signatureParameters, signatureToken, signaturePrivateKey); - addASiCContainerToDocument(doc, asicContainer2); - - Message message = createTestInvoiceMessage(toByteArray(doc)); - SBDMessageContext msgCtx = new SBDMessageContext(false, Direction.OUT); - ValidationResult result = signatureValidator.validate(message, msgCtx); - MatcherAssert.assertThat(result, CoreMatchers.notNullValue()); - MatcherAssert.assertThat(result.getErrors(), CoreMatchers.notNullValue()); - MatcherAssert.assertThat(result.getErrors().size(), CoreMatchers.equalTo(0)); + msgCtx.setDirection(Direction.IN); + result = signatureValidator.validate(message, msgCtx); + assertValidationResultContainsError(result, + ErrorCodes.RECEIVER_EDELIVERY_SIGNATURE_INVALID.getErrorCode(), + ErrorCodes.RECEIVER_EDELIVERY_SIGNATURE_INVALID.formatErrorMessage("Signature is not in XAdES format or contains no signatures"), + SignatureValidatorImpl.EDELIVERY_SIGNATURE_XPATH); } @Test - public void testAcceptsValidNonSaxonFormattedRecursivelyNestedAsicContainers() throws Exception { - DocumentBuilder builder = new DocumentBuilderProvider().get(); - Document doc; - // Non canonicalized uses different line endings in this case! - try(InputStream is = getClass().getClassLoader().getResourceAsStream("sbd-examples/SBD_OIOUBL_Invoice_signed_non_canonicalized.xml")) { - doc = builder.parse(is); - } + public void testValidationFailsIfSignatureIsASICFormat() throws Exception { + byte[] payload = loadAsByteArray("ubl-examples/OIOUBL_Invoice_v2p2.xml"); + Document sbd = wrapInSBDAsDocument(payload); - byte[] originalSbd = toByteArray(doc); - byte[] canonicalizedSbd = DocumentUtil.canonicalize(originalSbd); + ASiCWithXAdESService asicService = new ASiCWithXAdESService(injector.getInstance(CertificateVerifier.class)); - // Sanity check that original is not canonicalized already, because this implies the canonicalization implemented - // in the validator is actually handling canonicalization as intended. - MatcherAssert.assertThat("original document should not be canonicalized already", originalSbd, CoreMatchers.not(CoreMatchers.equalTo(canonicalizedSbd))); + ASiCWithXAdESSignatureParameters asicParameters = new ASiCWithXAdESSignatureParameters(); + asicParameters.bLevel().setSigningDate(new Date()); + asicParameters.setSigningCertificate(signaturePrivateKey.getCertificate()); + asicParameters.setCertificateChain(signaturePrivateKey.getCertificateChain()); + asicParameters.setSignatureLevel(SignatureLevel.XAdES_BASELINE_B); + asicParameters.aSiC().setContainerType(ASiCContainerType.ASiC_E); - Message message = createTestInvoiceMessage(originalSbd); - SBDMessageContext msgCtx = new SBDMessageContext(false, Direction.OUT); - ValidationResult result = signatureValidator.validate(message, msgCtx); - MatcherAssert.assertThat(result, CoreMatchers.notNullValue()); - MatcherAssert.assertThat(result.getErrors(), CoreMatchers.notNullValue()); - MatcherAssert.assertThat(result.getErrors().size(), CoreMatchers.equalTo(0)); - } + InMemoryDocument unsigned = new InMemoryDocument(DocumentUtil.canonicalize(payload), SignatureValidator.XAdES_SIGNATURE_PAYLOAD_NAME, MimeTypeEnum.XML); - @Test - public void testRejectsInvalidRecursivelyNestedAsicContainers() throws Exception { - // create inner, invalid asic - Document doc = loadAndWrapInSBD("ubl-examples/OIOUBL_Invoice_v2p2.xml"); - byte[] xml = toByteArray(doc); - byte[] delegation = createTransferDelegation(cvr_C1, cvr_C2).getBytes(StandardCharsets.UTF_8); - - InMemoryDocument sbd = new InMemoryDocument(xml, SignatureValidator.ASIC_SBD_FILENAME, MimeType.XML); - InMemoryDocument td = new InMemoryDocument(delegation, SignatureValidator.ASIC_TRANSFER_DELEGATION_FILENAME, MimeType.XML); - - List documentsToSign = Arrays.asList(sbd, td); - ToBeSigned dataToSign = service.getDataToSign(documentsToSign, innerSignatureParameters); - SignatureValue signatureValue = innerSignatureKeystoreWrapper.sign(dataToSign, innerSignatureParameters.getDigestAlgorithm(), innerSignaturePrivateKey); - signatureValue.setValue("foo".getBytes(StandardCharsets.UTF_8)); // invalid signature value - DSSDocument asicContainer = service.signDocument(documentsToSign, innerSignatureParameters, signatureValue); - addASiCContainerToDocument(doc, asicContainer); - byte[] originalSbd = toByteArray(doc); - - // Cx translates the document and re-signs - byte[] translatedXml = new String(xml, StandardCharsets.UTF_8).replace("A00095678", "" + UUID.randomUUID().toString() + "").getBytes(StandardCharsets.UTF_8); - byte[] delegation2 = createTransferDelegation(cvr_C2, cvr_C2).getBytes(StandardCharsets.UTF_8); // normally this would be C2 -> C3 the test will fail for other reasons - doc = parseDocument(translatedXml); + ToBeSigned dataToSign = asicService.getDataToSign(unsigned, asicParameters); + SignatureValue signatureValue = signatureToken.sign(dataToSign, asicParameters.getDigestAlgorithm(), signaturePrivateKey); + DSSDocument signedDocument = asicService.signDocument(unsigned, asicParameters, signatureValue); + addSignatureToSBD(sbd, signedDocument, EDeliverySpecification.NEMHANDEL_EDELIVERY_1_2); - sbd = new InMemoryDocument(translatedXml, SignatureValidator.ASIC_SBD_FILENAME, MimeType.XML); - DSSDocument original = new InMemoryDocument(originalSbd, SignatureValidator.ASIC_ORIGINAL_SBD_FILENAME, MimeType.XML); - td = new InMemoryDocument(delegation2, SignatureValidator.ASIC_TRANSFER_DELEGATION_FILENAME, MimeType.XML); - DSSDocument asicContainer2 = createAsicContainer(Arrays.asList(sbd, original, td), signatureParameters, signatureToken, signaturePrivateKey); - addASiCContainerToDocument(doc, asicContainer2); - - Message message = createTestInvoiceMessage(toByteArray(doc)); + Message message = createTestInvoiceMessage(toByteArray(sbd)); SBDMessageContext msgCtx = new SBDMessageContext(false, Direction.OUT); ValidationResult result = signatureValidator.validate(message, msgCtx); - MatcherAssert.assertThat(result, CoreMatchers.notNullValue()); - MatcherAssert.assertThat(result.getErrors(), CoreMatchers.notNullValue()); - MatcherAssert.assertThat(result.getErrors().size(), CoreMatchers.equalTo(1)); - MatcherAssert.assertThat(result.getErrors().get(0).getMessage(), CoreMatchers.containsString(ErrorCodes.SENDER_EDELIVERY_SIGNATURE_INVALID.formatErrorMessage(1, ""))); - MatcherAssert.assertThat(result.getErrors().get(0).getLineReference(), CoreMatchers.containsString(SignatureValidatorImpl.EDELIVERY_SIGNATURE_XPATH)); + assertValidationResultContainsError(result, + ErrorCodes.SENDER_EDELIVERY_SIGNATURE_INVALID.getErrorCode(), + ErrorCodes.SENDER_EDELIVERY_SIGNATURE_INVALID.formatErrorMessage("Signature is not in XAdES format or contains no signatures"), + SignatureValidatorImpl.EDELIVERY_SIGNATURE_XPATH); msgCtx.setDirection(Direction.IN); result = signatureValidator.validate(message, msgCtx); - MatcherAssert.assertThat(result, CoreMatchers.notNullValue()); - MatcherAssert.assertThat(result.getErrors(), CoreMatchers.notNullValue()); - MatcherAssert.assertThat(result.getErrors().size(), CoreMatchers.equalTo(1)); - MatcherAssert.assertThat(result.getErrors().get(0).getMessage(), CoreMatchers.containsString(ErrorCodes.RECEIVER_EDELIVERY_SIGNATURE_INVALID.formatErrorMessage(1, ""))); - MatcherAssert.assertThat(result.getErrors().get(0).getLineReference(), CoreMatchers.containsString(SignatureValidatorImpl.EDELIVERY_SIGNATURE_XPATH)); + assertValidationResultContainsError(result, + ErrorCodes.RECEIVER_EDELIVERY_SIGNATURE_INVALID.getErrorCode(), + ErrorCodes.RECEIVER_EDELIVERY_SIGNATURE_INVALID.formatErrorMessage("Signature is not in XAdES format or contains no signatures"), + SignatureValidatorImpl.EDELIVERY_SIGNATURE_XPATH); } @Test - public void testValidationFailsIfSBDDoesMatchSBDInASiCContainer() throws Exception { - Document doc = loadAndWrapInSBD("ubl-examples/OIOUBL_Invoice_v2p2.xml"); - byte[] xml = toByteArray(doc); - byte[] delegation = createValidTransferDelegation().getBytes(StandardCharsets.UTF_8); - - InMemoryDocument sbd = new InMemoryDocument(xml, SignatureValidator.ASIC_SBD_FILENAME, MimeType.XML); - InMemoryDocument td = new InMemoryDocument(delegation, SignatureValidator.ASIC_TRANSFER_DELEGATION_FILENAME, MimeType.XML); - DSSDocument asicContainer = createAsicContainer(Arrays.asList(sbd, td), signatureParameters, signatureToken, signaturePrivateKey); - addASiCContainerToDocument(doc, asicContainer); - byte[] original = toByteArray(doc); - - // change some node so they no longer match - Document altered = parseDocument(original); - MapNamespaceContext namespaceContext = new MapNamespaceContext(); - namespaceContext.addNamespace("sbd", Ns.SBDH); - namespaceContext.addNamespace("inv", "urn:oasis:names:specification:ubl:schema:xsd:Invoice-2"); - namespaceContext.addNamespace("cbc", "urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2"); - - XPath xpath = XPathFactory.newInstance().newXPath(); - xpath.setNamespaceContext(namespaceContext); - - Node node = XPathUtil.evaluateXPathNode(altered, - xpath, - "/sbd:StandardBusinessDocument/inv:Invoice/cbc:ID"); - node.setTextContent(UUID.randomUUID().toString()); - - Message message = createTestInvoiceMessage(toByteArray(altered)); + public void testValidationFailsIfPayloadIsNotPartOfSignature() throws Exception { + byte[] payload = loadAsByteArray("ubl-examples/OIOUBL_Invoice_v2p2.xml"); + Document doc = wrapInSBDAsDocument(payload); + + InMemoryDocument unsigned = new InMemoryDocument("foo".getBytes(StandardCharsets.UTF_8), "foo", MimeTypeEnum.TEXT); + DSSDocument signature = createXAdESSignature(unsigned, signatureParameters, signatureToken, signaturePrivateKey); + addSignatureToSBD(doc, signature, EDeliverySpecification.NEMHANDEL_EDELIVERY_1_2); + + Message message = createTestInvoiceMessage(toByteArray(doc)); SBDMessageContext msgCtx = new SBDMessageContext(false, Direction.OUT); ValidationResult result = signatureValidator.validate(message, msgCtx); - MatcherAssert.assertThat(result, CoreMatchers.notNullValue()); - MatcherAssert.assertThat(result.getErrors(), CoreMatchers.notNullValue()); - MatcherAssert.assertThat(result.getErrors().size(), CoreMatchers.equalTo(1)); - MatcherAssert.assertThat(result.getErrors().get(0).getMessage(), CoreMatchers.containsString(ErrorCodes.SENDER_EDELIVERY_SBD_DOES_NOT_MATCH_ASIC.formatErrorMessage(0))); - MatcherAssert.assertThat(result.getErrors().get(0).getLineReference(), CoreMatchers.containsString(SignatureValidatorImpl.EDELIVERY_SIGNATURE_XPATH)); + assertValidationResultContainsError(result, + ErrorCodes.SENDER_EDELIVERY_SIGNATURE_INVALID.getErrorCode(), + ErrorCodes.SENDER_EDELIVERY_SIGNATURE_INVALID.formatErrorMessage("BBB_CV_IRDOI_ANS: The reference data object is not intact!"), + SignatureValidatorImpl.EDELIVERY_SIGNATURE_XPATH); msgCtx.setDirection(Direction.IN); result = signatureValidator.validate(message, msgCtx); - MatcherAssert.assertThat(result, CoreMatchers.notNullValue()); - MatcherAssert.assertThat(result.getErrors(), CoreMatchers.notNullValue()); - MatcherAssert.assertThat(result.getErrors().size(), CoreMatchers.equalTo(1)); - MatcherAssert.assertThat(result.getErrors().get(0).getMessage(), CoreMatchers.containsString(ErrorCodes.RECEIVER_EDELIVERY_SBD_DOES_NOT_MATCH_ASIC.formatErrorMessage(0))); - MatcherAssert.assertThat(result.getErrors().get(0).getLineReference(), CoreMatchers.containsString(SignatureValidatorImpl.EDELIVERY_SIGNATURE_XPATH)); + assertValidationResultContainsError(result, + ErrorCodes.RECEIVER_EDELIVERY_SIGNATURE_INVALID.getErrorCode(), + ErrorCodes.RECEIVER_EDELIVERY_SIGNATURE_INVALID.formatErrorMessage("BBB_CV_IRDOI_ANS: The reference data object is not intact!"), + SignatureValidatorImpl.EDELIVERY_SIGNATURE_XPATH); } @Test public void testOnlyValidatesSignatureInNemhandelEdeliveryMode() throws Exception { Mode mode = injector.getInstance(Mode.class); - doReturn("TEST").when(mode).getIdentifier(); // simulate peppol test mode + doReturn(false).when(mode).isNemhandelEdeliveryMode(); // simulate non-Nemhandel mode + Message message = createTestInvoiceMessage(loadAndWrapInSBDAsByteArray("ubl-examples/OIOUBL_Invoice_v2p2.xml")); SBDMessageContext msgCtx = new SBDMessageContext(false, Direction.OUT); @@ -537,337 +378,141 @@ public class SignatureValidatorTest extends AbstractXmlTest { } @Test - public void testValidationFailsIfFirstLevelReceiverDoesNotMatchAPCvr() throws Exception { - String wrongReceiverCVR = "99887766"; - Document doc = loadAndWrapInSBD("ubl-examples/OIOUBL_Invoice_v2p2.xml"); - byte[] xml = toByteArray(doc); - byte[] delegation = createTransferDelegation(cvr_C2, wrongReceiverCVR).getBytes(StandardCharsets.UTF_8); - - InMemoryDocument sbd = new InMemoryDocument(xml, SignatureValidator.ASIC_SBD_FILENAME, MimeType.XML); - InMemoryDocument td = new InMemoryDocument(delegation, SignatureValidator.ASIC_TRANSFER_DELEGATION_FILENAME, MimeType.XML); - DSSDocument asicContainer = createAsicContainer(Arrays.asList(sbd, td), signatureParameters, signatureToken, signaturePrivateKey); - addASiCContainerToDocument(doc, asicContainer); - - Message message = createTestInvoiceMessage(toByteArray(doc)); - SBDMessageContext msgCtx = new SBDMessageContext(false, Direction.OUT); + public void testAcceptsUnsignedDocumentIfDocumentSignatureIsOptional() throws Exception { + Message message = createTestInvoiceMessage(loadAndWrapInSBDAsByteArray("ubl-examples/OIOUBL_Invoice_v2p2.xml")); + SBDMessageContext msgCtx = new SBDMessageContext(false, Direction.OUT, false); ValidationResult result = signatureValidator.validate(message, msgCtx); MatcherAssert.assertThat(result, CoreMatchers.notNullValue()); MatcherAssert.assertThat(result.getErrors(), CoreMatchers.notNullValue()); - MatcherAssert.assertThat(result.getErrors().size(), CoreMatchers.equalTo(1)); - MatcherAssert.assertThat(result.getErrors().get(0).getMessage(), CoreMatchers.containsString(ErrorCodes.SENDER_EDELIVERY_TRANSFER_DELEGATION_INVALID_RECEIVER_CVR.formatErrorMessage(wrongReceiverCVR, cvr_C2, 0))); - MatcherAssert.assertThat(result.getErrors().get(0).getLineReference(), CoreMatchers.containsString(SignatureValidatorImpl.EDELIVERY_SIGNATURE_XPATH)); - - msgCtx.setDirection(Direction.IN); - result = signatureValidator.validate(message, msgCtx); - MatcherAssert.assertThat(result, CoreMatchers.notNullValue()); - MatcherAssert.assertThat(result.getErrors(), CoreMatchers.notNullValue()); - MatcherAssert.assertThat(result.getErrors().size(), CoreMatchers.equalTo(1)); - MatcherAssert.assertThat(result.getErrors().get(0).getMessage(), CoreMatchers.containsString(ErrorCodes.RECEIVER_EDELIVERY_TRANSFER_DELEGATION_INVALID_RECEIVER_CVR.formatErrorMessage(wrongReceiverCVR, cvr_C2, 0))); - MatcherAssert.assertThat(result.getErrors().get(0).getLineReference(), CoreMatchers.containsString(SignatureValidatorImpl.EDELIVERY_SIGNATURE_XPATH)); + MatcherAssert.assertThat(result.getErrors().size(), CoreMatchers.equalTo(0)); } @Test - public void testValidationFailsIfTransferDelegationReceiverSchemeIsNotDKCVR() throws Exception { - Document doc = loadAndWrapInSBD("ubl-examples/OIOUBL_Invoice_v2p2.xml"); - byte[] xml = toByteArray(doc); - String delegationString = createValidTransferDelegation(); - byte[] delegation = delegationString.replace("", "").getBytes(StandardCharsets.UTF_8); - - InMemoryDocument sbd = new InMemoryDocument(xml, SignatureValidator.ASIC_SBD_FILENAME, MimeType.XML); - InMemoryDocument td = new InMemoryDocument(delegation, SignatureValidator.ASIC_TRANSFER_DELEGATION_FILENAME, MimeType.XML); - DSSDocument asicContainer = createAsicContainer(Arrays.asList(sbd, td), signatureParameters, signatureToken, signaturePrivateKey); - addASiCContainerToDocument(doc, asicContainer); - - Message message = createTestInvoiceMessage(toByteArray(doc)); - SBDMessageContext msgCtx = new SBDMessageContext(false, Direction.OUT); + public void testRejectsUnsignedDocumentInC3() throws Exception { + Message message = createTestInvoiceMessage(loadAndWrapInSBDAsByteArray("ubl-examples/OIOUBL_Invoice_v2p2.xml")); + SBDMessageContext msgCtx = new SBDMessageContext(false, Direction.IN, true); ValidationResult result = signatureValidator.validate(message, msgCtx); MatcherAssert.assertThat(result, CoreMatchers.notNullValue()); MatcherAssert.assertThat(result.getErrors(), CoreMatchers.notNullValue()); - MatcherAssert.assertThat(result.getErrors().size(), CoreMatchers.equalTo(1)); - MatcherAssert.assertThat(result.getErrors().get(0).getMessage(), CoreMatchers.containsString(ErrorCodes.SENDER_EDELIVERY_TRANSFER_DELEGATION_INVALID_RECEIVER_SCHEME.formatErrorMessage("foo", "DK:CVR", 0))); - MatcherAssert.assertThat(result.getErrors().get(0).getLineReference(), CoreMatchers.containsString(SignatureValidatorImpl.EDELIVERY_SIGNATURE_XPATH)); - - msgCtx.setDirection(Direction.IN); - result = signatureValidator.validate(message, msgCtx); - MatcherAssert.assertThat(result, CoreMatchers.notNullValue()); - MatcherAssert.assertThat(result.getErrors(), CoreMatchers.notNullValue()); - MatcherAssert.assertThat(result.getErrors().size(), CoreMatchers.equalTo(1)); - MatcherAssert.assertThat(result.getErrors().get(0).getMessage(), CoreMatchers.containsString(ErrorCodes.RECEIVER_EDELIVERY_TRANSFER_DELEGATION_INVALID_RECEIVER_SCHEME.formatErrorMessage("foo", "DK:CVR", 0))); - MatcherAssert.assertThat(result.getErrors().get(0).getLineReference(), CoreMatchers.containsString(SignatureValidatorImpl.EDELIVERY_SIGNATURE_XPATH)); + assertValidationResultContainsError(result, + ErrorCodes.RECEIVER_EDELIVERY_SIGNATURE_MISSING.getErrorCode(), + ErrorCodes.RECEIVER_EDELIVERY_SIGNATURE_MISSING.formatErrorMessage(), + SignatureValidatorImpl.EDELIVERY_SIGNATURE_XPATH); } @Test - public void testValidationFailsIfTransferDelegationSenderSchemeIsNotDKCVR() throws Exception { - Document doc = loadAndWrapInSBD("ubl-examples/OIOUBL_Invoice_v2p2.xml"); - byte[] xml = toByteArray(doc); - String delegationString = createValidTransferDelegation(); - byte[] delegation = delegationString.replace("", "").getBytes(StandardCharsets.UTF_8); - - InMemoryDocument sbd = new InMemoryDocument(xml, SignatureValidator.ASIC_SBD_FILENAME, MimeType.XML); - InMemoryDocument td = new InMemoryDocument(delegation, SignatureValidator.ASIC_TRANSFER_DELEGATION_FILENAME, MimeType.XML); - DSSDocument asicContainer = createAsicContainer(Arrays.asList(sbd, td), signatureParameters, signatureToken, signaturePrivateKey); - addASiCContainerToDocument(doc, asicContainer); - - Message message = createTestInvoiceMessage(toByteArray(doc)); - SBDMessageContext msgCtx = new SBDMessageContext(false, Direction.OUT); - ValidationResult result = signatureValidator.validate(message, msgCtx); - MatcherAssert.assertThat(result, CoreMatchers.notNullValue()); - MatcherAssert.assertThat(result.getErrors(), CoreMatchers.notNullValue()); - MatcherAssert.assertThat(result.getErrors().size(), CoreMatchers.equalTo(1)); - MatcherAssert.assertThat(result.getErrors().get(0).getMessage(), CoreMatchers.containsString(ErrorCodes.SENDER_EDELIVERY_TRANSFER_DELEGATION_INVALID_SENDER_SCHEME.formatErrorMessage("foo", "DK:CVR", 0))); - MatcherAssert.assertThat(result.getErrors().get(0).getLineReference(), CoreMatchers.containsString(SignatureValidatorImpl.EDELIVERY_SIGNATURE_XPATH)); - - msgCtx.setDirection(Direction.IN); - result = signatureValidator.validate(message, msgCtx); - MatcherAssert.assertThat(result, CoreMatchers.notNullValue()); - MatcherAssert.assertThat(result.getErrors(), CoreMatchers.notNullValue()); - MatcherAssert.assertThat(result.getErrors().size(), CoreMatchers.equalTo(1)); - MatcherAssert.assertThat(result.getErrors().get(0).getMessage(), CoreMatchers.containsString(ErrorCodes.RECEIVER_EDELIVERY_TRANSFER_DELEGATION_INVALID_SENDER_SCHEME.formatErrorMessage("foo", "DK:CVR", 0))); - MatcherAssert.assertThat(result.getErrors().get(0).getLineReference(), CoreMatchers.containsString(SignatureValidatorImpl.EDELIVERY_SIGNATURE_XPATH)); - } + public void testRejectsInvalidSignatureIfPresentEvenIfSignatureIsOptional() throws Exception { + byte[] payload = loadAsByteArray("ubl-examples/OIOUBL_Invoice_v2p2.xml"); + Document doc = wrapInSBDAsDocument(payload); - @Test - public void testValidationFailsForTransferDelegationIfSenderCVRDoesNotMatchCVRFromSigningCertificate() throws Exception { - Document doc = loadAndWrapInSBD("ubl-examples/OIOUBL_Invoice_v2p2.xml"); - byte[] xml = toByteArray(doc); - byte[] delegation = createTransferDelegation("12345678", cvr_C2).getBytes(StandardCharsets.UTF_8); // assume C2 and C3 has same cvr + InMemoryDocument unsigned = new InMemoryDocument(DocumentUtil.canonicalize(payload), SignatureValidator.XAdES_SIGNATURE_PAYLOAD_NAME, MimeTypeEnum.XML); - InMemoryDocument sbd = new InMemoryDocument(xml, SignatureValidator.ASIC_SBD_FILENAME, MimeType.XML); - InMemoryDocument td = new InMemoryDocument(delegation, SignatureValidator.ASIC_TRANSFER_DELEGATION_FILENAME, MimeType.XML); - DSSDocument asicContainer = createAsicContainer(Arrays.asList(sbd, td), signatureParameters, signatureToken, signaturePrivateKey); - addASiCContainerToDocument(doc, asicContainer); + ToBeSigned dataToSign = service.getDataToSign(unsigned, signatureParameters); + SignatureValue signatureValue = signatureToken.sign(dataToSign, signatureParameters.getDigestAlgorithm(), signaturePrivateKey); + signatureValue.setValue("foo".getBytes(StandardCharsets.UTF_8)); // invalid signature value + DSSDocument signature = service.signDocument(unsigned, signatureParameters, signatureValue); + addSignatureToSBD(doc, signature, EDeliverySpecification.NEMHANDEL_EDELIVERY_1_2); Message message = createTestInvoiceMessage(toByteArray(doc)); - SBDMessageContext msgCtx = new SBDMessageContext(false, Direction.OUT); + SBDMessageContext msgCtx = new SBDMessageContext(false, Direction.OUT, false); ValidationResult result = signatureValidator.validate(message, msgCtx); - MatcherAssert.assertThat(result, CoreMatchers.notNullValue()); - MatcherAssert.assertThat(result.getErrors(), CoreMatchers.notNullValue()); - MatcherAssert.assertThat(result.getErrors().size(), CoreMatchers.equalTo(1)); - MatcherAssert.assertThat(result.getErrors().get(0).getMessage(), CoreMatchers.containsString(ErrorCodes.SENDER_EDELIVERY_TRANSFER_DELEGATION_INVALID_SENDER_CVR.formatErrorMessage("12345678", cvr_C2, 0))); - MatcherAssert.assertThat(result.getErrors().get(0).getLineReference(), CoreMatchers.containsString(SignatureValidatorImpl.EDELIVERY_SIGNATURE_XPATH)); - - msgCtx.setDirection(Direction.IN); - result = signatureValidator.validate(message, msgCtx); - MatcherAssert.assertThat(result, CoreMatchers.notNullValue()); - MatcherAssert.assertThat(result.getErrors(), CoreMatchers.notNullValue()); - MatcherAssert.assertThat(result.getErrors().size(), CoreMatchers.equalTo(1)); - MatcherAssert.assertThat(result.getErrors().get(0).getMessage(), CoreMatchers.containsString(ErrorCodes.RECEIVER_EDELIVERY_TRANSFER_DELEGATION_INVALID_SENDER_CVR.formatErrorMessage("12345678", cvr_C2, 0))); - MatcherAssert.assertThat(result.getErrors().get(0).getLineReference(), CoreMatchers.containsString(SignatureValidatorImpl.EDELIVERY_SIGNATURE_XPATH)); + assertValidationResultContainsError(result, + ErrorCodes.SENDER_EDELIVERY_SIGNATURE_INVALID.getErrorCode(), + ErrorCodes.SENDER_EDELIVERY_SIGNATURE_INVALID.formatErrorMessage("BBB_CV_ISI_ANS: The signature is not intact!"), + SignatureValidatorImpl.EDELIVERY_SIGNATURE_XPATH); } @Test - public void testValidationFailsIfTransferDelegationSenderIsNotSpecified() throws Exception { - Document doc = loadAndWrapInSBD("ubl-examples/OIOUBL_Invoice_v2p2.xml"); - byte[] xml = toByteArray(doc); - String delegationString = createValidTransferDelegation(); - byte[] delegation = delegationString.replaceAll("(.*)", "").getBytes(StandardCharsets.UTF_8); + public void testExpectsDocumentToBeCanonicalizedBeforeSigning() throws Exception { + byte[] payload = loadAsByteArray("ubl-examples/OIOUBL_Invoice_v2p2.xml"); + Document doc = wrapInSBDAsDocument(payload); - InMemoryDocument sbd = new InMemoryDocument(xml, SignatureValidator.ASIC_SBD_FILENAME, MimeType.XML); - InMemoryDocument td = new InMemoryDocument(delegation, SignatureValidator.ASIC_TRANSFER_DELEGATION_FILENAME, MimeType.XML); - DSSDocument asicContainer = createAsicContainer(Arrays.asList(sbd, td), signatureParameters, signatureToken, signaturePrivateKey); - addASiCContainerToDocument(doc, asicContainer); + // dont canonicalize document before signing + InMemoryDocument unsigned = new InMemoryDocument(payload, SignatureValidator.XAdES_SIGNATURE_PAYLOAD_NAME, MimeTypeEnum.XML); + DSSDocument signature = createXAdESSignature(unsigned, signatureParameters, signatureToken, signaturePrivateKey); + addSignatureToSBD(doc, signature, EDeliverySpecification.NEMHANDEL_EDELIVERY_1_2); Message message = createTestInvoiceMessage(toByteArray(doc)); SBDMessageContext msgCtx = new SBDMessageContext(false, Direction.OUT); ValidationResult result = signatureValidator.validate(message, msgCtx); - MatcherAssert.assertThat(result, CoreMatchers.notNullValue()); - MatcherAssert.assertThat(result.getErrors(), CoreMatchers.notNullValue()); - MatcherAssert.assertThat(result.getErrors().size(), CoreMatchers.equalTo(1)); - MatcherAssert.assertThat(result.getErrors().get(0).getMessage(), CoreMatchers.containsString(ErrorCodes.SENDER_EDELIVERY_TRANSFER_DELEGATION_MISSING_SENDER_CVR.formatErrorMessage( 0))); - MatcherAssert.assertThat(result.getErrors().get(0).getLineReference(), CoreMatchers.containsString(SignatureValidatorImpl.EDELIVERY_SIGNATURE_XPATH)); + assertValidationResultContainsError(result, + ErrorCodes.SENDER_EDELIVERY_SIGNATURE_INVALID.getErrorCode(), // signature validation will fail because it will "compare" against a canonicalized value + ErrorCodes.SENDER_EDELIVERY_SIGNATURE_INVALID.formatErrorMessage("BBB_CV_IRDOI_ANS: The reference data object is not intact!"), + SignatureValidatorImpl.EDELIVERY_SIGNATURE_XPATH); msgCtx.setDirection(Direction.IN); result = signatureValidator.validate(message, msgCtx); - MatcherAssert.assertThat(result, CoreMatchers.notNullValue()); - MatcherAssert.assertThat(result.getErrors(), CoreMatchers.notNullValue()); - MatcherAssert.assertThat(result.getErrors().size(), CoreMatchers.equalTo(1)); - MatcherAssert.assertThat(result.getErrors().get(0).getMessage(), CoreMatchers.containsString(ErrorCodes.RECEIVER_EDELIVERY_TRANSFER_DELEGATION_MISSING_SENDER_CVR.formatErrorMessage( 0))); - MatcherAssert.assertThat(result.getErrors().get(0).getLineReference(), CoreMatchers.containsString(SignatureValidatorImpl.EDELIVERY_SIGNATURE_XPATH)); + assertValidationResultContainsError(result, + ErrorCodes.RECEIVER_EDELIVERY_SIGNATURE_INVALID.getErrorCode(), + ErrorCodes.RECEIVER_EDELIVERY_SIGNATURE_INVALID.formatErrorMessage("BBB_CV_IRDOI_ANS: The reference data object is not intact!"), + SignatureValidatorImpl.EDELIVERY_SIGNATURE_XPATH); } @Test - public void testValidationFailsIfTransferDelegationReceiverIsNotSpecified() throws Exception { - Document doc = loadAndWrapInSBD("ubl-examples/OIOUBL_Invoice_v2p2.xml"); - byte[] xml = toByteArray(doc); - String delegationString = createValidTransferDelegation(); - byte[] delegation = delegationString.replaceAll("(.*)", "").getBytes(StandardCharsets.UTF_8); - - InMemoryDocument sbd = new InMemoryDocument(xml, SignatureValidator.ASIC_SBD_FILENAME, MimeType.XML); - InMemoryDocument td = new InMemoryDocument(delegation, SignatureValidator.ASIC_TRANSFER_DELEGATION_FILENAME, MimeType.XML); - DSSDocument asicContainer = createAsicContainer(Arrays.asList(sbd, td), signatureParameters, signatureToken, signaturePrivateKey); - addASiCContainerToDocument(doc, asicContainer); - - Message message = createTestInvoiceMessage(toByteArray(doc)); - SBDMessageContext msgCtx = new SBDMessageContext(false, Direction.OUT); - ValidationResult result = signatureValidator.validate(message, msgCtx); - MatcherAssert.assertThat(result, CoreMatchers.notNullValue()); - MatcherAssert.assertThat(result.getErrors(), CoreMatchers.notNullValue()); - MatcherAssert.assertThat(result.getErrors().size(), CoreMatchers.equalTo(1)); - MatcherAssert.assertThat(result.getErrors().get(0).getMessage(), CoreMatchers.containsString(ErrorCodes.SENDER_EDELIVERY_TRANSFER_DELEGATION_MISSING_RECEIVER_CVR.formatErrorMessage( 0))); - MatcherAssert.assertThat(result.getErrors().get(0).getLineReference(), CoreMatchers.containsString(SignatureValidatorImpl.EDELIVERY_SIGNATURE_XPATH)); - - msgCtx.setDirection(Direction.IN); - result = signatureValidator.validate(message, msgCtx); - MatcherAssert.assertThat(result, CoreMatchers.notNullValue()); - MatcherAssert.assertThat(result.getErrors(), CoreMatchers.notNullValue()); - MatcherAssert.assertThat(result.getErrors().size(), CoreMatchers.equalTo(1)); - MatcherAssert.assertThat(result.getErrors().get(0).getMessage(), CoreMatchers.containsString(ErrorCodes.RECEIVER_EDELIVERY_TRANSFER_DELEGATION_MISSING_RECEIVER_CVR.formatErrorMessage( 0))); - MatcherAssert.assertThat(result.getErrors().get(0).getLineReference(), CoreMatchers.containsString(SignatureValidatorImpl.EDELIVERY_SIGNATURE_XPATH)); + public void testRejectsSignatureIfBusinessScopeDoesNotContainSpecificationVersion() throws Exception { + testRejectsSignatureForInvalidSpecificationVersionInBusinessScope(null); } - /** - * Note: This test makes sure the receiver cvr matches the signing certificate from the previous recursion level: - * Example: C1 signs SBD with transfer-delegation C1 -> C2. C2 makes transfer-delegation C2-C3 and resigns document which gives the following (simplified) nested structure: - * - C2 SBD - * - ASiC-E container (C2's certificate) - * - transfer-delegation.xml: C2 -> C3 - * - original-standard-business-document.xml (C1's SBD) - * - ASiC-E container (C1's certificate) - * - transfer-delegation.xml: C1 -> C2 - * - ... - * Must verify that C1's transfer-delegation receiver CVR matches CVR in the certificate from the ASiC-E container "above" - i.e. C2's ASiC-E container - */ @Test - public void testRejectsRecursivelyNestedAsicContainersIfTransferDelegationReceiverCVRDoesNotMatchCVRFromSigningCertificateForPreviousRecursionLevel() throws Exception { - String wrongReceiverCVR = "99887766"; - Document doc = loadAndWrapInSBD("ubl-examples/OIOUBL_Invoice_v2p2.xml"); - byte[] xml = toByteArray(doc); - byte[] delegation = createTransferDelegation(cvr_C1, wrongReceiverCVR).getBytes(StandardCharsets.UTF_8); // wrong receiver CVR - - - InMemoryDocument sbd = new InMemoryDocument(xml, SignatureValidator.ASIC_SBD_FILENAME, MimeType.XML); - InMemoryDocument td = new InMemoryDocument(delegation, SignatureValidator.ASIC_TRANSFER_DELEGATION_FILENAME, MimeType.XML); - DSSDocument asicContainer = createAsicContainer(Arrays.asList(sbd, td), innerSignatureParameters, innerSignatureKeystoreWrapper, innerSignaturePrivateKey); - addASiCContainerToDocument(doc, asicContainer); - - byte[] document1 = toByteArray(doc); + public void testRejectsSignatureIfBusinessScopeDoesContainUnsupportedSpecificationVersion() throws Exception { + testRejectsSignatureForInvalidSpecificationVersionInBusinessScope("foo"); + } - // Cx translates the document and resigns - byte[] translatedXml = new String(xml, StandardCharsets.UTF_8).replace("A00095678", "" + UUID.randomUUID().toString() + "").getBytes(StandardCharsets.UTF_8); - byte[] delegation2 = createTransferDelegation(cvr_C2, cvr_C2).getBytes(StandardCharsets.UTF_8); // we assume here that C2 and C3 have the same CVR so we can settle for using two keystores - doc = parseDocument(translatedXml); + private void testRejectsSignatureForInvalidSpecificationVersionInBusinessScope(String spec) throws Exception { + byte[] payload = loadAsByteArray("ubl-examples/OIOUBL_Invoice_v2p2.xml"); + Document doc = wrapInSBDAsDocument(payload); - sbd = new InMemoryDocument(translatedXml, SignatureValidator.ASIC_SBD_FILENAME, MimeType.XML); - DSSDocument original = new InMemoryDocument(document1, SignatureValidator.ASIC_ORIGINAL_SBD_FILENAME, MimeType.XML); - td = new InMemoryDocument(delegation2, SignatureValidator.ASIC_TRANSFER_DELEGATION_FILENAME, MimeType.XML); - DSSDocument asicContainer2 = createAsicContainer(Arrays.asList(sbd, original, td), signatureParameters, signatureToken, signaturePrivateKey); - addASiCContainerToDocument(doc, asicContainer2); + InMemoryDocument unsigned = new InMemoryDocument(DocumentUtil.canonicalize(payload), SignatureValidator.XAdES_SIGNATURE_PAYLOAD_NAME, MimeTypeEnum.XML); + DSSDocument signature = createXAdESSignature(unsigned, signatureParameters, signatureToken, signaturePrivateKey); + addSignatureToSBD(doc, signature, spec); Message message = createTestInvoiceMessage(toByteArray(doc)); SBDMessageContext msgCtx = new SBDMessageContext(false, Direction.OUT); ValidationResult result = signatureValidator.validate(message, msgCtx); - MatcherAssert.assertThat(result, CoreMatchers.notNullValue()); - MatcherAssert.assertThat(result.getErrors(), CoreMatchers.notNullValue()); - MatcherAssert.assertThat(result.getErrors().size(), CoreMatchers.equalTo(1)); - MatcherAssert.assertThat(result.getErrors().get(0).getMessage(), CoreMatchers.containsString(ErrorCodes.SENDER_EDELIVERY_TRANSFER_DELEGATION_INVALID_RECEIVER_CVR.formatErrorMessage(wrongReceiverCVR, cvr_C2, 1))); - MatcherAssert.assertThat(result.getErrors().get(0).getLineReference(), CoreMatchers.containsString(SignatureValidatorImpl.EDELIVERY_SIGNATURE_XPATH)); + List specList = Arrays.asList(EDeliverySpecification.NEMHANDEL_EDELIVERY_1_2.value()); + assertValidationResultContainsError(result, + ErrorCodes.SENDER_EDELIVERY_SIGNATURE_INVALID_SPECIFICATION_VERSION.getErrorCode(), + ErrorCodes.SENDER_EDELIVERY_SIGNATURE_INVALID_SPECIFICATION_VERSION.formatErrorMessage(specList), + SignatureValidatorImpl.EDELIVERY_SIGNATURE_SPEC_VERSION_XPATH); msgCtx.setDirection(Direction.IN); result = signatureValidator.validate(message, msgCtx); - MatcherAssert.assertThat(result, CoreMatchers.notNullValue()); - MatcherAssert.assertThat(result.getErrors(), CoreMatchers.notNullValue()); - MatcherAssert.assertThat(result.getErrors().size(), CoreMatchers.equalTo(1)); - MatcherAssert.assertThat(result.getErrors().get(0).getMessage(), CoreMatchers.containsString(ErrorCodes.RECEIVER_EDELIVERY_TRANSFER_DELEGATION_INVALID_RECEIVER_CVR.formatErrorMessage(wrongReceiverCVR, cvr_C2, 1))); - MatcherAssert.assertThat(result.getErrors().get(0).getLineReference(), CoreMatchers.containsString(SignatureValidatorImpl.EDELIVERY_SIGNATURE_XPATH)); + assertValidationResultContainsError(result, + ErrorCodes.RECEIVER_EDELIVERY_SIGNATURE_INVALID_SPECIFICATION_VERSION.getErrorCode(), + ErrorCodes.RECEIVER_EDELIVERY_SIGNATURE_INVALID_SPECIFICATION_VERSION.formatErrorMessage(specList), + SignatureValidatorImpl.EDELIVERY_SIGNATURE_SPEC_VERSION_XPATH); } - @Test - public void testValidationFailsIfOriginalStandardBusinessDocumentDoesNotContainASiCContainer() throws Exception { - Document doc = loadAndWrapInSBD("ubl-examples/OIOUBL_Invoice_v2p2.xml"); - byte[] xml = toByteArray(doc); - // no asic container in nested SBD - - // Cx translates the document and resigns - byte[] translatedXml = new String(xml, StandardCharsets.UTF_8).replace("A00095678", "" + UUID.randomUUID().toString() + "").getBytes(StandardCharsets.UTF_8); - byte[] delegation2 = createTransferDelegation(cvr_C2, cvr_C2).getBytes(StandardCharsets.UTF_8); // we assume here that C2 and C3 have the same CVR so we can settle for using two keystores - doc = parseDocument(translatedXml); - - DSSDocument sbd = new InMemoryDocument(translatedXml, SignatureValidator.ASIC_SBD_FILENAME, MimeType.XML); - DSSDocument original = new InMemoryDocument(xml, SignatureValidator.ASIC_ORIGINAL_SBD_FILENAME, MimeType.XML); - DSSDocument td = new InMemoryDocument(delegation2, SignatureValidator.ASIC_TRANSFER_DELEGATION_FILENAME, MimeType.XML); - DSSDocument asicContainer2 = createAsicContainer(Arrays.asList(sbd, original, td), signatureParameters, signatureToken, signaturePrivateKey); - addASiCContainerToDocument(doc, asicContainer2); - - Message message = createTestInvoiceMessage(toByteArray(doc)); - SBDMessageContext msgCtx = new SBDMessageContext(false, Direction.OUT); - ValidationResult result = signatureValidator.validate(message, msgCtx); - MatcherAssert.assertThat(result, CoreMatchers.notNullValue()); - MatcherAssert.assertThat(result.getErrors(), CoreMatchers.notNullValue()); - MatcherAssert.assertThat(result.getErrors().size(), CoreMatchers.equalTo(1)); - MatcherAssert.assertThat(result.getErrors().get(0).getMessage(), CoreMatchers.containsString(ErrorCodes.SENDER_EDELIVERY_SIGNATURE_MISSING.formatErrorMessage(1))); - MatcherAssert.assertThat(result.getErrors().get(0).getLineReference(), CoreMatchers.containsString(SignatureValidatorImpl.EDELIVERY_SIGNATURE_XPATH)); - - msgCtx.setDirection(Direction.IN); - result = signatureValidator.validate(message, msgCtx); + private void assertValidationResultContainsError(ValidationResult result, String errorCode, String errorMessage, String lineReference) { MatcherAssert.assertThat(result, CoreMatchers.notNullValue()); MatcherAssert.assertThat(result.getErrors(), CoreMatchers.notNullValue()); - MatcherAssert.assertThat(result.getErrors().size(), CoreMatchers.equalTo(1)); - MatcherAssert.assertThat(result.getErrors().get(0).getMessage(), CoreMatchers.containsString(ErrorCodes.RECEIVER_EDELIVERY_SIGNATURE_MISSING.formatErrorMessage(1))); - MatcherAssert.assertThat(result.getErrors().get(0).getLineReference(), CoreMatchers.containsString(SignatureValidatorImpl.EDELIVERY_SIGNATURE_XPATH)); - } - @Test - public void testAcceptsUnsignedDocumentIfDocumentSignatureIsOptional() throws Exception { - Message message = createTestInvoiceMessage(loadAndWrapInSBDAsByteArray("ubl-examples/OIOUBL_Invoice_v2p2.xml")); - SBDMessageContext msgCtx = new SBDMessageContext(false, Direction.OUT, false); - ValidationResult result = signatureValidator.validate(message, msgCtx); - MatcherAssert.assertThat(result, CoreMatchers.notNullValue()); - MatcherAssert.assertThat(result.getErrors(), CoreMatchers.notNullValue()); - MatcherAssert.assertThat(result.getErrors().size(), CoreMatchers.equalTo(0)); + ValidationMessage msg = result.getErrors().stream() + .filter(m -> errorCode.equals(m.getCode())) + .findFirst() + .orElse(null); + MatcherAssert.assertThat("should contain error code " + errorCode, msg, CoreMatchers.notNullValue()); + MatcherAssert.assertThat(msg.getMessage(), CoreMatchers.equalTo(errorMessage)); + MatcherAssert.assertThat(msg.getLineReference(), CoreMatchers.equalTo(lineReference)); } - @Test - public void testRejectsInvalidSignatureIfPresentEvenIfSignatureIsOptional() throws Exception { - Document doc = loadAndWrapInSBD("ubl-examples/OIOUBL_Invoice_v2p2.xml"); - byte[] xml = toByteArray(doc); - byte[] delegation = createValidTransferDelegation().getBytes(StandardCharsets.UTF_8); - - InMemoryDocument sbd = new InMemoryDocument(xml, SignatureValidator.ASIC_SBD_FILENAME, MimeType.XML); - InMemoryDocument td = new InMemoryDocument(delegation, SignatureValidator.ASIC_TRANSFER_DELEGATION_FILENAME, MimeType.XML); - - List documentsToSign = Arrays.asList(sbd, td); - ToBeSigned dataToSign = service.getDataToSign(documentsToSign, signatureParameters); - SignatureValue signatureValue = signatureToken.sign(dataToSign, signatureParameters.getDigestAlgorithm(), signaturePrivateKey); - signatureValue.setValue("foo".getBytes(StandardCharsets.UTF_8)); // invalid signature value - DSSDocument asicContainer = service.signDocument(documentsToSign, signatureParameters, signatureValue); - addASiCContainerToDocument(doc, asicContainer); - - Message message = createTestInvoiceMessage(toByteArray(doc)); - SBDMessageContext msgCtx = new SBDMessageContext(false, Direction.OUT, false); - ValidationResult result = signatureValidator.validate(message, msgCtx); - MatcherAssert.assertThat(result, CoreMatchers.notNullValue()); - MatcherAssert.assertThat(result.getErrors(), CoreMatchers.notNullValue()); - MatcherAssert.assertThat(result.getErrors().size(), CoreMatchers.equalTo(1)); - MatcherAssert.assertThat(result.getErrors().get(0).getMessage(), CoreMatchers.containsString(ErrorCodes.SENDER_EDELIVERY_SIGNATURE_INVALID.getErrorCode())); - MatcherAssert.assertThat(result.getErrors().get(0).getLineReference(), CoreMatchers.containsString(SignatureValidatorImpl.EDELIVERY_SIGNATURE_XPATH)); + private void addSignatureToSBD(Document doc, DSSDocument signature, EDeliverySpecification spec) throws IOException, XPathExpressionException { + addSignatureToSBD(doc, signature, spec != null ? spec.value() : null); } - - private void addASiCContainerToDocument(Document doc, DSSDocument asicContainer) throws IOException, XPathExpressionException { + private void addSignatureToSBD(Document doc, DSSDocument signature, String spec) throws IOException, XPathExpressionException { try(ByteArrayOutputStream bos = new ByteArrayOutputStream()) { - asicContainer.writeTo(bos); - appendNemhandelSignatureScopeNode(doc, bos.toByteArray()); + signature.writeTo(bos); + appendNemhandelSignatureScopeNode(doc, bos.toByteArray(), spec); } } - private DSSDocument createAsicContainer(List documentsToSign, ASiCWithXAdESSignatureParameters signatureParameters, KeystoreSignatureTokenConnectionWrapper token, KSPrivateKeyEntry signaturePrivateKey) throws IOException { - ToBeSigned dataToSign = service.getDataToSign(documentsToSign, signatureParameters); - SignatureValue signatureValue = token.sign(dataToSign, signatureParameters.getDigestAlgorithm(), signaturePrivateKey); - DSSDocument signedDocument = service.signDocument(documentsToSign, signatureParameters, signatureValue); + private DSSDocument createXAdESSignature(DSSDocument unsigned, XAdESSignatureParameters signatureParameters, KeystoreSignatureTokenConnectionWrapper signatureToken, KSPrivateKeyEntry signaturePrivateKey) { + ToBeSigned dataToSign = service.getDataToSign(unsigned, signatureParameters); + SignatureValue signatureValue = signatureToken.sign(dataToSign, signatureParameters.getDigestAlgorithm(), signaturePrivateKey); + DSSDocument signedDocument = service.signDocument(unsigned, signatureParameters, signatureValue); return signedDocument; } - private String createValidTransferDelegation() { - return createTransferDelegation(cvr_C2, cvr_C2); // assume C2 and C3 are same oxalis AP for testing purposes so we need fewer keystores - } - - private String createTransferDelegation(String fromCvr, String toCvr) { - String xml = "\n" + - "\n" + - " " + fromCvr + "\n" + - " " + toCvr + "\n" + - ""; - return xml; - } private Message createTestInvoiceMessage(byte[] data) { Account account = new Account("test-user", "test", "DK"); diff --git a/src/test/java/dk/erst/oxalis/as4/validation/signature/TransferDelegationTest.java b/src/test/java/dk/erst/oxalis/as4/validation/signature/TransferDelegationTest.java deleted file mode 100644 index e47a2aa9b3d361ea00b5af0ea1b78172dceae4c0..0000000000000000000000000000000000000000 --- a/src/test/java/dk/erst/oxalis/as4/validation/signature/TransferDelegationTest.java +++ /dev/null @@ -1,63 +0,0 @@ -package dk.erst.oxalis.as4.validation.signature; -import dk.erst.oxalis.as4.error.ErrorCodes; -import dk.erst.oxalis.as4.signature.TransferDelegationException; -import dk.erst.oxalis.as4.signature.TransferDelegationFactoryImpl; -import org.testng.Assert; -import org.testng.annotations.Test; - -import javax.xml.bind.JAXBException; -import java.nio.charset.StandardCharsets; - -import static org.junit.Assert.assertThrows; - -public class TransferDelegationTest { - @Test - public void testTransferDelegationContainsCorrectOutput() throws JAXBException, TransferDelegationException { - String senderCVR = "12345678"; - String receiverCVR = "99999999"; - TransferDelegationFactoryImpl tdf = new TransferDelegationFactoryImpl(); - String transferDelegationDocument = new String(tdf.createTransferDelegation(senderCVR, receiverCVR), StandardCharsets.UTF_8); - - String expectedTransferDelegation = "\n" + - "\n" + - " " + senderCVR + "\n" + - " " + receiverCVR + "\n" + - "\n"; - - Assert.assertEquals(transferDelegationDocument, expectedTransferDelegation); - } - - @Test - public void testTransferDelegationEmptyOrNullSenderCvr() throws JAXBException { - String senderCVR = null; - String receiverCVR = "99999999"; - TransferDelegationFactoryImpl tdf = new TransferDelegationFactoryImpl(); - - try { - Assert.assertThrows(TransferDelegationException.class, () -> { - tdf.createTransferDelegation(senderCVR, receiverCVR); - }); - tdf.createTransferDelegation(senderCVR, receiverCVR); - Assert.fail("Should not get here"); - } catch(TransferDelegationException ex) { - Assert.assertEquals(ex.getMessage(), ErrorCodes.SIGNING_EDELIVERY_TRANSFER_DELEGATION_EMPTY_OR_NULL_SENDER_CVR.formatErrorMessage()); - } - } - - @Test - public void testTransferDelegationEmptyOrNullReceiverCvr() throws JAXBException { - String senderCVR = "12345678"; - String receiverCVR = ""; - TransferDelegationFactoryImpl tdf = new TransferDelegationFactoryImpl(); - - try { - Assert.assertThrows(TransferDelegationException.class, () -> { - tdf.createTransferDelegation(senderCVR, receiverCVR); - }); - tdf.createTransferDelegation(senderCVR, receiverCVR); - Assert.fail("Should not get here"); - } catch(TransferDelegationException ex) { - Assert.assertEquals(ex.getMessage(), ErrorCodes.SIGNING_EDELIVERY_TRANSFER_DELEGATION_EMPTY_OR_NULL_RECEIVER_CVR.formatErrorMessage()); - } - } -} diff --git a/src/test/java/dk/erst/oxalis/as4/validation/version/VersionValidatorTest.java b/src/test/java/dk/erst/oxalis/as4/validation/version/VersionValidatorTest.java index 5cb94bef7c07fada60ced042148fe459f88cc8e9..c34c20ba86c9c99304956fb461e772be83847a4a 100644 --- a/src/test/java/dk/erst/oxalis/as4/validation/version/VersionValidatorTest.java +++ b/src/test/java/dk/erst/oxalis/as4/validation/version/VersionValidatorTest.java @@ -21,6 +21,7 @@ import network.oxalis.commons.config.ConfigModule; import network.oxalis.commons.filesystem.FileSystemModule; import network.oxalis.vefa.peppol.lookup.LookupClient; import org.joda.time.DateTime; +import org.joda.time.DateTimeZone; import org.joda.time.LocalDate; import org.junit.After; import org.junit.Before; @@ -36,6 +37,7 @@ import org.junit.Test; import java.nio.charset.StandardCharsets; import java.util.List; +import java.util.TimeZone; import java.util.UUID; import static org.hamcrest.CoreMatchers.containsString; @@ -50,7 +52,7 @@ import static org.testng.Assert.assertTrue; @PrepareForTest(OxalisAS4Version.class) public class VersionValidatorTest extends AbstractXmlTest { private Injector injector; - private VersionValidator versionValidator; + private VersionValidatorImpl versionValidator; private final DateTime YEAR_2000 = convertToStartOfDayDate(new LocalDate(2000, 1, 1)); private final DateTime YEAR_3000 = convertToStartOfDayDate(new LocalDate(3000, 1, 1)); @@ -419,6 +421,75 @@ public class VersionValidatorTest extends AbstractXmlTest { } + @Test + public void testGetSoonOutOfDateTextUTCtoCPHWinterTime() { + String text= versionValidator.getSoonOutOfDateText(new DateTime(2023, 12, 9, 13, 0, 0, DateTimeZone.forTimeZone(TimeZone.getTimeZone("UTC"))), + "test"); + assertEquals(text, + "\n" + + "###############################################################################\n" + + "# test is soon to be out of date! #\n" + + "###############################################################################\n" + + "# #\n" + + "# On Saturday, Dec 09, 2023 14:00:00 this test #\n" + + "# will be obsolete! #\n" + + "# Please make sure you have upgraded before then #\n" + + "# as by then it can no longer serve to receive or send documents. #\n" + + "# #\n" + + "# Find the newest version of test: #\n" + + "# #\n" + + "# https://rep.erst.dk/git/openebusiness/nemhandeledelivery/oxalis #\n" + + "# https://rep.erst.dk/git/openebusiness/nemhandeledelivery/oxalis-as4 #\n" + + "# #\n" + + "###############################################################################"); + } + + @Test + public void testGetSoonOutOfDateTextUTCtoCPHSummerTime() { + String text= versionValidator.getSoonOutOfDateText(new DateTime(2023, 6, 9, 13, 0, 0, DateTimeZone.forTimeZone(TimeZone.getTimeZone("UTC"))), + "test"); + assertEquals(text, + "\n" + + "###############################################################################\n" + + "# test is soon to be out of date! #\n" + + "###############################################################################\n" + + "# #\n" + + "# On Friday, Jun 09, 2023 15:00:00 this test #\n" + + "# will be obsolete! #\n" + + "# Please make sure you have upgraded before then #\n" + + "# as by then it can no longer serve to receive or send documents. #\n" + + "# #\n" + + "# Find the newest version of test: #\n" + + "# #\n" + + "# https://rep.erst.dk/git/openebusiness/nemhandeledelivery/oxalis #\n" + + "# https://rep.erst.dk/git/openebusiness/nemhandeledelivery/oxalis-as4 #\n" + + "# #\n" + + "###############################################################################"); + } + + @Test + public void testGetSoonOutOfDateTextCPHtoCPHTime() { + String text= versionValidator.getSoonOutOfDateText(new DateTime(2023, 12, 9, 13, 0, 0, DateTimeZone.forTimeZone(TimeZone.getTimeZone("Europe/Copenhagen"))), + "test"); + assertEquals(text, + "\n" + + "###############################################################################\n" + + "# test is soon to be out of date! #\n" + + "###############################################################################\n" + + "# #\n" + + "# On Saturday, Dec 09, 2023 13:00:00 this test #\n" + + "# will be obsolete! #\n" + + "# Please make sure you have upgraded before then #\n" + + "# as by then it can no longer serve to receive or send documents. #\n" + + "# #\n" + + "# Find the newest version of test: #\n" + + "# #\n" + + "# https://rep.erst.dk/git/openebusiness/nemhandeledelivery/oxalis #\n" + + "# https://rep.erst.dk/git/openebusiness/nemhandeledelivery/oxalis-as4 #\n" + + "# #\n" + + "###############################################################################"); + } + private Message createTestInvoiceMessage() { Account account = new Account("test-user", "test", "DK"); diff --git a/src/test/java/dk/erst/oxalis/as4/validation/version/model/EdelComponentVersionListTest.java b/src/test/java/dk/erst/oxalis/as4/validation/version/model/EdelComponentVersionListTest.java index 2a39d4ba01649a69a2c0d8cdcdee087d8b5efe61..88fdf8ec641594e6fa8cf0700437278dff85c22d 100644 --- a/src/test/java/dk/erst/oxalis/as4/validation/version/model/EdelComponentVersionListTest.java +++ b/src/test/java/dk/erst/oxalis/as4/validation/version/model/EdelComponentVersionListTest.java @@ -7,8 +7,6 @@ import org.testng.annotations.Test; import javax.xml.bind.JAXBContext; import javax.xml.bind.Unmarshaller; import java.io.StringReader; -import java.time.ZoneId; -import java.util.Calendar; import java.util.List; import java.util.TimeZone; @@ -17,26 +15,27 @@ import static org.junit.Assert.assertEquals; public class EdelComponentVersionListTest { @Test public void testUnmarshal() throws Exception { + JodaTimeAdapter jodaTimeAdapter = new JodaTimeAdapter(); String xml = " \n" + " \n" + - " 2023-12-24T23:59:59\n" + + " 2023-12-24T23:59:59+01:00\n" + " Foo\n" + " 1\n" + " http://nemhandel.dk/foo\n" + " some text\n" + " true\n" + - " 2023-10-24T12:30:45\n" + + " 2023-10-24T12:30:45+02:00\n" + " 1.0.0\n" + " \n" + " \n" + - " 2023-12-24T23:59:59\n" + + " 2023-12-24T23:59:59+01:00\n" + " Bar\n" + " 2\n" + " http://nemhandel.dk/bar\n" + " some text\n" + " true\n" + - " 2023-10-24T12:30:45\n" + + " 2023-10-24T12:30:45+02:00\n" + " 2.0.0\n" + " \n" + " "; @@ -48,33 +47,30 @@ public class EdelComponentVersionListTest { List componentVersions = componentVersionList.getEdelComponentVersion(); assertEquals(2, componentVersions.size()); - Calendar cal = Calendar.getInstance(); - cal.setTimeZone(TimeZone.getTimeZone(ZoneId.of("UTC"))); - cal.set(Calendar.MILLISECOND, 0); - cal.set(2023, Calendar.DECEMBER, 24, 23, 59, 59); - DateTime dateTimeDecember = new DateTime(cal.getTimeInMillis(), DateTimeZone.UTC); - cal.set(2023, Calendar.OCTOBER, 24, 12, 30, 45); - DateTime dateTimeOctober = new DateTime(cal.getTimeInMillis(), DateTimeZone.UTC); + DateTime dateTimeDecember = new DateTime(2023, 12, 24, 23, 59, 59, DateTimeZone.forTimeZone(TimeZone.getTimeZone("Europe/Copenhagen"))); + + DateTime dateTimeOctober = new DateTime(2023, 10, 24, 12, 30, 45, DateTimeZone.forTimeZone(TimeZone.getTimeZone("Europe/Copenhagen"))); + EdelComponentVersion version1 = componentVersions.get(0); - assertEquals(dateTimeDecember, version1.getBlockedDate()); + assertEquals(jodaTimeAdapter.marshal(dateTimeDecember), jodaTimeAdapter.marshal(version1.getBlockedDate())); assertEquals("Foo", version1.getComponentName()); assertEquals(1L, version1.getId().longValue()); assertEquals("http://nemhandel.dk/foo", version1.getInfoLink()); assertEquals("some text", version1.getInfoText()); assertEquals(true, version1.isMandatory()); - assertEquals(dateTimeOctober, version1.getThresholdDate()); + assertEquals(jodaTimeAdapter.marshal(dateTimeOctober), jodaTimeAdapter.marshal(version1.getThresholdDate())); assertEquals("1.0.0", version1.getVersion()); EdelComponentVersion version2 = componentVersions.get(1); - assertEquals(dateTimeDecember, version2.getBlockedDate()); + assertEquals(jodaTimeAdapter.marshal(dateTimeDecember), jodaTimeAdapter.marshal(version2.getBlockedDate())); assertEquals("Bar", version2.getComponentName()); assertEquals(2L, version2.getId().longValue()); assertEquals("http://nemhandel.dk/bar", version2.getInfoLink()); assertEquals("some text", version2.getInfoText()); assertEquals(true, version2.isMandatory()); - assertEquals(dateTimeOctober, version2.getThresholdDate()); + assertEquals(jodaTimeAdapter.marshal(dateTimeOctober), jodaTimeAdapter.marshal(version2.getThresholdDate())); assertEquals("2.0.0", version2.getVersion()); } } diff --git a/src/test/resources/cef-sbd-with-identifiers.xml b/src/test/resources/cef-sbd-with-identifiers.xml new file mode 100644 index 0000000000000000000000000000000000000000..b9eecfdb5abb10def620704b937591118cc25dc0 --- /dev/null +++ b/src/test/resources/cef-sbd-with-identifiers.xml @@ -0,0 +1,250 @@ + + + + 1.0 + + + + urn:oasis:names:tc:ebcore:partyid-type:unregistered:C1 + + + + + urn:oasis:names:tc:ebcore:partyid-type:unregistered:C4 + + + + + + NONE + 1.0 + 555bcb4c-940b-4694-9b90-d9b0ae1e937b + CEF Connectivity test + 2019-10-30T11:20:05.304+02:00 + + + + + + + DOCUMENTID + + + e-delivery + submitMessage + + + + + + PROCESSID + + e-delivery + + http://ec.europa.eu/edelivery/services/connectivity-service + + + + COUNTRY_C1 + DK + + + + + 2.1 + OIOUBL-2.1 + urn:www.nesubl.eu:profiles:profile5:ver2.0 + A00095678 + false + 555bcb4c-940b-4694-9b90-d9b0ae1e937b + 2005-11-20 + 380 + DKK + 5250124502 + + 5002701 + 9756b468-8815-1029-857a-e388fe63f399 + 2005-11-01 + + + + DK16356706 + + DK16356706 + + + Tavleverandøren HANS + + + StructuredDK + Leverandørvej + 11 + Dyssegård + 2870 + + DK + + + + DK16356706 + + 63 + Moms + + + + Tavleleverandøren + DK16356706 + + + 23456 + Hugo Jensen + 15812337 + Hugo@tavl.dk + + + + + + 5798009811639 + + 5798009811639 + + + Den Lille Skole + + + StructuredDK + Fredericiavej + 10 + Helsingør + 3000 + + DK + + + + 7778 + Hans Hansen + 26532147 + Hans@dls.dk + + + + + 2005-11-15 + + + 1 + 42 + 2005-11-25 + DK:BANK + + 1234567890 + A00095678 + + 1234 + + + + + 1 + 1 + 6312.50 + + + 1262.50 + + 5050.00 + 1262.50 + + StandardRated + 25 + + 63 + Moms + + + + + + 5050.00 + 1262.50 + 6312.50 + 6312.50 + + + 1 + 1.00 + 5000.00 + + 1 + + + 1250.00 + + 5000.00 + 1250.00 + + StandardRated + 25 + + 63 + Moms + + + + + + Hejsetavle + Hejsetavle + + 5712345780121 + + + + 5000.00 + 1 + 1 + + + + 2 + 2.00 + 50.00 + + 2 + + + 12.50 + + 50.00 + 12.50 + + StandardRated + 25 + + 63 + Moms + + + + + + Beslag + Beslag + + 5712345780111 + + + + 25.00 + 1 + 1 + + + + \ No newline at end of file diff --git a/src/test/resources/cef-sbd-without-identifiers.xml b/src/test/resources/cef-sbd-without-identifiers.xml new file mode 100644 index 0000000000000000000000000000000000000000..11bf74e547f310661af7ab03e46cd3300e632c36 --- /dev/null +++ b/src/test/resources/cef-sbd-without-identifiers.xml @@ -0,0 +1,249 @@ + + + + 1.0 + + + + urn:oasis:names:tc:ebcore:partyid-type:unregistered:C1 + + + + + urn:oasis:names:tc:ebcore:partyid-type:unregistered:C4 + + + + + + NONE + 1.0 + 555bcb4c-940b-4694-9b90-d9b0ae1e937b + CEF Connectivity test + 2019-10-30T11:20:05.304+02:00 + + + + + + + DOCUMENTID + + + + submitMessage + + + + + + PROCESSID + + + http://ec.europa.eu/edelivery/services/connectivity-service + + + + COUNTRY_C1 + DK + + + + + 2.1 + OIOUBL-2.1 + urn:www.nesubl.eu:profiles:profile5:ver2.0 + A00095678 + false + 555bcb4c-940b-4694-9b90-d9b0ae1e937b + 2005-11-20 + 380 + DKK + 5250124502 + + 5002701 + 9756b468-8815-1029-857a-e388fe63f399 + 2005-11-01 + + + + DK16356706 + + DK16356706 + + + Tavleverandøren HANS + + + StructuredDK + Leverandørvej + 11 + Dyssegård + 2870 + + DK + + + + DK16356706 + + 63 + Moms + + + + Tavleleverandøren + DK16356706 + + + 23456 + Hugo Jensen + 15812337 + Hugo@tavl.dk + + + + + + 5798009811639 + + 5798009811639 + + + Den Lille Skole + + + StructuredDK + Fredericiavej + 10 + Helsingør + 3000 + + DK + + + + 7778 + Hans Hansen + 26532147 + Hans@dls.dk + + + + + 2005-11-15 + + + 1 + 42 + 2005-11-25 + DK:BANK + + 1234567890 + A00095678 + + 1234 + + + + + 1 + 1 + 6312.50 + + + 1262.50 + + 5050.00 + 1262.50 + + StandardRated + 25 + + 63 + Moms + + + + + + 5050.00 + 1262.50 + 6312.50 + 6312.50 + + + 1 + 1.00 + 5000.00 + + 1 + + + 1250.00 + + 5000.00 + 1250.00 + + StandardRated + 25 + + 63 + Moms + + + + + + Hejsetavle + Hejsetavle + + 5712345780121 + + + + 5000.00 + 1 + 1 + + + + 2 + 2.00 + 50.00 + + 2 + + + 12.50 + + 50.00 + 12.50 + + StandardRated + 25 + + 63 + Moms + + + + + + Beslag + Beslag + + 5712345780111 + + + + 25.00 + 1 + 1 + + + + \ No newline at end of file diff --git a/src/test/resources/dummy-db-changelog.xml b/src/test/resources/dummy-db-changelog.xml new file mode 100644 index 0000000000000000000000000000000000000000..8c3d52b0bab2aaf888e6910910cff0411b46a2d3 --- /dev/null +++ b/src/test/resources/dummy-db-changelog.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + diff --git a/src/test/resources/largefile.txt b/src/test/resources/largefile.txt new file mode 100644 index 0000000000000000000000000000000000000000..78eb3d899336163444cb509785f0ba4f14867e24 --- /dev/null +++ b/src/test/resources/largefile.txt @@ -0,0 +1 @@ +UEsDBAoAAAgAAMVryVaKIflFHwAAAB8AAAAIAAAAbWltZXR5cGVhcHBsaWNhdGlvbi92bmQuZXRzaS5hc2ljLWUremlwUEsDBBQACAgIAMVryVYAAAAAAAAAAAAAAAAeAAAAc3RhbmRhcmQtYnVzaW5lc3MtZG9jdW1lbnQueG1s7NxZlxtHmqDp+/kVftiXc0gxuIjLUapHqaVKJ7ealDK7p27mhMiQKjq56DCCWan5ZXM/f2zAzdxp5u6ALGGoD4Zn5qZLMgnmAPV+BqvC89l//8fzZ8PfL15dXb588ZsbZ7du3xguXjx5+fTyxU+/ufGX77+5+fDGf//8s++uz188PX/19Levry5fXFxdffXyyevnFy+uh80//eLqNzf+4/r658effPKf//mft16/uHhycevlq58+eXLx4/mT609enD+/uPr5/MnF1SdL/5p/vTh/evHqxuf/2zAsvtS7NW+WbBa9+x/++m7bn292/dknH/+ld8u+u3iR/pnN//jt082/5/LHy4tXwxevr//j5avL619+c+Py6uWn9+/cvbnZ6+avPL35+ufLqxuf3z57eO/x2Z279+5/+uDhZ5+M/+j7f/Un03/3Z3/ePPPl3/+Zl7pz5+7de/fu3597qY//7Z99eEc+LHxyfp0eefIGfv761YvHL8+vLq8ev/0IHl/9fPEkLX/8+odnj6+e/MfF8/PH/7h6+vgv15fPNnvc/MPXF2/+5TfvfJY+rvSv/v6Xny8+vMN3bp199sn0L6RHf3G1+eeeXEye49HdR4/un53dvnn/04vbN+89Ovv05qPbd364effujw8v7t3/4eGT2+ebBy//wekLf55v8d3LpzVfvrp4+2Rfbf72Fy+efn/5/OLzO7c37/btOzdvn31/dvfx/UeP7z24de/eg//99tnj25s/M3P/yPv3fO1N/uzDn83vnrycbOCj/+nDpr/605d/+cPXf/z+268+3u78G/VPfmSPi7/23/7bn779019++/ubm4/r8eO3n9nK2/zJDg/0b3/+05dff/fd7s/zJgqbN2uz+VsXrx///Orlj5fPNo/2/v9x//HmD/adN/8B/5P7+vJPf/nj93/+v/7vL8922dhXv9v5BT/7pPi4P9tSsrdr8o/iQyv/yc/4xrt/z+Mn509+5b/ry5fPn7988cVPP726+Gnzr9v8jz+/fLH5d15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 \ No newline at end of file diff --git a/src/test/resources/nemkonto-examples/Kvittering 0.xml b/src/test/resources/nemkonto-examples/Kvittering 0.xml new file mode 100644 index 0000000000000000000000000000000000000000..7bb3a5840d672c311f91b33c3cfceb22f893b363 --- /dev/null +++ b/src/test/resources/nemkonto-examples/Kvittering 0.xml @@ -0,0 +1,29 @@ + + + + + + NKS + 5798000016446 + + + DATLEV + + Bad XML + + NKS KVITTERING0 73 + 2005-08-15T17:13:33 + REF12345 + + + + Authstn: The field length was less than minLen + + + + diff --git a/src/test/resources/nemkonto-examples/Kvittering 1.xml b/src/test/resources/nemkonto-examples/Kvittering 1.xml new file mode 100644 index 0000000000000000000000000000000000000000..b1da82871292a5d909c271e214985cc21c53399c --- /dev/null +++ b/src/test/resources/nemkonto-examples/Kvittering 1.xml @@ -0,0 +1,24 @@ + + + + + + NKS + 5798000016446 + + + DATLEV + + ACPT + + NKS KVITTERING1 73 + 2005-08-26T09:54:56 + TMO-20050826095549 + + + diff --git a/src/test/resources/nemkonto-examples/Payment_GLN_5798009811578.xml b/src/test/resources/nemkonto-examples/Payment_GLN_5798009811578.xml new file mode 100644 index 0000000000000000000000000000000000000000..f8a3ab7e89524d0b8e6641d71d39f4a27576d896 --- /dev/null +++ b/src/test/resources/nemkonto-examples/Payment_GLN_5798009811578.xml @@ -0,0 +1,101 @@ + + + + + + NAVIST + + 5798000011127 + + + NKS + 5798009811578 + + + + 5798000011127~802 + 2005-06-11T09:30:00Z + + + + + 5798009811127~802 + 2005-06-11T09:30:00Z + 1 + 200000 + 2 + true + + + + 25 + ADMID + + + + + + 2005-07-14 + TRF + + + BANK01234 + + + + + 81090001000539 + + + Deb.tekst AFL.D1.B3 + + + + 0027510647 + AFL.D1.B3.UPR01 + + + Kred. tekst ...UPR01 + + + 100000 + + + + + 27510647 + CVR + + + + true + NKSLEV + + + + + 2502414871 + AFL.D1.B3.UPR02 + + + Kred. tekst ...UPR02 + + + 100000 + + + + 2502414871 + + + true + NKSLEV + + + diff --git a/src/test/resources/nemkonto-examples/Reply - CPR-nr - DK konto og ingen kontonummer.XML b/src/test/resources/nemkonto-examples/Reply - CPR-nr - DK konto og ingen kontonummer.XML new file mode 100644 index 0000000000000000000000000000000000000000..8b86c753407e326889f5f66e74b6daf7242fa5c1 --- /dev/null +++ b/src/test/resources/nemkonto-examples/Reply - CPR-nr - DK konto og ingen kontonummer.XML @@ -0,0 +1,99 @@ + + + + 1234567 + system + bundt001 + + + + + 11223344 + + Betalingsreference001 + + 0503914883 + + + 2008-03-03T13:25:46Z + + + 9991 + 0503915530 + + + + + + + 11223344 + + Betalingsreference002 + + 0504854868 + + + 2008-03-03T13:25:46Z + + + 9994 + 0504855349 + + + + + + + 11223344 + + Betalingsreference003 + + 0712614455 + + + 2008-03-03T13:25:46Z + + + 2137 + 5005988889 + + + + + + + 11223344 + + Betalingsreference004 + + 1312814362 + + + 2008-03-03T13:25:46Z + + 2100 + Ingen NemKonto fundet + + + + + + 11223344 + + Betalingsreference005 + + 0706614818 + + + 2008-03-03T13:25:46Z + + 2100 + Ingen NemKonto fundet + + + diff --git a/src/test/resources/nemkonto-examples/Request - CVR-nr SE-nr P-nr.xml b/src/test/resources/nemkonto-examples/Request - CVR-nr SE-nr P-nr.xml new file mode 100644 index 0000000000000000000000000000000000000000..c043a70bb4fc3a1bd161839fca23eb34b263a82d --- /dev/null +++ b/src/test/resources/nemkonto-examples/Request - CVR-nr SE-nr P-nr.xml @@ -0,0 +1,74 @@ + + + + 1234567 + system + Bundt003 + + + + 11223344 + + Betalingsreference009 + + 82002049 + + + + + 11223344 + + Betalingsreference010 + + 15700211 + + + + + 11223344 + + Betalingsreference011 + + 14840400 + + + + + 11223344 + + Betalingsreference012 + + + 27510647 + 66112527 + + + + + + 11223344 + + Betalingsreference013 + + + 15700211 + 1000000201 + + + + + + 11223344 + + Betalingsreference014 + + + 00000000 + 16202010 + + + + diff --git a/src/test/resources/nemkonto-examples/Retursvar 2 med ACPT.xml b/src/test/resources/nemkonto-examples/Retursvar 2 med ACPT.xml new file mode 100644 index 0000000000000000000000000000000000000000..4b92c64c7194d8410cc670768c99a30d68aca1f8 --- /dev/null +++ b/src/test/resources/nemkonto-examples/Retursvar 2 med ACPT.xml @@ -0,0 +1,44 @@ + + + + + + NKS + 5798000016446 + + + DATLEV + + + NKS2C RETURSVAR2 0000000795 + 2005-08-17T14:00:44 + TMO-20050817014232 + + + + NKS2C RETURSVAR2 0000000795 + 2005-08-17T14:00:44 + + + + 1 + ADMID + + + + + + TMO-20050817014232 + NKSBetaling + + ACPT + + + diff --git a/src/test/resources/nemkonto-examples/Retursvar 5 med PART.xml b/src/test/resources/nemkonto-examples/Retursvar 5 med PART.xml new file mode 100644 index 0000000000000000000000000000000000000000..7f57b9b943fe0c959c4be2aa7cabeb59e5de0e59 --- /dev/null +++ b/src/test/resources/nemkonto-examples/Retursvar 5 med PART.xml @@ -0,0 +1,51 @@ + + + + + + NKS + 5798000016446 + + + DATLEV + + + NKS2C RETURSVAR5 0000000795 + 2005-08-17T14:00:44 + TMO-20050817014232 + + + + NKS2C RETURSVAR5 0000000795 + 2005-08-17T14:00:44 + + + + 1 + ADMID + + + + + + TMO-20050817014232 + NKSBetaling + + PART + + + + + 99999999-189,BAR,20270,2005 + + RJCT + BETALING(ER) STANDSET + + + diff --git a/src/test/resources/nemkonto-examples/Retursvar 7.xml b/src/test/resources/nemkonto-examples/Retursvar 7.xml new file mode 100644 index 0000000000000000000000000000000000000000..42736b0231c5a5cf494b5c7db4323896bc2ea850 --- /dev/null +++ b/src/test/resources/nemkonto-examples/Retursvar 7.xml @@ -0,0 +1,94 @@ + + + + + NKS2C RETURSVAR7 0000000073 + 2005-08-26T11:00:39 + + + + 1 + ADMID + + + + + + TMO-20050826095549 + NKSBetaling + + + + + 20101377-1,FLE,20507,12490, + + RJCT + RJCT + Konto findes ikke. Myndighed har valgt check retur. + + + + 100000 + + 1 + + + 12345678 + + + + + + + + + + 20101377-1,FLE,20509,12492, + + RJCT + RJCT + Konto findes ikke. Myndighed har valgt check retur. + + + + 200000 + + 1 + + + 23456789 + + + + + + + + + + 20101377-1,FLE,20511,12494, + + RJCT + RJCT + Konto findes ikke. Myndighed har valgt check retur. + + + + 300000 + + 1 + + + 34567890 + + + + + + + diff --git a/src/test/resources/nemkonto-examples/Retursvar 8.xml b/src/test/resources/nemkonto-examples/Retursvar 8.xml new file mode 100644 index 0000000000000000000000000000000000000000..7fcc4427b1279cd65ea639e40a3dae4d86985c60 --- /dev/null +++ b/src/test/resources/nemkonto-examples/Retursvar 8.xml @@ -0,0 +1,86 @@ + + + + + NKS2C RETURSVAR8 0000000079 + 2005-08-30T10:54:48 + + + + 1 + ADMID + + + + + + TMO-20050826022933 + NKSBetaling + + + 2005-08-26 + + + BANK01234 + + + + + 12340123456789 + + + Test udbetaling + + + 12345678-1,BAR,20589,12532, + + ACPT + + + 100000 + + 1 + + + 12345678 + + + + + 12341212121212 + + + + + + + 12345678-1,BAR,20591,12534, + + ACPT + + + 100000 + + 1 + + + + 12345678 + CVR + + + + + + 12341212121212 + + + + + + diff --git a/src/test/resources/nemkonto-examples/Retursvar 9.xml b/src/test/resources/nemkonto-examples/Retursvar 9.xml new file mode 100644 index 0000000000000000000000000000000000000000..a597c366504ad07c49a37ee9866fbcb8fb078526 --- /dev/null +++ b/src/test/resources/nemkonto-examples/Retursvar 9.xml @@ -0,0 +1,71 @@ + + + + + NKS2C RETURSVAR9 0000000079 + 2005-08-30T10:54:50 + + + + 1 + ADMID + + + + + + TMO-20050826022933 + NKSBetaling + + + + + 20101377-1,BAR,20589,12532, + + RJCT + 0001 + DEBITERING AFVIST Aftale mangler for udbetalingskonto + + + + 100000 + + 1 + + + 12345678 + + + + + + + + + + 20905808-1,BAR,20591,12534, + + RJCT + 0001 + DEBITERING AFVIST Aftale mangler for udbetalingskonto + + + + 200000 + + 1 + + + 23456789 + + + + + + + diff --git a/src/test/resources/oxalis_home/configure-liquibase-changelog-conf/oxalis.conf b/src/test/resources/oxalis_home/configure-liquibase-changelog-conf/oxalis.conf new file mode 100644 index 0000000000000000000000000000000000000000..97b1f1cdcad6c2821292e7127a485228dfd3bb88 --- /dev/null +++ b/src/test/resources/oxalis_home/configure-liquibase-changelog-conf/oxalis.conf @@ -0,0 +1,144 @@ + +jdbc.url = "jdbc:h2:mem:oxalis-liquibase-changelog-test;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=true;MODE=MYSQL;" +jdbc.liquibase.changelog = dummy-db-changelog.xml + +// dont preload schematron for unit tests to speed things up +document.type.reminder { + preloadSchema = false + preloadSchematron = false +} +document.type.utilityStatement { + preloadSchema = false + preloadSchematron = false +} +document.type.invoice { + preloadSchema = false + preloadSchematron = false +} +document.type.creditNote { + preloadSchema = false + preloadSchematron = false +} +document.type.applicationResponse { + preloadSchema = false + preloadSchematron = false +} +document.type.statement { + preloadSchema = false + preloadSchematron = false +} +document.type.order { + preloadSchema = false + preloadSchematron = false +} +document.type.orderResponseSimple { + preloadSchema = false + preloadSchematron = false +} +document.type.orderResponse { + preloadSchema = false + preloadSchematron = false +} +document.type.orderCancellation { + preloadSchema = false + preloadSchematron = false +} +document.type.orderChange { + preloadSchema = false + preloadSchematron = false +} +document.type.catalogue { + preloadSchema = false + preloadSchematron = false +} +document.type.catalogueDeletion { + preloadSchema = false + preloadSchematron = false +} +document.type.catalogueRequest { + preloadSchema = false + preloadSchematron = false +} +document.type.catalogueItemSpecificationUpdate { + preloadSchema = false + preloadSchematron = false +} +document.type.cataloguePricingUpdate { + preloadSchema = false + preloadSchematron = false +} +document.type.cataloguePricingUpdateDKPeppol { + preloadSchema = false + preloadSchematron = false +} +document.type.catalogueUpdateResponseDKPeppol { + preloadSchema = false + preloadSchematron = false +} +document.type.orderAgreementPeppol { + preloadSchema = false + preloadSchematron = false +} +document.type.orderAgreementResponseDKPeppol { + preloadSchema = false + preloadSchematron = false +} +document.type.reminder30 { + preloadSchema = false + preloadSchematron = false +} +document.type.simplifiedInvoice30 { + preloadSchema = false + preloadSchematron = false +} +document.type.invoiceResponseTransaction30 { + preloadSchema = false + preloadSchematron = false +} +document.type.messageLevelResponseTransaction30 { + preloadSchema = false + preloadSchematron = false +} +document.type.peppolBISBillingInvoice { + preloadSchema = false + preloadSchematron = false +} +document.type.peppolBISBillingCreditNote { + preloadSchema = false + preloadSchematron = false +} +document.type.peppolCatalogue { + preloadSchema = false + preloadSchematron = false +} +document.type.peppolCatalogueResponse { + preloadSchema = false + preloadSchematron = false +} +document.type.peppolOrder { + preloadSchema = false + preloadSchematron = false +} +document.type.peppolOrderAgreement { + preloadSchema = false + preloadSchematron = false +} +document.type.peppolDespatchAdvice { + preloadSchema = false + preloadSchematron = false +} +document.type.peppolPunchOut { + preloadSchema = false + preloadSchematron = false +} +document.type.peppolOrderChange { + preloadSchema = false + preloadSchematron = false +} +document.type.peppolOrderCancellation { + preloadSchema = false + preloadSchematron = false +} + + + diff --git a/src/test/resources/oxalis_home/schematron-preload-conf/oxalis.conf b/src/test/resources/oxalis_home/schematron-preload-conf/oxalis.conf index 521e37048a48ddce4cd866b1506f7cecda93056d..44581ff624691f6569d6f37aad43ef49fc614f93 100644 --- a/src/test/resources/oxalis_home/schematron-preload-conf/oxalis.conf +++ b/src/test/resources/oxalis_home/schematron-preload-conf/oxalis.conf @@ -1,3 +1,9 @@ -document.type.invoice.preloadSchematron = true -document.type.applicationResponse.preloadSchematron = true +document.type.invoice { + preloadSchema = true + preloadSchematron = true +} +document.type.applicationResponse { + preloadSchema = true + preloadSchematron = true +} diff --git a/src/test/resources/reference.conf b/src/test/resources/reference.conf index 4e4623a9d9d14c3c407dc1791423df83738d8b72..3e60ddf41e8eb7e51d8653de803ff09119790e1b 100644 --- a/src/test/resources/reference.conf +++ b/src/test/resources/reference.conf @@ -21,40 +21,142 @@ jdbc { } // dont preload schematron for unit tests to speed things up -document.type.reminder.preloadSchematron = false -document.type.utilityStatement.preloadSchematron = false -document.type.invoice.preloadSchematron = false -document.type.creditNote.preloadSchematron = false -document.type.applicationResponse.preloadSchematron = false -document.type.statement.preloadSchematron = false -document.type.order.preloadSchematron = false -document.type.orderResponseSimple.preloadSchematron = false -document.type.orderResponse.preloadSchematron = false -document.type.orderCancellation.preloadSchematron = false -document.type.orderChange.preloadSchematron = false -document.type.catalogue.preloadSchematron = false -document.type.catalogueDeletion.preloadSchematron = false -document.type.catalogueRequest.preloadSchematron = false -document.type.catalogueItemSpecificationUpdate.preloadSchematron = false -document.type.cataloguePricingUpdate.preloadSchematron = false -document.type.cataloguePricingUpdateDKPeppol.preloadSchematron = false -document.type.catalogueUpdateResponseDKPeppol.preloadSchematron = false -document.type.orderAgreementPeppol.preloadSchematron = false -document.type.orderAgreementResponseDKPeppol.preloadSchematron = false -document.type.reminder30.preloadSchematron = false -document.type.simplifiedInvoice30.preloadSchematron = false -document.type.invoiceResponseTransaction30.preloadSchematron = false -document.type.messageLevelResponseTransaction30.preloadSchematron = false -document.type.peppolBISBillingInvoice.preloadSchematron = false -document.type.peppolBISBillingCreditNote.preloadSchematron = false -document.type.peppolCatalogue.preloadSchematron = false -document.type.peppolCatalogueResponse.preloadSchematron = false -document.type.peppolOrder.preloadSchematron = false -document.type.peppolOrderAgreement.preloadSchematron = false -document.type.peppolDespatchAdvice.preloadSchematron = false -document.type.peppolPunchOut.preloadSchematron = false -document.type.peppolOrderChange.preloadSchematron = false -document.type.peppolOrderCancellation.preloadSchematron = false +document.type.reminder { + preloadSchema = false + preloadSchematron = false +} +document.type.utilityStatement { + preloadSchema = false + preloadSchematron = false +} +document.type.invoice { + preloadSchema = false + preloadSchematron = false +} +document.type.creditNote { + preloadSchema = false + preloadSchematron = false +} +document.type.applicationResponse { + preloadSchema = false + preloadSchematron = false +} +document.type.statement { + preloadSchema = false + preloadSchematron = false +} +document.type.order { + preloadSchema = false + preloadSchematron = false +} +document.type.orderResponseSimple { + preloadSchema = false + preloadSchematron = false +} +document.type.orderResponse { + preloadSchema = false + preloadSchematron = false +} +document.type.orderCancellation { + preloadSchema = false + preloadSchematron = false +} +document.type.orderChange { + preloadSchema = false + preloadSchematron = false +} +document.type.catalogue { + preloadSchema = false + preloadSchematron = false +} +document.type.catalogueDeletion { + preloadSchema = false + preloadSchematron = false +} +document.type.catalogueRequest { + preloadSchema = false + preloadSchematron = false +} +document.type.catalogueItemSpecificationUpdate { + preloadSchema = false + preloadSchematron = false +} +document.type.cataloguePricingUpdate { + preloadSchema = false + preloadSchematron = false +} +document.type.cataloguePricingUpdateDKPeppol { + preloadSchema = false + preloadSchematron = false +} +document.type.catalogueUpdateResponseDKPeppol { + preloadSchema = false + preloadSchematron = false +} +document.type.orderAgreementPeppol { + preloadSchema = false + preloadSchematron = false +} +document.type.orderAgreementResponseDKPeppol { + preloadSchema = false + preloadSchematron = false +} +document.type.reminder30 { + preloadSchema = false + preloadSchematron = false +} +document.type.simplifiedInvoice30 { + preloadSchema = false + preloadSchematron = false +} +document.type.invoiceResponseTransaction30 { + preloadSchema = false + preloadSchematron = false +} +document.type.messageLevelResponseTransaction30 { + preloadSchema = false + preloadSchematron = false +} +document.type.peppolBISBillingInvoice { + preloadSchema = false + preloadSchematron = false +} +document.type.peppolBISBillingCreditNote { + preloadSchema = false + preloadSchematron = false +} +document.type.peppolCatalogue { + preloadSchema = false + preloadSchematron = false +} +document.type.peppolCatalogueResponse { + preloadSchema = false + preloadSchematron = false +} +document.type.peppolOrder { + preloadSchema = false + preloadSchematron = false +} +document.type.peppolOrderAgreement { + preloadSchema = false + preloadSchematron = false +} +document.type.peppolDespatchAdvice { + preloadSchema = false + preloadSchematron = false +} +document.type.peppolPunchOut { + preloadSchema = false + preloadSchematron = false +} +document.type.peppolOrderChange { + preloadSchema = false + preloadSchematron = false +} +document.type.peppolOrderCancellation { + preloadSchema = false + preloadSchematron = false +} diff --git a/src/test/resources/schematron-error-examples/F-INV134_INV_The_Sum_of_PaymentTerms_Amount_Does_Not_Equal_PayableAmount_BZ1425.xml b/src/test/resources/schematron-error-examples/F-INV134_INV_The_Sum_of_PaymentTerms_Amount_Does_Not_Equal_PayableAmount_BZ1425.xml index 97829eacb94c6ab6dd3c9f41b4493cd24dc2247d..989c8f6d849fac26558d3c487ce829e5d6a99880 100644 --- a/src/test/resources/schematron-error-examples/F-INV134_INV_The_Sum_of_PaymentTerms_Amount_Does_Not_Equal_PayableAmount_BZ1425.xml +++ b/src/test/resources/schematron-error-examples/F-INV134_INV_The_Sum_of_PaymentTerms_Amount_Does_Not_Equal_PayableAmount_BZ1425.xml @@ -66,7 +66,7 @@ 2.1 - OIOUBL-2.02 + OIOUBL-2.1 Procurement-OrdSimR-BilSim-1.0 A00095678 false