Skip to content
This repository has been archived by the owner on Nov 7, 2023. It is now read-only.

Towards v1.0 beta1 #58

Open
wants to merge 10 commits into
base: next-phase
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 0 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@ Each transaction inside the transactions (list) of the EBL envelope consists of:
* **action** - action for processing the transaction such as `TRNS` (Transfer of possession). Please see the swagger spec for the full list.
* **comments** - free text comment for the party receiving the transaction
* **timestamp** - Unix epoch with millisecond precision of when the transaction was created
* **isToOrder** - indicator if the B/L is to order
* **platformHost** - base url of the platform which created the transaction
* **actor** - Identity information about the party who performed the action.
* **recipient** - Identity information about the party who is the recipient for the action (where applicable). See [Identity below](README.md#identity)
Expand Down Expand Up @@ -96,13 +95,6 @@ While the eBLPlatformIdentifier and legal name are required, it is optional but
This additional information can be used by the receiving platform to perform additional verification. For providing this additional information the DCSA Transaction Party object is used.
The DCSA Transaction Party object allows of the provision of LEI and DID among other as documented in the [DCSA Transaction Party object](https://app.swaggerhub.com/apis/dcsaorg/DCSA_EEC/0.12-alpha#/transactionParty).

### Service discovery
The platform domain part of the eBLPlatformIdentifier being a resolvable domain name can be used for DNS based service discovery.
With this an additional TXT record can be created linking to the API endpoint providing the PUT `/v1/transferblock` operation.

Example:
dcsaeblplatform.org can link to a TXT record containing the full API endpoint -> `api.dcsaeblplatform.org/v1/transferblocks`

## Security considerations

### Transport level security
Expand Down
16 changes: 11 additions & 5 deletions datamodel/initdb.d/03_ec_registry.sql
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,16 @@ CREATE TABLE ec_registry."party" (
ebl_platform_identifier varchar(255) NOT NULL PRIMARY KEY,
party_name varchar(255) NOT NULL,
registration_number varchar(255) NOT NULL,
country_of_registration varchar(2) NOT NULL,
address text,
tax_reference varchar(255),
lei varchar(20),
did varchar(255)
location_of_registration varchar(2) NOT NULL,
tax_reference varchar(255)
);

DROP TABLE IF EXISTS ec_registry.supporting_party_code CASCADE;
CREATE TABLE ec_registry.supporting_party_code (
id uuid NOT NULL PRIMARY KEY,
party_id varchar(255) NOT NULL REFERENCES ec_registry."party" (ebl_platform_identifier),
party_code varchar(100) NOT NULL,
party_code_list_provider varchar(3) NOT NULL
);

DROP TABLE IF EXISTS ec_registry."transaction" CASCADE;
Expand All @@ -41,6 +46,7 @@ CREATE TABLE ec_registry."transaction" (
platform_host varchar(255) NULL,
"timestamp" int8 NOT NULL,
transferee varchar(255) NULL,
actor varchar(255) NULL,
envelope_hash varchar(64) NULL,
CONSTRAINT uniquetimestampanddocumenthash UNIQUE ("timestamp", document_hash)
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
package org.dcsa.endorsementchain.persistence.entity;

import jakarta.persistence.*;
import java.util.List;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.Setter;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;

@Data
@Builder
@Entity
Expand All @@ -24,23 +22,18 @@ public class Party {
private String id;

@Column(name = "party_name", nullable = false)
private String name;
private String legalName;

@Column(name = "registration_number", nullable = false)
private String registrationNumber;

@Column(name = "country_of_registration", nullable = false, length = 2)
private String countryOfRegistration;

@Column(name = "address")
private String address;
@Column(name = "location_of_registration", nullable = false, length = 2)
private String locationOfRegistration;

@Column(name = "tax_reference")
private String taxReference;

@Column(name = "lei", length = 20)
private String lei;

@Column(name = "did")
private String did;
@OneToMany
@JoinColumn(name = "party_id", referencedColumnName = "ebl_platform_identifier")
private List<SupportingPartyCode> supportingPartyCodes;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package org.dcsa.endorsementchain.persistence.entity;

import jakarta.persistence.*;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Pattern;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.dcsa.endorsementchain.persistence.entity.enums.PartyCodeListProvider;

import java.util.UUID;

@Data
@Builder
@Entity
@AllArgsConstructor
@NoArgsConstructor
@Setter(AccessLevel.PRIVATE)
public class SupportingPartyCode {

@Id
@Column(name = "id", nullable = false)
private UUID id;

@Column(name = "party_id", nullable = false)
private String partyId;

@Pattern(regexp = "^\\S+(\\s*\\S+)*$")
@NotBlank
private String partyCode;

@Enumerated(EnumType.STRING)
@NotNull
private PartyCodeListProvider partyCodeListProvider;
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,6 @@ public class Transaction {
@Column(name = "timestamp", nullable = false)
private Long timestamp;

private Boolean isToOrder;

private String platformHost;

@ToString.Exclude
Expand All @@ -49,6 +47,12 @@ public class Transaction {
@JoinColumn(name = "transferee")
private Party party;

@ToString.Exclude
@EqualsAndHashCode.Exclude
@ManyToOne(cascade = CascadeType.ALL)
@JoinColumn(name = "actor")
private Party actor;

public Transaction linkTransactionToTransportDocument(TransportDocument transportDocument) {
if (Boolean.TRUE.equals(transportDocument.getIsExported())) {
throw ConcreteRequestErrorMessageException.internalServerError("Cannot link a transaction to an exported transportDocument");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package org.dcsa.endorsementchain.persistence.entity.enums;


import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;

@AllArgsConstructor(access = AccessLevel.PRIVATE)
public enum PartyCodeListProvider {
DID("Decentralized Identifier"),
LEI("Legal Entity Identifier"),
;

@Getter
private final String value;
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.dcsa.endorsementchain.components.jws.JWSSignerDetails;
import org.dcsa.endorsementchain.components.jws.PayloadSigner;
import org.dcsa.endorsementchain.components.jws.SignatureVerifier;
import org.dcsa.endorsementchain.transferobjects.SignedEndorsementChainEntryTO;
import org.dcsa.skernel.errors.exceptions.ConcreteRequestErrorMessageException;
Expand All @@ -16,11 +17,11 @@
@Slf4j
public class EndorsementChainEntrySignature {

private final JWSSignerDetails jwsSignerDetails;
private final SignatureVerifier signatureVerifier;
private final PayloadSigner payloadSigner;

public SignedEndorsementChainEntryTO createSignedEndorsementChainEntry(String endorsementChainEntry) {
String signature = sign(endorsementChainEntry);
String signature = payloadSigner.sign(endorsementChainEntry);
String envelopeHash = DigestUtils.sha256Hex(endorsementChainEntry);

return SignedEndorsementChainEntryTO.builder()
Expand All @@ -29,18 +30,6 @@ public SignedEndorsementChainEntryTO createSignedEndorsementChainEntry(String en
.build();
}

public String sign(String payload) {
JWSHeader header = new JWSHeader.Builder(jwsSignerDetails.algorithm()).build();
JWSObject jwsObject = new JWSObject(header, new Payload(payload));
try {
jwsObject.sign(jwsSignerDetails.signer());
} catch (JOSEException e) {
throw ConcreteRequestErrorMessageException.internalServerError(
"Unable to generate the JWS Object");
}
return jwsObject.serialize();
}

@SneakyThrows
public boolean verifyEndorsementChainHash(String cn, String signature, String endorsementChainEntryHash) {
JWSObject jwsObject = JWSObject.parse(signature);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ public JWSSignerDetails getJWSSignerDetails(@Qualifier("signing-jwk") JWKSet jwk
KeyType keyType = getValidKeyType(jwk);

return switch (keyType.getValue()) {
case "RSA" -> new JWSSignerDetails(JWSAlgorithm.RS256, new RSASSASigner(jwk.toRSAKey()));
case "RSA" -> new JWSSignerDetails(JWSAlgorithm.PS256, new RSASSASigner(jwk.toRSAKey()));
case "EC" -> {
ECKey ecKey = jwk.toECKey();
yield new JWSSignerDetails(getESAlgorithm(ecKey.getCurve()), new ECDSASigner(ecKey));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package org.dcsa.endorsementchain.components.jws;

public interface PayloadSigner {
String sign(String payload);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package org.dcsa.endorsementchain.components.jws.impl;

import com.nimbusds.jose.JOSEException;
import com.nimbusds.jose.JWSHeader;
import com.nimbusds.jose.JWSObject;
import com.nimbusds.jose.Payload;
import lombok.RequiredArgsConstructor;
import org.dcsa.endorsementchain.components.jws.JWSSignerDetails;
import org.dcsa.endorsementchain.components.jws.PayloadSigner;
import org.dcsa.skernel.errors.exceptions.ConcreteRequestErrorMessageException;
import org.springframework.stereotype.Component;

@Component
@RequiredArgsConstructor
public class DefaultPayloadSigner implements PayloadSigner {

private final JWSSignerDetails jwsSignerDetails;

public String sign(String payload) {
JWSHeader header = new JWSHeader.Builder(jwsSignerDetails.algorithm()).build();
JWSObject jwsObject = new JWSObject(header, new Payload(payload));
try {
jwsObject.sign(jwsSignerDetails.signer());
} catch (JOSEException e) {
throw ConcreteRequestErrorMessageException.internalServerError(
"Unable to generate the JWS Object");
}
return jwsObject.serialize();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public class ImportController {

private final ImportService importService;

@PutMapping(value = "/transferblocks", // TODO: Rename when we are ready to change the API
@PutMapping(value = "/transfer-transactions",
produces = MediaType.APPLICATION_JSON_VALUE,
consumes = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<String> importEBLEnvelope(@RequestBody @Valid EBLEnvelopeTO eblEnvelopeTO) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,34 @@
package org.dcsa.endorsementchain.mapping;

import java.util.List;
import org.dcsa.endorsementchain.persistence.entity.Party;
import org.dcsa.endorsementchain.persistence.entity.SupportingPartyCode;
import org.dcsa.endorsementchain.transferobjects.PartyTO;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Mapper(componentModel = "spring")
public interface PartyMapper {
PartyTO toDTO(Party party);
@Mapper(componentModel = "spring", uses = {
SupportingPartyCodeMapper.class,
})
@Component
public abstract class PartyMapper {

@Autowired
protected SupportingPartyCodeMapper codeMapper;

public abstract PartyTO toDTO(Party party);

@Mapping(expression = "java(partyId)", target = "id")
Party toDAO(PartyTO party, String partyId);
@Mapping(expression = "java(mapSupportingCodes(party))", target = "supportingPartyCodes")
public abstract Party toDAO(PartyTO party, String partyId);

protected List<SupportingPartyCode> mapSupportingCodes(PartyTO partyTO) {
var codes = partyTO.supportingPartyCodes();
if (codes == null) {
return null;
}
return codes.stream().map(c -> codeMapper.toDAO(c, partyTO.id())).toList();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package org.dcsa.endorsementchain.mapping;

import org.dcsa.endorsementchain.persistence.entity.SupportingPartyCode;
import org.dcsa.endorsementchain.transferobjects.SupportingPartyCodeTO;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;

@Mapper(componentModel = "spring")
public interface SupportingPartyCodeMapper {
SupportingPartyCodeTO toDTO(SupportingPartyCode supportingPartyCode);

@Mapping(source = "partyId", target = "partyId")
SupportingPartyCode toDAO(SupportingPartyCodeTO supportingPartyCodeTO, String partyId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import lombok.SneakyThrows;
import org.dcsa.endorsementchain.components.endorsementchain.EndorsementChainEntryList;
import org.dcsa.endorsementchain.components.endorsementchain.EndorsementChainEntrySignature;
import org.dcsa.endorsementchain.components.jws.PayloadSigner;
import org.dcsa.endorsementchain.components.jws.SignatureVerifier;
import org.dcsa.endorsementchain.persistence.entity.EndorsementChainEntry;
import org.dcsa.endorsementchain.persistence.entity.TransactionByTimestampComparator;
Expand Down Expand Up @@ -33,6 +34,7 @@ public class EndorsementChainEntryService {
private final EndorsementChainEntryRepository endorsementChainEntryRepository;
private final EndorsementChainEntrySignature signature;
private final SignatureVerifier signatureVerifier;
private final PayloadSigner payloadSigner;
private final TransactionMapper transactionMapper;
private final TransportDocumentRepository transportDocumentRepository;
private final ObjectMapper mapper;
Expand Down Expand Up @@ -139,7 +141,7 @@ String saveEndorsementEntries(List<EndorsementChainEntry> endorsementChainEntrie
ConcreteRequestErrorMessageException.internalServerError(
"Could not find a Envelope Hash on the EndorsementChainEntry"));

return signature.sign(envelopeHash);
return payloadSigner.sign(envelopeHash);
}

EndorsementChainEntry signedEndorsementEntryToEndorsementChainEntry(
Expand All @@ -160,6 +162,7 @@ EndorsementChainEntry signedEndorsementEntryToEndorsementChainEntry(
.build()
);

// FIXME: we should only resolve the latest transferee.
var transactions =
endorsementChainEntryTO.transactions().stream()
.map(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ private URI transfereeToPlatformHost(String transferee) {
String host = transferee.substring(transferee.indexOf("@") + 1);
return UriComponentsBuilder.fromHttpUrl("https://" + host)
.scheme("https")
.path("/v1/transferblocks")
.path("/v1/transfer-transactions")
.build()
.toUri();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,13 +116,6 @@ private Transaction createExportTransaction(String transferee, Set<Transaction>
return Transaction.builder()
.party(partyService.getPartyByTransferee(transferee))
.comments("The B/L exported to: " + transferee)
.isToOrder(
transactions.stream()
.map(Transaction::getIsToOrder)
.findAny()
.orElse(
true)) // When no local transactions exist only the export transaction will be
// created and isToOrder will be set to true
.platformHost(hostname+":"+port)
.timestamp(System.currentTimeMillis())
.action(TransactionAction.TRNS)
Expand Down
Loading
Loading