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' ;
113import { Verifier } from '../credentials/verify/Verifier' ;
12- import { NeedInfoError } from '../errors/NeedInfoError' ;
134import { ContractManager } from '../policies/contracts/ContractManager' ;
145import { TicketingStrategy } from '../ticketing/strategy/TicketingStrategy' ;
156import { Ticket } from '../ticketing/Ticket' ;
7+ import { AccessToken } from '../tokens/AccessToken' ;
168import { TokenFactory } from '../tokens/TokenFactory' ;
179import { processRequestPermission , switchODRLandCSSPermission } from '../util/rdf/RequestProcessing' ;
1810import { Result , Success } from '../util/Result' ;
1911import { reType } from '../util/ReType' ;
2012import { convertStringOrJsonLdIdentifierToString , ODRLContract , StringOrJsonLdIdentifier } from '../views/Contract' ;
13+ import { Permission } from '../views/Permission' ;
14+ import { BaseNegotiator } from './BaseNegotiator' ;
2115import { DialogInput } from './Input' ;
22- import { Negotiator } from './Negotiator' ;
2316import { DialogOutput } from './Output' ;
2417
25-
2618/**
2719 * A mocked Negotiator for demonstration purposes to display contract negotiation
2820 */
29- export class ContractNegotiator implements Negotiator {
21+ export class ContractNegotiator extends BaseNegotiator {
3022 protected readonly logger = getLoggerFor ( this ) ;
3123
3224 // protected readonly operationLogger = getOperationLogger();
@@ -45,7 +37,8 @@ export class ContractNegotiator implements Negotiator {
4537 protected ticketingStrategy : TicketingStrategy ,
4638 protected tokenFactory : TokenFactory ,
4739 ) {
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 !!!' ) ;
4942 }
5043
5144 /**
@@ -68,12 +61,33 @@ export class ContractNegotiator implements Negotiator {
6861 const updatedTicket = await this . processCredentials ( input , ticket ) ;
6962 this . logger . debug ( `Processed credentials ${ JSON . stringify ( updatedTicket ) } ` ) ;
7063
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 [ ] > ;
7286 let contract : ODRLContract | undefined ;
7387
7488 // Check contract availability
7589 try {
76- contract = this . contractManager . findContract ( updatedTicket )
90+ contract = this . contractManager . findContract ( ticket )
7791 } catch ( e ) {
7892 this . logger . debug ( `Error: ${ createErrorMessage ( e ) } ` ) ;
7993 }
@@ -86,7 +100,7 @@ export class ContractNegotiator implements Negotiator {
86100 } else {
87101 this . logger . debug ( `No existing contract discovered. Attempting to resolve ticket.` )
88102
89- const resolved = await this . ticketingStrategy . resolveTicket ( updatedTicket ) ;
103+ const resolved = await this . ticketingStrategy . resolveTicket ( ticket ) ;
90104 this . logger . debug ( `Resolved ticket. ${ JSON . stringify ( resolved ) } ` ) ;
91105
92106 if ( resolved . success ) {
@@ -103,144 +117,68 @@ export class ContractNegotiator implements Negotiator {
103117 result = resolved
104118 }
105119 }
120+ return result ;
121+ }
106122
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
128139 switchODRLandCSSPermission ( convertStringOrJsonLdIdentifierToString ( permission . action ) )
129- ) ;
130- }
140+ ]
141+ } ;
142+ } else {
143+ permissionMap [ id ] . resource_scopes . push (
144+ switchODRLandCSSPermission ( convertStringOrJsonLdIdentifierToString ( permission . action ) )
145+ ) ;
131146 }
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- } ) ;
167147 }
148+ let permissions : Permission [ ] = Object . values ( permissionMap ) ;
149+ this . logger . debug ( `granting permissions: ${ JSON . stringify ( permissions ) } ` ) ;
168150
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 }
182153
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 ) } ` ) ;
197155
198- await this . ticketStore . delete ( ticket ) ;
199- return stored ;
200- }
156+ const { token, tokenType } = await this . tokenFactory . serialize ( tokenContents ) ;
201157
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 ) } ` ) ;
205159
206- return await this . ticketingStrategy . initializeTicket ( permissions ) ;
207- }
160+ // TODO:: test logging
161+ // this.operationLogger.addLogEntry(serializePolicyInstantiation())
208162
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+ } ) ;
229174
230- return ticket ;
231- }
175+ if ( policyCreationResponse . status !== 201 ) { this . logger . warn ( 'Adding a policy did not succeed...' ) }
232176
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+ } ) ;
245183 }
246184}
0 commit comments