/// <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); }
/// <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); }