/// <summary>
        /// Provide a fault
        /// </summary>
        public bool ProvideFault(Exception error, RestResponseMessage response)
        {
            this.m_tracer.TraceEvent(EventLevel.Error, "Error on WCF FHIR Pipeline: {0}", error);

            RestOperationContext.Current.OutgoingResponse.StatusCode = ClassifyErrorCode(error);
            if (RestOperationContext.Current.OutgoingResponse.StatusCode == 401)
            {
                // Get to the root of the error
                while (error.InnerException != null)
                {
                    error = error.InnerException;
                }

                if (error is PolicyViolationException pve)
                {
                    var method = RestOperationContext.Current.AppliedPolicies.Any(o => o.GetType().Name.Contains("Basic")) ? "Basic" : "Bearer";
                    response.AddAuthenticateHeader(method, RestOperationContext.Current.IncomingRequest.Url.Host, "insufficient_scope", pve.PolicyId, error.Message);
                }
                else if (error is SecurityTokenException ste)
                {
                    response.AddAuthenticateHeader("Bearer", RestOperationContext.Current.IncomingRequest.Url.Host, "token_error", description: ste.Message);
                }
                else if (error is SecuritySessionException ses)
                {
                    switch (ses.Type)
                    {
                    case SessionExceptionType.Expired:
                    case SessionExceptionType.NotYetValid:
                    case SessionExceptionType.NotEstablished:
                        response.StatusCode = (int)System.Net.HttpStatusCode.Unauthorized;
                        response.AddAuthenticateHeader("Bearer", RestOperationContext.Current.IncomingRequest.Url.Host, "unauthorized", PermissionPolicyIdentifiers.Login, ses.Message);
                        break;

                    default:
                        response.StatusCode = (int)System.Net.HttpStatusCode.Forbidden;
                        break;
                    }
                }
            }

            var errorResult = DataTypeConverter.CreateErrorResult(error);

            // Return error in XML only at this point
            new FhirMessageDispatchFormatter().SerializeResponse(response, null, errorResult);
            return(true);
        }
        /// <summary>
        /// Provide fault
        /// </summary>
        public bool ProvideFault(Exception error, RestResponseMessage faultMessage)
        {
            var uriMatched = RestOperationContext.Current.IncomingRequest.Url;

            while (error.InnerException != null)
            {
                error = error.InnerException;
            }

            var fault      = new RestServiceFault(error);
            var authScheme = RestOperationContext.Current.AppliedPolicies.OfType <BasicAuthorizationAccessBehavior>().Any() ? "Basic" : "Bearer";
            var authRealm  = RestOperationContext.Current.IncomingRequest.Url.Host;

            // Formulate appropriate response
            if (error is DomainStateException)
            {
                faultMessage.StatusCode = (int)System.Net.HttpStatusCode.ServiceUnavailable;
            }
            else if (error is ObjectLockedException lockException)
            {
                faultMessage.StatusCode = 423;
                fault.Data.Add(lockException.LockedUser);
            }
            else if (error is PolicyViolationException)
            {
                var pve = error as PolicyViolationException;
                if (pve.PolicyDecision == PolicyGrantType.Elevate)
                {
                    // Ask the user to elevate themselves
                    faultMessage.StatusCode = 401;
                    faultMessage.AddAuthenticateHeader(authScheme, authRealm, "insufficient_scope", pve.PolicyId, error.Message);
                }
                else
                {
                    faultMessage.StatusCode = 403;
                }
            }
            else if (error is SecurityException)
            {
                faultMessage.StatusCode = (int)HttpStatusCode.Forbidden;
            }
            else if (error is SecurityTokenException)
            {
                // TODO: Audit this
                faultMessage.StatusCode = (int)System.Net.HttpStatusCode.Unauthorized;
                var authHeader = $"Bearer realm=\"{RestOperationContext.Current.IncomingRequest.Url.Host}\" error=\"invalid_token\" error_description=\"{error.Message}\"";
                faultMessage.AddAuthenticateHeader(authScheme, authRealm, error: "invalid_token", description: error.Message);
            }
            else if (error is LimitExceededException)
            {
                faultMessage.StatusCode        = (int)(HttpStatusCode)429;
                faultMessage.StatusDescription = "Too Many Requests";
                faultMessage.Headers.Add("Retry-After", "1200");
            }
            else if (error is AuthenticationException)
            {
                faultMessage.StatusCode = (int)System.Net.HttpStatusCode.Unauthorized;
                faultMessage.AddAuthenticateHeader(authScheme, authRealm, "invalid_token", description: error.Message);
            }
            else if (error is UnauthorizedAccessException)
            {
                faultMessage.StatusCode = (int)System.Net.HttpStatusCode.Forbidden;
            }
            else if (error is SecuritySessionException ses)
            {
                switch (ses.Type)
                {
                case SessionExceptionType.Expired:
                case SessionExceptionType.NotYetValid:
                case SessionExceptionType.NotEstablished:
                    faultMessage.StatusCode = (int)System.Net.HttpStatusCode.Unauthorized;
                    faultMessage.AddAuthenticateHeader(authScheme, authRealm, error: "unauthorized");
                    break;

                default:
                    faultMessage.StatusCode = (int)System.Net.HttpStatusCode.Forbidden;
                    break;
                }
            }
            else if (error is FaultException)
            {
                faultMessage.StatusCode = (int)(error as FaultException).StatusCode;
            }
            else if (error is Newtonsoft.Json.JsonException ||
                     error is System.Xml.XmlException)
            {
                faultMessage.StatusCode = (int)System.Net.HttpStatusCode.BadRequest;
            }
            else if (error is DuplicateKeyException || error is DuplicateNameException)
            {
                faultMessage.StatusCode = (int)System.Net.HttpStatusCode.Conflict;
            }
            else if (error is FileNotFoundException || error is KeyNotFoundException)
            {
                faultMessage.StatusCode = (int)System.Net.HttpStatusCode.NotFound;
            }
            else if (error is DomainStateException)
            {
                faultMessage.StatusCode = (int)System.Net.HttpStatusCode.ServiceUnavailable;
            }
            else if (error is DetectedIssueException)
            {
                faultMessage.StatusCode = (int)(System.Net.HttpStatusCode) 422;
            }
            else if (error is NotImplementedException)
            {
                faultMessage.StatusCode = (int)HttpStatusCode.NotImplemented;
            }
            else if (error is NotSupportedException)
            {
                faultMessage.StatusCode = (int)HttpStatusCode.MethodNotAllowed;
            }
            else if (error is PatchException)
            {
                faultMessage.StatusCode = (int)HttpStatusCode.Conflict;
            }
            else
            {
                faultMessage.StatusCode = (int)System.Net.HttpStatusCode.InternalServerError;
            }

            switch (faultMessage.StatusCode)
            {
            case 409:
            case 429:
            case 503:
                this.m_traceSource.TraceInfo("Issue on REST pipeline: {0}", error);
                break;

            case 401:
            case 403:
            case 501:
            case 405:
                this.m_traceSource.TraceWarning("Warning on REST pipeline: {0}", error);
                break;

            default:
                this.m_traceSource.TraceError("Error on REST pipeline: {0}", error);
                break;
            }

            RestMessageDispatchFormatter.CreateFormatter(RestOperationContext.Current.ServiceEndpoint.Description.Contract.Type).SerializeResponse(faultMessage, null, fault);
            AuditUtil.AuditNetworkRequestFailure(error, uriMatched, RestOperationContext.Current.IncomingRequest.Headers.AllKeys.ToDictionary(o => o, o => RestOperationContext.Current.IncomingRequest.Headers[o]), RestOperationContext.Current.OutgoingResponse.Headers.AllKeys.ToDictionary(o => o, o => RestOperationContext.Current.OutgoingResponse.Headers[o]));
            return(true);
        }
Example #3
0
        /// <summary>
        /// Provide the fault to the pipeline
        /// </summary>
        public bool ProvideFault(Exception error, RestResponseMessage response)
        {
            response.Body = new MemoryStream(System.Text.Encoding.UTF8.GetBytes(error.ToString()));

            var cause = error;

            while (cause.InnerException != null)
            {
                cause = cause.InnerException;
            }

            if (cause is FileNotFoundException || cause is KeyNotFoundException)
            {
                response.StatusCode = 404;
            }
            else if (cause is InvalidOperationException)
            {
                response.StatusCode = 500;
            }
            else if (cause is XmlException || cause is JsonException)
            {
                response.StatusCode = 400;
            }
            else if (cause is SecurityException || cause is UnauthorizedAccessException)
            {
                response.AddAuthenticateHeader("bearer", RestOperationContext.Current.IncomingRequest.Headers["Host"]);
                response.StatusCode = 401;
            }
            else if (cause is NotSupportedException)
            {
                response.StatusCode = 405;
            }
            else if (cause is NotImplementedException)
            {
                response.StatusCode = 501;
            }
            else if (cause is FaultException)
            {
                response.StatusCode = (cause as FaultException).StatusCode;
                if (cause.GetType() != typeof(FaultException)) // Special classification
                {
                    var errorData = error.GetType().GetRuntimeProperty("Body").GetValue(cause);
                    new DefaultDispatchFormatter().SerializeResponse(response, null, errorData);
                    return(true);
                }
            }
            else
            {
                response.StatusCode = 500;
            }

            // Load the exception screen
            using (var sr = new StreamReader(typeof(DefaultErrorHandler).Assembly.GetManifestResourceStream("RestSrvr.Resources.ServiceError.html")))
            {
                response.ContentType = "text/html";
                var errRet = sr.ReadToEnd().Replace("{status}", response.StatusCode.ToString())
                             .Replace("{description}", response.StatusDescription)
                             .Replace("{type}", error.GetType().Name)
                             .Replace("{message}", error.Message)
                             .Replace("{details}", error.ToString())
                             .Replace("{trace}", error.StackTrace);
                response.Body        = new MemoryStream(Encoding.UTF8.GetBytes(errRet));
                response.ContentType = "text/html";
            }
            return(true);
        }