1
- import {
2
- BadRequestHttpError ,
3
- createErrorMessage ,
4
- ForbiddenHttpError ,
5
- getLoggerFor ,
6
- HttpErrorClass ,
7
- KeyValueStorage
8
- } from '@solid/community-server' ;
9
- import { v4 } from 'uuid' ;
10
- import { AccessToken , Permission , Requirements } from '..' ;
1
+ import { createErrorMessage , getLoggerFor , KeyValueStorage } from '@solid/community-server' ;
2
+ import { Requirements } from '../credentials/Requirements' ;
11
3
import { Verifier } from '../credentials/verify/Verifier' ;
12
- import { NeedInfoError } from '../errors/NeedInfoError' ;
13
4
import { ContractManager } from '../policies/contracts/ContractManager' ;
14
5
import { TicketingStrategy } from '../ticketing/strategy/TicketingStrategy' ;
15
6
import { Ticket } from '../ticketing/Ticket' ;
7
+ import { AccessToken } from '../tokens/AccessToken' ;
16
8
import { TokenFactory } from '../tokens/TokenFactory' ;
17
9
import { processRequestPermission , switchODRLandCSSPermission } from '../util/rdf/RequestProcessing' ;
18
10
import { Result , Success } from '../util/Result' ;
19
11
import { reType } from '../util/ReType' ;
20
12
import { convertStringOrJsonLdIdentifierToString , ODRLContract , StringOrJsonLdIdentifier } from '../views/Contract' ;
13
+ import { Permission } from '../views/Permission' ;
14
+ import { BaseNegotiator } from './BaseNegotiator' ;
21
15
import { DialogInput } from './Input' ;
22
- import { Negotiator } from './Negotiator' ;
23
16
import { DialogOutput } from './Output' ;
24
17
25
-
26
18
/**
27
19
* A mocked Negotiator for demonstration purposes to display contract negotiation
28
20
*/
29
- export class ContractNegotiator implements Negotiator {
21
+ export class ContractNegotiator extends BaseNegotiator {
30
22
protected readonly logger = getLoggerFor ( this ) ;
31
23
32
24
// protected readonly operationLogger = getOperationLogger();
@@ -45,7 +37,8 @@ export class ContractNegotiator implements Negotiator {
45
37
protected ticketingStrategy : TicketingStrategy ,
46
38
protected tokenFactory : TokenFactory ,
47
39
) {
48
- this . logger . warn ( 'The Contract Negotiator is for demonstration purposes only! DO NOT USE THIS IN PRODUCTION !!!' )
40
+ super ( verifier , ticketStore , ticketingStrategy , tokenFactory ) ;
41
+ this . logger . warn ( 'The Contract Negotiator is for demonstration purposes only! DO NOT USE THIS IN PRODUCTION !!!' ) ;
49
42
}
50
43
51
44
/**
@@ -68,12 +61,33 @@ export class ContractNegotiator implements Negotiator {
68
61
const updatedTicket = await this . processCredentials ( input , ticket ) ;
69
62
this . logger . debug ( `Processed credentials ${ JSON . stringify ( updatedTicket ) } ` ) ;
70
63
71
- let result : Result < ODRLContract , Requirements [ ] >
64
+ // TODO:
65
+ const result = await this . toContract ( updatedTicket ) ;
66
+
67
+ if ( result . success ) {
68
+ // TODO:
69
+ return this . toResponse ( result . value ) ;
70
+ }
71
+
72
+ // ... on failure, deny if no solvable requirements
73
+ this . denyRequest ( ticket ) ;
74
+ }
75
+
76
+ /**
77
+ * Generates a contract based on the given ticket,
78
+ * or returns one previously made,
79
+ * and returns it as Success.
80
+ *
81
+ * In case the ticket is not resolved,
82
+ * the needed requirements will be returned as Failure.
83
+ */
84
+ protected async toContract ( ticket : Ticket ) : Promise < Result < ODRLContract , Requirements [ ] > > {
85
+ let result : Result < ODRLContract , Requirements [ ] > ;
72
86
let contract : ODRLContract | undefined ;
73
87
74
88
// Check contract availability
75
89
try {
76
- contract = this . contractManager . findContract ( updatedTicket )
90
+ contract = this . contractManager . findContract ( ticket )
77
91
} catch ( e ) {
78
92
this . logger . debug ( `Error: ${ createErrorMessage ( e ) } ` ) ;
79
93
}
@@ -86,7 +100,7 @@ export class ContractNegotiator implements Negotiator {
86
100
} else {
87
101
this . logger . debug ( `No existing contract discovered. Attempting to resolve ticket.` )
88
102
89
- const resolved = await this . ticketingStrategy . resolveTicket ( updatedTicket ) ;
103
+ const resolved = await this . ticketingStrategy . resolveTicket ( ticket ) ;
90
104
this . logger . debug ( `Resolved ticket. ${ JSON . stringify ( resolved ) } ` ) ;
91
105
92
106
if ( resolved . success ) {
@@ -103,144 +117,68 @@ export class ContractNegotiator implements Negotiator {
103
117
result = resolved
104
118
}
105
119
}
120
+ return result ;
121
+ }
106
122
107
- if ( result . success ) {
108
- let contract : ODRLContract = result . value
109
-
110
- this . logger . debug ( JSON . stringify ( contract , null , 2 ) )
111
-
112
- // todo: set resource scopes according to contract!
113
- // Using a map first as the contract could return multiple entries for the same resource_id
114
- // as it only allows 1 action per entry.
115
- const permissionMap : Record < string , Permission > = { } ;
116
- for ( const permission of contract . permission ) {
117
- const id = convertStringOrJsonLdIdentifierToString ( permission . target as StringOrJsonLdIdentifier ) ;
118
- if ( ! permissionMap [ id ] ) {
119
- permissionMap [ id ] = {
120
- // We do not accept AssetCollections as targets of an UMA access request formatted as an ODRL request!
121
- resource_id : id ,
122
- resource_scopes : [ // mapping from ODRL to internal CSS read permission
123
- switchODRLandCSSPermission ( convertStringOrJsonLdIdentifierToString ( permission . action ) )
124
- ]
125
- } ;
126
- } else {
127
- permissionMap [ id ] . resource_scopes . push (
123
+ // TODO: name
124
+ protected async toResponse ( contract : ODRLContract ) : Promise < DialogOutput > {
125
+
126
+ this . logger . debug ( JSON . stringify ( contract , null , 2 ) )
127
+
128
+ // todo: set resource scopes according to contract!
129
+ // Using a map first as the contract could return multiple entries for the same resource_id
130
+ // as it only allows 1 action per entry.
131
+ const permissionMap : Record < string , Permission > = { } ;
132
+ for ( const permission of contract . permission ) {
133
+ const id = convertStringOrJsonLdIdentifierToString ( permission . target as StringOrJsonLdIdentifier ) ;
134
+ if ( ! permissionMap [ id ] ) {
135
+ permissionMap [ id ] = {
136
+ // We do not accept AssetCollections as targets of an UMA access request formatted as an ODRL request!
137
+ resource_id : id ,
138
+ resource_scopes : [ // mapping from ODRL to internal CSS read permission
128
139
switchODRLandCSSPermission ( convertStringOrJsonLdIdentifierToString ( permission . action ) )
129
- ) ;
130
- }
140
+ ]
141
+ } ;
142
+ } else {
143
+ permissionMap [ id ] . resource_scopes . push (
144
+ switchODRLandCSSPermission ( convertStringOrJsonLdIdentifierToString ( permission . action ) )
145
+ ) ;
131
146
}
132
- let permissions : Permission [ ] = Object . values ( permissionMap ) ;
133
- this . logger . debug ( `granting permissions: ${ JSON . stringify ( permissions ) } ` ) ;
134
-
135
- // Create response
136
- const tokenContents : AccessToken = { permissions, contract }
137
-
138
- this . logger . debug ( `resolved result ${ JSON . stringify ( result ) } ` ) ;
139
-
140
- const { token, tokenType } = await this . tokenFactory . serialize ( tokenContents ) ;
141
-
142
- this . logger . debug ( `Minted token ${ JSON . stringify ( token ) } ` ) ;
143
-
144
- // TODO:: test logging
145
- // this.operationLogger.addLogEntry(serializePolicyInstantiation())
146
-
147
- // Store created instantiated policy (above contract variable) in the pod storage as an instantiated policy
148
- // todo: dynamic URL
149
- // todo: fix instantiated from url
150
- // contract['http://www.w3.org/ns/prov#wasDerivedFrom'] = [ 'urn:ucp:be-gov:policy:d81b8118-af99-4ab3-b2a7-63f8477b6386 ']
151
- // TODO: test-private error: this container does not exist and unauth does not have append perms
152
- const instantiatedPolicyContainer = 'http://localhost:3000/ruben/settings/policies/instantiated/' ;
153
- const policyCreationResponse = await fetch ( instantiatedPolicyContainer , {
154
- method : 'POST' ,
155
- headers : { 'content-type' : 'application/ld+json' } ,
156
- body : JSON . stringify ( contract , null , 2 )
157
- } ) ;
158
-
159
- if ( policyCreationResponse . status !== 201 ) { this . logger . warn ( 'Adding a policy did not succeed...' ) }
160
-
161
- // TODO:: dynamic contract link to stored signed contract.
162
- // If needed we can always embed here directly into the return JSON
163
- return ( {
164
- access_token : token ,
165
- token_type : tokenType ,
166
- } ) ;
167
147
}
148
+ let permissions : Permission [ ] = Object . values ( permissionMap ) ;
149
+ this . logger . debug ( `granting permissions: ${ JSON . stringify ( permissions ) } ` ) ;
168
150
169
- // ... on failure, deny if no solvable requirements
170
- const requiredClaims = ticket . required . map ( req => Object . keys ( req ) ) ;
171
- if ( requiredClaims . length === 0 ) throw new ForbiddenHttpError ( ) ;
172
-
173
- // ... require more info otherwise
174
- const id = v4 ( ) ;
175
- this . ticketStore . set ( id , ticket ) ;
176
- throw new NeedInfoError ( 'Need more info to authorize request ...' , id , {
177
- required_claims : {
178
- claim_token_format : requiredClaims ,
179
- } ,
180
- } ) ;
181
- }
151
+ // Create response
152
+ const tokenContents : AccessToken = { permissions, contract }
182
153
183
- /**
184
- * Helper function that retrieves a Ticket from the TicketStore if it exists,
185
- * or initializes a new one otherwise.
186
- *
187
- * @param input - The input of the negotiation dialog.
188
- *
189
- * @returns The Ticket describing the dialog at hand.
190
- */
191
- private async getTicket ( input : DialogInput ) : Promise < Ticket > {
192
- const { ticket, permission, permissions } = input ;
193
-
194
- if ( ticket ) {
195
- const stored = await this . ticketStore . get ( ticket ) ;
196
- if ( ! stored ) this . error ( BadRequestHttpError , 'The provided ticket is not valid.' ) ;
154
+ this . logger . debug ( `resolved result ${ JSON . stringify ( contract ) } ` ) ;
197
155
198
- await this . ticketStore . delete ( ticket ) ;
199
- return stored ;
200
- }
156
+ const { token, tokenType } = await this . tokenFactory . serialize ( tokenContents ) ;
201
157
202
- if ( ! permissions ) {
203
- this . error ( BadRequestHttpError , 'A token request without existing ticket should include requested permissions.' ) ;
204
- }
158
+ this . logger . debug ( `Minted token ${ JSON . stringify ( token ) } ` ) ;
205
159
206
- return await this . ticketingStrategy . initializeTicket ( permissions ) ;
207
- }
160
+ // TODO:: test logging
161
+ // this.operationLogger.addLogEntry(serializePolicyInstantiation())
208
162
209
- /**
210
- * Helper function that checks for the presence of Credentials and, if present,
211
- * verifies them and validates them in context of the provided Ticket.
212
- *
213
- * @param input - The input of the negotiation dialog.
214
- * @param ticket - The Ticket against which to validate any Credentials.
215
- *
216
- * @returns An updated Ticket in which the Credentials have been validated.
217
- */
218
- private async processCredentials ( input : DialogInput , ticket : Ticket ) : Promise < Ticket > {
219
- const { claim_token : token , claim_token_format : format } = input ;
220
-
221
- if ( token || format ) {
222
- if ( ! token ) this . error ( BadRequestHttpError , 'Request with a "claim_token_format" must contain a "claim_token".' ) ;
223
- if ( ! format ) this . error ( BadRequestHttpError , 'Request with a "claim_token" must contain a "claim_token_format".' ) ;
224
-
225
- const claims = await this . verifier . verify ( { token, format } ) ;
226
-
227
- return await this . ticketingStrategy . validateClaims ( ticket , claims ) ;
228
- }
163
+ // Store created instantiated policy (above contract variable) in the pod storage as an instantiated policy
164
+ // todo: dynamic URL
165
+ // todo: fix instantiated from url
166
+ // contract['http://www.w3.org/ns/prov#wasDerivedFrom'] = [ 'urn:ucp:be-gov:policy:d81b8118-af99-4ab3-b2a7-63f8477b6386 ']
167
+ // TODO: test-private error: this container does not exist and unauth does not have append perms
168
+ const instantiatedPolicyContainer = 'http://localhost:3000/ruben/settings/policies/instantiated/' ;
169
+ const policyCreationResponse = await fetch ( instantiatedPolicyContainer , {
170
+ method : 'POST' ,
171
+ headers : { 'content-type' : 'application/ld+json' } ,
172
+ body : JSON . stringify ( contract , null , 2 )
173
+ } ) ;
229
174
230
- return ticket ;
231
- }
175
+ if ( policyCreationResponse . status !== 201 ) { this . logger . warn ( 'Adding a policy did not succeed...' ) }
232
176
233
- /**
234
- * Logs and throws an error
235
- *
236
- * @param {HttpErrorClass } constructor - The error constructor.
237
- * @param {string } message - The error message.
238
- *
239
- * @throws An Error constructed with the provided constructor with the
240
- * provided message
241
- */
242
- private error ( constructor : HttpErrorClass , message : string ) : never {
243
- this . logger . warn ( message ) ;
244
- throw new constructor ( message ) ;
177
+ // TODO:: dynamic contract link to stored signed contract.
178
+ // If needed we can always embed here directly into the return JSON
179
+ return ( {
180
+ access_token : token ,
181
+ token_type : tokenType ,
182
+ } ) ;
245
183
}
246
184
}
0 commit comments