using System; using System.IO; using System.Collections.Generic; using System.Linq; using System.Net.Http; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using RestSharp; using RestSharp.Authenticators; using xCommons.Constants; using xCommons.Extensions; using xCommons.Providers; using xExceptions.Constants; using xHttpService.Constants; using xIdentityModels.Constants; using xIdentityModels.Extensions; using xIdentityModels.Models; using xIdentityService.Constants; using xIdentityService.Interfaces; using xIdentityService.Models; using xIdentityService.Extensions; using System.Diagnostics.Tracing; using MySqlX.XDevAPI.Common; namespace xIdentityService.Providers { /// /// an abstraction Implementation for Identity Provided Http Based Services ... /// public abstract class XBaseIdentityHttpProvider : IXBaseIdentityHttpProvider { /// /// Client Base Url ... /// /// public string BaseUrl { get; } /// /// Logger ... /// /// public ILogger Logger { get; } /// /// Powered Value ... /// /// public string XPoweredValue { get; } /// /// Default Client Timeout ... /// /// public int DefaultClientTimeout { get; } /// /// Default Buffering Chunk Size ... /// /// public int DefaultBufferingChunckSize { get; } /// /// Validation Provider ... /// /// public XValidationProvider ValidationProvider { get; } /// /// Token Provider ... /// private readonly IXTokenProvider tokenProvider; /// /// Identity Provider ... /// private readonly IXIdentityProvider identityProvider; public XBaseIdentityHttpProvider( string baseUrl, ILogger logger, string poweredValue, int defaultClientTimeout, IXTokenProvider tokenProvider, IXIdentityProvider identityProvider, XValidationProvider validationProvider, int defaultBufferingChunckSize = 8192 ) { // Logger = logger; BaseUrl = baseUrl; XPoweredValue = poweredValue; this.tokenProvider = tokenProvider; ValidationProvider = validationProvider; this.identityProvider = identityProvider; DefaultClientTimeout = defaultClientTimeout; DefaultBufferingChunckSize = defaultBufferingChunckSize; } // #region Tools ... /// /// Get an Instance of Http Client /// /// public HttpClient GetHttpClient(XTokenResponse tokens = null) { // HttpClient httpClient = null; // // httpClient = new HttpClient (); var httpClientHandler = new HttpClientHandler() { ServerCertificateCustomValidationCallback = (sender, cert, chain, sslPolicyErrors) => { // Logger.LogInformation( $"XSSL Handler: {Environment.NewLine}, sender: {sender}, {Environment.NewLine} cert: {cert}, {Environment.NewLine} chain: {chain}, {Environment.NewLine} sslPolicyErrors: {sslPolicyErrors}" ); // return true; }, ClientCertificateOptions = ClientCertificateOption.Manual, }; httpClient = new HttpClient(httpClientHandler); // // Set Authenticator ... if (!tokens.IsNull() && !tokens.AccessToken.IsNullOrEmpty()) { // httpClient.DefaultRequestHeaders .Add( XAuthorization.Header, $"{XAuthentication.BEARER} {tokens.AccessToken}"); } // // Refresh Token ... if (!tokens.IsNull() && !tokens.RefreshToken.IsNullOrEmpty()) { // httpClient.DefaultRequestHeaders .Add( XAuthorization.RefreshToken, tokens.RefreshToken); } // // ExpiresAt ... if (!tokens.IsNull() && tokens.ExpiresAt > 0) { // httpClient.DefaultRequestHeaders .Add( XAuthorization.ExpiresAt, tokens.ExpiresAt.AsHttpParamString()); } // // Set Default Timeout ... httpClient.Timeout = TimeSpan.FromSeconds(DefaultClientTimeout == -1 ? 10000 : DefaultClientTimeout); // return httpClient; } public HttpClient GetHttpClient(XUserClaimsInfoDto userInfo = null) { // XTokenResponse tokens = ToTokenResponse(userInfo); // return GetHttpClient(tokens); } /// /// Get an Instance of RestClient /// /// public RestClient GetRestClient(XTokenResponse tokens = null) { // var client = new RestClient(BaseUrl); // // Set Authenticator ... if (!tokens.IsNull() && !tokens.AccessToken.IsNullOrEmpty()) { client.Authenticator = new JwtAuthenticator(tokens.AccessToken); } // // Refresh Token ... if (!tokens.IsNull() && !tokens.RefreshToken.IsNullOrEmpty()) { client.AddDefaultHeader(XAuthorization.RefreshToken, tokens.RefreshToken); } // // ExpiresAt ... if (!tokens.IsNull() && tokens.ExpiresAt > 0) { client.AddDefaultHeader(XAuthorization.ExpiresAt, tokens.ExpiresAt.AsHttpParamString()); } // // Set Default Timeout ... client.Timeout = DefaultClientTimeout; // // SSL Validation Handler ... client.RemoteCertificateValidationCallback = (sender, cert, chain, sslPolicyErrors) => { // Logger.LogInformation( $"XSSL Handler: {Environment.NewLine}, sender: {sender}, {Environment.NewLine} cert: {cert}, {Environment.NewLine} chain: {chain}, {Environment.NewLine} sslPolicyErrors: {sslPolicyErrors}" ); // return true; }; // return client; } public RestClient GetRestClient(XUserClaimsInfoDto userInfo = null) { // XTokenResponse tokens = ToTokenResponse(userInfo); // return GetRestClient(tokens); } /// /// Get an Instance of RestRequest /// /// /// /// /// /// /// public RestRequest GetRestRequet( Enum endpoint, bool addXPoweredValue = true, IDictionary @params = null, IDictionary headers = null, IDictionary queryStrings = null ) { // var url = PrepareUrl( @params: @params, endpoint: endpoint, queryStrings: queryStrings ); // // Create request ... var request = new RestRequest(url, DataFormat.Json); // if (addXPoweredValue) { request.AddHeader(XAuthorization.XPoweredBy, XPoweredValue); } // // Add Headers to request ... if (!headers.IsNull() && headers.Count > 0) { // var enumerator = headers.GetEnumerator(); while (enumerator.MoveNext()) { // var item = enumerator.Current; request.AddHeader(item.Key, item.Value); } } // return request; } /// /// Handle Call Specific Request and retrieve Response /// /// /// /// /// /// /// /// public async Task RunRestRequest( RestClient client, RestRequest request, XHttpMethod method, bool supportRefreshingTokens = true, CancellationToken cancellationToken = default ) { // var response = await Task.Run(async () => { // IRestResponse resp = null; // // Call Endpoint Service using Specified Method ... switch (method) { // // Get ... case XHttpMethod.GET: resp = client.Get(request); break; // // Put ... case XHttpMethod.PUT: resp = client.Put(request); break; // // Post ... case XHttpMethod.POST: resp = client.Post(request); break; // // Delete ... case XHttpMethod.DELETE: resp = client.Delete(request); break; } // if (!resp.IsSuccessful) { // // Log Exception ... Logger.LogError($"Error: {resp.ErrorMessage}"); Logger.LogError($"Exception: {resp.ErrorException.ToJSON()}"); if (!resp.ErrorMessage.IsNullOrEmpty() && resp.ErrorMessage.Contains("timed out")) { XException.Timeout.Throw(); } // // TODO: Check UnAuthorized Status here for // Refresh Tokens or Issue Correct Exception ... if (supportRefreshingTokens && resp.StatusCode == System.Net.HttpStatusCode.Unauthorized) { // var isRefreshed = request.Parameters.Any(rp => rp.Name == XParam.XIsRefreshed.GetStringValue() && rp.Value.ToString().ToNormalString() == true.ToJSON()); // Logger.LogInformation($"isRefreshed: {isRefreshed}"); // // Get Access Token ... XTokenResponse tokens = null; var accessToken = request.Parameters.FirstOrDefault(rp => rp.Name.ToNormalString() == XAuthorization.Header.ToNormalString()).Value.ToString(); if (!accessToken.IsNullOrEmpty()) { // accessToken = accessToken.Replace(XAuthorization.TokenIdentifier, ""); // if (!accessToken.IsNullOrEmpty()) { tokens = await tokenProvider.RetrieveToken(accessToken); } } // // Refresh Token and then re do request ... if (!isRefreshed && !accessToken.IsNullOrEmpty() && !tokens.IsNull() && tokens.IsRefreshable()) { // request.AddHeader(XParam.XIsRefreshed.GetStringValue(), true.ToJSON()); // // Refreshe Tokens ... var refreshedTokens = await identityProvider .RefreshTokens(tokens); // // Renew Rest Client with Refreshed Tokens ... var timeout = client.Timeout; client = GetRestClient(refreshedTokens); client.Timeout = Timeout.Infinite; // // Do Request Again using renew Client ... return await RunRestRequest( client: client, request: request, method: method); } } // var ex = resp.Content.ToXError(); if (!ex.IsNull()) { throw ex.ToException(); } // // UnAuthorized ... if (resp.StatusCode == System.Net.HttpStatusCode.Unauthorized) { XException.NotAuthorized.Throw(); } // // NotFound ... if (resp.StatusCode == System.Net.HttpStatusCode.NotFound) { XException.NotFound.Throw(); } // XException.ActionFailed.Throw(); } // var result = resp.Content.FromJSON(); return result; }, cancellationToken: cancellationToken); // return response; } /// /// Access a Get Request Stream as AsyncEnumerable ... /// /// /// /// /// /// /// /// /// /// /// public async Task StreamData( Enum endpoint, int bufferLength = 8194, bool addXPoweredValue = true, XTokenResponse tokens = null, IDictionary @params = null, IDictionary headers = null, IDictionary queryStrings = null, [System.Runtime.CompilerServices.EnumeratorCancellation] CancellationToken cancellationToken = default ) { // var url = PrepareUrl( @params: @params, endpoint: endpoint, attachBaseUrl: true, queryStrings: queryStrings ); var client = GetHttpClient(tokens); if (addXPoweredValue) { // client.DefaultRequestHeaders .Add( XAuthorization.XPoweredBy, XPoweredValue ); } // // For SSE, you might want to set the Accept header, though not strictly necessary for just streaming client.DefaultRequestHeaders.Accept.Clear(); client.DefaultRequestHeaders.Accept .Add( new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("text/event-stream")); // var response = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead, cancellationToken); response.EnsureSuccessStatusCode(); // var stream = await response.Content.ReadAsStreamAsync(); // return stream; // using (var response = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead, cancellationToken)) // { // // // int readedBuffers = 0; // response.EnsureSuccessStatusCode(); // var buffer = new byte[bufferLength]; // var stream = await response.Content.ReadAsStreamAsync(); // // // while ((readedBuffers = await stream.ReadAsync(buffer, 0, buffer.Length)) > 0) // { // // // var content = buffer.FromBytes(); // Console.WriteLine(content); // } // // await foreach (var message in stream) { // // Console.WriteLine(message); // // } // // await foreach (var item in stream.ToAsyncEnumerable(cancellationToken: cancellationToken)) // // { // // // // // Console.WriteLine(item); // // // T value = item.IsNull() // // // ? default(T) // // // : item.FromBytes().FromJSON(); // // // // // // // yield return value; // // } // } /// /// /// /// // // var eventSource = new EventSource(url); // // eventSource. // // var client = GetHttpClient(tokens); // // if (addXPoweredValue) // // { // // // // // client.DefaultRequestHeaders // // .Add( // // XAuthorization.XPoweredBy, // // XPoweredValue); // // } // // // // // // For SSE, you might want to set the Accept header, though not strictly necessary for just streaming // // client.DefaultRequestHeaders.Accept.Clear(); // // client.DefaultRequestHeaders.Accept // // .Add( // // new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("text/event-stream")); // // // // // using (var response = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead, cancellationToken)) // // { // // // // // response.EnsureSuccessStatusCode(); // // var stream = await response.Content.ReadAsStreamAsync(); // // await foreach (var item in stream.ToAsyncEnumerable(cancellationToken: cancellationToken)) // // { // // // // // T value = item.IsNull() // // ? default(T) // // : item.FromBytes().FromJSON(); // // // // // yield return value; // // } // // } } /// /// Handle Call Specified Request and Retrieve XBaseRestResponse ... /// /// /// /// /// /// /// /// an instance of XBaseRestResponse public async Task> RunRestRequestByResponse( RestClient client, RestRequest request, XHttpMethod method, bool supportRefreshingTokens = true, CancellationToken cancellationToken = default ) { // var response = await Task.Run(async () => { // IRestResponse resp = null; // // Call Endpoint Service using Specified Method ... switch (method) { // // Get ... case XHttpMethod.GET: resp = client.Get(request); break; // // Put ... case XHttpMethod.PUT: resp = client.Put(request); break; // // Post ... case XHttpMethod.POST: resp = client.Post(request); break; // // Delete ... case XHttpMethod.DELETE: resp = client.Delete(request); break; } // if (!resp.IsSuccessful) { // // Log Exception ... Logger.LogError($"Error: {resp.ErrorMessage}"); Logger.LogError($"Exception: {resp.ErrorException.ToJSON()}"); if (!resp.ErrorMessage.IsNullOrEmpty() && resp.ErrorMessage.Contains("timed out")) { XException.Timeout.Throw(); } // // TODO: Check UnAuthorized Status here for // Refresh Tokens or Issue Correct Exception ... if (supportRefreshingTokens && resp.StatusCode == System.Net.HttpStatusCode.Unauthorized) { // var isRefreshed = request.Parameters.Any(rp => rp.Name == XParam.XIsRefreshed.GetStringValue() && rp.Value.ToString().ToNormalString() == true.ToJSON()); // Logger.LogInformation($"isRefreshed: {isRefreshed}"); // // Get Access Token ... XTokenResponse tokens = null; var accessToken = request.Parameters.FirstOrDefault(rp => rp.Name.ToNormalString() == XAuthorization.Header.ToNormalString()).Value.ToString(); if (!accessToken.IsNullOrEmpty()) { // accessToken = accessToken.Replace(XAuthorization.TokenIdentifier, ""); // if (!accessToken.IsNullOrEmpty()) { tokens = await tokenProvider.RetrieveToken(accessToken); } } // // Refresh Token and then re do request ... if (!isRefreshed && !accessToken.IsNullOrEmpty() && !tokens.IsNull() && tokens.IsRefreshable()) { // request.AddHeader(XParam.XIsRefreshed.GetStringValue(), true.ToJSON()); // // Refreshe Tokens ... var refreshedTokens = await identityProvider .RefreshTokens(tokens); // // Renew Rest Client with Refreshed Tokens ... var timeout = client.Timeout; client = GetRestClient(refreshedTokens); client.Timeout = Timeout.Infinite; // // Do Request Again using renew Client ... return await RunRestRequestByResponse( client: client, request: request, method: method ); } } // var ex = resp.Content.ToXError(); if (!ex.IsNull()) { throw ex.ToException(); } // // UnAuthorized ... if (resp.StatusCode == System.Net.HttpStatusCode.Unauthorized) { XException.NotAuthorized.Throw(); } // // NotFound ... if (resp.StatusCode == System.Net.HttpStatusCode.NotFound) { XException.NotFound.Throw(); } // XException.ActionFailed.Throw(); } // var result = new XBaseRestResponse { Response = resp, Model = resp.Content.FromJSON() }; // return result; }, cancellationToken: cancellationToken); // return response; } /// /// Get User SelectBy Param /// /// /// /// /// public string GetUserSelectByParam( XActionRequest model, bool forceNotNull = true, ICollection excludes = null ) { // // Validate Args ... ValidationProvider.NotNull(model); // var id = model.UserId; var userName = model.UserName; var email = model.Email; var phoneNumber = model.MobileNumber; // if (excludes != null) { // if (excludes.Contains(XUserSelectBy.ID)) { id = null; } if (excludes.Contains(XUserSelectBy.Email)) { email = null; } if (excludes.Contains(XUserSelectBy.MobileNumber)) { phoneNumber = null; } if (excludes.Contains(XUserSelectBy.Username)) { userName = null; } } var selectByParam = id.IsNullOrEmpty() ? userName.IsNullOrEmpty() ? email.IsNullOrEmpty() ? phoneNumber.IsNullOrEmpty() ? null : phoneNumber : email : userName : id; // // Check result ... if (forceNotNull && selectByParam.IsNullOrEmpty()) { XException.InvalidData.Throw(); } // return selectByParam; } /// /// Prepare Url Address of Specified Enum by Filling Params /// and Query Strings Attach ... /// /// /// /// /// /// public string PrepareUrl( Enum endpoint, bool attachBaseUrl = false, IDictionary @params = null, IDictionary queryStrings = null ) { // // Retrieving Url Template ... var url = endpoint.GetStringValue(); // // Filling Parmas and Query Strings of Url ... url = PrepareUrl( url: url, @params: @params, queryStrings: queryStrings, attachBaseUrl: attachBaseUrl ); // return url; } /// /// Prepare Url Address by Filling Params /// and Query Strings Attach ... /// /// /// /// /// /// public string PrepareUrl( string url, bool attachBaseUrl = false, IDictionary @params = null, IDictionary queryStrings = null ) { // if (attachBaseUrl) { // var baseUrl = BaseUrl; if (!baseUrl.EndsWith("/")) { baseUrl = $"{baseUrl}/"; } // url = $"{baseUrl}{url}"; } // // Add Route Payloads ... if (!@params.IsNull() && @params.Count > 0) { // var enumerator = @params.GetEnumerator(); while (enumerator.MoveNext()) { // var item = enumerator.Current; // url = url.Replace(item.Key, item.Value.ToUrlEncoded()); } } // // Add Query Strings ... if (!queryStrings.IsNull() && queryStrings.Count > 0) { // url = $"{url}?"; var enumerator = queryStrings.GetEnumerator(); while (enumerator.MoveNext()) { // var item = enumerator.Current; // url += $"{item.Key}={item.Value.ToUrlEncoded()}&"; } // // remove lates & ... if (url.EndsWith("&")) { url = url.Substring(0, url.Length - 1); } } // return url; } /// /// Converts UserInfo to TokenResponse model ... /// /// /// public XTokenResponse ToTokenResponse(XUserClaimsInfoDto userInfo = null) { // XTokenResponse result = null; // if (!userInfo.IsNull()) { // result = new XTokenResponse { AccessToken = userInfo.AccessToken.IsNullOrEmpty() ? "" : userInfo.AccessToken, RefreshToken = userInfo.RefreshToken.IsNullOrEmpty() ? "" : userInfo.RefreshToken, ExpiresAt = userInfo.ExpiresAt, }; // if (userInfo.AccessToken.IsNullOrEmpty()) { result = null; } } // return result; } #endregion } }