/// <summary> /// Adds request charge to the response headers and throws a <see cref="RequestRateExceededException"/> /// if the status code is 429. /// </summary> /// <param name="requestContext">The request context. Allowed to be null.</param> /// <param name="ex">The exception</param> public static void ProcessException(this IFhirRequestContext requestContext, Exception ex) { if (requestContext == null) { return; } EnsureArg.IsNotNull(ex, nameof(ex)); if (ex is DocumentClientException dce) { requestContext.AddRequestChargeToFhirRequestContext( responseRequestCharge: dce.RequestCharge, collectionSizeUsage: null, statusCode: dce.StatusCode); if (dce.StatusCode == HttpStatusCode.TooManyRequests) { throw new RequestRateExceededException(dce.RetryAfter); } else if (dce.Message.Contains("Invalid Continuation Token", StringComparison.OrdinalIgnoreCase)) { throw new Core.Exceptions.RequestNotValidException(Core.Resources.InvalidContinuationToken); } } }
public override void OnResultExecuted(ResultExecutedContext filterContext) { EnsureArg.IsNotNull(filterContext, nameof(filterContext)); var actionDescriptor = filterContext.ActionDescriptor as ControllerActionDescriptor; var attribute = s_attributeDict.GetOrAdd(actionDescriptor, GetAttributeToAdd); // If anonymous allowed, don't audit. if (attribute is AllowAnonymousAttribute) { base.OnResultExecuted(filterContext); return; } IFhirRequestContext fhirRequestContext = _fhirRequestContextAccessor.FhirRequestContext; var fhirResult = filterContext.Result as FhirResult; _auditLogger.LogAudit( AuditAction.Executed, fhirRequestContext.RequestSubType.Code, fhirResult?.Resource?.TypeName, fhirRequestContext.Uri, (HttpStatusCode)filterContext.HttpContext.Response.StatusCode, _fhirRequestContextAccessor.FhirRequestContext.CorrelationId, _claimsIndexer.Extract()); base.OnResultExecuted(filterContext); }
private void Log(AuditAction auditAction, HttpStatusCode?statusCode, HttpContext httpContext, IClaimsExtractor claimsExtractor) { IFhirRequestContext fhirRequestContext = _fhirRequestContextAccessor.RequestContext; string auditEventType = fhirRequestContext.AuditEventType; // We are retaining AuditEventType when CustomError occurs. Below check ensures that the audit log is not entered for the custom error request httpContext.Request.RouteValues.TryGetValue("action", out object actionName); if (!string.IsNullOrEmpty(actionName?.ToString()) && KnownRoutes.CustomError.Contains(actionName?.ToString(), StringComparison.OrdinalIgnoreCase)) { return; } // Audit the call if an audit event type is associated with the action. // Since AuditEventType holds value for both AuditEventType and FhirAnonymousOperationType ensure that we only log the AuditEventType if (!string.IsNullOrEmpty(auditEventType) && !FhirAnonymousOperationTypeList.Contains(auditEventType, StringComparer.OrdinalIgnoreCase)) { _auditLogger.LogAudit( auditAction, operation: auditEventType, resourceType: fhirRequestContext.ResourceType, requestUri: fhirRequestContext.Uri, statusCode: statusCode, correlationId: fhirRequestContext.CorrelationId, callerIpAddress: httpContext.Connection?.RemoteIpAddress?.ToString(), callerClaims: claimsExtractor.Extract(), customHeaders: _auditHeaderReader.Read(httpContext)); } }
private async Task AddRequestChargeToFhirRequestContext(double responseRequestCharge, HttpStatusCode?statusCode) { IFhirRequestContext requestContext = _fhirRequestContextAccessor.FhirRequestContext; // If there has already been a request to the database for this request, then we want to append a second charge header. if (requestContext.ResponseHeaders.TryGetValue(CosmosDbHeaders.RequestCharge, out StringValues existingHeaderValue)) { requestContext.ResponseHeaders[CosmosDbHeaders.RequestCharge] = StringValues.Concat(existingHeaderValue, responseRequestCharge.ToString(CultureInfo.InvariantCulture)); } else { requestContext.ResponseHeaders[CosmosDbHeaders.RequestCharge] = responseRequestCharge.ToString(CultureInfo.InvariantCulture); } var cosmosMetrics = new CosmosStorageRequestMetricsNotification(requestContext.AuditEventType, requestContext.ResourceType) { TotalRequestCharge = responseRequestCharge, }; if (statusCode.HasValue && statusCode == HttpStatusCode.TooManyRequests) { cosmosMetrics.IsThrottled = true; } try { await _mediator.Publish(cosmosMetrics, CancellationToken.None); } catch (Exception ex) { _logger.LogCritical(ex, "Unable to publish CosmosDB metric."); } }
private async Task EmitExceptionNotificationAsync(HttpStatusCode statusCode, Exception exception) { var exceptionNotification = new ExceptionNotification(); try { IFhirRequestContext fhirRequestContext = _fhirRequestContextAccessor.RequestContext; var innerMostException = exception.GetBaseException(); exceptionNotification.CorrelationId = fhirRequestContext?.CorrelationId; exceptionNotification.FhirOperation = fhirRequestContext?.AuditEventType; exceptionNotification.OuterExceptionType = exception.GetType().ToString(); exceptionNotification.ResourceType = fhirRequestContext?.ResourceType; exceptionNotification.StatusCode = statusCode; exceptionNotification.ExceptionMessage = exception.Message; exceptionNotification.StackTrace = exception.StackTrace; exceptionNotification.InnerMostExceptionType = innerMostException.GetType().ToString(); exceptionNotification.InnerMostExceptionMessage = innerMostException.Message; exceptionNotification.HResult = exception.HResult; exceptionNotification.Source = exception.Source; exceptionNotification.OuterMethod = exception.TargetSite?.Name; exceptionNotification.IsRequestEntityTooLarge = exception.IsRequestEntityTooLarge(); exceptionNotification.IsRequestRateExceeded = exception.IsRequestRateExceeded(); exceptionNotification.BaseException = exception; await _mediator.Publish(exceptionNotification, CancellationToken.None); } catch (Exception e) { // Failures in publishing exception notifications should not cause the API to return an error. _logger.LogWarning(e, "Failure while publishing Exception notification."); } }
public async Task GivenAFhirRequestContextAccessor_WhenOnDifferentAsyncThreads_TheFhirContextIsDifferent() { var fhirRequestContextAccessor = new FhirRequestContextAccessor(); var thread1 = Task.Run(async() => { IFhirRequestContext fhirRequestContext = Substitute.For <IFhirRequestContext>(); fhirRequestContext.CorrelationId.Returns(Guid.NewGuid().ToString()); fhirRequestContextAccessor.FhirRequestContext = fhirRequestContext; await Task.Delay(50); return(fhirRequestContextAccessor.FhirRequestContext); }); var thread2 = Task.Run(async() => { IFhirRequestContext fhirRequestContext = Substitute.For <IFhirRequestContext>(); fhirRequestContext.CorrelationId.Returns(Guid.NewGuid().ToString()); fhirRequestContextAccessor.FhirRequestContext = fhirRequestContext; await Task.Delay(0); return(fhirRequestContextAccessor.FhirRequestContext); }); var correlationId1 = (await thread1).CorrelationId; var correlationId2 = (await thread2).CorrelationId; Assert.NotEqual(Guid.Empty.ToString(), correlationId1); Assert.NotEqual(Guid.Empty.ToString(), correlationId2); Assert.NotEqual(correlationId1, correlationId2); }
public static RequestContextAccessor <IFhirRequestContext> SetupAccessor(this IFhirRequestContext context) { RequestContextAccessor <IFhirRequestContext> accessor = Substitute.For <RequestContextAccessor <IFhirRequestContext> >(); accessor.RequestContext.Returns(context); return(accessor); }
public async Task Invoke(HttpContext context) { try { // Call the next delegate/middleware in the pipeline await _next(context); } finally { var statusCode = (HttpStatusCode)context.Response.StatusCode; // The authorization middleware runs before MVC middleware and therefore, // information related to route and audit will not be populated if authentication fails. // Handle such condition and populate them here if possible. if (_fhirRequestContextAccessor.FhirRequestContext.RouteName == null && (statusCode == HttpStatusCode.Unauthorized || statusCode == HttpStatusCode.Forbidden)) { RouteData routeData = context.GetRouteData(); if (routeData?.Values != null) { routeData.Values.TryGetValue("controller", out object controllerName); routeData.Values.TryGetValue("action", out object actionName); routeData.Values.TryGetValue(KnownActionParameterNames.ResourceType, out object resourceType); IFhirRequestContext fhirRequestContext = _fhirRequestContextAccessor.FhirRequestContext; fhirRequestContext.AuditEventType = _auditEventTypeMapping.GetAuditEventType( controllerName?.ToString(), actionName?.ToString()); fhirRequestContext.ResourceType = resourceType?.ToString(); } } } }
/// <summary> /// Updates the request context with Cosmos DB info and updates response headers with the session token and request change values. /// </summary> /// <param name="responseMessage">The response message</param> public async Task ProcessResponse(ResponseMessage responseMessage) { var responseRequestCharge = responseMessage.Headers.RequestCharge; _queryLogger.LogQueryExecutionResult( responseMessage.Headers.ActivityId, responseMessage.Headers.RequestCharge, responseMessage.ContinuationToken == null ? null : "<nonempty>", int.TryParse(responseMessage.Headers["x-ms-item-count"], out var count) ? count : 0, double.TryParse(responseMessage.Headers["x-ms-request-duration-ms"], out var duration) ? duration : 0, responseMessage.Headers["x-ms-documentdb-partitionkeyrangeid"]); IFhirRequestContext fhirRequestContext = _fhirRequestContextAccessor.RequestContext; if (fhirRequestContext == null) { return; } var sessionToken = responseMessage.Headers.Session; if (!string.IsNullOrEmpty(sessionToken)) { fhirRequestContext.ResponseHeaders[CosmosDbHeaders.SessionToken] = sessionToken; } if (fhirRequestContext.Properties.TryGetValue(Constants.CosmosDbResponseMessagesProperty, out object propertyValue)) { // This is planted in FhirCosmosSearchService in order for us to relay the individual responses // back for analysis of the selectivity of the search. ((ConcurrentBag <ResponseMessage>)propertyValue).Add(responseMessage); } await AddRequestChargeToFhirRequestContext(responseRequestCharge, responseMessage.StatusCode); }
private (ConsistencyLevel?consistencyLevel, string sessionToken) GetConsistencyHeaders() { IFhirRequestContext fhirRequestContext = _fhirRequestContextAccessor.FhirRequestContext; if (fhirRequestContext == null) { return(null, null); } ConsistencyLevel?requestedConsistencyLevel = null; if (fhirRequestContext.RequestHeaders.TryGetValue(CosmosDbHeaders.ConsistencyLevel, out var values)) { if (!Enum.TryParse(values, out ConsistencyLevel parsedLevel)) { throw new BadRequestException(string.Format(CultureInfo.CurrentCulture, Resources.UnrecognizedConsistencyLevel, values, ValidConsistencyLevelsForErrorMessage)); } if (parsedLevel != _inner.ConsistencyLevel) { if (!ValidateConsistencyLevel(parsedLevel)) { throw new BadRequestException(string.Format(Resources.InvalidConsistencyLevel, parsedLevel, _inner.ConsistencyLevel)); } requestedConsistencyLevel = parsedLevel; } } fhirRequestContext.RequestHeaders.TryGetValue(CosmosDbHeaders.SessionToken, out values); return(requestedConsistencyLevel, values); }
public static IFhirRequestContextAccessor SetupAccessor(this IFhirRequestContext context) { IFhirRequestContextAccessor accessor = Substitute.For <IFhirRequestContextAccessor>(); accessor.FhirRequestContext.Returns(context); return(accessor); }
private static void AddRequestChargeToFhirRequestContext(this IFhirRequestContext requestContext, double responseRequestCharge, long?collectionSizeUsage, HttpStatusCode?statusCode) { // If there has already been a request to the database for this request, then there will already by a request charge. // We want to update it to the new total. // Instead of parsing the header value, we could store the double value on the IFhirRequestContext in addition to storing the header value. // The problem with that approach is that the request charge is a Cosmos DB-specific concept and the IFhirRequestContext is independent of data store. // Also, at the time of writing, we do not typically issue more than one request to the database per request anyway, so the performance impact should // not be felt. requestContext.StorageRequestMetrics = requestContext.StorageRequestMetrics ?? new CosmosStorageRequestMetrics(requestContext.AuditEventType, requestContext.ResourceType); var cosmosMetrics = requestContext.StorageRequestMetrics as CosmosStorageRequestMetrics; if (cosmosMetrics == null) { return; } cosmosMetrics.TotalRequestCharge += responseRequestCharge; requestContext.ResponseHeaders[CosmosDbHeaders.RequestCharge] = cosmosMetrics.TotalRequestCharge.ToString(CultureInfo.InvariantCulture); if (collectionSizeUsage.HasValue) { cosmosMetrics.CollectionSizeUsageKilobytes = collectionSizeUsage; } if (statusCode.HasValue && statusCode == HttpStatusCode.TooManyRequests) { cosmosMetrics.ThrottledCount += 1; } cosmosMetrics.RequestCount++; }
public async Task GivenAnHttpRequest_WhenExecutingFhirRequestContextMiddleware_ThenCorrectRequestTypeShouldBeSet() { IFhirRequestContext fhirRequestContext = await SetupAsync(CreateHttpContext()); Assert.NotNull(fhirRequestContext.RequestType); Assert.Equal(ValueSets.AuditEventType.RestFulOperation.System, fhirRequestContext.RequestType.System); Assert.Equal(ValueSets.AuditEventType.RestFulOperation.Code, fhirRequestContext.RequestType.Code); }
public override void OnActionExecuting(ActionExecutingContext context) { IFhirRequestContext fhirRequestContext = _fhirRequestContextAccessor.RequestContext; fhirRequestContext.RouteName = context.ActionDescriptor?.AttributeRouteInfo?.Name; // Set the resource type based on the route data RouteData routeData = context.RouteData; if (routeData?.Values != null) { if (routeData.Values.TryGetValue(KnownActionParameterNames.ResourceType, out object resourceType)) { fhirRequestContext.ResourceType = resourceType?.ToString(); } } if (context.ActionDescriptor is ControllerActionDescriptor controllerActionDescriptor) { // if controllerActionDescriptor.ActionName is CustomError then retain the AuditEventType from previous context // e.g. In case of 500 error - we want to make sure we log the AuditEventType of the original request for which the error occurred in RequestMetric. fhirRequestContext.AuditEventType = KnownRoutes.CustomError.Contains(controllerActionDescriptor.ActionName, StringComparison.OrdinalIgnoreCase) ? fhirRequestContext.AuditEventType : _auditEventTypeMapping.GetAuditEventType( controllerActionDescriptor.ControllerName, controllerActionDescriptor.ActionName); // If this is a request from the batch and transaction route, we need to examine the payload to set the AuditEventType if (fhirRequestContext.AuditEventType == AuditEventSubType.BundlePost) { if (context.ActionArguments.TryGetValue(KnownActionParameterNames.Bundle, out object value)) { if (!(value is Hl7.Fhir.Model.Bundle bundle)) { return; } switch (bundle.Type) { case Hl7.Fhir.Model.Bundle.BundleType.Batch: fhirRequestContext.AuditEventType = AuditEventSubType.Batch; break; case Hl7.Fhir.Model.Bundle.BundleType.Transaction: fhirRequestContext.AuditEventType = AuditEventSubType.Transaction; break; } } } } if (context.HttpContext.Request.Headers.TryGetValue(KnownHeaders.PartiallyIndexedParamsHeaderName, out var headerValues)) { fhirRequestContext.IncludePartiallyIndexedSearchParams = true; } base.OnActionExecuting(context); }
private void AddSessionTokenToResponseHeaders(string sessionToken) { if (!string.IsNullOrEmpty(sessionToken)) { IFhirRequestContext fhirRequestContext = _fhirRequestContextAccessor.FhirRequestContext; if (fhirRequestContext != null) { fhirRequestContext.ResponseHeaders[CosmosDbConsistencyHeaders.SessionToken] = sessionToken; } } }
public override void OnActionExecuting(ActionExecutingContext context) { IFhirRequestContext fhirRequestContext = _fhirRequestContextAccessor.FhirRequestContext; fhirRequestContext.RouteName = context.ActionDescriptor?.AttributeRouteInfo?.Name; // Set the resource type based on the route data RouteData routeData = context.RouteData; if (routeData?.Values != null) { if (routeData.Values.TryGetValue(KnownActionParameterNames.ResourceType, out object resourceType)) { fhirRequestContext.ResourceType = resourceType?.ToString(); } } if (context.ActionDescriptor is ControllerActionDescriptor controllerActionDescriptor) { fhirRequestContext.AuditEventType = _auditEventTypeMapping.GetAuditEventType( controllerActionDescriptor.ControllerName, controllerActionDescriptor.ActionName); // If this is a request from the batch and transaction route, we need to examine the payload to set the AuditEventType if (fhirRequestContext.AuditEventType == AuditEventSubType.BundlePost) { if (context.ActionArguments.TryGetValue(KnownActionParameterNames.Bundle, out object value)) { if (!(value is Hl7.Fhir.Model.Bundle bundle)) { return; } switch (bundle.Type) { case Hl7.Fhir.Model.Bundle.BundleType.Batch: fhirRequestContext.AuditEventType = AuditEventSubType.Batch; break; case Hl7.Fhir.Model.Bundle.BundleType.Transaction: fhirRequestContext.AuditEventType = AuditEventSubType.Transaction; break; } } } } if (context.HttpContext.Request.Headers.TryGetValue(KnownHeaders.PartiallyIndexedParamsHeaderName, out var headerValues)) { fhirRequestContext.IncludePartiallyIndexedSearchParams = true; } base.OnActionExecuting(context); }
public async Task GivenAnHttpContextWithPrincipal_WhenExecutingFhirRequestContextMiddleware_ThenPrincipalShouldBeSet() { HttpContext httpContext = CreateHttpContext(); var principal = new ClaimsPrincipal(); httpContext.User = principal; IFhirRequestContext fhirRequestContext = await SetupAsync(httpContext); Assert.Same(principal, fhirRequestContext.Principal); }
public BundleFactoryTests() { _bundleFactory = new BundleFactory( _urlResolver, _fhirRequestContextAccessor); IFhirRequestContext fhirRequestContext = Substitute.For <IFhirRequestContext>(); fhirRequestContext.CorrelationId.Returns(_correlationId); _fhirRequestContextAccessor.RequestContext.Returns(fhirRequestContext); }
public async Task Invoke(HttpContext context) { if (context.Request.Path.HasValue && context.Request.Path.StartsWithSegments(FhirServerApplicationBuilderExtensions.HealthCheckPath, System.StringComparison.InvariantCultureIgnoreCase)) { // Don't emit events for health check await _next(context); return; } var apiNotification = new ApiResponseNotification(); using (var timer = _logger.BeginTimedScope("ApiNotificationMiddleware") as ActionTimer) { try { await _next(context); } finally { apiNotification.Latency = timer.GetElapsedTime(); try { IFhirRequestContext fhirRequestContext = _fhirRequestContextAccessor.FhirRequestContext; // For now, we will only emit metrics for audited actions (e.g., metadata will not emit metrics). if (fhirRequestContext?.AuditEventType != null) { apiNotification.Authentication = fhirRequestContext.Principal?.Identity.AuthenticationType; apiNotification.FhirOperation = fhirRequestContext.AuditEventType; apiNotification.Protocol = context.Request.Scheme; apiNotification.ResourceType = fhirRequestContext.ResourceType; apiNotification.StatusCode = (HttpStatusCode)context.Response.StatusCode; await _mediator.Publish(apiNotification, CancellationToken.None); if (fhirRequestContext.StorageRequestMetrics != null) { await _mediator.Publish(fhirRequestContext.StorageRequestMetrics, CancellationToken.None); } } } catch (Exception e) { // Failures in publishing API notifications should not cause the API to return an error. _logger.LogCritical(e, "Failure while publishing API notification."); } } } }
/// <summary> /// Updates the request context with Cosmos DB info and updates response headers with the session token and request change values. /// </summary> /// <param name="requestContext">The request context. Allowed to be null.</param> /// <param name="sessionToken">THe session token</param> /// <param name="responseRequestCharge">The request charge.</param> /// <param name="collectionSizeUsageKilobytes">The size usage of the Cosmos collection in kilobytes.</param> /// <param name="statusCode">The HTTP status code.</param> private static void UpdateFhirRequestContext(this IFhirRequestContext requestContext, string sessionToken, double responseRequestCharge, long?collectionSizeUsageKilobytes, HttpStatusCode?statusCode) { if (requestContext == null) { return; } if (!string.IsNullOrEmpty(sessionToken)) { requestContext.ResponseHeaders[CosmosDbHeaders.SessionToken] = sessionToken; } requestContext.AddRequestChargeToFhirRequestContext(responseRequestCharge, collectionSizeUsageKilobytes, statusCode); }
/// <summary> /// Updates the response headers with the session token and request change values. /// </summary> /// <param name="requestContext">The request context. Allowed to be null.</param> /// <param name="sessionToken">THe session token</param> /// <param name="responseRequestCharge">The request charge.</param> public static void UpdateResponseHeaders(this IFhirRequestContext requestContext, string sessionToken, double responseRequestCharge) { if (requestContext == null) { return; } if (!string.IsNullOrEmpty(sessionToken)) { requestContext.ResponseHeaders[CosmosDbHeaders.SessionToken] = sessionToken; } requestContext.AddRequestChargeToResponseHeaders(responseRequestCharge); }
public override void OnActionExecuting(ActionExecutingContext context) { IFhirRequestContext fhirRequestContext = _fhirRequestContextAccessor.FhirRequestContext; fhirRequestContext.RouteName = context.ActionDescriptor?.AttributeRouteInfo?.Name; var controllerActionDescriptor = context.ActionDescriptor as ControllerActionDescriptor; if (controllerActionDescriptor != null) { fhirRequestContext.AuditEventType = _auditEventTypeMapping.GetAuditEventType( controllerActionDescriptor.ControllerName, controllerActionDescriptor.ActionName); } }
/// <inheritdoc /> public ResourceWrapper Create(Resource resource, bool deleted) { RawResource rawResource = _rawResourceFactory.Create(resource); IReadOnlyCollection <SearchIndexEntry> searchIndices = _searchIndexer.Extract(resource); IFhirRequestContext fhirRequestContext = _fhirRequestContextAccessor.FhirRequestContext; return(new ResourceWrapperWithSearchIndices( resource, rawResource, new ResourceRequest(fhirRequestContext.Uri, fhirRequestContext.Method), deleted, searchIndices, _compartmentIndexer.Extract(resource.ResourceType, searchIndices), _claimsIndexer.Extract())); }
public BundleFactoryTests() { _resourceDeserializer = new ResourceDeserializer( (FhirResourceFormat.Json, new Func <string, string, DateTimeOffset, ResourceElement>((str, version, lastUpdated) => _fhirJsonParser.Parse(str).ToResourceElement()))); _bundleFactory = new BundleFactory( _urlResolver, _fhirRequestContextAccessor, _resourceDeserializer); IFhirRequestContext fhirRequestContext = Substitute.For <IFhirRequestContext>(); fhirRequestContext.CorrelationId.Returns(_correlationId); _fhirRequestContextAccessor.FhirRequestContext.Returns(fhirRequestContext); }
private static void AddRequestChargeToResponseHeaders(this IFhirRequestContext requestContext, double responseRequestCharge) { // If there has already been a request to the database for this request, then there will already by a request charge. // We want to update it to the new total. // Instead of parsing the header value, we could store the double value on the IFhirRequestContext in addition to storing the header value. // The problem with that approach is that the request charge is a Cosmos DB-specific concept and the IFhirRequestContext is independent of data store. // Also, at the time of writing, we do not typically issue more than one request to the database per request anyway, so the performance impact should // not be felt. if (requestContext.ResponseHeaders.TryGetValue(CosmosDbHeaders.RequestCharge, out StringValues existingValues) && double.TryParse(existingValues.ToString(), NumberStyles.Number, CultureInfo.InvariantCulture, out double existingCharge)) { responseRequestCharge += existingCharge; } requestContext.ResponseHeaders[CosmosDbHeaders.RequestCharge] = responseRequestCharge.ToString(CultureInfo.InvariantCulture); }
/// <inheritdoc /> public ResourceWrapper Create(ResourceElement resource, bool deleted, bool keepMeta) { RawResource rawResource = _rawResourceFactory.Create(resource, keepMeta); IReadOnlyCollection <SearchIndexEntry> searchIndices = _searchIndexer.Extract(resource); string searchParamHash = _searchParameterDefinitionManager.GetSearchParameterHashForResourceType(resource.InstanceType); IFhirRequestContext fhirRequestContext = _fhirRequestContextAccessor.FhirRequestContext; return(new ResourceWrapper( resource, rawResource, new ResourceRequest(fhirRequestContext.Method, fhirRequestContext.Uri), deleted, searchIndices, _compartmentIndexer.Extract(resource.InstanceType, searchIndices), _claimsExtractor.Extract(), searchParamHash)); }
/// <summary> /// Adds request charge to the response headers and throws a <see cref="RequestRateExceededException"/> /// if the status code is 429. /// </summary> /// <param name="requestContext">The request context. Allowed to be null.</param> /// <param name="ex">The exception</param> public static void ProcessException(this IFhirRequestContext requestContext, Exception ex) { if (requestContext == null) { return; } EnsureArg.IsNotNull(ex, nameof(ex)); if (ex is DocumentClientException dce) { requestContext.AddRequestChargeToResponseHeaders(dce.RequestCharge); if (dce.StatusCode == HttpStatusCode.TooManyRequests) { throw new RequestRateExceededException(dce.RetryAfter); } } }
public async Task Invoke(HttpContext context) { try { await _next(context); } catch (Exception exception) { var exceptionNotification = new ExceptionNotification(); try { IFhirRequestContext fhirRequestContext = _fhirRequestContextAccessor.RequestContext; var innerMostException = exception.GetBaseException(); exceptionNotification.CorrelationId = fhirRequestContext?.CorrelationId; exceptionNotification.FhirOperation = fhirRequestContext?.AuditEventType; exceptionNotification.OuterExceptionType = exception.GetType().ToString(); exceptionNotification.ResourceType = fhirRequestContext?.ResourceType; exceptionNotification.StatusCode = (HttpStatusCode)context.Response.StatusCode; exceptionNotification.ExceptionMessage = exception.Message; exceptionNotification.StackTrace = exception.StackTrace; exceptionNotification.InnerMostExceptionType = innerMostException.GetType().ToString(); exceptionNotification.InnerMostExceptionMessage = innerMostException.Message; exceptionNotification.HResult = exception.HResult; exceptionNotification.Source = exception.Source; exceptionNotification.OuterMethod = exception.TargetSite?.Name; exceptionNotification.IsRequestEntityTooLarge = exception.IsRequestEntityTooLarge(); exceptionNotification.IsRequestRateExceeded = exception.IsRequestRateExceeded(); exceptionNotification.BaseException = exception; await _mediator.Publish(exceptionNotification, CancellationToken.None); } catch (Exception e) { // Failures in publishing exception notifications should not cause the API to return an error. _logger.LogWarning(e, "Failure while publishing Exception notification."); } // Rethrowing the exception so the BaseExceptionMiddleware can handle it. We are only notifying there is an exception at this point. throw; } }
private void Log(AuditAction auditAction, string controllerName, string actionName, HttpStatusCode?statusCode, string resourceType, HttpContext httpContext, IClaimsExtractor claimsExtractor) { string auditEventType = _auditEventTypeMapping.GetAuditEventType(controllerName, actionName); // Audit the call if an audit event type is associated with the action. if (auditEventType != null) { IFhirRequestContext fhirRequestContext = _fhirRequestContextAccessor.FhirRequestContext; _auditLogger.LogAudit( auditAction, operation: auditEventType, resourceType: resourceType, requestUri: fhirRequestContext.Uri, statusCode: statusCode, correlationId: fhirRequestContext.CorrelationId, callerIpAddress: httpContext.Connection?.RemoteIpAddress?.ToString(), callerClaims: claimsExtractor.Extract()); } }
private void Log(AuditAction auditAction, HttpStatusCode?statusCode, HttpContext httpContext, IClaimsExtractor claimsExtractor) { IFhirRequestContext fhirRequestContext = _fhirRequestContextAccessor.FhirRequestContext; string auditEventType = fhirRequestContext.AuditEventType; // Audit the call if an audit event type is associated with the action. if (!string.IsNullOrEmpty(auditEventType)) { _auditLogger.LogAudit( auditAction, operation: auditEventType, resourceType: fhirRequestContext.ResourceType, requestUri: fhirRequestContext.Uri, statusCode: statusCode, correlationId: fhirRequestContext.CorrelationId, callerIpAddress: httpContext.Connection?.RemoteIpAddress?.ToString(), callerClaims: claimsExtractor.Extract(), customHeaders: _auditHeaderReader.Read(httpContext)); } }