Skip to content

Commit 5ff4633

Browse files
committed
Updated parsing of VAU responses:
* Read the content from the byte array, without converting it to string first which might cause unexpexted changes to the result * Headers are now correctly added to both HttpResponseMessage and HttpContent Added note to README regarding usage with record system
1 parent 27784e3 commit 5ff4633

10 files changed

Lines changed: 7667 additions & 72 deletions

File tree

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,11 @@ Diese wird in VauMessage 4 gespeichert und zurück zum Client geschickt.
5252
Der Client öffnet die Nachricht, entschlüsselt die Ciphertext-KeyConfirmation und vergleicht wieder den erhalten Hash mit selbst berechneten.
5353
Erst dann ist der Handshake abgeschlossen.
5454

55+
## Nutzung mit den APIs des Aktensystems
56+
57+
Unter Verwendung der Klasse `VauHttpClientHandler` lassen sich Aufrufe des Aktensystems via `HttpClient` transparent ver- und entschlüsseln. Die Tests in der Datei [VauClientTest.cs](lib-vau-csharp-test/VauClientTest.cs)
58+
zeigen beispielhaft, wie diese Klasse mit von [NSwag](https://github.com/RicoSuter/NSwag) generierten Clients genutzt werden kann.
59+
5560
## License
5661

5762
Copyright 2024 gematik GmbH

lib-vau-csharp-test/EpaApiClients/Medication/MedicationServiceClient.cs

Lines changed: 7418 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
{
2+
"runtime": "Net80",
3+
"documentGenerator": {
4+
"fromDocument": {
5+
"url": "https://gemspec.gematik.de/ig/fhir/epa-medication/1.1.5/epa-medication-service-client.openapi.yaml",
6+
"newLineBehavior": "Auto"
7+
}
8+
},
9+
"codeGenerators": {
10+
"openApiToCSharpClient": {
11+
"generateClientClasses": true,
12+
"generateDtoTypes": true,
13+
"injectHttpClient": true,
14+
"disposeHttpClient": false,
15+
"generateExceptionClasses": true,
16+
"exceptionClass": "MedicationServiceException",
17+
"wrapDtoExceptions": true,
18+
"useHttpClientCreationMethod": false,
19+
"httpClientType": "System.Net.Http.HttpClient",
20+
"useHttpRequestMessageCreationMethod": false,
21+
"useBaseUrl": true,
22+
"generateBaseUrlProperty": true,
23+
"generateSyncMethods": false,
24+
"exposeJsonSerializerSettings": false,
25+
"clientClassAccessModifier": "public",
26+
"typeAccessModifier": "public",
27+
"generateContractsOutput": false,
28+
"parameterDateTimeFormat": "s",
29+
"generateUpdateJsonSerializerSettingsMethod": true,
30+
"serializeTypeInformation": false,
31+
"queryNullValue": "",
32+
"className": "MedicationServiceClient",
33+
"operationGenerationMode": "MultipleClientsFromOperationId",
34+
"generateOptionalParameters": false,
35+
"generateJsonMethods": false,
36+
"wrapResponses": false,
37+
"generateResponseClasses": true,
38+
"responseClass": "MedicationServicerResponse",
39+
"namespace": "lib_vau_csharp_test.EpaApiClients.Medication",
40+
"requiredPropertiesMustBeDefined": true,
41+
"dateType": "System.DateTime",
42+
"dateTimeType": "System.DateTime",
43+
"timeType": "System.TimeSpan",
44+
"timeSpanType": "System.TimeSpan",
45+
"generateDefaultValues": true,
46+
"generateDataAnnotations": true,
47+
"excludedTypeNames": [],
48+
"handleReferences": false,
49+
"generateImmutableArrayProperties": false,
50+
"generateImmutableDictionaryProperties": false,
51+
"output": "MedicationServiceClient.cs",
52+
"jsonLibrary": "SystemTextJson",
53+
"jsonSerializerSettingsTransformationMethod": null
54+
}
55+
}
56+
}
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
{
2+
Information: {
3+
Producer: iText® 5.5.13.4 ©2000-2024 iText Group NV (AGPL-version),
4+
Title: Medikationsliste,
5+
CreationDate: DateTimeOffset_1,
6+
ModifiedDate: DateTimeOffset_1
7+
},
8+
Pages: [
9+
{
10+
Rotation: {
11+
Value: 90,
12+
SwapsAxis: true,
13+
Radians: -1.5707963267949
14+
},
15+
Text:
16+
Verordnung
17+
sdatum
18+
Dispensierd
19+
atum
20+
Wirkstoffname Wirkst
21+
ärke
22+
Arzneimittelbez
23+
eichnung
24+
Form Dosiera
25+
ngabe/
26+
Gebrau
27+
chsanw
28+
eisung
29+
PZN Verordner abgebende
30+
Apotheke
31+
32+
01.07.2024 22.07.2024 - 03953522
33+
Metoprolol-
34+
ratiopharm®
35+
100 mg
36+
Tabletten
37+
TAB 1-0-1 03953522 Dr. Max
38+
Manfred
39+
Mustermann
40+
Arztpraxis Dr.
41+
Mustermann
42+
Arztpraxis Dr.
43+
Mustermann
44+
45+
01.07.2024 25.07.2024
46+
Ibuprofen
47+
(substance)
48+
400 mg 10019621
49+
IBU-ratiopharm
50+
400mg akut
51+
Schmerztablette
52+
n
53+
Ibuprofen
54+
(substance)
55+
TAB 1-0-1 10019621 Dr. Max
56+
Manfred
57+
Mustermann
58+
Arztpraxis Dr.
59+
Mustermann
60+
Arztpraxis Dr.
61+
Mustermann
62+
63+
13.01.2024 14.01.2024 - 10019621
64+
IBU-ratiopharm
65+
400mg akut
66+
Schmerztablette
67+
n
68+
1-0-1 10019621 Dr. Max
69+
Manfred
70+
Mustermann
71+
Arztpraxis Dr.
72+
Mustermann
73+
Arztpraxis Dr.
74+
Mustermann
75+
76+
12.01.2024 16.02.2024
77+
Doxorubicin
78+
85 mg
79+
Doxorubicin
80+
112100
81+
00
82+
1-0-1 Dr. Max
83+
Manfred
84+
Mustermann
85+
Arztpraxis Dr.
86+
Mustermann
87+
Arztpraxis Dr.
88+
Mustermann
89+
90+
01.01.2024 17.01.2024 - 1-0-1 Dr. Max
91+
Manfred
92+
Mustermann
93+
Arztpraxis Dr.
94+
Mustermann
95+
Arztpraxis Dr.
96+
Mustermann
97+
98+
}
99+
]
100+
}

lib-vau-csharp-test/VauClientTest.cs

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,20 @@
44
using System.Threading.Tasks;
55

66
using lib_vau_csharp;
7+
78
using lib_vau_csharp_test.EpaApiClients.Auth;
89
using lib_vau_csharp_test.EpaApiClients.EntitlementManagement;
910

11+
using Mauve.Erezept.API.EpaServiceClients.MedicationService;
12+
1013
using Microsoft.Extensions.DependencyInjection;
1114

1215
using NUnit.Framework;
1316

17+
using VerifyNUnit;
18+
19+
using VerifyTests;
20+
1421
using ErrorType = lib_vau_csharp_test.EpaApiClients.EntitlementManagement.ErrorType;
1522

1623
namespace lib_vau_csharp_test
@@ -20,6 +27,9 @@ public class VauClientTest
2027
{
2128
private VauClient vauClient;
2229

30+
[OneTimeSetUp]
31+
public void InitVerifyPdfPig() => VerifyPdfPig.Initialize();
32+
2333
[SetUp]
2434
public void Setup()
2535
{
@@ -64,12 +74,12 @@ public void UsageWithIHttpClientFactory()
6474
services.AddSingleton<IVauClientProvider, VauClientProviderSingleInstance>();
6575
services.AddHttpClient("VAU").ConfigurePrimaryHttpMessageHandler<VauHttpClientHandler>();
6676
services.AddTransient<IEntitlementManagementClient>(sp =>
67-
{
68-
var httpClientFactory = sp.GetService<IHttpClientFactory>();
69-
var httpClient = httpClientFactory.CreateClient("VAU");
70-
httpClient.BaseAddress = Constants.EpaDeploymentUrl;
71-
return ActivatorUtilities.CreateInstance<EntitlementManagementClient>(sp, httpClient);
72-
});
77+
{
78+
var httpClientFactory = sp.GetService<IHttpClientFactory>();
79+
var httpClient = httpClientFactory.CreateClient("VAU");
80+
httpClient.BaseAddress = Constants.EpaDeploymentUrl;
81+
return ActivatorUtilities.CreateInstance<EntitlementManagementClient>(sp, httpClient);
82+
});
7383

7484
var sp = services.BuildServiceProvider();
7585

@@ -85,10 +95,23 @@ public void ThrowsInvalidOperationExceptionIfHandshakeWasNotPerformed(Func<Task>
8595
Assert.ThrowsAsync<InvalidOperationException>(() => method(), $"Expected method {methodName} to throw an InvalidOperationException.");
8696
}
8797

98+
[Test(Description = "Expects medication data to be imported.")]
99+
public async Task GetMedicationListPdf()
100+
{
101+
var vauHttpClientHandler = new VauHttpClientHandler(new ReturnInstanceVauClientProvider(vauClient, true));
102+
var httpClient = new HttpClient(vauHttpClientHandler) { BaseAddress = Constants.EpaDeploymentUrl };
103+
104+
var medicationServiceClient = new MedicationServiceClient(httpClient);
105+
106+
using FileResponse response = await medicationServiceClient.RenderEMLAsPDFAsync(Guid.NewGuid(), "Z123456783", "Test/1.0");
107+
108+
await Verifier.Verify(response.Stream, "pdf");
109+
}
110+
88111
private static IEnumerable ThrowingMethods()
89112
{
90113
var client = new VauClient(new HttpClient());
91-
114+
92115
yield return new TestCaseData(() => client.DecryptResponse(new HttpResponseMessage()), nameof(client.DecryptResponse));
93116
yield return new TestCaseData(() => client.EncryptRequest(new HttpRequestMessage { RequestUri = new Uri("https://example.com") }), nameof(client.EncryptRequest));
94117
yield return new TestCaseData(() => client.GetStatus(), nameof(client.GetStatus));

lib-vau-csharp-test/lib-vau-csharp-test.csproj

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,15 +30,17 @@
3030
<PrivateAssets>all</PrivateAssets>
3131
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
3232
</PackageReference>
33-
<PackageReference Include="nunit" Version="4.3.2" />
34-
<PackageReference Include="NUnit3TestAdapter" Version="5.0.0" />
33+
<PackageReference Include="nunit" Version="4.4.0" />
34+
<PackageReference Include="NUnit3TestAdapter" Version="5.1.0" />
3535
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
3636
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.8" />
3737
<PackageReference Include="Microsoft.Extensions.Http" Version="9.0.8" />
3838
<PackageReference Include="NSwag.MSBuild" Version="14.4.0">
3939
<PrivateAssets>all</PrivateAssets>
4040
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
4141
</PackageReference>
42+
<PackageReference Include="Verify.NUnit" Version="30.7.3" />
43+
<PackageReference Include="Verify.PdfPig" Version="2.1.0" />
4244
<ProjectReference Include="..\lib-vau-csharp\lib-vau-csharp.csproj" />
4345
</ItemGroup>
4446

lib-vau-csharp/VauClient.cs

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -34,12 +34,12 @@ namespace lib_vau_csharp
3434
public class VauClient
3535
{
3636
private static readonly JsonSerializerOptions JsonSerializerOptions = new JsonSerializerOptions { PropertyNameCaseInsensitive = true, PropertyNamingPolicy = JsonNamingPolicy.KebabCaseUpper };
37-
37+
3838
private readonly HttpClient httpClient;
3939
private readonly VauClientStateMachine vauClientStateMachine;
4040

4141
public AbstractVauStateMachine VauStateMachine => vauClientStateMachine;
42-
42+
4343
public ConnectionId ConnectionId { get; private set; }
4444

4545
/// <summary>
@@ -62,7 +62,7 @@ public async Task DoHandshake()
6262
{
6363
if (ConnectionId != null)
6464
throw new InvalidOperationException("Connection has already been established.");
65-
65+
6666
byte[] message3Encoded = await DoHandShakeStage1();
6767
await DoHandShakeStage2(message3Encoded);
6868
}
@@ -94,9 +94,9 @@ public async Task<HttpResponseMessage> SendMessage(byte[] message)
9494
content.Headers.ContentType = MediaTypeHeader.Octet;
9595
var response = await httpClient.PostAsync(ConnectionId.Cid, content).ConfigureAwait(false);
9696
byte[] serverMessageEncoded = await response.Content.ReadAsByteArrayAsync().ConfigureAwait(false);
97-
string responseMessage = Encoding.UTF8.GetString(vauClientStateMachine.DecryptVauMessage(serverMessageEncoded));
98-
99-
VauResponse.Parse(responseMessage, response);
97+
var decryptedResponse = vauClientStateMachine.DecryptVauMessage(serverMessageEncoded);
98+
99+
VauResponse.Parse(decryptedResponse, response);
100100
return response;
101101
}
102102

@@ -107,7 +107,7 @@ public async Task<HttpResponseMessage> SendMessage(byte[] message)
107107
/// <exception cref="ArgumentNullException"></exception>
108108
/// <exception cref="InvalidOperationException">Thrown in case the current instance is not connected to a VAU.</exception>
109109
public async Task EncryptRequest(HttpRequestMessage httpRequest) => await EncryptRequest(httpRequest, httpRequest.RequestUri).ConfigureAwait(false);
110-
110+
111111
/// <summary>
112112
/// Encrypts the given <paramref name="httpRequest"/> to send to the VAU using the provided <paramref name="uri"/>.
113113
/// </summary>
@@ -150,19 +150,18 @@ public async Task DecryptResponse(HttpResponseMessage response)
150150
EnsureConnected();
151151

152152
byte[] encryptedResponse = await response.Content.ReadAsByteArrayAsync().ConfigureAwait(false);
153-
154-
string decryptedResponse = Encoding.UTF8.GetString(vauClientStateMachine.DecryptVauMessage(encryptedResponse));
155-
153+
byte[] decryptedResponse = vauClientStateMachine.DecryptVauMessage(encryptedResponse);
154+
156155
VauResponse.Parse(decryptedResponse, response);
157156
}
158-
157+
159158
private async Task<byte[]> DoHandShakeStage1()
160159
{
161160
var message1Encoded = vauClientStateMachine.generateMessage1();
162161

163162
var content = new ByteArrayContent(message1Encoded);
164163
content.Headers.ContentType = MediaTypeHeader.Cbor;
165-
164+
166165
using var response = await httpClient.PostAsync("VAU", content).ConfigureAwait(false);
167166

168167
var message2Encoded = await response.Content.ReadAsByteArrayAsync().ConfigureAwait(false);
@@ -194,7 +193,7 @@ private void EnsureConnected()
194193
throw new InvalidOperationException($"No connection has been established, call {nameof(DoHandshake)} first.");
195194
}
196195
}
197-
196+
198197
public record VauStatus(string VauType, string VauVersion, string UserAuthentication, [property: JsonPropertyName("KeyID")] string KeyId, string ConnectionStart)
199198
{
200199
public bool IsTelematikIdAuthenticated(string telematikId) => UserAuthentication.Contains(telematikId);

0 commit comments

Comments
 (0)