using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.Http; using System.Text; using System.Threading.Tasks; using System.Web; using IdentityModel; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.WebUtilities; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using xCommons.Extensions; using xExceptions.Constants; using xIdentityService.Configuration; using xIdentityService.Constants; using xIdentityService.Interfaces; using xIdentityService.Providers; using xModels.Base; namespace xIdentityService.Middlewares { /// /// Proxy Server Middleware ... /// public class XProxyMiddleware : XBaseClass { // #region Pros ... private readonly HttpClient HTTP_CLIENT; private readonly RequestDelegate NEXT_MIDDLEWARE; private readonly XProxyConfiguration CONFIGURATION; #endregion // #region Constructor ... public XProxyMiddleware ( ILoggerFactory loggerFactory, RequestDelegate nextMiddleware, XProxyConfiguration configuration, IHttpClientFactory httpClientFactory ) : base (loggerFactory) { // this.CONFIGURATION = configuration; this.NEXT_MIDDLEWARE = nextMiddleware; // // Create Http Client ... this.HTTP_CLIENT = httpClientFactory .CreateClient (XProxyConstants.XPROXY_HTTP_CLIENT); // // Logging Server Configuration ... LogXProxyServerConfig (); } #endregion // #region Public ... public async Task InvokeAsync (HttpContext context) { // #region Bussiness Logic ... // var routeDescriptor = GetRouteDescriptor (context.Request); if (!routeDescriptor.IsNull ()) { // #region Handle Authorization ... // try { // // Handle XPowered ... if (routeDescriptor.EnableXPoweredByAthorization) { // var isPoweredByPass = context.Request.Headers .Any (h => h.Key .ToNormalString () == xCommons.Constants.XAuthorization.XPoweredBy .ToNormalString () && h.Value .ToString () .ToNormalString () == CONFIGURATION.XPoweredValue .ToNormalString () ); // if (!isPoweredByPass) { ThrowNotAuthorizedException (); } } // // Handle EndableAuthorization ... if (routeDescriptor.EnableAuthorization) { // // Retrieve User Info for Authentication ... var userName = context.User.Identity.Name; var isUserAuthenticated = !userName.IsNull () && context.User.Identity.IsAuthenticated; if (!isUserAuthenticated && !routeDescriptor.AllowedScopes.HasChild () ) { ThrowNotAuthorizedException (); } // // Check Allowed Scopes Authorization ... if (routeDescriptor.AllowedScopes.HasChild ()) { // var scopeClaims = context.User.Claims .Where (c => c.Type == JwtClaimTypes.Scope) .Select (c => c.Value); var isScopePassed = scopeClaims.HasChild () && scopeClaims.Any (c => routeDescriptor.AllowedScopes .Any (allowedScope => allowedScope .ToNormalString () == c .ToNormalString () ) ); if (!isScopePassed) { ThrowNotAuthorizedException (); } } // // Check Allowed Roles Authorization ... if ( isUserAuthenticated && routeDescriptor.AllowedRoles.HasChild () ) { // var roleClaim = context.User.Claims .FirstOrDefault (c => c.Type == JwtClaimTypes.Role); var isRolePassed = !roleClaim.IsNull () && routeDescriptor.AllowedRoles .Any (role => role .ToNormalString () == roleClaim.Value .ToNormalString ()); if (!isRolePassed) { ThrowNotAuthorizedException (); } } } } catch { context.Response.StatusCode = (int) HttpStatusCode.Unauthorized; return; } #endregion // // Extract Request Uri from Received Request ... var requestUri = GetUri (context.Request); if (!requestUri.IsNull ()) { // // Retrieve Request based on current ... var request = GetRequestMessage ( uri: requestUri, context: context ); // SendLog ($"Send Request to Recieve Response ...", LogLevel.Information); // // Send Request and Retrieve Response ... try { // var response = await HTTP_CLIENT.SendAsync ( request, HttpCompletionOption.ResponseContentRead ); // // Set Current Response Status Code ... context.Response.StatusCode = (int) response.StatusCode; // // Retrieve Response Headers and Set To Current Response ... HandleResponseHeaders (context, response); // // Processing Response Content ... await ProcessResponseContent (context, response); } catch (Exception ex) { // LogMessage ($"Exception: {ex.Message}", LogLevel.Error); // context.Response.StatusCode = (int) HttpStatusCode.BadRequest; } // return; } } #endregion // await NEXT_MIDDLEWARE (context); } #endregion // #region Private ... /// /// Throw Required Exception ... /// private void ThrowNotAuthorizedException () { throw XException.NotAuthorized.ToException (); } /// /// Show propper logs if Enabled ... /// /// /// private void SendLog (string message, LogLevel logLevel = LogLevel.Information) { // if (!CONFIGURATION.EnableLogging) { return; } // LogMessage ( message: message, logLevel: logLevel ); } /// /// Log Proxy Server Configuration ... /// private void LogXProxyServerConfig () { // // Retrieve XProxyConfiguration class from Configurations and Validate them ... if (CONFIGURATION.IsNull ()) { throw XException.InvalidConfiguration.ToException (); } // // Try to Log Proxy Configuration ... ConsoleMessage ("==============================="); ConsoleMessage ("= Proxy Server Configurations: "); ConsoleMessage ("==============================="); // ConsoleMessage ($"Host: {CONFIGURATION.Host}"); ConsoleMessage ($"XPoweredValue: {CONFIGURATION.XPoweredValue}"); ConsoleMessage ($"EnableLogging: {CONFIGURATION.EnableLogging}"); ConsoleMessage ($"DisableSSLCheck: {CONFIGURATION.DisableSSLCheck}"); ConsoleMessage ($"AllowAutoRedirect: {CONFIGURATION.AllowAutoRedirect}"); ConsoleMessage ($" "); ConsoleMessage ($"Routes: "); ConsoleMessage ($" "); // CONFIGURATION.Routes .ToList () .ForEach (routeDescriptor => { ConsoleMessage ("==============================="); ConsoleMessage ($"= Rote => {routeDescriptor.Route}"); ConsoleMessage ("==============================="); ConsoleMessage ($"AllowedRoles: {routeDescriptor.AllowedRoles.ToJSON()}"); ConsoleMessage ($"AllowedScopes: {routeDescriptor.AllowedScopes.ToJSON()}"); ConsoleMessage ($"EnableAuthorization: {routeDescriptor.EnableAuthorization}"); ConsoleMessage ($"EnableXPoweredByAthorization: {routeDescriptor.EnableXPoweredByAthorization}"); ConsoleMessage ($" "); }); } /// /// Retrieve Destination Request Path based on Current Request ... /// /// /// private Uri GetUri (HttpRequest request) { // // Create temp result ... Uri result = null; var requestPath = ""; // SendLog ($"Receive Request: {request.Path} ...", LogLevel.Information); // // Try to Findout Route Descriptor ... var routeDescriptor = GetRouteDescriptor (request); // // Route Descriptor Exists && Path for Delegating ... if (!routeDescriptor.IsNull () && request.Path .StartsWithSegments (routeDescriptor.Route) ) { // // Prepare Request Path by Cleaning Starter Path Segment ... requestPath = request.Path .ToString () .Replace (routeDescriptor.Route, ""); if (requestPath.StartsWith ("/")) { requestPath = requestPath.Substring (1); } // // Decoding URL ... requestPath = HttpUtility.UrlDecode (requestPath); // var colonIndex = requestPath.IndexOf (":"); if (colonIndex > -1) { // var doubleSlash = requestPath.Substring (colonIndex + 1, 2); var isDoubleSlash = doubleSlash == "//"; // if (!isDoubleSlash) { // var protocol = requestPath .Substring (0, colonIndex); var section = requestPath .Substring (colonIndex + 2, requestPath.Length - colonIndex - 2); // requestPath = $"{protocol}://{section}"; } } } // // Validate Request Path ... if (!requestPath.IsNullOrEmpty ()) { // result = new Uri (requestPath); // SendLog ($"Process Request: {requestPath} ...", LogLevel.Information); } // return result; } /// /// Retrieve Propper Request Message based on HttpContext and uri ...F /// /// /// /// private HttpRequestMessage GetRequestMessage ( HttpContext context, Uri uri ) { // var result = new HttpRequestMessage (); FillRequest (context, result); // // Handle Queries ... SendLog ($"Start Processing Queries: "); foreach (var query in context.Request.Query) { // var key = query.Key; var value = HttpUtility.UrlDecode (query.Value); // uri = new Uri ( QueryHelpers.AddQueryString ( uri.OriginalString, new Dictionary { [key] = value } ) ); // SendLog ($"Query : {key}:{value} was Processed ..."); } // result.RequestUri = uri; result.Headers.Host = uri.Host; // // Retrieve Request Method ... result.Method = GetMethod (context.Request.Method); // return result; } /// /// Fill Request Message by provide HttpContext ... /// /// /// private void FillRequest ( HttpContext context, HttpRequestMessage message ) { // var requestMethod = context.Request.Method; // if (!HttpMethods.IsGet (requestMethod) && !HttpMethods.IsHead (requestMethod) && !HttpMethods.IsTrace (requestMethod) && !HttpMethods.IsDelete (requestMethod) ) { // var streamContent = new StreamContent (context.Request.Body); message.Content = streamContent; } // // Handle Base Headers ... var regularHeaders = context.Request.Headers .Where (hr => hr.Key != XProxyConstants.XPROXY_ACCEPTED_HEADERS && !hr.Key .Contains ( XProxyConstants.XPROXY_FORWARD_HEADER ) ); // // Handle Aceepted Headers ... IEnumerable acceptedHeaders = null; var acceptedHeadersKeyValue = context.Request.Headers .FirstOrDefault (hr => hr.Key == XProxyConstants.XPROXY_ACCEPTED_HEADERS ); if (!acceptedHeadersKeyValue.IsNull ()) { // acceptedHeaders = acceptedHeadersKeyValue.Value .ToString () .ParseListString (); // // Filter Regular Headers ... if ( regularHeaders.HasChild () && acceptedHeaders.HasChild () ) { // regularHeaders = regularHeaders .Where (rhr => acceptedHeaders .Contains (rhr.Key) ); } } foreach (var header in regularHeaders) { // SendLog ($"Adding Header: {header.Key}:{header.Value} ..."); // message.Headers.Add ( header.Key, header.Value .ToString () ); } // // Handle ForWarded Headers ... // Since Forward Headers is Custom kind, this means user actually need this // Header, so there is no need to put them on AcceptedHeaders ... var forwardHeaders = context.Request.Headers .Where (hr => hr.Key .Contains ( XProxyConstants.XPROXY_FORWARD_HEADER ) ); foreach (var header in forwardHeaders) { // var key = header.Key .Replace ( XProxyConstants.XPROXY_FORWARD_HEADER, string.Empty ); // SendLog ($"Forwarding Header: {header.Key} to {key} ..."); // message.Headers .Add ( key, header.Value .ToString () ); } } /// /// Retreive and Extract Method Type of HttpRequest ... /// /// /// private static HttpMethod GetMethod (string method) { // if (HttpMethods.IsDelete (method)) { return HttpMethod.Delete; } else if (HttpMethods.IsGet (method)) { return HttpMethod.Get; } else if (HttpMethods.IsHead (method)) { return HttpMethod.Head; } else if (HttpMethods.IsOptions (method)) { return HttpMethod.Options; } else if (HttpMethods.IsPost (method)) { return HttpMethod.Post; } else if (HttpMethods.IsPut (method)) { return HttpMethod.Put; } else if (HttpMethods.IsTrace (method)) { return HttpMethod.Trace; } // return new HttpMethod (method); } /// /// add all headers exists in HttpResponse MEssage to Current Context Response Headers ... /// /// /// private void HandleResponseHeaders ( HttpContext context, HttpResponseMessage message ) { // // Handle Message Headers ... foreach (var header in message.Headers) { context.Response.Headers[header.Key] = header.Value .ToArray (); } // // Handle Message Content Headers ... foreach (var header in message.Content.Headers) { context.Response.Headers[header.Key] = header.Value .ToArray (); } // context.Response.Headers.Remove ("transfer-encoding"); } /// /// Processing Response Content and Handle it on HttpContextResponse ... /// /// /// /// private async Task ProcessResponseContent ( HttpContext context, HttpResponseMessage message ) { // // Reading Content as Byte Array ... var contentBytes = await message.Content .ReadAsByteArrayAsync (); // // Check ContentType of Response ... if ( IsContentOfType (message, XResponseContentTypes.HTML) || IsContentOfType (message, XResponseContentTypes.JSON) || IsContentOfType (message, XResponseContentTypes.JavaScript) ) { // // If their String Content ... var stringContent = Encoding.UTF8.GetString (contentBytes); // // TODO: here we can change String Contents ... // // Writing new Content to Response ... await context.Response .WriteAsync ( stringContent, Encoding.UTF8 ); } else { // // if they have non string content ... await context.Response.Body .WriteAsync ( buffer: contentBytes, offset: 0, count: contentBytes.Length ); } // SendLog ($"Response Content Processed ...", LogLevel.Information); } /// /// Check Content Type of Specific Response ... /// /// /// /// private bool IsContentOfType ( HttpResponseMessage response, string type ) { // // Create EMpty Result ... var result = false; // // Retrieve and Check Content Type of Response ... if (response.Content?.Headers?.ContentType != null) { result = response.Content.Headers.ContentType.MediaType == type; } // return result; } /// /// Retrieve and Extract Route Descriptor ... /// /// /// private XProxyRouteDescriptor GetRouteDescriptor (HttpRequest request) { // // Validate Args ... if ( CONFIGURATION.IsNull () || !CONFIGURATION.Routes.HasChild () ) { return null; } // var result = CONFIGURATION.Routes .FirstOrDefault (rd => !rd.Route.IsNullOrEmpty () && request.Path .StartsWithSegments (rd.Route .ToNormalString () ) ); // return result; } #endregion } /// /// Provide all requirements for Dependency Injection Handler ... /// public static class XDIHelperExtension { /// /// Retrieve XProxy Middelware Configuration from Configuration ... /// /// /// public static XProxyConfiguration GetXProxyConfiguration (this IConfiguration config) { // var xProxyConfiguration = config.GetSection (ConfigurationNodeNames.XPROXY_CONFIGURATION_NODE_NAME); return xProxyConfiguration.Get (); } /// /// Register HTTP Handler for XProxy Server ... /// /// /// /// public static void AddXProxyHttpHandler ( this IServiceCollection services, IConfiguration configuration, HttpClientHandler httpHandler = null ) { // var proxyConfig = configuration.GetXProxyConfiguration (); if (proxyConfig.IsNull ()) { throw XException.InvalidConfiguration.ToException (); } // services.AddXProxyHttpHandler ( httpHandler: httpHandler, configuration: proxyConfig ); } /// /// Register HTTP Handler for XProxy Server ... /// /// /// /// public static void AddXProxyHttpHandler ( this IServiceCollection services, XProxyConfiguration configuration, HttpClientHandler httpHandler = null ) { // if (configuration.IsNull ()) { throw XException.InvalidConfiguration.ToException (); } // if (httpHandler.IsNull ()) { httpHandler = new HttpClientHandler (); } // httpHandler.AllowAutoRedirect = configuration.AllowAutoRedirect; // if (configuration.DisableSSLCheck) { httpHandler.ServerCertificateCustomValidationCallback = (sender, certificate, chain, sslPolicyErrors) => true; } // services .AddHttpClient (XProxyConstants.XPROXY_HTTP_CLIENT) .ConfigurePrimaryHttpMessageHandler (() => { return httpHandler; }); } /// /// Register XProxy On Server ... /// /// /// /// public static void AddXProxyServer ( this IServiceCollection services, IConfiguration configuration, HttpClientHandler httpHandler = null ) { // var proxyConfig = configuration.GetXProxyConfiguration (); // services.AddXProxyServer ( httpHandler: httpHandler, configuration: proxyConfig ); } /// /// Register XProxy On Server ... /// /// /// /// public static void AddXProxyServer ( this IServiceCollection services, XProxyConfiguration configuration, HttpClientHandler httpHandler = null ) { // // Validate Args ... if (configuration.IsNull ()) { throw XException.InvalidConfiguration.ToException (); } // // Register Proxy Configuration ... services.AddSingleton (configuration); // // Regiter Proxy Handler ... services.AddXProxyHttpHandler ( httpHandler: httpHandler, configuration: configuration ); } /// /// Register XProxy On Server ... /// /// /// public static void AddXProxyClient ( this IServiceCollection services, IConfiguration configuration ) { // var proxyConfig = configuration.GetXProxyConfiguration (); // services.AddXProxyClient ( configuration: proxyConfig ); } /// /// Register XProxy On Client ... /// /// /// public static void AddXProxyClient ( this IServiceCollection services, XProxyConfiguration configuration ) { // // Validate Args ... if (configuration.IsNull ()) { throw XException.InvalidConfiguration.ToException (); } // // Register Proxy Configuration ... services.AddSingleton (configuration); // // Register IXProxyHelper ... services.AddSingleton (); } /// /// Adding XProxy Middleware to Applications ... /// /// public static void UseXProxy (this IApplicationBuilder app) { app.UseMiddleware (); } } }