-
Notifications
You must be signed in to change notification settings - Fork 529
/
Copy pathProvenanceHeaderBehavior.cs
113 lines (96 loc) · 5.25 KB
/
ProvenanceHeaderBehavior.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
// -------------------------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
// -------------------------------------------------------------------------------------------------
using System.Threading;
using System.Threading.Tasks;
using EnsureThat;
using Hl7.Fhir.Model;
using Hl7.Fhir.Serialization;
using MediatR;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Primitives;
using Microsoft.Health.Fhir.Api.Features.Exceptions;
using Microsoft.Health.Fhir.Core.Extensions;
using Microsoft.Health.Fhir.Core.Features;
using Microsoft.Health.Fhir.Core.Messages.Create;
using Microsoft.Health.Fhir.Core.Messages.Upsert;
namespace Microsoft.Health.Fhir.Api.Features.Resources
{
/// <summary>
/// Intercepts create/update requests and checks presence of "X-Provenance" header.
/// If header present it proceed normal work with target request and then create provenance object with provenance.target equal to that object.
/// </summary>
public sealed class ProvenanceHeaderBehavior :
IPipelineBehavior<CreateResourceRequest, UpsertResourceResponse>,
IPipelineBehavior<UpsertResourceRequest, UpsertResourceResponse>,
IPipelineBehavior<ConditionalCreateResourceRequest, UpsertResourceResponse>,
IPipelineBehavior<ConditionalUpsertResourceRequest, UpsertResourceResponse>
{
private readonly FhirJsonParser _fhirJsonParser;
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly IMediator _mediator;
private IProvenanceHeaderState _state;
public ProvenanceHeaderBehavior(FhirJsonParser fhirJsonParser, IHttpContextAccessor httpContextAccessor, IMediator mediator, IProvenanceHeaderState state)
{
EnsureArg.IsNotNull(fhirJsonParser, nameof(fhirJsonParser));
EnsureArg.IsNotNull(httpContextAccessor, nameof(httpContextAccessor));
EnsureArg.IsNotNull(mediator, nameof(mediator));
_fhirJsonParser = fhirJsonParser;
_httpContextAccessor = httpContextAccessor;
_mediator = mediator;
_state = state;
}
public async Task<UpsertResourceResponse> Handle(ConditionalUpsertResourceRequest request, RequestHandlerDelegate<UpsertResourceResponse> next, CancellationToken cancellationToken)
=> await GenericHandle(next, cancellationToken);
public async Task<UpsertResourceResponse> Handle(ConditionalCreateResourceRequest request, RequestHandlerDelegate<UpsertResourceResponse> next, CancellationToken cancellationToken)
=> await GenericHandle(next, cancellationToken);
public async Task<UpsertResourceResponse> Handle(UpsertResourceRequest request, RequestHandlerDelegate<UpsertResourceResponse> next, CancellationToken cancellationToken)
=> await GenericHandle(next, cancellationToken);
public async Task<UpsertResourceResponse> Handle(CreateResourceRequest request, RequestHandlerDelegate<UpsertResourceResponse> next, CancellationToken cancellationToken)
=> await GenericHandle(next, cancellationToken);
private async Task<UpsertResourceResponse> GenericHandle(RequestHandlerDelegate<UpsertResourceResponse> next, CancellationToken cancellationToken)
{
if (_state.Intercepted)
{
return await next();
}
_state.Intercepted = true;
Provenance provenance = GetProvenanceFromHeader();
var response = await next();
if (response != null && provenance != null)
{
// Set target to provided resource.
provenance.Target = new System.Collections.Generic.List<ResourceReference>()
{
new ResourceReference($"{response.Outcome.RawResourceElement.InstanceType}/{response.Outcome.RawResourceElement.Id}/_history/{response.Outcome.RawResourceElement.VersionId}"),
};
// Create Provenance resource.
// TODO: It should probaby go through controller to trigger audit events, but it's quite tricky to do now.
await _mediator.Send<UpsertResourceResponse>(new CreateResourceRequest(provenance.ToResourceElement(), bundleResourceContext: null), cancellationToken);
}
return response;
}
private Provenance GetProvenanceFromHeader()
{
if (!_httpContextAccessor.HttpContext.Request.Headers.TryGetValue(KnownHeaders.ProvenanceHeader, out Microsoft.Extensions.Primitives.StringValues value))
{
return null;
}
Provenance provenance;
try
{
provenance = _fhirJsonParser.Parse<Provenance>(value);
}
catch
{
throw new ProvenanceHeaderException(Core.Resources.ProvenanceHeaderMalformed);
}
if (provenance.Target != null && provenance.Target.Count > 0)
{
throw new ProvenanceHeaderException(Core.Resources.ProvenanceHeaderShouldntHaveTarget);
}
return provenance;
}
}
}