@@ -47,7 +47,8 @@ interface
4747 { TSHLWebServer }
4848 TSHLWebServer = class (TFhirWebServerEndpoint)
4949 private
50- FPassword : String;
50+ FPassword : String;
51+ FVhlKey : TJWK;
5152 FDB : TFDBManager;
5253 procedure SetDB (AValue: TFDBManager);
5354 function processCreate (request: TIdHTTPRequestInfo; response: TIdHTTPResponseInfo; c : TFDBConnection) : String;
@@ -74,6 +75,7 @@ TSHLWebEndPoint = class (TFHIRServerEndPoint)
7475 private
7576 FSHLServer : TSHLWebServer;
7677 FPassword : String;
78+ FVhlKey : TJsonObject;
7779 procedure checkDatabase ;
7880 public
7981 constructor Create(config : TFHIRServerConfigSection; settings : TFHIRServerSettings; i18n : TI18nSupport; db : TFDBManager);
@@ -101,10 +103,12 @@ function TSHLWebServer.processCreate(request: TIdHTTPRequestInfo; response: TIdH
101103 req, resp : TJsonObject;
102104 exp : TDateTime;
103105 days : integer;
106+ vhl : boolean;
104107begin
105108 result := ' Create SHL context' ;
106109 req := TJsonParser.parse(request.PostStream);
107110 try
111+ vhl := req.bool[' vhl' ];
108112 if (req.str[' password' ] = FPassword) then
109113 begin
110114 days := req.int[' days' ];
@@ -116,12 +120,13 @@ function TSHLWebServer.processCreate(request: TIdHTTPRequestInfo; response: TIdH
116120 resp.str[' pword' ] := NewGuidId;
117121 resp.str[' link' ] := ' https://' +common.host+PathWithSlash+resp.str[' uuid' ];
118122
119- c.SQL := ' Insert into SHL (uuid, pword, expiry, mimetype) values (:u, :p, :e, :m)' ;
123+ c.SQL := ' Insert into SHL (uuid, pword, expiry, mimetype, vhl ) values (:u, :p, :e, :m, :v )' ;
120124 c.prepare;
121125 c.BindString(' u' , resp.str[' uuid' ]);
122126 c.BindString(' p' , resp.str[' pword' ]);
123127 c.BindTimeStamp(' e' , DateTimeToTS(exp));
124128 c.BindString(' m' , req.str[' mimetype' ]);
129+ c.BindIntegerFromBoolean(' v' , vhl);
125130 c.execute;
126131 c.terminate;
127132 response.ResponseNo := 200 ;
@@ -146,41 +151,61 @@ function TSHLWebServer.processCreate(request: TIdHTTPRequestInfo; response: TIdH
146151function TSHLWebServer.processUpload (request: TIdHTTPRequestInfo; response: TIdHTTPResponseInfo; c : TFDBConnection): String;
147152var
148153 p : THTTPParameters;
149- bytes : TBytes;
154+ bytes, hcert : TBytes;
155+ req, resp : TJsonObject;
150156begin
151157 result := ' upload SHL content' ;
152158 p := THTTPParameters.create(request.QueryParams, true);
153159 try
154- bytes := StreamToBytes(request.PostStream);
155- if (p.has(' uuid' ) and p.has(' pword' )) then
156- begin
157- c.sql := ' select pword from SHL where uuid = '' ' +SQLWrapString(p[' uuid' ])+' '' ' ;
158- c.Prepare;
159- c.Execute;
160- if not c.FetchNext then
161- raise ERestfulException.create(' processCreate' , 404 , itSecurity, ' uuid "' +p[' uuid' ]+' " not found' , nil );
162- if p[' pword' ] <> c.ColStringByName[' pword' ] then
163- raise ERestfulException.create(' processCreate' , 404 , itSecurity, ' password failure' , nil );
164- c.terminate;
165- c.SQL := ' update SHL set blob = :b where uuid = '' ' +SQLWrapString(p[' uuid' ])+' '' ' ;
166- c.prepare;
167- c.BindBlob(' b' , bytes);
168- c.Execute;
169- c.terminate;
170- response.ResponseNo := 200 ;
171- response.ResponseText := ' OK' ;
172- response.ContentText := ' { "msg": "OK" }' ;
173- end
174- else
175- raise ERestfulException.create(' processCreate' , 404 , itSecurity, ' uuid and/or pword not found' , nil );
160+ req := TJSONParser.Parse(request.PostStream);
161+ try
162+ bytes := DecodeBase64(req.str[' cnt' ]);
163+ hcert := DecodeBase64(req.str[' hcert' ]);
164+ if (p.has(' uuid' ) and p.has(' pword' )) then
165+ begin
166+ c.sql := ' select pword from SHL where uuid = '' ' +SQLWrapString(p[' uuid' ])+' '' ' ;
167+ c.Prepare;
168+ c.Execute;
169+ if not c.FetchNext then
170+ raise ERestfulException.create(' processCreate' , 404 , itSecurity, ' uuid "' +p[' uuid' ]+' " not found' , nil );
171+ if p[' pword' ] <> c.ColStringByName[' pword' ] then
172+ raise ERestfulException.create(' processCreate' , 404 , itSecurity, ' password failure' , nil );
173+ c.terminate;
174+ c.SQL := ' update SHL set blob = :b where uuid = '' ' +SQLWrapString(p[' uuid' ])+' '' ' ;
175+ c.prepare;
176+ c.BindBlob(' b' , bytes);
177+ c.Execute;
178+ c.terminate;
179+ response.ResponseNo := 200 ;
180+ response.ResponseText := ' OK' ;
181+ if hcert <> nil then
182+ begin
183+ resp := TJsonObject.create;
184+ try
185+ bytes := TJWTUtils.Sign_ES256(hcert, FVhlKey);
186+ resp[' signature' ] := EncodeBase64(bytes);
187+ resp[' kid' ] := FVhlKey.id;
188+ response.ContentText := TJSONWriter.writeObjectStr(resp, true);
189+ finally
190+ resp.free;
191+ end ;
192+ end
193+ else
194+ response.ContentText := ' { "msg": "OK" }' ;
195+ end
196+ else
197+ raise ERestfulException.create(' processCreate' , 404 , itSecurity, ' uuid and/or pword not found' , nil );
198+ finally
199+ req.free;
200+ end ;
176201 finally
177202 p.free;
178203 end ;
179204end ;
180205
181206function TSHLWebServer.processManifest (request: TIdHTTPRequestInfo; response: TIdHTTPResponseInfo; c: TFDBConnection): String;
182207var
183- req, resp, f : TJsonObject;
208+ req, resp, f, l, r, m, cnt : TJsonObject;
184209 uuid, b64 : String;
185210begin
186211 uuid := request.Document.subString(PathWithSlash.length);
@@ -192,28 +217,49 @@ function TSHLWebServer.processManifest(request: TIdHTTPRequestInfo; response: TI
192217 c.prepare;
193218 c.BindString(' u' , uuid);
194219 c.execute;
195- if c.FetchNext then
220+ if not c.FetchNext then
221+ begin
222+ response.ResponseNo := 404 ;
223+ response.ResponseText:= ' Not Found' ;
224+ response.ContentText := ' SHL Not Found' ;
225+ end
226+ else if c.ColIntegerByName[' vhl' ] = 1 then
196227 begin
197228 f := resp.forceArr[' files' ].addObject;
198229 f.str[' contentType' ] := c.GetColStringByName(' mimetype' );
199230 f.str[' location' ] := ' https://' +common.host+PathWithSlash+' data/' +uuid;
200- b64 := c.GetColStringByName(' blob' ); // it's already base64 encoded
201- // EncodeBase64(c.GetColBlobByName('blob'));
231+ b64 := c.GetColStringByName(' blob' );
202232 if (not req.has(' embeddedLengthMax' )) or (b64.Length < req.int[' embeddedLengthMax' ]) then
203233 f.str[' embedded' ] := b64;
204234 response.ResponseNo := 200 ;
205235 response.ResponseText:= ' OK' ;
206236 response.ContentText := TJSONWriter.writeObjectStr(resp, true);
207237 response.ContentType := ' application/json' ;
208- c.Terminate;
209238 end
210239 else
211240 begin
212- c.Terminate;
213- response.ResponseNo := 404 ;
214- response.ResponseText:= ' Not Found' ;
215- response.ContentText := ' SHL Not Found' ;
241+ resp[' resourceType' ] := ' Bundle' ;
242+ resp[' type' ] := ' searchSet' ;
243+ l := resp.forceArr[' link' ].addObject;
244+ l[' relation' ] := ' self' ;
245+ l[' url' ] := ' https://' +common.host+request.URI;
246+ f := resp.forceArr[' entry' ].addObject;
247+ f[' fullUrl' ] := ' https://' +common.host+PathWithSlash+' DocumentReference/' +uuid;
248+ r := f.forceObj[' resource' ];
249+ r[' resourceType' ] := ' DocumentReference' ;
250+ r[' id' ] := uuid;
251+ m := r.forceObj[' masterIdentifier' ];
252+ m[' system' ] := ' urn:ietf:rfc:3986' ;
253+ m[' value' ] := f[' fullUrl' ];
254+ cnt := r.forceArr[' content' ].addObject;
255+ cnt[' url' ] := ' https://' +common.host+PathWithSlash+' data/' +uuid;
256+ cnt[' contentType' ] := c.GetColStringByName(' mimetype' );
257+ response.ResponseNo := 200 ;
258+ response.ResponseText:= ' OK' ;
259+ response.ContentText := TJSONWriter.writeObjectStr(resp, true);
260+ response.ContentType := ' application/json' ;
216261 end ;
262+ c.Terminate;
217263 finally
218264 resp.free;
219265 end ;
@@ -272,6 +318,7 @@ constructor TSHLWebServer.Create(code, path: String; common: TFHIRWebServerCommo
272318destructor TSHLWebServer.Destroy;
273319begin
274320 FDB.Free;
321+ FVhlKey.free;
275322 inherited Destroy;
276323end ;
277324
@@ -353,6 +400,7 @@ procedure TSHLWebEndPoint.checkDatabase;
353400 ' pword nchar(40) ' +ColCanBeNull(c.owner.platform, False)+' , ' +
354401 ' mimetype nchar(60) ' +ColCanBeNull(c.owner.platform, False)+' , ' +
355402 ' expiry ' +DBDateTimeType(c.owner.platform)+' ' +ColCanBeNull(c.owner.platform, False)+' , ' +
403+ ' vhl int ' +ColCanBeNull(c.owner.platform, true)+' , ' +
356404 ' blob ' +DBBlobType(c.owner.platform)+' ' +ColCanBeNull(c.owner.platform, true)+' ) ' +
357405 CreateTableInfo(c.owner.platform));
358406 c.ExecSQL(' Create INDEX SK_SHL_UUID ON SHL (uuid)' );
@@ -369,10 +417,28 @@ procedure TSHLWebEndPoint.checkDatabase;
369417 end
370418 else
371419 begin
420+ t := m.GetTable(' SHL' );
421+ if not t.hasColumn(' vhl' ) then
422+ begin
423+ c.StartTransact;
424+ try
425+ c.ExecSQL(' ALTER TABLE SHL ADD vhl int ' +ColCanBeNull(c.owner.platform, true));
426+ c.Commit;
427+ except
428+ on e:exception do
429+ begin
430+ Logging.log(e.message);
431+ c.Rollback;
432+ recordStack(e);
433+ raise;
434+ end ;
435+ end ;
436+ end ;
372437 end ;
373438 finally
374439 m.free;
375440 end ;
441+ FVhlKey := TJSONParser.Parse(c.Lookup(' Config' , ' ConfigKey' , ' jwk' , ' Value' , ' {}' ));
376442 c.Release;
377443 except
378444 on e: Exception do
@@ -389,6 +455,7 @@ constructor TSHLWebEndPoint.Create(config: TFHIRServerConfigSection; settings: T
389455
390456destructor TSHLWebEndPoint.Destroy;
391457begin
458+ FVhlKey.free;
392459 inherited Destroy;
393460end ;
394461
@@ -403,6 +470,7 @@ function TSHLWebEndPoint.makeWebEndPoint(common: TFHIRWebServerCommon): TFhirWeb
403470 FSHLServer := TSHLWebServer.Create(config.name , config[' path' ].value , common);
404471 FSHLServer.DB := Database.Link;
405472 FSHLServer.FPassword := FPassword;
473+ FSHLServer.FVhlKey := TJWK.create(FVhlKey.link);
406474 WebEndPoint := FSHLServer;
407475 result := FSHLServer.link;
408476end ;
0 commit comments