@twin.org/dataspace-control-plane-service 0.0.3-next.17 → 0.0.3-next.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -100
- package/dist/es/dataspaceControlPlaneRoutes.js +6 -0
- package/dist/es/dataspaceControlPlaneRoutes.js.map +1 -1
- package/dist/es/dataspaceControlPlaneService.js +145 -55
- package/dist/es/dataspaceControlPlaneService.js.map +1 -1
- package/dist/types/dataspaceControlPlaneService.d.ts +2 -1
- package/docs/changelog.md +29 -1
- package/docs/examples.md +144 -1
- package/docs/reference/classes/DataspaceControlPlaneService.md +7 -1
- package/docs/reference/interfaces/IDataspaceControlPlaneServiceConstructorOptions.md +0 -60
- package/locales/en.json +8 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Dataspace Control Plane Service
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
This package implements agreement negotiation and transfer process lifecycle management for control plane operations. It coordinates protocol-compliant state transitions and persists transfer state that can be consumed by downstream data plane components.
|
|
4
|
+
|
|
5
|
+
Its behaviour follows the [Eclipse Dataspace Protocol](https://eclipse-dataspace-protocol-base.github.io/DataspaceProtocol/) and is designed for policy-aware transfer orchestration.
|
|
4
6
|
|
|
5
7
|
## Installation
|
|
6
8
|
|
|
@@ -8,104 +10,6 @@ Dataspace Control Plane contract implementation and REST endpoint definitions, i
|
|
|
8
10
|
npm install @twin.org/dataspace-control-plane-service
|
|
9
11
|
```
|
|
10
12
|
|
|
11
|
-
## Overview
|
|
12
|
-
|
|
13
|
-
The Control Plane manages DSP protocol operations including contract negotiation and transfer process management.
|
|
14
|
-
|
|
15
|
-
### Key Features
|
|
16
|
-
|
|
17
|
-
- **Transfer Process Management**: Create, start, complete, suspend, and terminate transfers
|
|
18
|
-
- **Contract Negotiation**: Negotiate agreements with providers via Policy Negotiation Point (PNP)
|
|
19
|
-
- **Agreement Validation**: Validate agreements via Policy Administration Point (PAP)
|
|
20
|
-
- **Dataset Validation**: Validate datasets via Federated Catalogue
|
|
21
|
-
- **Entity Storage**: Persists `TransferProcessEntity` to shared storage (accessible by Data Plane)
|
|
22
|
-
- **Token Generation**: Creates transfer tokens with configurable expiration
|
|
23
|
-
- **State Machine**: Enforces valid state transitions per DSP specification
|
|
24
|
-
|
|
25
|
-
### Constructor Options
|
|
26
|
-
|
|
27
|
-
- `loggingComponentType` (default: `logging`) - Logging component type
|
|
28
|
-
- `identityComponentType` (default: `identity`) - Identity component for token signing
|
|
29
|
-
- `identityAuthenticationComponentType` (default: `identity-authentication`) - Token validation component
|
|
30
|
-
- `transferProcessEntityStorageType` (default: `transfer-process-entity`) - Entity storage for transfer processes
|
|
31
|
-
- `policyAdministrationPointComponentType` (default: `policy-administration-point`) - Agreement lookup component
|
|
32
|
-
- `policyNegotiationPointComponentType` (default: `policy-negotiation-point`) - Contract negotiation component
|
|
33
|
-
- `federatedCatalogueComponentType` (default: `federated-catalogue`) - Dataset validation component
|
|
34
|
-
- `trustComponentType` (default: `trust`) - Trust verification component
|
|
35
|
-
|
|
36
|
-
### DSP Protocols
|
|
37
|
-
|
|
38
|
-
The Control Plane implements two separate DSP protocols, following the Eclipse Dataspace Protocol specification:
|
|
39
|
-
|
|
40
|
-
#### 1. Contract Negotiation Protocol
|
|
41
|
-
|
|
42
|
-
Negotiate agreements with providers before initiating transfers. Integrates with the Rights Management Policy Negotiation Point (PNP).
|
|
43
|
-
|
|
44
|
-
**REST API Endpoints:**
|
|
45
|
-
|
|
46
|
-
- `POST /control-plane/negotiations` - Initiate contract negotiation
|
|
47
|
-
- `GET /control-plane/negotiations/:negotiationId` - Get negotiation status
|
|
48
|
-
|
|
49
|
-
**Example Flow:**
|
|
50
|
-
|
|
51
|
-
```bash
|
|
52
|
-
# Step 1: Initiate negotiation
|
|
53
|
-
curl -X POST https://consumer.example.com/control-plane/negotiations \
|
|
54
|
-
-H "Authorization: Bearer ${TOKEN}" \
|
|
55
|
-
-H "Content-Type: application/json" \
|
|
56
|
-
-d '{
|
|
57
|
-
"offerId": "offer-from-catalog",
|
|
58
|
-
"providerEndpoint": "https://provider.example.com/negotiations"
|
|
59
|
-
}'
|
|
60
|
-
# Response: { agreement: {...}, agreementId: "...", negotiationId: "..." }
|
|
61
|
-
|
|
62
|
-
# Step 2: Check negotiation status (optional)
|
|
63
|
-
curl -X GET https://consumer.example.com/control-plane/negotiations/${NEGOTIATION_ID} \
|
|
64
|
-
-H "Authorization: Bearer ${TOKEN}"
|
|
65
|
-
# Response: { negotiationId: "...", state: "FINALIZED", ... }
|
|
66
|
-
```
|
|
67
|
-
|
|
68
|
-
**Service Methods:**
|
|
69
|
-
|
|
70
|
-
- `negotiateAgreement()` - Initiate contract negotiation (returns agreement)
|
|
71
|
-
- `getNegotiation()` - Retrieve negotiation state
|
|
72
|
-
|
|
73
|
-
**Flow**: Catalog → Negotiation → Agreement → Transfer
|
|
74
|
-
|
|
75
|
-
#### 2. Transfer Process Protocol
|
|
76
|
-
|
|
77
|
-
Manage the lifecycle of data transfers using negotiated agreements.
|
|
78
|
-
|
|
79
|
-
**REST API Endpoints:**
|
|
80
|
-
|
|
81
|
-
- `POST /control-plane/transfers/request` - Request a transfer
|
|
82
|
-
- `GET /control-plane/transfers/:pid` - Get transfer process state
|
|
83
|
-
- `POST /control-plane/transfers/:pid/start` - Start a transfer
|
|
84
|
-
- `POST /control-plane/transfers/:pid/complete` - Complete a transfer
|
|
85
|
-
- `POST /control-plane/transfers/:pid/suspend` - Suspend a transfer
|
|
86
|
-
- `POST /control-plane/transfers/:pid/terminate` - Terminate a transfer
|
|
87
|
-
|
|
88
|
-
**Service Methods:**
|
|
89
|
-
|
|
90
|
-
- `requestTransfer()` - Initiate a transfer request (requires existing agreement)
|
|
91
|
-
- `getTransferProcess()` - Retrieve transfer process state
|
|
92
|
-
- `startTransfer()` - Start an approved transfer
|
|
93
|
-
- `completeTransfer()` - Mark transfer as complete
|
|
94
|
-
- `suspendTransfer()` - Temporarily suspend a transfer
|
|
95
|
-
- `terminateTransfer()` - Terminate a transfer
|
|
96
|
-
|
|
97
|
-
### Shared Storage Architecture
|
|
98
|
-
|
|
99
|
-
The Control Plane writes `TransferProcessEntity` to entity storage, which is read by the Data Plane for `consumerPid` resolution:
|
|
100
|
-
|
|
101
|
-
```text
|
|
102
|
-
Control Plane Data Plane
|
|
103
|
-
│ │
|
|
104
|
-
│ set(TransferProcessEntity) │ get(consumerPid)
|
|
105
|
-
│ │
|
|
106
|
-
└───── Entity Storage ──────────┘
|
|
107
|
-
```
|
|
108
|
-
|
|
109
13
|
## Examples
|
|
110
14
|
|
|
111
15
|
Usage of the APIs is shown in the examples [docs/examples.md](docs/examples.md)
|
|
@@ -37,6 +37,7 @@ export function generateRestRoutesDataspaceControlPlane(baseRouteName, component
|
|
|
37
37
|
tag: tagsDataspaceControlPlane[0].name,
|
|
38
38
|
method: "POST",
|
|
39
39
|
path: `${baseRouteName}/transfers/request`,
|
|
40
|
+
skipAuth: true,
|
|
40
41
|
handler: async (httpRequestContext, request) => requestTransferHandler(httpRequestContext, componentName, request)
|
|
41
42
|
});
|
|
42
43
|
// GET /transfers/:pid - Get Transfer Process state (DSP)
|
|
@@ -46,6 +47,7 @@ export function generateRestRoutesDataspaceControlPlane(baseRouteName, component
|
|
|
46
47
|
tag: tagsDataspaceControlPlane[0].name,
|
|
47
48
|
method: "GET",
|
|
48
49
|
path: `${baseRouteName}/transfers/:pid`,
|
|
50
|
+
skipAuth: true,
|
|
49
51
|
handler: async (httpRequestContext, request) => getTransferProcessHandler(httpRequestContext, componentName, request)
|
|
50
52
|
});
|
|
51
53
|
// POST /transfers/:pid/start - Start Transfer Process (DSP)
|
|
@@ -55,6 +57,7 @@ export function generateRestRoutesDataspaceControlPlane(baseRouteName, component
|
|
|
55
57
|
tag: tagsDataspaceControlPlane[0].name,
|
|
56
58
|
method: "POST",
|
|
57
59
|
path: `${baseRouteName}/transfers/:pid/start`,
|
|
60
|
+
skipAuth: true,
|
|
58
61
|
handler: async (httpRequestContext, request) => startTransferHandler(httpRequestContext, componentName, request)
|
|
59
62
|
});
|
|
60
63
|
// POST /transfers/:pid/complete - Complete Transfer Process (DSP)
|
|
@@ -64,6 +67,7 @@ export function generateRestRoutesDataspaceControlPlane(baseRouteName, component
|
|
|
64
67
|
tag: tagsDataspaceControlPlane[0].name,
|
|
65
68
|
method: "POST",
|
|
66
69
|
path: `${baseRouteName}/transfers/:pid/complete`,
|
|
70
|
+
skipAuth: true,
|
|
67
71
|
handler: async (httpRequestContext, request) => completeTransferHandler(httpRequestContext, componentName, request)
|
|
68
72
|
});
|
|
69
73
|
// POST /transfers/:pid/suspend - Suspend Transfer Process (DSP)
|
|
@@ -73,6 +77,7 @@ export function generateRestRoutesDataspaceControlPlane(baseRouteName, component
|
|
|
73
77
|
tag: tagsDataspaceControlPlane[0].name,
|
|
74
78
|
method: "POST",
|
|
75
79
|
path: `${baseRouteName}/transfers/:pid/suspend`,
|
|
80
|
+
skipAuth: true,
|
|
76
81
|
handler: async (httpRequestContext, request) => suspendTransferHandler(httpRequestContext, componentName, request)
|
|
77
82
|
});
|
|
78
83
|
// POST /transfers/:pid/terminate - Terminate Transfer Process (DSP)
|
|
@@ -82,6 +87,7 @@ export function generateRestRoutesDataspaceControlPlane(baseRouteName, component
|
|
|
82
87
|
tag: tagsDataspaceControlPlane[0].name,
|
|
83
88
|
method: "POST",
|
|
84
89
|
path: `${baseRouteName}/transfers/:pid/terminate`,
|
|
90
|
+
skipAuth: true,
|
|
85
91
|
handler: async (httpRequestContext, request) => terminateTransferHandler(httpRequestContext, componentName, request)
|
|
86
92
|
});
|
|
87
93
|
return routes;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dataspaceControlPlaneRoutes.js","sourceRoot":"","sources":["../../src/dataspaceControlPlaneRoutes.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright 2025 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport type {\n\tIHostingComponent,\n\tIHttpRequestContext,\n\tIRestRoute,\n\tITag\n} from \"@twin.org/api-models\";\nimport { ComponentFactory, Guards } from \"@twin.org/core\";\nimport type {\n\tIDataspaceControlPlaneComponent,\n\tIRequestTransferRequest,\n\tIRequestTransferResponse,\n\tIGetTransferProcessRequest,\n\tIGetTransferProcessResponse,\n\tIStartTransferRequest,\n\tIStartTransferResponse,\n\tICompleteTransferRequest,\n\tICompleteTransferResponse,\n\tISuspendTransferRequest,\n\tISuspendTransferResponse,\n\tITerminateTransferRequest,\n\tITerminateTransferResponse\n} from \"@twin.org/dataspace-models\";\nimport { nameof } from \"@twin.org/nameof\";\nimport { HeaderHelper, HeaderTypes } from \"@twin.org/web\";\nimport { transformErrorToStatusCode } from \"./utils/transferErrorUtils.js\";\n\n/**\n * The source used when communicating about these routes.\n */\nconst ROUTES_SOURCE = \"dataspaceControlPlaneRoutes\";\n\n/**\n * The tags to associate with the DSP protocol routes.\n */\nexport const tagsDataspaceControlPlane: ITag[] = [\n\t{\n\t\tname: \"Transfer Process\",\n\t\tdescription:\n\t\t\t\"DSP Transfer Process Protocol endpoints for initiating and managing data transfers.\"\n\t}\n];\n\n/**\n * The REST routes for dataspace control plane (DSP Protocol only).\n * These routes implement the Eclipse Dataspace Protocol Transfer Process Protocol.\n *\n * Contract Negotiation is handled internally via PNP callbacks — no REST endpoints needed.\n * PNP registers its own inbound callback routes for negotiation messages from providers.\n *\n * @param baseRouteName Prefix to prepend to the paths.\n * @param componentName The name of the component to use in the routes stored in the ComponentFactory.\n * @returns The generated DSP protocol routes.\n */\nexport function generateRestRoutesDataspaceControlPlane(\n\tbaseRouteName: string,\n\tcomponentName: string\n): IRestRoute[] {\n\tconst routes: IRestRoute[] = [];\n\n\t// ============================================================================\n\t// TRANSFER PROCESS PROTOCOL (DSP)\n\t// ============================================================================\n\n\t// POST /transfers/request - Request Transfer Process (DSP)\n\troutes.push({\n\t\toperationId: \"requestTransfer\",\n\t\tsummary: \"Request Transfer Process (DSP)\",\n\t\ttag: tagsDataspaceControlPlane[0].name,\n\t\tmethod: \"POST\",\n\t\tpath: `${baseRouteName}/transfers/request`,\n\t\thandler: async (httpRequestContext, request) =>\n\t\t\trequestTransferHandler(httpRequestContext, componentName, request)\n\t});\n\n\t// GET /transfers/:pid - Get Transfer Process state (DSP)\n\troutes.push({\n\t\toperationId: \"getTransferProcess\",\n\t\tsummary: \"Get Transfer Process state (DSP)\",\n\t\ttag: tagsDataspaceControlPlane[0].name,\n\t\tmethod: \"GET\",\n\t\tpath: `${baseRouteName}/transfers/:pid`,\n\t\thandler: async (httpRequestContext, request) =>\n\t\t\tgetTransferProcessHandler(httpRequestContext, componentName, request)\n\t});\n\n\t// POST /transfers/:pid/start - Start Transfer Process (DSP)\n\troutes.push({\n\t\toperationId: \"startTransfer\",\n\t\tsummary: \"Start Transfer Process (DSP)\",\n\t\ttag: tagsDataspaceControlPlane[0].name,\n\t\tmethod: \"POST\",\n\t\tpath: `${baseRouteName}/transfers/:pid/start`,\n\t\thandler: async (httpRequestContext, request) =>\n\t\t\tstartTransferHandler(httpRequestContext, componentName, request)\n\t});\n\n\t// POST /transfers/:pid/complete - Complete Transfer Process (DSP)\n\troutes.push({\n\t\toperationId: \"completeTransfer\",\n\t\tsummary: \"Complete Transfer Process (DSP)\",\n\t\ttag: tagsDataspaceControlPlane[0].name,\n\t\tmethod: \"POST\",\n\t\tpath: `${baseRouteName}/transfers/:pid/complete`,\n\t\thandler: async (httpRequestContext, request) =>\n\t\t\tcompleteTransferHandler(httpRequestContext, componentName, request)\n\t});\n\n\t// POST /transfers/:pid/suspend - Suspend Transfer Process (DSP)\n\troutes.push({\n\t\toperationId: \"suspendTransfer\",\n\t\tsummary: \"Suspend Transfer Process (DSP)\",\n\t\ttag: tagsDataspaceControlPlane[0].name,\n\t\tmethod: \"POST\",\n\t\tpath: `${baseRouteName}/transfers/:pid/suspend`,\n\t\thandler: async (httpRequestContext, request) =>\n\t\t\tsuspendTransferHandler(httpRequestContext, componentName, request)\n\t});\n\n\t// POST /transfers/:pid/terminate - Terminate Transfer Process (DSP)\n\troutes.push({\n\t\toperationId: \"terminateTransfer\",\n\t\tsummary: \"Terminate Transfer Process (DSP)\",\n\t\ttag: tagsDataspaceControlPlane[0].name,\n\t\tmethod: \"POST\",\n\t\tpath: `${baseRouteName}/transfers/:pid/terminate`,\n\t\thandler: async (httpRequestContext, request) =>\n\t\t\tterminateTransferHandler(httpRequestContext, componentName, request)\n\t});\n\n\treturn routes;\n}\n\n// ============================================================================\n// TRANSFER PROCESS PROTOCOL HANDLERS\n// ============================================================================\n\n/**\n * Request Transfer Process handler.\n * @param httpRequestContext The request context for the API.\n * @param componentName The name of the component to use.\n * @param request The API request containing headers and body.\n * @returns The response.\n */\nasync function requestTransferHandler(\n\thttpRequestContext: IHttpRequestContext,\n\tcomponentName: string,\n\trequest: IRequestTransferRequest\n): Promise<IRequestTransferResponse> {\n\tGuards.object(ROUTES_SOURCE, nameof(request), request);\n\tGuards.object(ROUTES_SOURCE, nameof(request.body), request.body);\n\n\tconst trustPayload = HeaderHelper.extractBearer(request.headers[HeaderTypes.Authorization]);\n\n\tconst component = ComponentFactory.get<IDataspaceControlPlaneComponent>(componentName);\n\tconst result = await component.requestTransfer(request.body, trustPayload);\n\n\treturn {\n\t\tbody: result,\n\t\tstatusCode: transformErrorToStatusCode(result)\n\t};\n}\n\n/**\n * Get Transfer Process handler.\n * @param httpRequestContext The request context for the API.\n * @param componentName The name of the component to use.\n * @param request The API request containing headers and path parameters.\n * @returns The response.\n */\nasync function getTransferProcessHandler(\n\thttpRequestContext: IHttpRequestContext,\n\tcomponentName: string,\n\trequest: IGetTransferProcessRequest\n): Promise<IGetTransferProcessResponse> {\n\tGuards.object(ROUTES_SOURCE, nameof(request), request);\n\tGuards.object(ROUTES_SOURCE, nameof(request.pathParams), request.pathParams);\n\tGuards.stringValue(ROUTES_SOURCE, nameof(request.pathParams.pid), request.pathParams.pid);\n\n\tconst trustPayload = HeaderHelper.extractBearer(request.headers[HeaderTypes.Authorization]);\n\n\tconst component = ComponentFactory.get<IDataspaceControlPlaneComponent>(componentName);\n\tconst result = await component.getTransferProcess(request.pathParams.pid, trustPayload);\n\n\treturn {\n\t\tbody: result,\n\t\tstatusCode: transformErrorToStatusCode(result)\n\t};\n}\n\n/**\n * Start Transfer Process handler.\n * @param httpRequestContext The request context for the API.\n * @param componentName The name of the component to use.\n * @param request The API request containing headers, path parameters, and body.\n * @returns The response.\n */\nasync function startTransferHandler(\n\thttpRequestContext: IHttpRequestContext,\n\tcomponentName: string,\n\trequest: IStartTransferRequest\n): Promise<IStartTransferResponse> {\n\tGuards.object(ROUTES_SOURCE, nameof(request), request);\n\tGuards.object(ROUTES_SOURCE, nameof(request.body), request.body);\n\n\tconst trustPayload = HeaderHelper.extractBearer(request.headers[HeaderTypes.Authorization]);\n\n\tconst hostingComponent = ComponentFactory.get<IHostingComponent>(\n\t\thttpRequestContext.hostingComponentType ?? \"hosting\"\n\t);\n\n\tconst component = ComponentFactory.get<IDataspaceControlPlaneComponent>(componentName);\n\tconst result = await component.startTransfer(\n\t\trequest.body,\n\t\tawait hostingComponent.getPublicOrigin(httpRequestContext.serverRequest.url),\n\t\ttrustPayload\n\t);\n\n\treturn {\n\t\tbody: result,\n\t\tstatusCode: transformErrorToStatusCode(result)\n\t};\n}\n\n/**\n * Complete Transfer Process handler.\n * @param httpRequestContext The request context for the API.\n * @param componentName The name of the component to use.\n * @param request The API request containing headers, path parameters, and body.\n * @returns The response.\n */\nasync function completeTransferHandler(\n\thttpRequestContext: IHttpRequestContext,\n\tcomponentName: string,\n\trequest: ICompleteTransferRequest\n): Promise<ICompleteTransferResponse> {\n\tGuards.object(ROUTES_SOURCE, nameof(request), request);\n\tGuards.object(ROUTES_SOURCE, nameof(request.body), request.body);\n\n\tconst trustPayload = HeaderHelper.extractBearer(request.headers[HeaderTypes.Authorization]);\n\n\tconst component = ComponentFactory.get<IDataspaceControlPlaneComponent>(componentName);\n\tconst result = await component.completeTransfer(request.body, trustPayload);\n\n\treturn {\n\t\tbody: result,\n\t\tstatusCode: transformErrorToStatusCode(result)\n\t};\n}\n\n/**\n * Suspend Transfer Process handler.\n * @param httpRequestContext The request context for the API.\n * @param componentName The name of the component to use.\n * @param request The API request containing headers, path parameters, and body.\n * @returns The response.\n */\nasync function suspendTransferHandler(\n\thttpRequestContext: IHttpRequestContext,\n\tcomponentName: string,\n\trequest: ISuspendTransferRequest\n): Promise<ISuspendTransferResponse> {\n\tGuards.object(ROUTES_SOURCE, nameof(request), request);\n\tGuards.object(ROUTES_SOURCE, nameof(request.body), request.body);\n\n\tconst trustPayload = HeaderHelper.extractBearer(request.headers[HeaderTypes.Authorization]);\n\n\tconst component = ComponentFactory.get<IDataspaceControlPlaneComponent>(componentName);\n\tconst result = await component.suspendTransfer(request.body, trustPayload);\n\n\treturn {\n\t\tbody: result,\n\t\tstatusCode: transformErrorToStatusCode(result)\n\t};\n}\n\n/**\n * Terminate Transfer Process handler.\n * @param httpRequestContext The request context for the API.\n * @param componentName The name of the component to use.\n * @param request The API request containing headers, path parameters, and body.\n * @returns The response.\n */\nasync function terminateTransferHandler(\n\thttpRequestContext: IHttpRequestContext,\n\tcomponentName: string,\n\trequest: ITerminateTransferRequest\n): Promise<ITerminateTransferResponse> {\n\tGuards.object(ROUTES_SOURCE, nameof(request), request);\n\tGuards.object(ROUTES_SOURCE, nameof(request.body), request.body);\n\n\tconst trustPayload = HeaderHelper.extractBearer(request.headers[HeaderTypes.Authorization]);\n\n\tconst component = ComponentFactory.get<IDataspaceControlPlaneComponent>(componentName);\n\tconst result = await component.terminateTransfer(request.body, trustPayload);\n\n\treturn {\n\t\tbody: result,\n\t\tstatusCode: transformErrorToStatusCode(result)\n\t};\n}\n"]}
|
|
1
|
+
{"version":3,"file":"dataspaceControlPlaneRoutes.js","sourceRoot":"","sources":["../../src/dataspaceControlPlaneRoutes.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright 2025 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport type {\n\tIHostingComponent,\n\tIHttpRequestContext,\n\tIRestRoute,\n\tITag\n} from \"@twin.org/api-models\";\nimport { ComponentFactory, Guards } from \"@twin.org/core\";\nimport type {\n\tIDataspaceControlPlaneComponent,\n\tIRequestTransferRequest,\n\tIRequestTransferResponse,\n\tIGetTransferProcessRequest,\n\tIGetTransferProcessResponse,\n\tIStartTransferRequest,\n\tIStartTransferResponse,\n\tICompleteTransferRequest,\n\tICompleteTransferResponse,\n\tISuspendTransferRequest,\n\tISuspendTransferResponse,\n\tITerminateTransferRequest,\n\tITerminateTransferResponse\n} from \"@twin.org/dataspace-models\";\nimport { nameof } from \"@twin.org/nameof\";\nimport { HeaderHelper, HeaderTypes } from \"@twin.org/web\";\nimport { transformErrorToStatusCode } from \"./utils/transferErrorUtils.js\";\n\n/**\n * The source used when communicating about these routes.\n */\nconst ROUTES_SOURCE = \"dataspaceControlPlaneRoutes\";\n\n/**\n * The tags to associate with the DSP protocol routes.\n */\nexport const tagsDataspaceControlPlane: ITag[] = [\n\t{\n\t\tname: \"Transfer Process\",\n\t\tdescription:\n\t\t\t\"DSP Transfer Process Protocol endpoints for initiating and managing data transfers.\"\n\t}\n];\n\n/**\n * The REST routes for dataspace control plane (DSP Protocol only).\n * These routes implement the Eclipse Dataspace Protocol Transfer Process Protocol.\n *\n * Contract Negotiation is handled internally via PNP callbacks — no REST endpoints needed.\n * PNP registers its own inbound callback routes for negotiation messages from providers.\n *\n * @param baseRouteName Prefix to prepend to the paths.\n * @param componentName The name of the component to use in the routes stored in the ComponentFactory.\n * @returns The generated DSP protocol routes.\n */\nexport function generateRestRoutesDataspaceControlPlane(\n\tbaseRouteName: string,\n\tcomponentName: string\n): IRestRoute[] {\n\tconst routes: IRestRoute[] = [];\n\n\t// ============================================================================\n\t// TRANSFER PROCESS PROTOCOL (DSP)\n\t// ============================================================================\n\n\t// POST /transfers/request - Request Transfer Process (DSP)\n\troutes.push({\n\t\toperationId: \"requestTransfer\",\n\t\tsummary: \"Request Transfer Process (DSP)\",\n\t\ttag: tagsDataspaceControlPlane[0].name,\n\t\tmethod: \"POST\",\n\t\tpath: `${baseRouteName}/transfers/request`,\n\t\tskipAuth: true,\n\t\thandler: async (httpRequestContext, request) =>\n\t\t\trequestTransferHandler(httpRequestContext, componentName, request)\n\t});\n\n\t// GET /transfers/:pid - Get Transfer Process state (DSP)\n\troutes.push({\n\t\toperationId: \"getTransferProcess\",\n\t\tsummary: \"Get Transfer Process state (DSP)\",\n\t\ttag: tagsDataspaceControlPlane[0].name,\n\t\tmethod: \"GET\",\n\t\tpath: `${baseRouteName}/transfers/:pid`,\n\t\tskipAuth: true,\n\t\thandler: async (httpRequestContext, request) =>\n\t\t\tgetTransferProcessHandler(httpRequestContext, componentName, request)\n\t});\n\n\t// POST /transfers/:pid/start - Start Transfer Process (DSP)\n\troutes.push({\n\t\toperationId: \"startTransfer\",\n\t\tsummary: \"Start Transfer Process (DSP)\",\n\t\ttag: tagsDataspaceControlPlane[0].name,\n\t\tmethod: \"POST\",\n\t\tpath: `${baseRouteName}/transfers/:pid/start`,\n\t\tskipAuth: true,\n\t\thandler: async (httpRequestContext, request) =>\n\t\t\tstartTransferHandler(httpRequestContext, componentName, request)\n\t});\n\n\t// POST /transfers/:pid/complete - Complete Transfer Process (DSP)\n\troutes.push({\n\t\toperationId: \"completeTransfer\",\n\t\tsummary: \"Complete Transfer Process (DSP)\",\n\t\ttag: tagsDataspaceControlPlane[0].name,\n\t\tmethod: \"POST\",\n\t\tpath: `${baseRouteName}/transfers/:pid/complete`,\n\t\tskipAuth: true,\n\t\thandler: async (httpRequestContext, request) =>\n\t\t\tcompleteTransferHandler(httpRequestContext, componentName, request)\n\t});\n\n\t// POST /transfers/:pid/suspend - Suspend Transfer Process (DSP)\n\troutes.push({\n\t\toperationId: \"suspendTransfer\",\n\t\tsummary: \"Suspend Transfer Process (DSP)\",\n\t\ttag: tagsDataspaceControlPlane[0].name,\n\t\tmethod: \"POST\",\n\t\tpath: `${baseRouteName}/transfers/:pid/suspend`,\n\t\tskipAuth: true,\n\t\thandler: async (httpRequestContext, request) =>\n\t\t\tsuspendTransferHandler(httpRequestContext, componentName, request)\n\t});\n\n\t// POST /transfers/:pid/terminate - Terminate Transfer Process (DSP)\n\troutes.push({\n\t\toperationId: \"terminateTransfer\",\n\t\tsummary: \"Terminate Transfer Process (DSP)\",\n\t\ttag: tagsDataspaceControlPlane[0].name,\n\t\tmethod: \"POST\",\n\t\tpath: `${baseRouteName}/transfers/:pid/terminate`,\n\t\tskipAuth: true,\n\t\thandler: async (httpRequestContext, request) =>\n\t\t\tterminateTransferHandler(httpRequestContext, componentName, request)\n\t});\n\n\treturn routes;\n}\n\n// ============================================================================\n// TRANSFER PROCESS PROTOCOL HANDLERS\n// ============================================================================\n\n/**\n * Request Transfer Process handler.\n * @param httpRequestContext The request context for the API.\n * @param componentName The name of the component to use.\n * @param request The API request containing headers and body.\n * @returns The response.\n */\nasync function requestTransferHandler(\n\thttpRequestContext: IHttpRequestContext,\n\tcomponentName: string,\n\trequest: IRequestTransferRequest\n): Promise<IRequestTransferResponse> {\n\tGuards.object(ROUTES_SOURCE, nameof(request), request);\n\tGuards.object(ROUTES_SOURCE, nameof(request.body), request.body);\n\n\tconst trustPayload = HeaderHelper.extractBearer(request.headers[HeaderTypes.Authorization]);\n\n\tconst component = ComponentFactory.get<IDataspaceControlPlaneComponent>(componentName);\n\tconst result = await component.requestTransfer(request.body, trustPayload);\n\n\treturn {\n\t\tbody: result,\n\t\tstatusCode: transformErrorToStatusCode(result)\n\t};\n}\n\n/**\n * Get Transfer Process handler.\n * @param httpRequestContext The request context for the API.\n * @param componentName The name of the component to use.\n * @param request The API request containing headers and path parameters.\n * @returns The response.\n */\nasync function getTransferProcessHandler(\n\thttpRequestContext: IHttpRequestContext,\n\tcomponentName: string,\n\trequest: IGetTransferProcessRequest\n): Promise<IGetTransferProcessResponse> {\n\tGuards.object(ROUTES_SOURCE, nameof(request), request);\n\tGuards.object(ROUTES_SOURCE, nameof(request.pathParams), request.pathParams);\n\tGuards.stringValue(ROUTES_SOURCE, nameof(request.pathParams.pid), request.pathParams.pid);\n\n\tconst trustPayload = HeaderHelper.extractBearer(request.headers[HeaderTypes.Authorization]);\n\n\tconst component = ComponentFactory.get<IDataspaceControlPlaneComponent>(componentName);\n\tconst result = await component.getTransferProcess(request.pathParams.pid, trustPayload);\n\n\treturn {\n\t\tbody: result,\n\t\tstatusCode: transformErrorToStatusCode(result)\n\t};\n}\n\n/**\n * Start Transfer Process handler.\n * @param httpRequestContext The request context for the API.\n * @param componentName The name of the component to use.\n * @param request The API request containing headers, path parameters, and body.\n * @returns The response.\n */\nasync function startTransferHandler(\n\thttpRequestContext: IHttpRequestContext,\n\tcomponentName: string,\n\trequest: IStartTransferRequest\n): Promise<IStartTransferResponse> {\n\tGuards.object(ROUTES_SOURCE, nameof(request), request);\n\tGuards.object(ROUTES_SOURCE, nameof(request.body), request.body);\n\n\tconst trustPayload = HeaderHelper.extractBearer(request.headers[HeaderTypes.Authorization]);\n\n\tconst hostingComponent = ComponentFactory.get<IHostingComponent>(\n\t\thttpRequestContext.hostingComponentType ?? \"hosting\"\n\t);\n\n\tconst component = ComponentFactory.get<IDataspaceControlPlaneComponent>(componentName);\n\tconst result = await component.startTransfer(\n\t\trequest.body,\n\t\tawait hostingComponent.getPublicOrigin(httpRequestContext.serverRequest.url),\n\t\ttrustPayload\n\t);\n\n\treturn {\n\t\tbody: result,\n\t\tstatusCode: transformErrorToStatusCode(result)\n\t};\n}\n\n/**\n * Complete Transfer Process handler.\n * @param httpRequestContext The request context for the API.\n * @param componentName The name of the component to use.\n * @param request The API request containing headers, path parameters, and body.\n * @returns The response.\n */\nasync function completeTransferHandler(\n\thttpRequestContext: IHttpRequestContext,\n\tcomponentName: string,\n\trequest: ICompleteTransferRequest\n): Promise<ICompleteTransferResponse> {\n\tGuards.object(ROUTES_SOURCE, nameof(request), request);\n\tGuards.object(ROUTES_SOURCE, nameof(request.body), request.body);\n\n\tconst trustPayload = HeaderHelper.extractBearer(request.headers[HeaderTypes.Authorization]);\n\n\tconst component = ComponentFactory.get<IDataspaceControlPlaneComponent>(componentName);\n\tconst result = await component.completeTransfer(request.body, trustPayload);\n\n\treturn {\n\t\tbody: result,\n\t\tstatusCode: transformErrorToStatusCode(result)\n\t};\n}\n\n/**\n * Suspend Transfer Process handler.\n * @param httpRequestContext The request context for the API.\n * @param componentName The name of the component to use.\n * @param request The API request containing headers, path parameters, and body.\n * @returns The response.\n */\nasync function suspendTransferHandler(\n\thttpRequestContext: IHttpRequestContext,\n\tcomponentName: string,\n\trequest: ISuspendTransferRequest\n): Promise<ISuspendTransferResponse> {\n\tGuards.object(ROUTES_SOURCE, nameof(request), request);\n\tGuards.object(ROUTES_SOURCE, nameof(request.body), request.body);\n\n\tconst trustPayload = HeaderHelper.extractBearer(request.headers[HeaderTypes.Authorization]);\n\n\tconst component = ComponentFactory.get<IDataspaceControlPlaneComponent>(componentName);\n\tconst result = await component.suspendTransfer(request.body, trustPayload);\n\n\treturn {\n\t\tbody: result,\n\t\tstatusCode: transformErrorToStatusCode(result)\n\t};\n}\n\n/**\n * Terminate Transfer Process handler.\n * @param httpRequestContext The request context for the API.\n * @param componentName The name of the component to use.\n * @param request The API request containing headers, path parameters, and body.\n * @returns The response.\n */\nasync function terminateTransferHandler(\n\thttpRequestContext: IHttpRequestContext,\n\tcomponentName: string,\n\trequest: ITerminateTransferRequest\n): Promise<ITerminateTransferResponse> {\n\tGuards.object(ROUTES_SOURCE, nameof(request), request);\n\tGuards.object(ROUTES_SOURCE, nameof(request.body), request.body);\n\n\tconst trustPayload = HeaderHelper.extractBearer(request.headers[HeaderTypes.Authorization]);\n\n\tconst component = ComponentFactory.get<IDataspaceControlPlaneComponent>(componentName);\n\tconst result = await component.terminateTransfer(request.body, trustPayload);\n\n\treturn {\n\t\tbody: result,\n\t\tstatusCode: transformErrorToStatusCode(result)\n\t};\n}\n"]}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ContextIdKeys, ContextIdStore } from "@twin.org/context";
|
|
2
|
-
import { ArrayHelper, BaseError, ComponentFactory, Converter, GeneralError, Guards, Is, NotFoundError,
|
|
2
|
+
import { ArrayHelper, BaseError, ComponentFactory, Converter, GeneralError, Guards, Is, NotFoundError, RandomHelper, StringHelper, UnauthorizedError, ValidationError } from "@twin.org/core";
|
|
3
3
|
import { JsonLdHelper } from "@twin.org/data-json-ld";
|
|
4
4
|
import { DataspaceAppFactory, TransferProcessRole } from "@twin.org/dataspace-models";
|
|
5
5
|
import { EngineCoreFactory } from "@twin.org/engine-models";
|
|
@@ -333,10 +333,9 @@ export class DataspaceControlPlaneService {
|
|
|
333
333
|
* Called by: Consumer when it wants to request a new Transfer Process
|
|
334
334
|
*/
|
|
335
335
|
async requestTransfer(request, trustPayload) {
|
|
336
|
-
await TrustHelper.verifyTrust(this._trustComponent, trustPayload, "requestTransfer");
|
|
337
|
-
const validationFailures =
|
|
338
|
-
|
|
339
|
-
if (!isConformant) {
|
|
336
|
+
const trustInfo = await TrustHelper.verifyTrust(this._trustComponent, trustPayload, "requestTransfer");
|
|
337
|
+
const validationFailures = await DataspaceProtocolHelper.validate(JsonLdHelper.toNodeObject(request));
|
|
338
|
+
if (validationFailures.length > 0) {
|
|
340
339
|
await this._loggingComponent?.log({
|
|
341
340
|
level: "error",
|
|
342
341
|
source: DataspaceControlPlaneService.CLASS_NAME,
|
|
@@ -344,9 +343,7 @@ export class DataspaceControlPlaneService {
|
|
|
344
343
|
message: "invalidTransferRequest",
|
|
345
344
|
data: { validationFailures }
|
|
346
345
|
});
|
|
347
|
-
|
|
348
|
-
validationFailures
|
|
349
|
-
});
|
|
346
|
+
return transformToTransferError(new ValidationError(DataspaceControlPlaneService.CLASS_NAME, "request", validationFailures), request);
|
|
350
347
|
}
|
|
351
348
|
const providerPid = `urn:uuid:${RandomHelper.generateUuidV7()}`;
|
|
352
349
|
let datasetId;
|
|
@@ -372,9 +369,17 @@ export class DataspaceControlPlaneService {
|
|
|
372
369
|
});
|
|
373
370
|
}
|
|
374
371
|
const assigneeIdentity = OdrlPolicyHelper.extractAssigneeIdentity(agreement);
|
|
375
|
-
|
|
372
|
+
const assigneeIds = ArrayHelper.fromObjectOrArray(assigneeIdentity);
|
|
373
|
+
if (!assigneeIds.includes(trustInfo.identity)) {
|
|
374
|
+
throw new UnauthorizedError(DataspaceControlPlaneService.CLASS_NAME, "callerNotAuthorizedForAgreement");
|
|
375
|
+
}
|
|
376
|
+
consumerIdentity = trustInfo.identity;
|
|
376
377
|
const assignerIdentity = OdrlPolicyHelper.extractAssignerIdentity(agreement);
|
|
377
|
-
|
|
378
|
+
const assignerIds = ArrayHelper.fromObjectOrArray(assignerIdentity);
|
|
379
|
+
if (assignerIds.length > 1) {
|
|
380
|
+
throw new GeneralError(DataspaceControlPlaneService.CLASS_NAME, "multipleAssignersNotSupported");
|
|
381
|
+
}
|
|
382
|
+
providerIdentity = assignerIds[0];
|
|
378
383
|
datasetId = this.extractDatasetId(agreement);
|
|
379
384
|
await this.validateCatalogDataset(datasetId, agreement);
|
|
380
385
|
policies = [agreement];
|
|
@@ -390,8 +395,8 @@ export class DataspaceControlPlaneService {
|
|
|
390
395
|
state: DataspaceProtocolTransferProcessStateType.REQUESTED,
|
|
391
396
|
agreementId: request.agreementId,
|
|
392
397
|
datasetId,
|
|
393
|
-
consumerIdentity
|
|
394
|
-
providerIdentity
|
|
398
|
+
consumerIdentity,
|
|
399
|
+
providerIdentity,
|
|
395
400
|
// offerId should reference Catalog Offer (via Agreement)
|
|
396
401
|
// For now, use agreementId as reference (proper flow: Catalog → Negotiation → Agreement)
|
|
397
402
|
offerId: request.agreementId,
|
|
@@ -437,16 +442,21 @@ export class DataspaceControlPlaneService {
|
|
|
437
442
|
* Role Performed: Provider / Consumer
|
|
438
443
|
*/
|
|
439
444
|
async startTransfer(message, publicOrigin, trustPayload) {
|
|
440
|
-
await TrustHelper.verifyTrust(this._trustComponent, trustPayload, "startTransfer");
|
|
441
|
-
const validationFailures =
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
445
|
+
const trustInfo = await TrustHelper.verifyTrust(this._trustComponent, trustPayload, "startTransfer");
|
|
446
|
+
const validationFailures = await DataspaceProtocolHelper.validate(JsonLdHelper.toNodeObject(message));
|
|
447
|
+
if (validationFailures.length > 0) {
|
|
448
|
+
await this._loggingComponent?.log({
|
|
449
|
+
level: "error",
|
|
450
|
+
source: DataspaceControlPlaneService.CLASS_NAME,
|
|
451
|
+
ts: Date.now(),
|
|
452
|
+
message: "invalidTransferStartMessage",
|
|
453
|
+
data: { validationFailures }
|
|
454
|
+
});
|
|
455
|
+
return transformToTransferError(new ValidationError(DataspaceControlPlaneService.CLASS_NAME, "message", validationFailures), message);
|
|
447
456
|
}
|
|
448
457
|
try {
|
|
449
458
|
const { entity, role } = await this.lookupTransferByMessage(message);
|
|
459
|
+
this.validateCallerIsProvider(trustInfo.identity, entity);
|
|
450
460
|
if (entity.state !== DataspaceProtocolTransferProcessStateType.REQUESTED &&
|
|
451
461
|
entity.state !== DataspaceProtocolTransferProcessStateType.SUSPENDED) {
|
|
452
462
|
return transformToTransferError(new GeneralError(DataspaceControlPlaneService.CLASS_NAME, "invalidStateForStart", {
|
|
@@ -501,13 +511,21 @@ export class DataspaceControlPlaneService {
|
|
|
501
511
|
providerPid: entity.providerPid
|
|
502
512
|
}), message);
|
|
503
513
|
}
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
514
|
+
// Provider signs the data access token with its own identity.
|
|
515
|
+
// The subject contains the transfer context claims that the data plane
|
|
516
|
+
// will verify when the consumer presents this token.
|
|
517
|
+
if (!Is.stringValue(entity.providerIdentity)) {
|
|
518
|
+
throw new GeneralError(DataspaceControlPlaneService.CLASS_NAME, "providerIdentityMissing");
|
|
519
|
+
}
|
|
520
|
+
const accessToken = await this._trustComponent.generate(entity.providerIdentity, this._overrideTrustGeneratorType, {
|
|
521
|
+
subject: {
|
|
522
|
+
consumerPid: entity.consumerPid,
|
|
523
|
+
providerPid: entity.providerPid,
|
|
524
|
+
agreementId: entity.agreementId,
|
|
525
|
+
datasetId: entity.datasetId
|
|
526
|
+
}
|
|
509
527
|
});
|
|
510
|
-
const tokenString =
|
|
528
|
+
const tokenString = accessToken;
|
|
511
529
|
const fullEndpoint = `${publicOrigin}/${this._dataPlanePath}`;
|
|
512
530
|
response.dataAddress = {
|
|
513
531
|
"@type": DataspaceProtocolTransferProcessTypes.DataAddress,
|
|
@@ -586,14 +604,21 @@ export class DataspaceControlPlaneService {
|
|
|
586
604
|
* @returns Transfer Process (DSP compliant) with state COMPLETED, or TransferError if the operation fails.
|
|
587
605
|
*/
|
|
588
606
|
async completeTransfer(message, trustPayload) {
|
|
589
|
-
await TrustHelper.verifyTrust(this._trustComponent, trustPayload, "completeTransfer");
|
|
590
|
-
const validationFailures =
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
607
|
+
const trustInfo = await TrustHelper.verifyTrust(this._trustComponent, trustPayload, "completeTransfer");
|
|
608
|
+
const validationFailures = await DataspaceProtocolHelper.validate(JsonLdHelper.toNodeObject(message));
|
|
609
|
+
if (validationFailures.length > 0) {
|
|
610
|
+
await this._loggingComponent?.log({
|
|
611
|
+
level: "error",
|
|
612
|
+
source: DataspaceControlPlaneService.CLASS_NAME,
|
|
613
|
+
ts: Date.now(),
|
|
614
|
+
message: "invalidTransferCompletionMessage",
|
|
615
|
+
data: { validationFailures }
|
|
616
|
+
});
|
|
617
|
+
return transformToTransferError(new ValidationError(DataspaceControlPlaneService.CLASS_NAME, "message", validationFailures), message);
|
|
594
618
|
}
|
|
595
619
|
try {
|
|
596
620
|
const { entity, role } = await this.lookupTransferByMessage(message);
|
|
621
|
+
this.validateCallerIsConsumer(trustInfo.identity, entity);
|
|
597
622
|
if (entity.state !== DataspaceProtocolTransferProcessStateType.STARTED) {
|
|
598
623
|
return transformToTransferError(new GeneralError(DataspaceControlPlaneService.CLASS_NAME, "invalidStateForComplete", {
|
|
599
624
|
consumerPid: entity.consumerPid,
|
|
@@ -634,14 +659,21 @@ export class DataspaceControlPlaneService {
|
|
|
634
659
|
* @returns Transfer Process (DSP compliant) with state SUSPENDED, or TransferError if the operation fails.
|
|
635
660
|
*/
|
|
636
661
|
async suspendTransfer(message, trustPayload) {
|
|
637
|
-
await TrustHelper.verifyTrust(this._trustComponent, trustPayload, "suspendTransfer");
|
|
638
|
-
const validationFailures =
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
662
|
+
const trustInfo = await TrustHelper.verifyTrust(this._trustComponent, trustPayload, "suspendTransfer");
|
|
663
|
+
const validationFailures = await DataspaceProtocolHelper.validate(JsonLdHelper.toNodeObject(message));
|
|
664
|
+
if (validationFailures.length > 0) {
|
|
665
|
+
await this._loggingComponent?.log({
|
|
666
|
+
level: "error",
|
|
667
|
+
source: DataspaceControlPlaneService.CLASS_NAME,
|
|
668
|
+
ts: Date.now(),
|
|
669
|
+
message: "invalidTransferSuspensionMessage",
|
|
670
|
+
data: { validationFailures }
|
|
671
|
+
});
|
|
672
|
+
return transformToTransferError(new ValidationError(DataspaceControlPlaneService.CLASS_NAME, "message", validationFailures), message);
|
|
642
673
|
}
|
|
643
674
|
try {
|
|
644
675
|
const { entity, role } = await this.lookupTransferByMessage(message);
|
|
676
|
+
this.validateCallerIsTransferParty(trustInfo.identity, entity);
|
|
645
677
|
if (entity.state !== DataspaceProtocolTransferProcessStateType.STARTED) {
|
|
646
678
|
return transformToTransferError(new GeneralError(DataspaceControlPlaneService.CLASS_NAME, "invalidStateForSuspend", {
|
|
647
679
|
consumerPid: entity.consumerPid,
|
|
@@ -683,14 +715,21 @@ export class DataspaceControlPlaneService {
|
|
|
683
715
|
* @returns Transfer Process (DSP compliant) with state TERMINATED, or TransferError if the operation fails.
|
|
684
716
|
*/
|
|
685
717
|
async terminateTransfer(message, trustPayload) {
|
|
686
|
-
await TrustHelper.verifyTrust(this._trustComponent, trustPayload, "terminateTransfer");
|
|
687
|
-
const validationFailures =
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
718
|
+
const trustInfo = await TrustHelper.verifyTrust(this._trustComponent, trustPayload, "terminateTransfer");
|
|
719
|
+
const validationFailures = await DataspaceProtocolHelper.validate(JsonLdHelper.toNodeObject(message));
|
|
720
|
+
if (validationFailures.length > 0) {
|
|
721
|
+
await this._loggingComponent?.log({
|
|
722
|
+
level: "error",
|
|
723
|
+
source: DataspaceControlPlaneService.CLASS_NAME,
|
|
724
|
+
ts: Date.now(),
|
|
725
|
+
message: "invalidTransferTerminationMessage",
|
|
726
|
+
data: { validationFailures }
|
|
727
|
+
});
|
|
728
|
+
return transformToTransferError(new ValidationError(DataspaceControlPlaneService.CLASS_NAME, "message", validationFailures), message);
|
|
691
729
|
}
|
|
692
730
|
try {
|
|
693
731
|
const { entity, role } = await this.lookupTransferByMessage(message);
|
|
732
|
+
this.validateCallerIsTransferParty(trustInfo.identity, entity);
|
|
694
733
|
entity.state = DataspaceProtocolTransferProcessStateType.TERMINATED;
|
|
695
734
|
entity.dateModified = new Date();
|
|
696
735
|
await this._transferProcessStorage.set(this.modelToStorageEntity(entity));
|
|
@@ -725,9 +764,10 @@ export class DataspaceControlPlaneService {
|
|
|
725
764
|
* @returns Transfer Process (DSP compliant) with current state, or TransferError if the operation fails.
|
|
726
765
|
*/
|
|
727
766
|
async getTransferProcess(pid, trustPayload) {
|
|
728
|
-
await TrustHelper.verifyTrust(this._trustComponent, trustPayload, "getTransferProcess");
|
|
767
|
+
const trustInfo = await TrustHelper.verifyTrust(this._trustComponent, trustPayload, "getTransferProcess");
|
|
729
768
|
try {
|
|
730
769
|
const { entity, role } = await this.lookupTransferByPid(pid);
|
|
770
|
+
this.validateCallerIsTransferParty(trustInfo.identity, entity);
|
|
731
771
|
await this._loggingComponent?.log({
|
|
732
772
|
level: "info",
|
|
733
773
|
source: DataspaceControlPlaneService.CLASS_NAME,
|
|
@@ -759,13 +799,15 @@ export class DataspaceControlPlaneService {
|
|
|
759
799
|
* Returns immediately with a negotiationId. The caller is notified
|
|
760
800
|
* via the registered INegotiationCallback when the negotiation completes.
|
|
761
801
|
*
|
|
802
|
+
* @param datasetId The dataset ID from the provider's catalog.
|
|
762
803
|
* @param offerId The offer ID from the provider's catalog.
|
|
763
804
|
* @param providerEndpoint The provider's contract negotiation endpoint URL.
|
|
764
805
|
* @param publicOrigin The public origin URL of this control plane (for callbacks).
|
|
765
806
|
* @param trustPayload The trust payload for authentication.
|
|
766
807
|
* @returns The negotiation ID. Use the registered callback for completion notification.
|
|
767
808
|
*/
|
|
768
|
-
async negotiateAgreement(offerId, providerEndpoint, publicOrigin, trustPayload) {
|
|
809
|
+
async negotiateAgreement(datasetId, offerId, providerEndpoint, publicOrigin, trustPayload) {
|
|
810
|
+
Guards.stringValue(DataspaceControlPlaneService.CLASS_NAME, "datasetId", datasetId);
|
|
769
811
|
Guards.stringValue(DataspaceControlPlaneService.CLASS_NAME, "offerId", offerId);
|
|
770
812
|
Guards.stringValue(DataspaceControlPlaneService.CLASS_NAME, "providerEndpoint", providerEndpoint);
|
|
771
813
|
Guards.stringValue(DataspaceControlPlaneService.CLASS_NAME, "publicOrigin", publicOrigin);
|
|
@@ -774,17 +816,19 @@ export class DataspaceControlPlaneService {
|
|
|
774
816
|
source: DataspaceControlPlaneService.CLASS_NAME,
|
|
775
817
|
ts: Date.now(),
|
|
776
818
|
message: "startingContractNegotiation",
|
|
777
|
-
data: { offerId, providerEndpoint, publicOrigin }
|
|
819
|
+
data: { datasetId, offerId, providerEndpoint, publicOrigin }
|
|
778
820
|
});
|
|
779
|
-
const catalogResult = await this._federatedCatalogueComponent.get(
|
|
821
|
+
const catalogResult = await this._federatedCatalogueComponent.get(datasetId);
|
|
780
822
|
if (isCatalogError(catalogResult)) {
|
|
781
823
|
if (isCatalogErrorName(catalogResult, NotFoundError.CLASS_NAME)) {
|
|
782
|
-
throw new NotFoundError(DataspaceControlPlaneService.CLASS_NAME, "datasetNotFoundInCatalog",
|
|
824
|
+
throw new NotFoundError(DataspaceControlPlaneService.CLASS_NAME, "datasetNotFoundInCatalog", datasetId, {
|
|
825
|
+
datasetId,
|
|
783
826
|
offerId,
|
|
784
827
|
providerEndpoint
|
|
785
828
|
});
|
|
786
829
|
}
|
|
787
830
|
throw new GeneralError(DataspaceControlPlaneService.CLASS_NAME, "catalogLookupFailedForNegotiation", {
|
|
831
|
+
datasetId,
|
|
788
832
|
offerId,
|
|
789
833
|
providerEndpoint,
|
|
790
834
|
errorCode: catalogResult.code
|
|
@@ -960,13 +1004,13 @@ export class DataspaceControlPlaneService {
|
|
|
960
1004
|
});
|
|
961
1005
|
}
|
|
962
1006
|
const assignerIdentity = OdrlPolicyHelper.extractAssignerIdentity(agreement);
|
|
963
|
-
const
|
|
964
|
-
if (
|
|
1007
|
+
const assignerIds = ArrayHelper.fromObjectOrArray(assignerIdentity);
|
|
1008
|
+
if (!assignerIds.includes(currentOrgId)) {
|
|
965
1009
|
throw new UnauthorizedError(DataspaceControlPlaneService.CLASS_NAME, "agreementAssignerMismatch", {
|
|
966
1010
|
consumerPid,
|
|
967
1011
|
agreementId: OdrlPolicyHelper.getUid(agreement),
|
|
968
1012
|
expectedOrgId: currentOrgId,
|
|
969
|
-
actualAssigner:
|
|
1013
|
+
actualAssigner: assignerIds.join(", ")
|
|
970
1014
|
});
|
|
971
1015
|
}
|
|
972
1016
|
await this._loggingComponent?.log({
|
|
@@ -1020,26 +1064,27 @@ export class DataspaceControlPlaneService {
|
|
|
1020
1064
|
}
|
|
1021
1065
|
// Extract assigner UID (can be string, array, or IOdrlParty object)
|
|
1022
1066
|
const assignerIdentity = OdrlPolicyHelper.extractAssignerIdentity(agreement);
|
|
1023
|
-
const
|
|
1024
|
-
if (
|
|
1067
|
+
const assignerIds = ArrayHelper.fromObjectOrArray(assignerIdentity);
|
|
1068
|
+
if (!assignerIds.includes(currentOrgId)) {
|
|
1025
1069
|
throw new UnauthorizedError(DataspaceControlPlaneService.CLASS_NAME, "agreementAssignerMismatchProvider", {
|
|
1026
1070
|
providerPid,
|
|
1027
1071
|
agreementId: OdrlPolicyHelper.getUid(agreement),
|
|
1028
1072
|
expectedOrgId: currentOrgId,
|
|
1029
|
-
actualAssigner:
|
|
1073
|
+
actualAssigner: assignerIds.join(", ")
|
|
1030
1074
|
});
|
|
1031
1075
|
}
|
|
1032
1076
|
// Validate that Agreement assignee matches expected consumer identity
|
|
1033
1077
|
// This ensures we're pushing to the correct consumer
|
|
1034
1078
|
// If assignee is undefined but consumerIdentity has a value, this is also a mismatch
|
|
1035
1079
|
const assigneeIdentity = OdrlPolicyHelper.extractAssigneeIdentity(agreement);
|
|
1036
|
-
const
|
|
1037
|
-
if (
|
|
1080
|
+
const assigneeIds = ArrayHelper.fromObjectOrArray(assigneeIdentity);
|
|
1081
|
+
if (!Is.stringValue(entity.consumerIdentity) ||
|
|
1082
|
+
!assigneeIds.includes(entity.consumerIdentity)) {
|
|
1038
1083
|
throw new UnauthorizedError(DataspaceControlPlaneService.CLASS_NAME, "agreementAssigneeMismatch", {
|
|
1039
1084
|
providerPid,
|
|
1040
1085
|
agreementId: OdrlPolicyHelper.getUid(agreement),
|
|
1041
1086
|
expectedConsumerIdentity: entity.consumerIdentity,
|
|
1042
|
-
actualAssignee:
|
|
1087
|
+
actualAssignee: assigneeIds.join(", ")
|
|
1043
1088
|
});
|
|
1044
1089
|
}
|
|
1045
1090
|
await this._loggingComponent?.log({
|
|
@@ -1189,6 +1234,42 @@ export class DataspaceControlPlaneService {
|
|
|
1189
1234
|
}
|
|
1190
1235
|
return agreement;
|
|
1191
1236
|
}
|
|
1237
|
+
/**
|
|
1238
|
+
* Validate that the caller's verified identity matches the consumer of a transfer process.
|
|
1239
|
+
* @param callerIdentity The identity from the verified trust token.
|
|
1240
|
+
* @param entity The transfer process entity.
|
|
1241
|
+
* @throws UnauthorizedError if the caller is not the consumer.
|
|
1242
|
+
* @internal
|
|
1243
|
+
*/
|
|
1244
|
+
validateCallerIsConsumer(callerIdentity, entity) {
|
|
1245
|
+
if (callerIdentity !== entity.consumerIdentity) {
|
|
1246
|
+
throw new UnauthorizedError(DataspaceControlPlaneService.CLASS_NAME, "callerNotAuthorizedAsConsumer");
|
|
1247
|
+
}
|
|
1248
|
+
}
|
|
1249
|
+
/**
|
|
1250
|
+
* Validate that the caller's verified identity matches the provider of a transfer process.
|
|
1251
|
+
* @param callerIdentity The identity from the verified trust token.
|
|
1252
|
+
* @param entity The transfer process entity.
|
|
1253
|
+
* @throws UnauthorizedError if the caller is not the provider.
|
|
1254
|
+
* @internal
|
|
1255
|
+
*/
|
|
1256
|
+
validateCallerIsProvider(callerIdentity, entity) {
|
|
1257
|
+
if (callerIdentity !== entity.providerIdentity) {
|
|
1258
|
+
throw new UnauthorizedError(DataspaceControlPlaneService.CLASS_NAME, "callerNotAuthorizedAsProvider");
|
|
1259
|
+
}
|
|
1260
|
+
}
|
|
1261
|
+
/**
|
|
1262
|
+
* Validate that the caller's verified identity matches either the consumer or provider of a transfer process.
|
|
1263
|
+
* @param callerIdentity The identity from the verified trust token.
|
|
1264
|
+
* @param entity The transfer process entity.
|
|
1265
|
+
* @throws UnauthorizedError if the caller is not a party to the transfer.
|
|
1266
|
+
* @internal
|
|
1267
|
+
*/
|
|
1268
|
+
validateCallerIsTransferParty(callerIdentity, entity) {
|
|
1269
|
+
if (callerIdentity !== entity.consumerIdentity && callerIdentity !== entity.providerIdentity) {
|
|
1270
|
+
throw new UnauthorizedError(DataspaceControlPlaneService.CLASS_NAME, "callerNotAuthorizedForTransfer");
|
|
1271
|
+
}
|
|
1272
|
+
}
|
|
1192
1273
|
/**
|
|
1193
1274
|
* Extract dataset ID from Agreement target.
|
|
1194
1275
|
* @param agreement Agreement.
|
|
@@ -1322,6 +1403,10 @@ export class DataspaceControlPlaneService {
|
|
|
1322
1403
|
async validateAgreementMatchesOffer(agreement, catalogDataset) {
|
|
1323
1404
|
Guards.object(DataspaceControlPlaneService.CLASS_NAME, "agreement", agreement);
|
|
1324
1405
|
Guards.object(DataspaceControlPlaneService.CLASS_NAME, "catalogDataset", catalogDataset);
|
|
1406
|
+
const datasetId = getJsonLdId(catalogDataset);
|
|
1407
|
+
if (!Is.stringValue(datasetId)) {
|
|
1408
|
+
throw new NotFoundError(DataspaceControlPlaneService.CLASS_NAME, "catalogDatasetMissingId");
|
|
1409
|
+
}
|
|
1325
1410
|
const rawOffers = this.getCatalogDatasetPolicies(catalogDataset);
|
|
1326
1411
|
if (!Is.arrayValue(rawOffers)) {
|
|
1327
1412
|
await this._loggingComponent?.log({
|
|
@@ -1369,15 +1454,20 @@ export class DataspaceControlPlaneService {
|
|
|
1369
1454
|
data: {
|
|
1370
1455
|
agreementId: OdrlPolicyHelper.getUid(agreement) ?? "",
|
|
1371
1456
|
offerId: OdrlPolicyHelper.getUid(matchingOffer) ?? "",
|
|
1372
|
-
datasetId
|
|
1457
|
+
datasetId
|
|
1373
1458
|
}
|
|
1374
1459
|
});
|
|
1375
1460
|
}
|
|
1376
1461
|
/**
|
|
1377
1462
|
* Check if an Agreement is derived from an Offer.
|
|
1463
|
+
* Per the DS Protocol spec, Offers within a Dataset's hasPolicy array must NOT
|
|
1464
|
+
* include an explicit "target" property — the target is implicitly the Dataset itself.
|
|
1465
|
+
* When the offer has no explicit targets, we use the datasetId as the implicit target
|
|
1466
|
+
* so that the comparison with the agreement's target can succeed.
|
|
1378
1467
|
* @param agreement Agreement to check.
|
|
1379
1468
|
* @param offer Offer to compare against.
|
|
1380
1469
|
* @returns True if Agreement appears derived from Offer.
|
|
1470
|
+
* @see https://eclipse-dataspace-protocol-base.github.io/DataspaceProtocol/2025-1-err1/#lower-level-types
|
|
1381
1471
|
* @internal
|
|
1382
1472
|
*/
|
|
1383
1473
|
isPolicyDerivedFrom(agreement, offer) {
|