/// <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);
        }
Ejemplo n.º 3
0
        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));
            }
        }
Ejemplo n.º 4
0
        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.");
            }
        }
Ejemplo n.º 5
0
        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.");
            }
        }
Ejemplo n.º 6
0
        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);
        }
Ejemplo n.º 7
0
        public static RequestContextAccessor <IFhirRequestContext> SetupAccessor(this IFhirRequestContext context)
        {
            RequestContextAccessor <IFhirRequestContext> accessor = Substitute.For <RequestContextAccessor <IFhirRequestContext> >();

            accessor.RequestContext.Returns(context);
            return(accessor);
        }
Ejemplo n.º 8
0
        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();
                    }
                }
            }
        }
Ejemplo n.º 9
0
        /// <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);
        }
Ejemplo n.º 10
0
        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);
        }
Ejemplo n.º 11
0
        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);
        }
Ejemplo n.º 14
0
        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);
        }
Ejemplo n.º 18
0
        public BundleFactoryTests()
        {
            _bundleFactory = new BundleFactory(
                _urlResolver,
                _fhirRequestContextAccessor);

            IFhirRequestContext fhirRequestContext = Substitute.For <IFhirRequestContext>();

            fhirRequestContext.CorrelationId.Returns(_correlationId);

            _fhirRequestContextAccessor.RequestContext.Returns(fhirRequestContext);
        }
Ejemplo n.º 19
0
        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);
        }
Ejemplo n.º 21
0
        /// <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);
        }
Ejemplo n.º 22
0
        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);
            }
        }
Ejemplo n.º 23
0
        /// <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()));
        }
Ejemplo n.º 24
0
        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);
        }
Ejemplo n.º 25
0
        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));
        }
Ejemplo n.º 27
0
        /// <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;
            }
        }
Ejemplo n.º 29
0
        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());
            }
        }
Ejemplo n.º 30
0
        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));
            }
        }