/// <summary> /// Perform the dispatching function of the service /// </summary> internal bool Dispatch(RestRequestMessage requestMessage, RestResponseMessage responseMessage) { try { this.m_traceSource.TraceEvent(TraceEventType.Verbose, 0, "Begin service dispatch of {0} {1} > {2}", requestMessage.Method, requestMessage.Url, this.m_service.Name); // Endpoint var ep = this.m_service.Endpoints.FirstOrDefault(o => o.Dispatcher.CanDispatch(requestMessage)); // Find the endpoint if (ep == null) { throw new FaultException(404, "Resource not Found"); } RestOperationContext.Current.ServiceEndpoint = ep; // Apply the policy on the specified context foreach (var pol in this.m_servicePolicies) { RestOperationContext.Current.AddAppliedPolicy(pol); pol.Apply(requestMessage); } return(ep.Dispatcher.Dispatch(this, requestMessage, responseMessage)); } catch (Exception e) { return(this.HandleFault(e, responseMessage)); } }
/// <summary> /// Handle the provided fault /// </summary> internal bool HandleFault(Exception e, RestResponseMessage responseMessage) { var erh = this.m_errorHandlers.FirstOrDefault(o => o.HandleError(e)); erh?.ProvideFault(e, responseMessage); return(true); }
/// <summary> /// Provide the actual fault message /// </summary> public bool ProvideFault(Exception error, RestResponseMessage response) { if (error is SecurityException) { response.StatusCode = 403; } else if (error is FileNotFoundException || error is KeyNotFoundException) { response.StatusCode = 404; } else if (error is DuplicateNameException) { response.StatusCode = 409; } else { response.StatusCode = 500; } var errorResponse = new ErrorResult(error); response.Body = new MemoryStream(Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(errorResponse))); response.ContentType = "application/json"; return(true); }
/// <summary> /// Process the request from the <paramref name="state"/> passed /// on the action. /// </summary> private void DoProcessRequestInternal(Object accept) { { var context = accept as HttpListenerContext; try { RestOperationContext.Current = new RestOperationContext(context); var requestMessage = new RestRequestMessage(context.Request); using (var responseMessage = new RestResponseMessage(context.Response)) { this.m_serviceDispatcher.Dispatch(requestMessage, responseMessage); if (requestMessage.Method.ToLowerInvariant() != "head") { responseMessage.FlushResponseStream(); } } } catch (Exception e) { this.m_traceSource.TraceEvent(TraceEventType.Error, e.HResult, e.ToString()); } finally { RestOperationContext.Current.Dispose(); } } }
/// <summary> /// Provide the fault /// </summary> public bool ProvideFault(Exception error, RestResponseMessage response) { var errCode = WebErrorUtility.ClassifyException(error, true); var hdlr = ApplicationContext.Current.GetService <IAppletManagerService>().Applets.SelectMany(o => o.ErrorAssets).FirstOrDefault(o => o.ErrorCode == errCode); #if DEBUG var ie = error; while (ie != null) { this.m_tracer.TraceError("{0} - ({1}){2} - {3}", error == ie ? "" : "Caused By", RestOperationContext.Current.EndpointOperation?.Description.InvokeMethod.Name, ie.GetType().FullName, ie.Message); ie = ie.InnerException; } #else if (error is TargetInvocationException) { this.m_tracer.TraceError("{0} - {1} / {2}", RestOperationContext.Current.EndpointOperation.Description.InvokeMethod.Name, error.Message, error.InnerException?.Message); } else { this.m_tracer.TraceError("{0} - {1}", RestOperationContext.Current.EndpointOperation.Description.InvokeMethod.Name, error.Message); } #endif // Grab the asset handler try { if (hdlr != null) { response.Body = new MemoryStream(new byte[0]); RestOperationContext.Current.OutgoingResponse.Redirect(hdlr.Asset); } else { RestOperationContext.Current.OutgoingResponse.StatusCode = errCode; using (var sr = new StreamReader(typeof(AgsWebErrorHandlerServiceBehavior).Assembly.GetManifestResourceStream("SanteDB.DisconnectedClient.Ags.Resources.GenericError.html"))) { string errRsp = 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); RestOperationContext.Current.OutgoingResponse.ContentType = "text/html"; response.Body = new MemoryStream(Encoding.UTF8.GetBytes(errRsp)); } } AuditUtil.AuditNetworkRequestFailure(error, RestOperationContext.Current.IncomingRequest.Url, 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); } catch (Exception e) { Tracer.GetTracer(typeof(AgsWebErrorHandlerServiceBehavior)).TraceError("Could not provide fault: {0}", e.ToString()); throw; } }
/// <summary> /// Before sending the response /// </summary> public void BeforeSendResponse(RestResponseMessage response) { var processingTime = DateTime.Now.Subtract(httpCorrelation.Value); this.m_traceSource.TraceEvent(EventLevel.Verbose, "HTTP RSP {0} : {1} ({2} ms)", httpCorrelation.Key, response.StatusCode, processingTime.TotalMilliseconds); }
/// <summary> /// Before sending a response /// </summary> public void BeforeSendResponse(RestResponseMessage response) { //response.Headers.Add("Content-Security-Policy", $"script-src-elem 'nonce-{this.Nonce}'; script-src 'self'"); response.Headers.Add("Content-Security-Policy", $"script-src-elem 'self' 'nonce-{this.Nonce}' 'strict-dynamic'; script-src 'self' 'nonce-{this.Nonce}'"); response.Headers.Add("X-XSS-Protection", "1; mode=block"); response.Headers.Add("X-Frame-Options", "deny"); response.Headers.Add("Feature-Policy", "autoplay 'none'; accelerometer 'none'; geolocation 'none'; payment 'none'"); response.Headers.Add("X-Content-Type-Options", "nosniff"); }
/// <summary> /// Rest http request is made by this method with parameters supplied /// </summary> /// <param name="restRequestParameter"></param> /// <returns></returns> public async Task <HttpResponseMessage> SendRequestAsync(RestRequestParameter restRequestParameter) { HttpResponseMessage response; Uri url = restRequestParameter.GetRequestUri(); if (restRequestParameter.IgnoreCertificateErrors) { ServicePointManager.ServerCertificateValidationCallback += (sender, certificate, chain, errors) => { return(true); }; } using (HttpClient httpClient = CreateHttpClient(restRequestParameter.RequestTimeOut, restRequestParameter.ClientCertificates)) { HttpRequestMessage httpRequestMessage = null; try { httpRequestMessage = new HttpRequestMessage(restRequestParameter.HttpMethod, url) { Content = restRequestParameter.RequestContent }; if (restRequestParameter.HeaderParameters != null) { foreach (var headerDefinition in restRequestParameter.HeaderParameters.Keys) { httpRequestMessage.Headers.Add(headerDefinition, restRequestParameter.HeaderParameters[headerDefinition]); } } response = await httpClient.SendAsync(httpRequestMessage); } catch (Exception ex) { Exception sourceException = ex; if (ex.InnerException != null) { sourceException = ex.InnerException; } RestResponseMessage restReponseMessage = new RestResponseMessage(HttpStatusCode.BadRequest, sourceException) { RequestMessage = httpRequestMessage }; response = restReponseMessage; } } return(response); }
/// <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> /// /// </summary> /// <param name="response"></param> /// <returns></returns> private async Task <Exception> ParseResponseErrorMessageContent(HttpResponseMessage response) { Exception exceptionResult; RestResponseMessage responseMessage = response as RestResponseMessage; if (responseMessage != null) { exceptionResult = responseMessage.Exception; } else { string errorMessage = await response.Content.ReadAsStringAsync(); exceptionResult = new RestServiceException((int)response.StatusCode, errorMessage); } return(exceptionResult); }
/// <summary> /// Before sending the response /// </summary> public void BeforeSendResponse(RestResponseMessage response) { var ext = RestOperationContext.Current.IncomingRequest.Url.AbsolutePath; if (ext.Contains(".")) { ext = ext.Substring(ext.LastIndexOf(".")); if (this.cacheExtensions.Contains(ext)) { RestOperationContext.Current.OutgoingResponse.AddHeader("Cache-Control", "public, max-age=28800"); RestOperationContext.Current.OutgoingResponse.AddHeader("Expires", DateTime.UtcNow.AddHours(1).ToString("ddd, dd MMM yyyy HH:mm:ss 'GMT'")); } else { RestOperationContext.Current.OutgoingResponse.AddHeader("Cache-Control", "no-cache"); } } }
/// <summary> /// Provide a fault /// </summary> public bool ProvideFault(Exception error, RestResponseMessage response) { this.m_tracer.TraceEvent(EventLevel.Error, "Error on OAUTH Pipeline: {0}", error); // Error OAuthError err = new OAuthError() { Error = OAuthErrorType.invalid_request, ErrorDescription = error.Message }; JsonSerializer serializer = new JsonSerializer(); response.ContentType = "application/json"; using (var stw = new StringWriter()) { serializer.Serialize(stw, err); response.Body = new MemoryStream(Encoding.UTF8.GetBytes(stw.ToString())); } return(true); }
/// <summary> /// Before send response /// </summary> public void BeforeSendResponse(RestResponseMessage response) { // TODO: Add a configuration option to disable this var resourcePath = RestOperationContext.Current.IncomingRequest.Url.AbsolutePath.Substring(RestOperationContext.Current.ServiceEndpoint.Description.ListenUri.AbsolutePath.Length); var settings = this.m_settings.Resource.FirstOrDefault(o => o.Name == "*" || o.Name == resourcePath); if (settings != null) { Dictionary <String, String> requiredHeaders = new Dictionary <string, string>() { { "Access-Control-Allow-Origin", settings.Domain }, { "Access-Control-Allow-Methods", String.Join(",", settings.Verbs) }, { "Access-Control-Allow-Headers", String.Join(",", settings.Headers) } }; foreach (var kv in requiredHeaders) { if (!RestOperationContext.Current.OutgoingResponse.Headers.AllKeys.Contains(kv.Key)) { RestOperationContext.Current.OutgoingResponse.Headers.Add(kv.Key, kv.Value); } } } }
/// <summary> /// Serialize the reply /// </summary> public void SerializeResponse(RestResponseMessage responseMessage, object[] parameters, object result) { try { // Outbound control var httpRequest = RestOperationContext.Current.IncomingRequest; string accepts = httpRequest.Headers["Accept"], contentType = httpRequest.Headers["Content-Type"], formatParm = httpRequest.QueryString["_format"]; // Result is serializable if (result?.GetType().GetCustomAttribute <XmlTypeAttribute>() != null || result?.GetType().GetCustomAttribute <JsonObjectAttribute>() != null) { XmlSerializer xsz = XmlModelSerializerFactory.Current.CreateSerializer(result.GetType()); MemoryStream ms = new MemoryStream(); xsz.Serialize(ms, result); contentType = "application/fhir+xml"; ms.Seek(0, SeekOrigin.Begin); // The request was in JSON or the accept is JSON if (accepts?.StartsWith("application/fhir+json") == true || contentType?.StartsWith("application/fhir+json") == true || formatParm?.Contains("application/fhir+json") == true) { // Parse XML object Object fhirObject = null; using (StreamReader sr = new StreamReader(ms)) { String fhirContent = sr.ReadToEnd(); var parser = new FhirXmlParser(); parser.Settings.AllowUnrecognizedEnums = true; parser.Settings.AcceptUnknownMembers = true; parser.Settings.DisallowXsiAttributesOnRoot = false; fhirObject = parser.Parse <Resource>(fhirContent); } // Now we serialize to JSON byte[] body = new FhirJsonSerializer().SerializeToBytes(fhirObject as Hl7.Fhir.Model.Resource); ms.Dispose(); ms = new MemoryStream(body); // Prepare reply for the WCF pipeline contentType = "application/fhir+json"; } responseMessage.Body = ms; } else if (result is XmlSchema) { MemoryStream ms = new MemoryStream(); (result as XmlSchema).Write(ms); ms.Seek(0, SeekOrigin.Begin); contentType = "text/xml"; responseMessage.Body = ms; } else if (result is Stream) // TODO: This is messy, clean it up { responseMessage.Body = result as Stream; } RestOperationContext.Current.OutgoingResponse.ContentType = contentType; RestOperationContext.Current.OutgoingResponse.AppendHeader("X-PoweredBy", String.Format("{0} v{1} ({2})", Assembly.GetEntryAssembly().GetName().Name, Assembly.GetEntryAssembly().GetName().Version, Assembly.GetEntryAssembly().GetCustomAttribute <AssemblyInformationalVersionAttribute>()?.InformationalVersion)); RestOperationContext.Current.OutgoingResponse.AppendHeader("X-GeneratedOn", DateTime.Now.ToString("o")); } catch (Exception e) { this.m_traceSource.TraceEvent(EventLevel.Error, e.ToString()); throw; } }
/// <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> Constructor for a new instance of the Delete_Verify_WebContentViewer class </summary> /// <param name="RequestSpecificValues"> All the necessary, non-global data specific to the current request </param> /// <param name="StaticPage"> Static page info for this request </param> public Delete_Verify_WebContentViewer(RequestCache RequestSpecificValues, HTML_Based_Content StaticPage) : base(RequestSpecificValues, StaticPage) { // Pull the web content page if (RequestSpecificValues.Current_Mode.WebContentID.HasValue) { webContent = SobekEngineClient.WebContent.Get_HTML_Based_Content(RequestSpecificValues.Current_Mode.WebContentID.Value, true, RequestSpecificValues.Tracer); } // This should never occur, but just a double check if ((webContent == null) || (!webContent.WebContentID.HasValue)) { RequestSpecificValues.Current_Mode.Mode = Display_Mode_Enum.Aggregation; UrlWriterHelper.Redirect(RequestSpecificValues.Current_Mode); return; } // Ensure there IS a logged on user RequestSpecificValues.Tracer.Add_Trace("Delete_Item_MySobekViewer.Delete_Verify_WebContentViewer", "Validate user"); if ((RequestSpecificValues.Current_User == null) || (!RequestSpecificValues.Current_User.LoggedOn)) { RequestSpecificValues.Current_Mode.Mode = Display_Mode_Enum.Aggregation; UrlWriterHelper.Redirect(RequestSpecificValues.Current_Mode); return; } // If the user was logged on, but did not have permissions, show an error message canDelete = true; deleted = false; if (!webContent.Can_Delete(RequestSpecificValues.Current_User)) { errorMessage = "ERROR: You do not have permission to delete this page"; canDelete = false; } else if (HttpContext.Current.Request.RequestType == "POST") { string save_value = HttpContext.Current.Request.Form["admin_delete_item"]; // Better say "DELETE", or just send back to the item if ((save_value != null) && (String.Compare(save_value, "DELETE", StringComparison.OrdinalIgnoreCase) == 0)) { string entered_value = HttpContext.Current.Request.Form["admin_delete_confirm"]; if ((entered_value == null) || (entered_value.ToUpper() != "DELETE")) { errorMessage = "ERROR: To verify this deletion, type DELETE into the text box and press CONFIRM"; } else { string deleteReason = "Requested via web application"; RestResponseMessage message = SobekEngineClient.WebContent.Delete_HTML_Based_Content(webContent.WebContentID.Value, RequestSpecificValues.Current_User.Full_Name, deleteReason, RequestSpecificValues.Tracer); errorMessage = message.Message; if ((message.ErrorTypeEnum != ErrorRestTypeEnum.Successful) && (String.IsNullOrEmpty(errorMessage))) { errorMessage = "Error encountered on SobekCM engine."; } else { errorMessage = "Successfully deleted this web content page."; // Clear cached data here on the client CachedDataManager.WebContent.Clear_All_Web_Content_Lists(); CachedDataManager.WebContent.Clear_All_Web_Content_Pages(); CachedDataManager.WebContent.Clear_Page_Details(); UI_ApplicationCache_Gateway.WebContent_Hierarchy_Clear(); deleted = true; } } } } }
/// <summary> /// Implemented below /// </summary> public abstract void SerializeResponse(RestResponseMessage response, object[] parameters, object result);
/// <summary> /// Serialize the reply /// </summary> public override void SerializeResponse(RestResponseMessage response, object[] parameters, object result) { try { // Outbound control var httpRequest = RestOperationContext.Current.IncomingRequest; string accepts = httpRequest.Headers["Accept"]?.ToLower(), contentType = httpRequest.Headers["Content-Type"]?.ToLower(); // The request was in JSON or the accept is JSON if (result is Stream) // TODO: This is messy, clean it up { contentType = "application/octet-stream"; response.Body = result as Stream; } else if (accepts?.StartsWith("application/json+sdb-viewmodel") == true && typeof(IdentifiedData).IsAssignableFrom(result?.GetType())) { var viewModel = httpRequest.Headers["X-SanteDB-ViewModel"] ?? httpRequest.QueryString["_viewModel"]; // Create the view model serializer using (AuthenticationContextExtensions.TryEnterDeviceContext()) { var viewModelSerializer = new JsonViewModelSerializer(); viewModelSerializer.LoadSerializerAssembly(typeof(ActExtensionViewModelSerializer).Assembly); if (!String.IsNullOrEmpty(viewModel)) { var viewModelDescription = ApplicationContext.Current.GetService <IAppletManagerService>()?.Applets.GetViewModelDescription(viewModel); viewModelSerializer.ViewModel = viewModelDescription; } else { viewModelSerializer.ViewModel = m_defaultViewModel; } using (var tms = new MemoryStream()) using (StreamWriter sw = new StreamWriter(tms, Encoding.UTF8)) using (JsonWriter jsw = new JsonTextWriter(sw)) { viewModelSerializer.Serialize(jsw, result as IdentifiedData); jsw.Flush(); sw.Flush(); response.Body = new MemoryStream(tms.ToArray()); } } contentType = "application/json+sdb-viewModel"; } // The request was in XML and/or the accept is JSON else if ((accepts?.StartsWith("application/xml") == true || contentType?.StartsWith("application/xml") == true) && result?.GetType().GetCustomAttribute <XmlTypeAttribute>() != null) { XmlSerializer xsz = XmlModelSerializerFactory.Current.CreateSerializer(result.GetType()); MemoryStream ms = new MemoryStream(); xsz.Serialize(ms, result); contentType = "application/xml"; ms.Seek(0, SeekOrigin.Begin); response.Body = ms; } else if (result is XmlSchema) { MemoryStream ms = new MemoryStream(); (result as XmlSchema).Write(ms); ms.Seek(0, SeekOrigin.Begin); contentType = "text/xml"; response.Body = ms; } else if (result != null) { // Prepare the serializer JsonSerializer jsz = new JsonSerializer(); jsz.Converters.Add(new StringEnumConverter()); // Write json data using (MemoryStream ms = new MemoryStream()) using (StreamWriter sw = new StreamWriter(ms, Encoding.UTF8)) using (JsonWriter jsw = new JsonTextWriter(sw)) { jsz.DateFormatHandling = DateFormatHandling.IsoDateFormat; jsz.NullValueHandling = NullValueHandling.Ignore; jsz.ReferenceLoopHandling = ReferenceLoopHandling.Ignore; jsz.TypeNameHandling = TypeNameHandling.Auto; jsz.Converters.Add(new StringEnumConverter()); jsz.Serialize(jsw, result); jsw.Flush(); sw.Flush(); response.Body = new MemoryStream(ms.ToArray()); } // Prepare reply for the WCF pipeline contentType = "application/json"; } else if (response.StatusCode == 0) { response.StatusCode = 204; // no content } RestOperationContext.Current.OutgoingResponse.ContentType = RestOperationContext.Current.OutgoingResponse.ContentType ?? contentType; } catch (Exception e) { this.m_traceSource.TraceError("Error Serializing Dispatch Reply: {0}", e.ToString()); new AgsErrorHandlerServiceBehavior().ProvideFault(e, response); } }
/// <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); }
/// <summary> /// Provide the fault /// </summary> /// <param name="error"></param> /// <param name="response"></param> /// <returns></returns> public bool ProvideFault(Exception error, RestResponseMessage faultMessage) { try { #if DEBUG this.m_tracer.TraceWarning("Error on pipeline: {0}", error); #else if (error is TargetInvocationException tie) { this.m_tracer.TraceWarning("{0} - {1} / {2}", RestOperationContext.Current?.EndpointOperation?.Description?.InvokeMethod?.Name, error.Message, error.InnerException?.Message); } else { this.m_tracer.TraceWarning("{0} - {1}", RestOperationContext.Current?.EndpointOperation?.Description?.InvokeMethod?.Name, error.Message); } #endif if (faultMessage == null) { if (RestOperationContext.Current.OutgoingResponse == null) { this.m_tracer.TraceWarning("Client hangup"); return(false); } this.m_tracer.TraceWarning("For some reason the fault message is null - "); faultMessage = new RestResponseMessage(RestOperationContext.Current.OutgoingResponse); } var ie = error; while (ie != null) { this.m_tracer.TraceWarning("{0} - ({1}){2} - {3}", error == ie ? "" : "Caused By", RestOperationContext.Current.EndpointOperation?.Description.InvokeMethod.Name, ie?.GetType().FullName, ie.Message); // TODO: Do we need this or can we just capture the innermost exception as the cause? if (ie is RestClientException <RestServiceFault> || ie is SecurityException || ie is DetectedIssueException || ie is FileNotFoundException || ie is KeyNotFoundException) { error = ie; } ie = ie.InnerException; } faultMessage.StatusCode = WebErrorUtility.ClassifyException(error); object fault = (error as RestClientException <RestServiceFault>)?.Result ?? new RestServiceFault(error); if (error is FaultException && error?.GetType() != typeof(FaultException)) // Special classification { fault = error?.GetType().GetRuntimeProperty("Body").GetValue(error); } var formatter = RestMessageDispatchFormatter.CreateFormatter(RestOperationContext.Current.ServiceEndpoint.Description.Contract.Type); if (formatter != null) { formatter.SerializeResponse(faultMessage, null, fault); } else { RestOperationContext.Current.OutgoingResponse.OutputStream.Write(System.Text.Encoding.UTF8.GetBytes(error.Message), 0, System.Text.Encoding.UTF8.GetByteCount(error.Message)); } try { if (ApplicationServiceContext.Current.GetService <IOperatingSystemInfoService>()?.OperatingSystem != OperatingSystemID.Android) { AuditUtil.AuditNetworkRequestFailure(error, RestOperationContext.Current.IncomingRequest.Url, 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])); } } catch (Exception e) { this.m_tracer.TraceError("Could not send network request failure - {0}", e); } } catch (Exception e) { this.m_tracer.TraceError("Error providing fault: {0}", e); } return(true); }
/// <summary> /// Dispatch the message /// </summary> internal bool Dispatch(ServiceDispatcher serviceDispatcher, RestRequestMessage requestMessage, RestResponseMessage responseMessage) { try { this.m_traceSource.TraceEvent(TraceEventType.Verbose, 0, "Begin operation dispatch of {0} {1} to {2}", requestMessage.Method, requestMessage.Url, this.m_endpointOperation.Description.InvokeMethod); foreach (var pol in this.m_operationPolicies) { pol.Apply(this.m_endpointOperation, requestMessage); } var invoke = this.m_endpointOperation.Description.InvokeMethod; var parameters = new object[invoke.GetParameters().Length]; // By default parameters are passed by name var parmMatch = this.m_dispatchRegex.Match(requestMessage.OperationPath); for (int i = 0; i < this.m_regexGroupNames.Length; i++) { var pindex = Array.FindIndex(invoke.GetParameters(), o => $"{{{o.Name}}}" == this.m_regexGroupNames[i]); var sparm = invoke.GetParameters()[pindex]; object sval = parmMatch.Groups[i + 1].Value; if (sparm.ParameterType == typeof(int)) { sval = Int32.Parse(sval.ToString()); } else if (sparm.ParameterType == typeof(Guid)) { sval = Guid.Parse(sval.ToString()); } parameters[pindex] = sval; } this.DispatchFormatter.DeserializeRequest(this.m_endpointOperation, requestMessage, parameters); this.m_traceSource.TraceData(TraceEventType.Verbose, 0, parameters); // Validate parameters if (!Enumerable.Range(0, invoke.GetParameters().Length).All(o => parameters[o] == null || invoke.GetParameters()[o].ParameterType.IsAssignableFrom(parameters[o]?.GetType()))) { throw new FaultException(400, "Bad Request"); } // Gather instance object instance = serviceDispatcher.Service.Instance; if (serviceDispatcher.Service.InstanceMode == Attributes.ServiceInstanceMode.PerCall) { instance = Activator.CreateInstance(serviceDispatcher.Service.BehaviorType); } object result = invoke.Invoke(instance, parameters); // Does the invoke override content-type? var format = invoke.ReturnParameter.GetCustomAttribute <MessageFormatAttribute>()?.MessageFormat; if (format.HasValue) { responseMessage.Format = format.Value; } if (result == null && responseMessage.StatusCode == 0) { responseMessage.StatusCode = 204; } else { this.DispatchFormatter.SerializeResponse(responseMessage, parameters, result); } parameters = null; return(true); } catch (TargetInvocationException e) { this.m_traceSource.TraceEvent(TraceEventType.Error, 0, e.ToString()); return(serviceDispatcher.HandleFault(e.InnerException, responseMessage)); } catch (Exception e) { this.m_traceSource.TraceEvent(TraceEventType.Error, e.HResult, e.ToString()); return(serviceDispatcher.HandleFault(e, responseMessage)); } }
/// <summary> /// Before sending the response /// </summary> public void BeforeSendResponse(RestResponseMessage response) { try { string encodings = RestOperationContext.Current.IncomingRequest.Headers.Get("Accept-Encoding"); string compressionScheme = String.Empty; if (!string.IsNullOrEmpty(encodings)) { encodings = encodings.ToLowerInvariant(); if (encodings.Contains("lzma")) { compressionScheme = "lzma"; } else if (encodings.Contains("bzip2")) { compressionScheme = "bzip2"; } else if (encodings.Contains("gzip")) { compressionScheme = "gzip"; } else if (encodings.Contains("deflate")) { compressionScheme = "deflate"; } else { response.Headers.Add("X-CompressResponseStream", "no-known-accept"); } } // No reply = no compress :) if (response.Body == null) { return; } // Finally compress // Compress if (!String.IsNullOrEmpty(compressionScheme)) { try { response.Headers.Add("Content-Encoding", compressionScheme); response.Headers.Add("X-CompressResponseStream", compressionScheme); // Read binary contents of the message var memoryStream = new MemoryStream(); using (var compressor = CompressionUtil.GetCompressionScheme(compressionScheme).CreateCompressionStream(memoryStream)) response.Body.CopyTo(compressor); response.Body.Dispose(); memoryStream.Seek(0, SeekOrigin.Begin); response.Body = memoryStream; } catch (Exception e) { this.m_traceSource.TraceEvent(EventLevel.Error, e.ToString()); } } } catch (Exception e) { this.m_traceSource.TraceEvent(EventLevel.Error, e.ToString()); } }
/// <summary> Constructor for a new instance of the Aggregations_Mgmt_AdminViewer class </summary> /// <param name="RequestSpecificValues"> All the necessary, non-global data specific to the current request </param> /// <remarks> Postback from handling an edit or new aggregation is handled here in the constructor </remarks> public Aggregations_Mgmt_AdminViewer(RequestCache RequestSpecificValues) : base(RequestSpecificValues) { RequestSpecificValues.Tracer.Add_Trace("Aggregations_Mgmt_AdminViewer.Constructor", String.Empty); // Set some defaults actionMessage = String.Empty; enteredCode = String.Empty; enteredParent = String.Empty; enteredType = String.Empty; enteredShortname = String.Empty; enteredName = String.Empty; enteredDescription = String.Empty; enteredThematicHeading = String.Empty; enteredIsActive = true; enteredIsHidden = false; addedNewCollection = false; // If the user cannot edit this, go back if ((RequestSpecificValues.Current_User == null) || ((!RequestSpecificValues.Current_User.Is_System_Admin) && (!RequestSpecificValues.Current_User.Is_Portal_Admin))) { RequestSpecificValues.Current_Mode.Mode = Display_Mode_Enum.My_Sobek; RequestSpecificValues.Current_Mode.My_Sobek_Type = My_Sobek_Type_Enum.Home; UrlWriterHelper.Redirect(RequestSpecificValues.Current_Mode); return; } // If this is a postback, handle any events first if (RequestSpecificValues.Current_Mode.isPostBack) { try { // Pull the standard values NameValueCollection form = HttpContext.Current.Request.Form; string save_value = form["admin_aggr_tosave"].ToUpper().Trim(); string new_aggregation_code = String.Empty; if (form["admin_aggr_code"] != null) { new_aggregation_code = form["admin_aggr_code"].ToUpper().Trim(); } // Check for reset request as well string reset_aggregation_code = String.Empty; if (form["admin_aggr_reset"] != null) { reset_aggregation_code = form["admin_aggr_reset"].ToLower().Trim(); } string delete_aggregation_code = String.Empty; if (form["admin_aggr_delete"] != null) { delete_aggregation_code = form["admin_aggr_delete"].ToLower().Trim(); } // Was this to delete the aggregation? if (delete_aggregation_code.Length > 0) { string delete_error; int errorCode = SobekCM_Database.Delete_Item_Aggregation(delete_aggregation_code, RequestSpecificValues.Current_User.Is_System_Admin, RequestSpecificValues.Current_User.Full_Name, RequestSpecificValues.Tracer, out delete_error); if (errorCode <= 0) { string delete_folder = UI_ApplicationCache_Gateway.Settings.Servers.Base_Design_Location + "aggregations\\" + delete_aggregation_code; if (!SobekCM_File_Utilities.Delete_Folders_Recursively(delete_folder)) { actionMessage = "Deleted '" + delete_aggregation_code.ToUpper() + "' aggregation<br /><br />Unable to remove aggregation directory<br /><br />Some of the files may be in use"; } else { actionMessage = "Deleted '" + delete_aggregation_code.ToUpper() + "' aggregation"; } } else { actionMessage = delete_error; } CachedDataManager.Aggregations.Clear(); // Reload the list of all codes, to include this new one and the new hierarchy lock (UI_ApplicationCache_Gateway.Aggregations) { Engine_Database.Populate_Code_Manager(UI_ApplicationCache_Gateway.Aggregations, RequestSpecificValues.Tracer); } } // If there is a reset request here, purge the aggregation from the cache if (reset_aggregation_code.Length > 0) { CachedDataManager.Aggregations.Remove_Item_Aggregation(reset_aggregation_code, RequestSpecificValues.Tracer); } // If there was a save value continue to pull the rest of the data if (save_value.Length > 0) { bool is_active = false; bool is_hidden = true; // Was this to save a new aggregation (from the main page) or edit an existing (from the popup form)? if (save_value == new_aggregation_code) { addedNewCollection = true; // Pull the values from the submitted form string new_type = form["admin_aggr_type"]; string new_parent = form["admin_aggr_parent"].Trim(); string new_name = form["admin_aggr_name"].Trim(); string new_shortname = form["admin_aggr_shortname"].Trim(); string new_description = form["admin_aggr_desc"].Trim(); string new_link = form["admin_aggr_link"].Trim(); string new_thematic_heading = form["admin_aggr_heading"].Trim(); object temp_object = form["admin_aggr_isactive"]; if (temp_object != null) { is_active = true; } temp_object = form["admin_aggr_ishidden"]; if (temp_object != null) { is_hidden = false; } // Convert to the integer id for the parent and begin to do checking List <string> errors = new List <string>(); if (String.IsNullOrEmpty(new_parent)) { errors.Add("You must select a PARENT for this new aggregation"); } // Validate the code if (new_aggregation_code.Length > 20) { errors.Add("New aggregation code must be twenty characters long or less"); } else if (new_aggregation_code.Length == 0) { errors.Add("You must enter a CODE for this item aggregation"); } else if (UI_ApplicationCache_Gateway.Aggregations[new_aggregation_code.ToUpper()] != null) { errors.Add("New code must be unique... <i>" + new_aggregation_code + "</i> already exists"); } else if (UI_ApplicationCache_Gateway.Settings.Static.Reserved_Keywords.Contains(new_aggregation_code.ToLower())) { errors.Add("That code is a system-reserved keyword. Try a different code."); } else { bool alphaNumericTest = new_aggregation_code.All(C => Char.IsLetterOrDigit(C) || C == '_' || C == '-'); if (!alphaNumericTest) { errors.Add("New aggregation code must be only letters and numbers"); new_aggregation_code = new_aggregation_code.Replace("\"", ""); } } // Was there a type and name if (new_type.Length == 0) { errors.Add("You must select a TYPE for this new aggregation"); } if (new_description.Length == 0) { errors.Add("You must enter a DESCRIPTION for this new aggregation"); } if (new_name.Length == 0) { errors.Add("You must enter a NAME for this new aggregation"); } else { if (new_shortname.Length == 0) { new_shortname = new_name; } } if (errors.Count > 0) { // Create the error message actionMessage = "ERROR: Invalid entry for new item aggregation<br />"; foreach (string error in errors) { actionMessage = actionMessage + "<br />" + error; } // Save all the values that were entered enteredCode = new_aggregation_code; enteredDescription = new_description; enteredIsActive = is_active; enteredIsHidden = is_hidden; enteredName = new_name; enteredParent = new_parent; enteredShortname = new_shortname; enteredType = new_type; enteredLink = new_link; enteredThematicHeading = new_thematic_heading; } else { // Get the correct type string correct_type = "Collection"; switch (new_type) { case "coll": correct_type = "Collection"; break; case "group": correct_type = "Collection Group"; break; case "subcoll": correct_type = "SubCollection"; break; case "inst": correct_type = "Institution"; break; case "exhibit": correct_type = "Exhibit"; break; case "subinst": correct_type = "Institutional Division"; break; } // Make sure inst and subinst start with 'i' if (new_type.IndexOf("inst") >= 0) { if (new_aggregation_code[0] == 'I') { new_aggregation_code = "i" + new_aggregation_code.Substring(1); } if (new_aggregation_code[0] != 'i') { new_aggregation_code = "i" + new_aggregation_code; } } // Get the thematic heading id (no checks here) string thematicHeading = null; if (form["admin_aggr_heading"] != null) { int thematicHeadingId = Convert.ToInt32(form["admin_aggr_heading"]); foreach (Thematic_Heading thisHeading in UI_ApplicationCache_Gateway.Thematic_Headings) { if (thisHeading.ID == thematicHeadingId) { thematicHeading = thisHeading.Text; break; } } } // Create the new aggregation argument object New_Aggregation_Arguments args = new New_Aggregation_Arguments { Active = is_active, Code = new_aggregation_code, Description = new_description, External_Link = enteredLink, Hidden = is_hidden, Name = new_name, ParentCode = new_parent, ShortName = new_shortname, Thematic_Heading = thematicHeading, Type = correct_type, User = RequestSpecificValues.Current_User.Full_Name }; // Try to add this aggregation RestResponseMessage msg = SobekEngineClient.Aggregations.Add_New_Aggregation(args); // We are going to save some of the values here anyway, to assist with bulk adds enteredIsActive = is_active; enteredIsHidden = is_hidden; enteredParent = new_parent; enteredType = new_type; enteredLink = new_link; enteredThematicHeading = new_thematic_heading; if (msg.ErrorTypeEnum == ErrorRestTypeEnum.Successful) { RequestSpecificValues.Current_Mode.Mode = Display_Mode_Enum.Aggregation; RequestSpecificValues.Current_Mode.Aggregation = new_aggregation_code; actionMessage = "New item aggregation (" + new_aggregation_code.ToUpper() + ") saved successfully.<br /><br /><a href=\"" + UrlWriterHelper.Redirect_URL(RequestSpecificValues.Current_Mode, true) + "\" target=\"" + new_aggregation_code + "_AGGR\">Click here to view the new aggregation</a>"; RequestSpecificValues.Current_Mode.Mode = Display_Mode_Enum.Administrative; RequestSpecificValues.Current_Mode.Admin_Type = Admin_Type_Enum.Aggregations_Mgmt; // Clear all aggregation information (and thematic heading info) from the cache as well CachedDataManager.Aggregations.Clear(); } else { actionMessage = msg.Message; enteredCode = new_aggregation_code; enteredShortname = new_shortname; enteredName = new_name; enteredDescription = new_description; } } } } } catch { actionMessage = "General error while reading postback information"; } } }
/// <summary> /// Decrement the current load /// </summary> public void BeforeSendResponse(RestResponseMessage response) { Interlocked.Decrement(ref this.m_currentLoad); }
/// <summary> /// Dispatch the HttpRequest message to the appropriate service /// </summary> internal bool Dispatch(ServiceDispatcher serviceDispatcher, RestRequestMessage requestMessage, RestResponseMessage responseMessage) { // Allow message inspectors to inspect the message before next stage try { this.m_traceSource.TraceEvent(TraceEventType.Verbose, 0, "Begin endpoint dispatch of {0} {1} > {2}", requestMessage.Method, requestMessage.Url, this.m_serviceEndpoint.Description.Contract); foreach (var mfi in this.m_messageInspector) { mfi.AfterReceiveRequest(requestMessage); } var ops = this.m_serviceEndpoint.Operations.Where(o => o.Dispatcher.CanDispatch(requestMessage)); if (ops.Count() == 0) { throw new FaultException(404, $"Resource not Found - {requestMessage.Url.AbsolutePath}"); } var op = ops.FirstOrDefault(o => requestMessage.Method.ToLowerInvariant() == o.Description.Method.ToLowerInvariant()); if (op == null) { throw new FaultException(405, "Method not permitted"); } RestOperationContext.Current.EndpointOperation = op; op.Dispatcher.Dispatch(serviceDispatcher, requestMessage, responseMessage); // Allow message inspectors to inspect before sending response foreach (var mfi in this.m_messageInspector) { mfi.BeforeSendResponse(responseMessage); } return(true); } catch (Exception e) { this.m_traceSource.TraceEvent(TraceEventType.Error, e.HResult, e.ToString()); return(serviceDispatcher.HandleFault(e, responseMessage)); } }
/// <summary> /// Serialize the reply /// </summary> public void SerializeResponse(RestResponseMessage responseMessage, object[] parameters, object result) { try { // Outbound control var httpRequest = RestOperationContext.Current.IncomingRequest; string accepts = httpRequest.Headers["Accept"], contentType = httpRequest.Headers["Content-Type"], formatParm = httpRequest.QueryString["_format"]; var isOutputPretty = httpRequest.QueryString["_pretty"] == "true"; SummaryType?summaryType = SummaryType.False; if (httpRequest.QueryString["_summary"] != null) { summaryType = EnumUtility.ParseLiteral <SummaryType>(httpRequest.QueryString["_summary"], true); } if (accepts == "*/*") // Any = null { accepts = null; } contentType = accepts ?? contentType ?? formatParm; // No specified content type if (string.IsNullOrEmpty(contentType)) { contentType = this.m_configuration.DefaultResponseFormat == FhirResponseFormatConfiguration.Json ? "application/fhir+json" : "application/fhir+xml"; } var charset = ContentType.GetCharSetFromHeaderValue(contentType); var format = ContentType.GetMediaTypeFromHeaderValue(contentType); if (result is Base baseObject) { var ms = new MemoryStream(); // The request was in JSON or the accept is JSON switch (format) { case "application/fhir+xml": using (var xw = XmlWriter.Create(ms, new XmlWriterSettings { Encoding = new UTF8Encoding(false), Indent = isOutputPretty })) { new FhirXmlSerializer().Serialize(baseObject, xw, summaryType.Value); } break; case "application/fhir+json": using (var sw = new StreamWriter(ms, new UTF8Encoding(false), 1024, true)) using (var jw = new JsonTextWriter(sw) { Formatting = isOutputPretty ? Formatting.Indented : Formatting.None, DateFormatHandling = DateFormatHandling.IsoDateFormat }) { new FhirJsonSerializer(new SerializerSettings { Pretty = isOutputPretty }).Serialize(baseObject, jw); } break; default: throw new FhirException((HttpStatusCode)406, OperationOutcome.IssueType.NotSupported, $"{contentType} not supported"); } ms.Seek(0, SeekOrigin.Begin); responseMessage.Body = ms; } else if (result == null) { responseMessage.StatusCode = 204; // no content } else { throw new InvalidOperationException("FHIR return values must inherit from Base"); } RestOperationContext.Current.OutgoingResponse.ContentType = contentType; RestOperationContext.Current.OutgoingResponse.AppendHeader("X-PoweredBy", string.Format("{0} v{1} ({2})", Assembly.GetEntryAssembly().GetName().Name, Assembly.GetEntryAssembly().GetName().Version, Assembly.GetEntryAssembly().GetCustomAttribute <AssemblyInformationalVersionAttribute>()?.InformationalVersion)); RestOperationContext.Current.OutgoingResponse.AppendHeader("X-GeneratedOn", DateTime.Now.ToString("o")); } catch (Exception e) { this.m_traceSource.TraceEvent(EventLevel.Error, e.ToString()); throw; } }
/// <summary> /// Before sending a response /// </summary> public void BeforeSendResponse(RestResponseMessage response) { // Decrement our instance Interlocked.Decrement(ref this.m_requests); }
/// <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); // Get to the root of the error while (error.InnerException != null) { error = error.InnerException; } // Formulate appropriate response if (error is DomainStateException) { RestOperationContext.Current.OutgoingResponse.StatusCode = (int)System.Net.HttpStatusCode.ServiceUnavailable; } else if (error is PolicyViolationException) { var pve = error as PolicyViolationException; if (pve.PolicyDecision == PolicyGrantType.Elevate) { // Ask the user to elevate themselves RestOperationContext.Current.OutgoingResponse.StatusCode = 401; var authHeader = $"{(RestOperationContext.Current.AppliedPolicies.Any(o=>o.GetType().Name.Contains("Basic")) ? "Basic" : "Bearer")} realm=\"{RestOperationContext.Current.IncomingRequest.Url.Host}\" error=\"insufficient_scope\" scope=\"{pve.PolicyId}\" error_description=\"{error.Message}\""; RestOperationContext.Current.OutgoingResponse.AddHeader("WWW-Authenticate", authHeader); } else { RestOperationContext.Current.OutgoingResponse.StatusCode = 403; } } else if (error is SecurityException || error is UnauthorizedAccessException) { RestOperationContext.Current.OutgoingResponse.StatusCode = (int)System.Net.HttpStatusCode.Forbidden; } else if (error is SecurityTokenException) { RestOperationContext.Current.OutgoingResponse.StatusCode = (int)System.Net.HttpStatusCode.Unauthorized; RestOperationContext.Current.OutgoingResponse.AddHeader("WWW-Authenticate", $"Bearer"); } else if (error is FaultException) { RestOperationContext.Current.OutgoingResponse.StatusCode = (int)(error as FaultException).StatusCode; } else if (error is Newtonsoft.Json.JsonException || error is System.Xml.XmlException) { RestOperationContext.Current.OutgoingResponse.StatusCode = (int)System.Net.HttpStatusCode.BadRequest; } else if (error is FileNotFoundException) { RestOperationContext.Current.OutgoingResponse.StatusCode = (int)System.Net.HttpStatusCode.NotFound; } else if (error is DbException || error is ConstraintException) { RestOperationContext.Current.OutgoingResponse.StatusCode = (int)(System.Net.HttpStatusCode) 422; } else if (error is PatchException) { RestOperationContext.Current.OutgoingResponse.StatusCode = (int)System.Net.HttpStatusCode.Conflict; } else { RestOperationContext.Current.OutgoingResponse.StatusCode = (int)System.Net.HttpStatusCode.InternalServerError; } // Construct an error result var errorResult = new OperationOutcome() { Issue = new List <Issue>() { new Issue() { Diagnostics = error.Message, Severity = IssueSeverity.Error, Code = new FhirCoding(new Uri("http://hl7.org/fhir/issue-type"), "exception") } } }; if (error is DetectedIssueException) { foreach (var iss in (error as DetectedIssueException).Issues) { errorResult.Issue.Add(new Issue() { Diagnostics = iss.Text, Severity = iss.Priority == DetectedIssuePriorityType.Error ? IssueSeverity.Error : iss.Priority == DetectedIssuePriorityType.Warning ? IssueSeverity.Warning : IssueSeverity.Information }); } } // Return error in XML only at this point new FhirMessageDispatchFormatter().SerializeResponse(response, null, errorResult); return(true); }
/// <summary> /// Default serialization of the response /// </summary> public void SerializeResponse(RestResponseMessage responseMessage, object[] parameters, object result) { var acceptHeader = RestOperationContext.Current.IncomingRequest.Headers["Accept"]; ContentType contentType = null; if (!String.IsNullOrEmpty(acceptHeader)) { contentType = acceptHeader.Split(',').Select(o => new ContentType(o)).First(); } // By default unless Accept is application/json , we always prefer application/xml if (result == null) { if (responseMessage.StatusCode == 200) { responseMessage.StatusCode = 204; } } else if (result is Stream) { responseMessage.ContentType = responseMessage.ContentType ?? "application/octet-stream"; responseMessage.Body = result as Stream; } else if (result.GetType().IsPrimitive || result is string || result is Guid) { var ms = new MemoryStream(Encoding.UTF8.GetBytes(result.ToString())); responseMessage.ContentType = responseMessage.ContentType ?? "text/plain"; responseMessage.Body = ms; } else if (responseMessage.Format == MessageFormatType.Json || contentType?.MediaType == "application/json" || RestOperationContext.Current.IncomingRequest.Url.AbsolutePath.EndsWith(".json")) { // Prepare the serializer JsonSerializer jsz = new JsonSerializer(); var ms = new MemoryStream(); using (var tms = new MemoryStream()) using (StreamWriter sw = new StreamWriter(tms, new UTF8Encoding(false))) using (JsonWriter jsw = new JsonTextWriter(sw)) { jsz.DateFormatHandling = DateFormatHandling.IsoDateFormat; jsz.NullValueHandling = NullValueHandling.Ignore; jsz.ReferenceLoopHandling = ReferenceLoopHandling.Ignore; jsz.TypeNameHandling = TypeNameHandling.Auto; jsz.Converters.Add(new StringEnumConverter()); jsz.Serialize(jsw, result); jsw.Flush(); sw.Flush(); tms.Seek(0, SeekOrigin.Begin); tms.CopyTo(ms); } ms.Seek(0, SeekOrigin.Begin); responseMessage.ContentType = responseMessage.ContentType ?? "application/json"; responseMessage.Body = ms; } else if (responseMessage.Format == MessageFormatType.Xml || contentType?.MediaType == "application/xml" || RestOperationContext.Current.IncomingRequest.Url.AbsolutePath.EndsWith(".xml")) { if (typeof(ExpandoObject).IsAssignableFrom(result.GetType()) || typeof(IEnumerable <ExpandoObject>).IsAssignableFrom(result.GetType())) { // Custom serialization for XML of a dynamic if (result.GetType() == typeof(ExpandoObject)) { result = new List <ExpandoObject>() { result as ExpandoObject } } ; var ms = new MemoryStream(); using (var xw = XmlWriter.Create(ms, new XmlWriterSettings() { CloseOutput = false })) // Write dynamic { xw.WriteStartElement("ArrayOfDynamic", "http://tempuri.org"); // Iterate through objects foreach (var itm in result as IEnumerable) { xw.WriteStartElement("item", "http://tempuri.org"); foreach (var prop in itm as ExpandoObject) { xw.WriteStartElement(prop.Key); if (prop.Value is Guid) { xw.WriteValue(prop.Value.ToString()); } else if (prop.Value != null) { xw.WriteValue(prop.Value); } xw.WriteEndElement(); } xw.WriteEndElement(); } xw.WriteEndElement(); } ms.Seek(0, SeekOrigin.Begin); responseMessage.ContentType = responseMessage.ContentType ?? "application/xml"; responseMessage.Body = ms; } else { if (!this.m_serializers.TryGetValue(result.GetType(), out XmlSerializer serializer)) { serializer = new XmlSerializer(result.GetType()); this.m_serializers.TryAdd(result.GetType(), serializer); } var ms = new MemoryStream(); serializer.Serialize(ms, result); ms.Seek(0, SeekOrigin.Begin); responseMessage.ContentType = responseMessage.ContentType ?? "application/xml"; responseMessage.Body = ms; } } else { throw new ArgumentException($"Unsupported response format requested"); } } }
public void BeforeSendResponse(RestResponseMessage response) { Console.WriteLine("RSP: {0} ({1})", sessionId, response.StatusCode); }