From 15954fb679803e8bd253f6c7a1aea985238fb3b3 Mon Sep 17 00:00:00 2001 From: SmithThe4th Date: Thu, 9 Mar 2023 12:07:12 -0500 Subject: [PATCH] Revert "[SG-648] BEEEP-Refactor DuoApi class to use Httpclient (#2691)" (#2792) This reverts commit f11c58e396756d668592329a893bdbba2a5e3bc1. --- src/Api/Controllers/TwoFactorController.cs | 5 +- .../Models/Request/TwoFactorRequestModels.cs | 3 +- .../Api/Response/Duo/DuoResponseModel.cs | 27 ------ src/Core/Utilities/DuoApi.cs | 91 +++++++++++++------ 4 files changed, 65 insertions(+), 61 deletions(-) delete mode 100644 src/Core/Models/Api/Response/Duo/DuoResponseModel.cs diff --git a/src/Api/Controllers/TwoFactorController.cs b/src/Api/Controllers/TwoFactorController.cs index 244d3c738..07d7197d6 100644 --- a/src/Api/Controllers/TwoFactorController.cs +++ b/src/Api/Controllers/TwoFactorController.cs @@ -11,6 +11,7 @@ using Bit.Core.Repositories; using Bit.Core.Services; using Bit.Core.Settings; using Bit.Core.Utilities; +using Bit.Core.Utilities.Duo; using Fido2NetLib; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Identity; @@ -152,7 +153,7 @@ public class TwoFactorController : Controller try { var duoApi = new DuoApi(model.IntegrationKey, model.SecretKey, model.Host); - await duoApi.JSONApiCall("GET", "/auth/v2/check"); + duoApi.JSONApiCall("GET", "/auth/v2/check"); } catch (DuoException) { @@ -209,7 +210,7 @@ public class TwoFactorController : Controller try { var duoApi = new DuoApi(model.IntegrationKey, model.SecretKey, model.Host); - await duoApi.JSONApiCall("GET", "/auth/v2/check"); + duoApi.JSONApiCall("GET", "/auth/v2/check"); } catch (DuoException) { diff --git a/src/Api/Models/Request/TwoFactorRequestModels.cs b/src/Api/Models/Request/TwoFactorRequestModels.cs index a2b236edf..18ba944ea 100644 --- a/src/Api/Models/Request/TwoFactorRequestModels.cs +++ b/src/Api/Models/Request/TwoFactorRequestModels.cs @@ -3,7 +3,6 @@ using Bit.Api.Models.Request.Accounts; using Bit.Core.Entities; using Bit.Core.Enums; using Bit.Core.Models; -using Bit.Core.Utilities; using Fido2NetLib; namespace Bit.Api.Models.Request; @@ -105,7 +104,7 @@ public class UpdateTwoFactorDuoRequestModel : SecretVerificationRequestModel, IV public override IEnumerable Validate(ValidationContext validationContext) { - if (!DuoApi.ValidHost(Host)) + if (!Core.Utilities.Duo.DuoApi.ValidHost(Host)) { yield return new ValidationResult("Host is invalid.", new string[] { nameof(Host) }); } diff --git a/src/Core/Models/Api/Response/Duo/DuoResponseModel.cs b/src/Core/Models/Api/Response/Duo/DuoResponseModel.cs deleted file mode 100644 index 573d77ab0..000000000 --- a/src/Core/Models/Api/Response/Duo/DuoResponseModel.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System.Text.Json.Serialization; - -namespace Bit.Core.Models.Api.Response.Duo; - -public class DuoResponseModel -{ - [JsonPropertyName("stat")] - public string Stat { get; set; } - - [JsonPropertyName("code")] - public int? Code { get; set; } - - [JsonPropertyName("message")] - public string Message { get; set; } - - [JsonPropertyName("message_detail")] - public string MessageDetail { get; set; } - - [JsonPropertyName("response")] - public Response Response { get; set; } -} - -public class Response -{ - [JsonPropertyName("time")] - public int Time { get; set; } -} diff --git a/src/Core/Utilities/DuoApi.cs b/src/Core/Utilities/DuoApi.cs index 662ca643d..b5a3f040d 100644 --- a/src/Core/Utilities/DuoApi.cs +++ b/src/Core/Utilities/DuoApi.cs @@ -15,9 +15,8 @@ using System.Text; using System.Text.Json; using System.Text.RegularExpressions; using System.Web; -using Bit.Core.Models.Api.Response.Duo; -namespace Bit.Core.Utilities; +namespace Bit.Core.Utilities.Duo; public class DuoApi { @@ -28,8 +27,6 @@ public class DuoApi private readonly string _ikey; private readonly string _skey; - private readonly HttpClient _httpClient = new(); - public DuoApi(string ikey, string skey, string host) { _ikey = ikey; @@ -95,6 +92,11 @@ public class DuoApi return string.Concat("Basic ", Encode64(auth)); } + public string ApiCall(string method, string path, Dictionary parameters = null) + { + return ApiCall(method, path, parameters, 0, out var statusCode); + } + /// The request timeout, in milliseconds. /// Specify 0 to use the system-default timeout. Use caution if /// you choose to specify a custom timeout - some API @@ -102,7 +104,8 @@ public class DuoApi /// return a response until an out-of-band authentication process /// has completed. In some cases, this may take as much as a /// small number of minutes. - private async Task<(string result, HttpStatusCode statusCode)> ApiCall(string method, string path, Dictionary parameters, int timeout) + public string ApiCall(string method, string path, Dictionary parameters, int timeout, + out HttpStatusCode statusCode) { if (parameters == null) { @@ -118,39 +121,58 @@ public class DuoApi query = "?" + canonParams; } } - var url = $"{UrlScheme}://{_host}{path}{query}"; + var url = string.Format("{0}://{1}{2}{3}", UrlScheme, _host, path, query); var dateString = RFC822UtcNow(); var auth = Sign(method, path, canonParams, dateString); - var request = new HttpRequestMessage - { - Method = new HttpMethod(method), - RequestUri = new Uri(url), - }; + var request = (HttpWebRequest)WebRequest.Create(url); + request.Method = method; + request.Accept = "application/json"; request.Headers.Add("Authorization", auth); request.Headers.Add("X-Duo-Date", dateString); - request.Headers.UserAgent.ParseAdd(UserAgent); - - if (timeout > 0) - { - _httpClient.Timeout = TimeSpan.FromMilliseconds(timeout); - } + request.UserAgent = UserAgent; if (method.Equals("POST") || method.Equals("PUT")) { - request.Content = new StringContent(canonParams, Encoding.UTF8, "application/x-www-form-urlencoded"); + var data = Encoding.UTF8.GetBytes(canonParams); + request.ContentType = "application/x-www-form-urlencoded"; + request.ContentLength = data.Length; + using (var requestStream = request.GetRequestStream()) + { + requestStream.Write(data, 0, data.Length); + } + } + if (timeout > 0) + { + request.Timeout = timeout; } - var response = await _httpClient.SendAsync(request); - var result = await response.Content.ReadAsStringAsync(); - var statusCode = response.StatusCode; - return (result, statusCode); + // Do the request and process the result. + HttpWebResponse response; + try + { + response = (HttpWebResponse)request.GetResponse(); + } + catch (WebException ex) + { + response = (HttpWebResponse)ex.Response; + if (response == null) + { + throw; + } + } + using (var reader = new StreamReader(response.GetResponseStream())) + { + statusCode = response.StatusCode; + return reader.ReadToEnd(); + } } - public async Task JSONApiCall(string method, string path, Dictionary parameters = null) + public T JSONApiCall(string method, string path, Dictionary parameters = null) + where T : class { - return await JSONApiCall(method, path, parameters, 0); + return JSONApiCall(method, path, parameters, 0); } /// The request timeout, in milliseconds. @@ -160,18 +182,27 @@ public class DuoApi /// return a response until an out-of-band authentication process /// has completed. In some cases, this may take as much as a /// small number of minutes. - private async Task JSONApiCall(string method, string path, Dictionary parameters, int timeout) + public T JSONApiCall(string method, string path, Dictionary parameters, int timeout) + where T : class { - var (res, statusCode) = await ApiCall(method, path, parameters, timeout); + var res = ApiCall(method, path, parameters, timeout, out var statusCode); try { - var obj = JsonSerializer.Deserialize(res); - if (obj.Stat == "OK") + // TODO: We should deserialize this into our own DTO and not work on dictionaries. + var dict = JsonSerializer.Deserialize>(res); + if (dict["stat"].ToString() == "OK") { - return obj.Response; + return JsonSerializer.Deserialize(dict["response"].ToString()); } - throw new ApiException(obj.Code ?? 0, (int)statusCode, obj.Message, obj.MessageDetail); + var check = ToNullableInt(dict["code"].ToString()); + var code = check.GetValueOrDefault(0); + var messageDetail = string.Empty; + if (dict.ContainsKey("message_detail")) + { + messageDetail = dict["message_detail"].ToString(); + } + throw new ApiException(code, (int)statusCode, dict["message"].ToString(), messageDetail); } catch (ApiException) {