/// <summary> /// Response for missing/Invalid headers. /// </summary> /// <param name="ignoreMissingAuth">Whether or not errors due to missing <see cref="HeaderNames.Authorization"/> should be thrown.</param> /// <returns>The appropriate <see cref="IActionResult"/>.</returns> protected IActionResult HeadersIssue(bool ignoreMissingAuth) { HeadersException headersException; try { var _ = new ApiHeaders(Request.GetTypedHeaders(), ignoreMissingAuth); throw new InvalidOperationException("Expected a header parse exception!"); } catch (HeadersException ex) { headersException = ex; } var errorMessage = new ErrorMessageResponse(ErrorCode.BadHeaders) { AdditionalData = headersException.Message }; if (headersException.MissingOrMalformedHeaders.HasFlag(HeaderTypes.Accept)) { return(StatusCode(HttpStatusCode.NotAcceptable, errorMessage)); } return(BadRequest(errorMessage)); }
public void TestConstruction() { Assert.ThrowsException <ArgumentNullException>(() => new ApiHeaders(null, null)); Assert.ThrowsException <ArgumentNullException>(() => new ApiHeaders(productHeaderValue, null)); var headers = new ApiHeaders(productHeaderValue, String.Empty); headers = new ApiHeaders(productHeaderValue, String.Empty, OAuthProvider.GitHub); }
/// <inheritdoc /> public async Task <IServerClient> CreateFromLogin( Uri host, string username, string password, IEnumerable <IRequestLogger>?requestLoggers = null, TimeSpan?timeout = null, bool attemptRefreshLogin = true, CancellationToken cancellationToken = default) { if (host == null) { throw new ArgumentNullException(nameof(host)); } if (username == null) { throw new ArgumentNullException(nameof(username)); } if (password == null) { throw new ArgumentNullException(nameof(password)); } requestLoggers ??= Enumerable.Empty <IRequestLogger>(); Token token; var loginHeaders = new ApiHeaders(productHeaderValue, username, password); using (var api = ApiClientFactory.CreateApiClient(host, loginHeaders, null)) { foreach (var requestLogger in requestLoggers) { api.AddRequestLogger(requestLogger); } if (timeout.HasValue) { api.Timeout = timeout.Value; } token = await api.Update <Token>(Routes.Root, cancellationToken).ConfigureAwait(false); } var apiHeaders = new ApiHeaders(productHeaderValue, token.Bearer !); var client = new ServerClient(ApiClientFactory.CreateApiClient(host, apiHeaders, loginHeaders), token); if (timeout.HasValue) { client.Timeout = timeout.Value; } foreach (var requestLogger in requestLoggers) { client.AddRequestLogger(requestLogger); } return(client); }
#pragma warning disable CA1506 public async Task <IActionResult> Home(CancellationToken cancellationToken) { if (controlPanelConfiguration.Enable) { Response.Headers.Add( HeaderNames.Vary, new StringValues( new[] { HeaderNames.UserAgent, ApiHeaders.ApiVersionHeader })); } // we only allow authorization header issues if (ApiHeaders == null) { // if we are using a browser and the control panel, redirect to the app page if (controlPanelConfiguration.Enable && browserResolver.Browser.Type != BrowserType.Generic) { Logger.LogDebug("Unauthorized browser request (User-Agent: \"{0}\"), redirecting to control panel...", browserResolver.UserAgent); return(Redirect(Core.Application.ControlPanelRoute)); } try { var headers = new ApiHeaders(Request.GetTypedHeaders(), true); if (!headers.Compatible()) { return(StatusCode( HttpStatusCode.UpgradeRequired, new ErrorMessage(ErrorCode.ApiMismatch))); } } catch (HeadersException) { return(HeadersIssue(true)); } } return(Json(new ServerInformation { Version = assemblyInformationProvider.Version, ApiVersion = ApiHeaders.Version, DMApiVersion = DMApiConstants.Version, MinimumPasswordLength = generalConfiguration.MinimumPasswordLength, InstanceLimit = generalConfiguration.InstanceLimit, UserLimit = generalConfiguration.UserLimit, UserGroupLimit = generalConfiguration.UserGroupLimit, ValidInstancePaths = generalConfiguration.ValidInstancePaths, WindowsHost = platformIdentifier.IsWindows, SwarmServers = swarmService.GetSwarmServers(), OAuthProviderInfos = await oAuthProviders.ProviderInfos(cancellationToken).ConfigureAwait(false), UpdateInProgress = serverControl.UpdateInProgress, })); }
/// <summary> /// Adds the additional headers. /// </summary> /// <param name="headerName">Name of the header.</param> /// <param name="headerValue">The header value.</param> protected void AddARequestHeader(string headerName, string headerValue) { if (ApiHeaders.ContainsKey(headerName)) { ApiHeaders[headerName] = headerValue; } else { ApiHeaders.Add(headerName, headerValue); } }
#pragma warning disable CA1506 public async Task <IActionResult> Home(CancellationToken cancellationToken) { if (controlPanelConfiguration.Enable) { Response.Headers.Add( HeaderNames.Vary, new StringValues( new[] { ApiHeaders.ApiVersionHeader })); } if (ApiHeaders == null) { if (controlPanelConfiguration.Enable && !Request.Headers.TryGetValue(ApiHeaders.ApiVersionHeader, out _)) { Logger.LogDebug("No API headers on request, redirecting to control panel..."); return(Redirect(ControlPanelController.ControlPanelRoute)); } try { // we only allow authorization header issues var headers = new ApiHeaders(Request.GetTypedHeaders(), true); if (!headers.Compatible()) { return(StatusCode( HttpStatusCode.UpgradeRequired, new ErrorMessageResponse(ErrorCode.ApiMismatch))); } } catch (HeadersException) { return(HeadersIssue(true)); } } return(Json(new ServerInformationResponse { Version = assemblyInformationProvider.Version, ApiVersion = ApiHeaders.Version, DMApiVersion = DMApiConstants.InteropVersion, MinimumPasswordLength = generalConfiguration.MinimumPasswordLength, InstanceLimit = generalConfiguration.InstanceLimit, UserLimit = generalConfiguration.UserLimit, UserGroupLimit = generalConfiguration.UserGroupLimit, ValidInstancePaths = generalConfiguration.ValidInstancePaths, WindowsHost = platformIdentifier.IsWindows, SwarmServers = swarmService.GetSwarmServers(), OAuthProviderInfos = await oAuthProviders.ProviderInfos(cancellationToken).ConfigureAwait(false), UpdateInProgress = serverControl.UpdateInProgress, })); }
private void ReadConfig() { Config = Configuration.Settings; BaseUrl = Config.BaseUrl; var client = this.GetType().Name.Replace("Client", string.Empty); if (Config.Routes.Length > 0 && Config.Routes.Any(r => r.Client.Equals(client, StringComparison.OrdinalIgnoreCase))) { BaseRoute = Config.Routes.First(r => r.Client.Equals(client, StringComparison.OrdinalIgnoreCase)).Path; } else { BaseRoute = Config.DefaultRoute.Replace("[client]", client); } ApiHeaders.Add("appCode", Config.ClientAppCode); //var userName = $@"{Environment.UserDomainName}\{Environment.UserName}"; //ApiHeaders.Add("clientUser", userName); }
public void Setup() { _apiHeaders = new ApiHeaders(); _apiMethods = new ApiMethods(ROOT_URL, isLogsOn); _endpoints = new Endpoints(); }
public void Init() { apiHeaders = new ApiHeaders(); }
/// <inheritdoc /> #pragma warning disable CA1506 // TODO: Decomplexify public override async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) { // ALL valid token and login requests that match a route go through this function // 404 is returned before if (AuthenticationContext != null && AuthenticationContext.User == null) { // valid token, expired password await Unauthorized().ExecuteResultAsync(context).ConfigureAwait(false); return; } // validate the headers try { ApiHeaders = new ApiHeaders(Request.GetTypedHeaders()); if (!ApiHeaders.Compatible()) { await StatusCode((int)HttpStatusCode.UpgradeRequired, new ErrorMessage { Message = "Provided API version is incompatible with server version!" }).ExecuteResultAsync(context).ConfigureAwait(false); return; } if (requireInstance) { if (!ApiHeaders.InstanceId.HasValue) { await BadRequest(new ErrorMessage { Message = "Missing Instance header!" }).ExecuteResultAsync(context).ConfigureAwait(false); return; } if (AuthenticationContext.InstanceUser == null) { // accessing an instance they don't have access to or one that's disabled await Forbid().ExecuteResultAsync(context).ConfigureAwait(false); return; } } } catch (InvalidOperationException e) { if (requireHeaders) { await BadRequest(new ErrorMessage { Message = e.Message }).ExecuteResultAsync(context).ConfigureAwait(false); return; } } if (ModelState?.IsValid == false) { var errorMessages = ModelState.SelectMany(x => x.Value.Errors).Select(x => x.ErrorMessage).ToList(); // HACK // do some fuckery to remove RequiredAttribute errors for (var I = 0; I < errorMessages.Count; ++I) { var message = errorMessages[I]; if (message.StartsWith("The ", StringComparison.Ordinal) && message.EndsWith(" field is required.", StringComparison.Ordinal)) { errorMessages.RemoveAt(I); --I; } } if (errorMessages.Count > 0) { await BadRequest(new ErrorMessage { Message = String.Join(Environment.NewLine, errorMessages) }).ExecuteResultAsync(context).ConfigureAwait(false); return; } } if (ApiHeaders != null) { Logger.LogDebug("Request made by User ID {0}. Api version: {1}. User-Agent: {2}. Type: {3}. Route {4}{5} to Instance {6}", AuthenticationContext?.User.Id.ToString(CultureInfo.InvariantCulture), ApiHeaders.ApiVersion, ApiHeaders.RawUserAgent, Request.Method, Request.Path, Request.QueryString, ApiHeaders.InstanceId); } try { await base.OnActionExecutionAsync(context, next).ConfigureAwait(false); } catch (OperationCanceledException e) { Logger.LogDebug("Request cancelled! Exception: {0}", e); throw; } }
/// <inheritdoc /> public IApiClient CreateApiClient(Uri url, ApiHeaders apiHeaders) => new ApiClient(new HttpClient(), url, apiHeaders);
/// <inheritdoc /> public async Task InjectClaimsIntoContext(TokenValidatedContext tokenValidatedContext, CancellationToken cancellationToken) { if (tokenValidatedContext == null) { throw new ArgumentNullException(nameof(tokenValidatedContext)); } // Find the user id in the token var userIdClaim = tokenValidatedContext.Principal.FindFirst(JwtRegisteredClaimNames.Sub); if (userIdClaim == default) { throw new InvalidOperationException("Missing required claim!"); } long userId; try { userId = Int64.Parse(userIdClaim.Value, CultureInfo.InvariantCulture); } catch (Exception e) { throw new InvalidOperationException("Failed to parse user ID!", e); } ApiHeaders apiHeaders; try { apiHeaders = new ApiHeaders(tokenValidatedContext.HttpContext.Request.GetTypedHeaders()); } catch (InvalidOperationException) { // we are not responsible for handling header validation issues return; } // This populates the CurrentAuthenticationContext field for use by us and subsequent controllers await authenticationContextFactory.CreateAuthenticationContext(userId, apiHeaders.InstanceId, tokenValidatedContext.SecurityToken.ValidFrom, cancellationToken).ConfigureAwait(false); var authenticationContext = authenticationContextFactory.CurrentAuthenticationContext; var enumerator = Enum.GetValues(typeof(RightsType)); var claims = new List <Claim>(); foreach (RightsType I in enumerator) { // if there's no instance user, do a weird thing and add all the instance roles // we need it so we can get to OnActionExecutionAsync where we can properly decide between BadRequest and Forbid // if user is null that means they got the token with an expired password var rightInt = authenticationContext.User == null || (RightsHelper.IsInstanceRight(I) && authenticationContext.InstanceUser == null) ? ~0U : authenticationContext.GetRight(I); var rightEnum = RightsHelper.RightToType(I); var right = (Enum)Enum.ToObject(rightEnum, rightInt); foreach (Enum J in Enum.GetValues(rightEnum)) { if (right.HasFlag(J)) { claims.Add(new Claim(ClaimTypes.Role, RightsHelper.RoleName(I, J))); } } } tokenValidatedContext.Principal.AddIdentity(new ClaimsIdentity(claims)); }
/// <summary> /// Runs after a <see cref="Token"/> has been validated. Creates the <see cref="IAuthenticationContext"/> for the <see cref="ControllerBase.Request"/> /// </summary> /// <param name="context">The <see cref="TokenValidatedContext"/> for the operation</param> /// <returns>A <see cref="Task"/> representing the running operation</returns> public static async Task OnTokenValidated(TokenValidatedContext context) { var databaseContext = context.HttpContext.RequestServices.GetRequiredService <IDatabaseContext>(); var authenticationContextFactory = context.HttpContext.RequestServices.GetRequiredService <IAuthenticationContextFactory>(); var userIdClaim = context.Principal.FindFirst(JwtRegisteredClaimNames.Sub); if (userIdClaim == default(Claim)) { throw new InvalidOperationException("Missing required claim!"); } long userId; try { userId = Int64.Parse(userIdClaim.Value, CultureInfo.InvariantCulture); } catch (Exception e) { throw new InvalidOperationException("Failed to parse user ID!", e); } ApiHeaders apiHeaders; try { apiHeaders = new ApiHeaders(context.HttpContext.Request.GetTypedHeaders()); } catch { //let OnActionExecutionAsync handle the reponse return; } await authenticationContextFactory.CreateAuthenticationContext(userId, apiHeaders.InstanceId, context.SecurityToken.ValidFrom, context.HttpContext.RequestAborted).ConfigureAwait(false); var authenticationContext = authenticationContextFactory.CurrentAuthenticationContext; var enumerator = Enum.GetValues(typeof(RightsType)); var claims = new List <Claim>(); foreach (RightsType I in enumerator) { //if there's no instance user, do a weird thing and add all the instance roles //we need it so we can get to OnActionExecutionAsync where we can properly decide between BadRequest and Forbid //if user is null that means they got the token with an expired password var rightInt = authenticationContext.User == null || (RightsHelper.IsInstanceRight(I) && authenticationContext.InstanceUser == null) ? ~0U : authenticationContext.GetRight(I); var rightEnum = RightsHelper.RightToType(I); var right = (Enum)Enum.ToObject(rightEnum, rightInt); foreach (Enum J in Enum.GetValues(rightEnum)) { if (right.HasFlag(J)) { claims.Add(new Claim(ClaimTypes.Role, RightsHelper.RoleName(I, J))); } } } context.Principal.AddIdentity(new ClaimsIdentity(claims)); }
/// <inheritdoc /> #pragma warning disable CA1506 // TODO: Decomplexify public override async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) { // ALL valid token and login requests that match a route go through this function // 404 is returned before if (AuthenticationContext != null && AuthenticationContext.User == null) { // valid token, expired password await Unauthorized().ExecuteResultAsync(context).ConfigureAwait(false); return; } // validate the headers try { ApiHeaders = new ApiHeaders(Request.GetTypedHeaders()); if (!ApiHeaders.Compatible()) { await StatusCode( (int)HttpStatusCode.UpgradeRequired, new ErrorMessage(ErrorCode.ApiMismatch)) .ExecuteResultAsync(context) .ConfigureAwait(false); return; } if (requireInstance) { if (!ApiHeaders.InstanceId.HasValue) { await BadRequest(new ErrorMessage(ErrorCode.InstanceHeaderRequired)).ExecuteResultAsync(context).ConfigureAwait(false); return; } if (AuthenticationContext.InstanceUser == null) { // accessing an instance they don't have access to or one that's disabled await Forbid().ExecuteResultAsync(context).ConfigureAwait(false); return; } } } catch (InvalidOperationException e) { if (requireHeaders) { await BadRequest( new ErrorMessage(ErrorCode.BadHeaders) { AdditionalData = e.Message }) .ExecuteResultAsync(context) .ConfigureAwait(false); return; } } if (ModelState?.IsValid == false) { var errorMessages = ModelState .SelectMany(x => x.Value.Errors) .Select(x => x.ErrorMessage) // We use RequiredAttributes purely for preventing properties from becoming nullable in the databases // We validate missing required fields in controllers // Unfortunately, we can't remove the whole validator for that as it checks other things like StringLength // This is the best way to deal with it unfortunately .Where(x => !x.EndsWith(" field is required.", StringComparison.Ordinal)); if (errorMessages.Any()) { await BadRequest( new ErrorMessage(ErrorCode.ModelValidationFailure) { AdditionalData = String.Join(Environment.NewLine, errorMessages) }) .ExecuteResultAsync(context).ConfigureAwait(false); return; } ModelState.Clear(); } if (ApiHeaders != null) { Logger.LogDebug( "Request details: User ID {0}. Api version: {1}. User-Agent: {2}. Type: {3}. Route {4}{5} to Instance {6}", AuthenticationContext?.User.Id.Value.ToString(CultureInfo.InvariantCulture), ApiHeaders.ApiVersion.Semver(), ApiHeaders.RawUserAgent, Request.Method, Request.Path, Request.QueryString, ApiHeaders.InstanceId); } try { await base.OnActionExecutionAsync(context, next).ConfigureAwait(false); } catch (OperationCanceledException e) { Logger.LogDebug("Request cancelled! Exception: {0}", e); throw; } }
/// <inheritdoc /> #pragma warning disable CA1506 // TODO: Decomplexify public override async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) { if (context == null) { throw new ArgumentNullException(nameof(context)); } // ALL valid token and login requests that match a route go through this function // 404 is returned before if (AuthenticationContext != null && AuthenticationContext.User == null) { // valid token, expired password await Unauthorized().ExecuteResultAsync(context).ConfigureAwait(false); return; } // validate the headers try { ApiHeaders = new ApiHeaders(Request.GetTypedHeaders()); if (!ApiHeaders.Compatible()) { await StatusCode( HttpStatusCode.UpgradeRequired, new ErrorMessageResponse(ErrorCode.ApiMismatch)) .ExecuteResultAsync(context) .ConfigureAwait(false); return; } var errorCase = await ValidateRequest(context.HttpContext.RequestAborted).ConfigureAwait(false); if (errorCase != null) { await errorCase.ExecuteResultAsync(context).ConfigureAwait(false); return; } } catch (HeadersException) { if (requireHeaders) { await HeadersIssue(false) .ExecuteResultAsync(context) .ConfigureAwait(false); return; } } if (ModelState?.IsValid == false) { var errorMessages = ModelState .SelectMany(x => x.Value.Errors) .Select(x => x.ErrorMessage) // We use RequiredAttributes purely for preventing properties from becoming nullable in the databases // We validate missing required fields in controllers // Unfortunately, we can't remove the whole validator for that as it checks other things like StringLength // This is the best way to deal with it unfortunately .Where(x => !x.EndsWith(" field is required.", StringComparison.Ordinal)); if (errorMessages.Any()) { await BadRequest( new ErrorMessageResponse(ErrorCode.ModelValidationFailure) { AdditionalData = String.Join(Environment.NewLine, errorMessages) }) .ExecuteResultAsync(context).ConfigureAwait(false); return; } ModelState.Clear(); } using (ApiHeaders?.InstanceId != null ? LogContext.PushProperty("Instance", ApiHeaders.InstanceId) : null) using (AuthenticationContext != null ? LogContext.PushProperty("User", AuthenticationContext.User.Id) : null) using (LogContext.PushProperty("Request", $"{Request.Method} {Request.Path}")) { if (ApiHeaders != null) { Logger.LogDebug( "Starting API request: Version: {0}. {1}: {2}", ApiHeaders.ApiVersion.Semver(), HeaderNames.UserAgent, ApiHeaders.RawUserAgent); } else if (Request.Headers.TryGetValue(HeaderNames.UserAgent, out var userAgents)) { Logger.LogDebug( "Starting unauthorized API request. {0}: {1}", HeaderNames.UserAgent, userAgents); } else { Logger.LogDebug( "Starting unauthorized API request. No {0}!", HeaderNames.UserAgent); } await base.OnActionExecutionAsync(context, next).ConfigureAwait(false); } }
#pragma warning disable CA1506 // TODO: Decomplexify public async Task <IActionResult> CreateToken(CancellationToken cancellationToken) { if (ApiHeaders == null) { // Get the exact error var errorMessage = "Missing API headers!"; try { var _ = new ApiHeaders(Request.GetTypedHeaders()); } catch (InvalidOperationException ex) { errorMessage = ex.Message; } Response.Headers.Add(HeaderNames.WWWAuthenticate, new StringValues("basic realm=\"Create TGS4 bearer token\"")); return(BadRequest( new Api.Models.ErrorMessage(Api.Models.ErrorCode.BadHeaders) { AdditionalData = errorMessage })); } if (ApiHeaders.IsTokenAuthentication) { return(BadRequest(new Api.Models.ErrorMessage(Api.Models.ErrorCode.TokenWithToken))); } ISystemIdentity systemIdentity; try { // trust the system over the database because a user's name can change while still having the same SID systemIdentity = await systemIdentityFactory.CreateSystemIdentity(ApiHeaders.Username, ApiHeaders.Password, cancellationToken).ConfigureAwait(false); } catch (NotImplementedException) { systemIdentity = null; } using (systemIdentity) { // Get the user from the database IQueryable <User> query = DatabaseContext.Users.AsQueryable(); string canonicalName = Models.User.CanonicalizeName(ApiHeaders.Username); if (systemIdentity == null) { query = query.Where(x => x.CanonicalName == canonicalName); } else { query = query.Where(x => x.CanonicalName == canonicalName || x.SystemIdentifier == systemIdentity.Uid); } var users = await query.Select(x => new User { Id = x.Id, PasswordHash = x.PasswordHash, Enabled = x.Enabled, Name = x.Name }).ToListAsync(cancellationToken).ConfigureAwait(false); // Pick the DB user first var user = users .OrderByDescending(dbUser => dbUser.PasswordHash != null) .FirstOrDefault(); // No user? You're not allowed if (user == null) { return(Unauthorized()); } // A system user may have had their name AND password changed to one in our DB... // Or a DB user was created that had the same user/pass as a system user // Dumb admins... // FALLBACK TO THE DB USER HERE, DO NOT REVEAL A SYSTEM LOGIN!!! // This of course, allows system users to discover TGS users in this (HIGHLY IMPROBABLE) case but that is not our fault var originalHash = user.PasswordHash; var isDbUser = originalHash != null; bool usingSystemIdentity = systemIdentity != null && !isDbUser; if (!usingSystemIdentity) { // DB User password check and update if (!cryptographySuite.CheckUserPassword(user, ApiHeaders.Password)) { return(Unauthorized()); } if (user.PasswordHash != originalHash) { Logger.LogDebug("User ID {0}'s password hash needs a refresh, updating database.", user.Id); var updatedUser = new User { Id = user.Id }; DatabaseContext.Users.Attach(updatedUser); updatedUser.PasswordHash = user.PasswordHash; await DatabaseContext.Save(cancellationToken).ConfigureAwait(false); } } else if (systemIdentity.Username != user.Name) { // System identity username change update Logger.LogDebug("User ID {0}'s system identity needs a refresh, updating database.", user.Id); DatabaseContext.Users.Attach(user); user.Name = systemIdentity.Username; user.CanonicalName = Models.User.CanonicalizeName(user.Name); await DatabaseContext.Save(cancellationToken).ConfigureAwait(false); } // Now that the bookeeping is done, tell them to f**k off if necessary if (!user.Enabled.Value) { Logger.LogTrace("Not logging in disabled user {0}.", user.Id); return(Forbid()); } var token = await tokenFactory.CreateToken(user, cancellationToken).ConfigureAwait(false); if (usingSystemIdentity) { // expire the identity slightly after the auth token in case of lag var identExpiry = token.ExpiresAt; identExpiry += tokenFactory.ValidationParameters.ClockSkew; identExpiry += TimeSpan.FromSeconds(15); identityCache.CacheSystemIdentity(user, systemIdentity, identExpiry); } Logger.LogDebug("Successfully logged in user {0}!", user.Id); return(Json(token)); } }
/// <inheritdoc /> public IApiClient CreateApiClient(Uri url, ApiHeaders apiHeaders, ApiHeaders?tokenRefreshHeaders) => new ApiClient(new HttpClientImplementation(), url, apiHeaders, tokenRefreshHeaders);
/// <inheritdoc /> public IApiClient CreateApiClient(Uri url, ApiHeaders apiHeaders) => new ApiClient(url, apiHeaders);
/// <summary> /// Executes the asynchronous. /// </summary> /// <typeparam name="T"></typeparam> /// <param name="action">The action.</param> /// <param name="actionTemplate">The action template.</param> /// <param name="parameters">The parameters.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns></returns> /// <exception cref="ApiClientException"> /// null /// </exception> protected async override Task <T> ExecuteAsync <T>(HttpVerb action, string actionTemplate, Parameters parameters, CancellationToken cancellationToken = default(CancellationToken)) { var opInfo = new StackTrace().GetOperationInfo(); var methodName = opInfo.MetaInfo.Name; var urlBuilder = GetRequestUrl(actionTemplate, parameters); var client = default(HttpClient); try { using (client = Config.WindowsAuthentication ? new HttpClient(new HttpClientHandler() { UseDefaultCredentials = true }) : new HttpClient()) { using (var request = new HttpRequestMessage()) { //Stuffing header parameters if (parameters?.ContainsKey(BindingSource.Header) ?? false) { parameters[BindingSource.Header]?.ToList().ForEach(p => { request.Headers.TryAddWithoutValidation(p.Name, ConvertToString(p.Value.ToString(), CultureInfo.InvariantCulture)); }); } if ((ApiHeaders?.Count ?? 0) > 0) { ApiHeaders?.ToList().ForEach(h => { request.Headers.TryAddWithoutValidation(h.Key, ConvertToString(h.Value, CultureInfo.InvariantCulture)); }); } ////Stuffing body parameters //if (parameters?.ContainsKey(BindingSource.Body) ?? false) //{ // if ((parameters[BindingSource.Body]?.Count ?? 0) > 1) // throw new ApiClientException( // $"Target of invocation exception in calling {methodName} due to an error: 'API call has multiple parameter bindings to the body element.'"); // var p = parameters[BindingSource.Body]?.ToList().FirstOrDefault(); // var content = // new StringContent(JsonConvert.SerializeObject(p.Value, _settings.Value)); // content.Headers.ContentType = MediaTypeHeaderValue.Parse("application/json"); // request.Content = content; //} //Binding RequestConent which could be a form / body element if (parameters.RequestContent != null) { request.Content = parameters.RequestContent; } //Setting Http action verb request.Method = new HttpMethod(Enum.GetName(typeof(HttpVerb), action)); request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); PrepareRequest(client, request, urlBuilder); var url = urlBuilder.ToString(); request.RequestUri = new Uri(url, UriKind.RelativeOrAbsolute); PrepareRequest(client, request, url); var response = await client .SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken) .ConfigureAwait(false); try { var headers = response?.Headers.ToDictionary(h => h.Key, h => h.Value); response?.Content?.Headers?.ToList().ForEach(h => { headers[h.Key] = h.Value; }); ProcessResponse(client, response); if (response.StatusCode == HttpStatusCode.OK) { var responseData = response.Content == null ? null : await response.Content.ReadAsStringAsync().ConfigureAwait(false); var result = default(T); try { result = JsonConvert.DeserializeObject <T>(responseData, _settings.Value); return(result); } catch (Exception ex) { throw new ApiClientException( $"Target of invocation exception in calling {methodName} due to an error: \"Could not deserialize the response body.\"", (int)response.StatusCode, responseData, headers, ex); } } else if (response.StatusCode != HttpStatusCode.OK && response.StatusCode != HttpStatusCode.NoContent) { var responseData = response.Content == null ? null : await response.Content.ReadAsStringAsync().ConfigureAwait(false); throw new ApiClientException( $"Target of invocation exception in calling {methodName} due to an error: \"The HTTP status code of the response was not expected (\"{Enum.GetName(typeof(HttpStatusCode), response.StatusCode)}:{(int) response.StatusCode}).", (int)response.StatusCode, responseData, headers, null); } return(default(T)); } finally { if (response != null) { response.Dispose(); } } } } } finally { if (client != null) { client.Dispose(); } } }