1
0
mirror of https://github.com/bitwarden/server.git synced 2024-11-26 12:55:17 +01:00

Revert "[SG-648] BEEEP-Refactor DuoApi class to use Httpclient (#2691)" (#2792)

This reverts commit f11c58e396.
This commit is contained in:
SmithThe4th 2023-03-09 12:07:12 -05:00 committed by GitHub
parent 03bbc7195b
commit 15954fb679
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 65 additions and 61 deletions

View File

@ -11,6 +11,7 @@ using Bit.Core.Repositories;
using Bit.Core.Services; using Bit.Core.Services;
using Bit.Core.Settings; using Bit.Core.Settings;
using Bit.Core.Utilities; using Bit.Core.Utilities;
using Bit.Core.Utilities.Duo;
using Fido2NetLib; using Fido2NetLib;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity;
@ -152,7 +153,7 @@ public class TwoFactorController : Controller
try try
{ {
var duoApi = new DuoApi(model.IntegrationKey, model.SecretKey, model.Host); var duoApi = new DuoApi(model.IntegrationKey, model.SecretKey, model.Host);
await duoApi.JSONApiCall("GET", "/auth/v2/check"); duoApi.JSONApiCall<object>("GET", "/auth/v2/check");
} }
catch (DuoException) catch (DuoException)
{ {
@ -209,7 +210,7 @@ public class TwoFactorController : Controller
try try
{ {
var duoApi = new DuoApi(model.IntegrationKey, model.SecretKey, model.Host); var duoApi = new DuoApi(model.IntegrationKey, model.SecretKey, model.Host);
await duoApi.JSONApiCall("GET", "/auth/v2/check"); duoApi.JSONApiCall<object>("GET", "/auth/v2/check");
} }
catch (DuoException) catch (DuoException)
{ {

View File

@ -3,7 +3,6 @@ using Bit.Api.Models.Request.Accounts;
using Bit.Core.Entities; using Bit.Core.Entities;
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.Models; using Bit.Core.Models;
using Bit.Core.Utilities;
using Fido2NetLib; using Fido2NetLib;
namespace Bit.Api.Models.Request; namespace Bit.Api.Models.Request;
@ -105,7 +104,7 @@ public class UpdateTwoFactorDuoRequestModel : SecretVerificationRequestModel, IV
public override IEnumerable<ValidationResult> Validate(ValidationContext validationContext) public override IEnumerable<ValidationResult> 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) }); yield return new ValidationResult("Host is invalid.", new string[] { nameof(Host) });
} }

View File

@ -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; }
}

View File

@ -15,9 +15,8 @@ using System.Text;
using System.Text.Json; using System.Text.Json;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Web; using System.Web;
using Bit.Core.Models.Api.Response.Duo;
namespace Bit.Core.Utilities; namespace Bit.Core.Utilities.Duo;
public class DuoApi public class DuoApi
{ {
@ -28,8 +27,6 @@ public class DuoApi
private readonly string _ikey; private readonly string _ikey;
private readonly string _skey; private readonly string _skey;
private readonly HttpClient _httpClient = new();
public DuoApi(string ikey, string skey, string host) public DuoApi(string ikey, string skey, string host)
{ {
_ikey = ikey; _ikey = ikey;
@ -95,6 +92,11 @@ public class DuoApi
return string.Concat("Basic ", Encode64(auth)); return string.Concat("Basic ", Encode64(auth));
} }
public string ApiCall(string method, string path, Dictionary<string, string> parameters = null)
{
return ApiCall(method, path, parameters, 0, out var statusCode);
}
/// <param name="timeout">The request timeout, in milliseconds. /// <param name="timeout">The request timeout, in milliseconds.
/// Specify 0 to use the system-default timeout. Use caution if /// Specify 0 to use the system-default timeout. Use caution if
/// you choose to specify a custom timeout - some API /// 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 /// return a response until an out-of-band authentication process
/// has completed. In some cases, this may take as much as a /// has completed. In some cases, this may take as much as a
/// small number of minutes.</param> /// small number of minutes.</param>
private async Task<(string result, HttpStatusCode statusCode)> ApiCall(string method, string path, Dictionary<string, string> parameters, int timeout) public string ApiCall(string method, string path, Dictionary<string, string> parameters, int timeout,
out HttpStatusCode statusCode)
{ {
if (parameters == null) if (parameters == null)
{ {
@ -118,39 +121,58 @@ public class DuoApi
query = "?" + canonParams; query = "?" + canonParams;
} }
} }
var url = $"{UrlScheme}://{_host}{path}{query}"; var url = string.Format("{0}://{1}{2}{3}", UrlScheme, _host, path, query);
var dateString = RFC822UtcNow(); var dateString = RFC822UtcNow();
var auth = Sign(method, path, canonParams, dateString); var auth = Sign(method, path, canonParams, dateString);
var request = new HttpRequestMessage var request = (HttpWebRequest)WebRequest.Create(url);
{ request.Method = method;
Method = new HttpMethod(method), request.Accept = "application/json";
RequestUri = new Uri(url),
};
request.Headers.Add("Authorization", auth); request.Headers.Add("Authorization", auth);
request.Headers.Add("X-Duo-Date", dateString); request.Headers.Add("X-Duo-Date", dateString);
request.Headers.UserAgent.ParseAdd(UserAgent); request.UserAgent = UserAgent;
if (timeout > 0)
{
_httpClient.Timeout = TimeSpan.FromMilliseconds(timeout);
}
if (method.Equals("POST") || method.Equals("PUT")) 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); // Do the request and process the result.
var result = await response.Content.ReadAsStringAsync(); HttpWebResponse response;
var statusCode = response.StatusCode; try
return (result, statusCode); {
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<Response> JSONApiCall(string method, string path, Dictionary<string, string> parameters = null) public T JSONApiCall<T>(string method, string path, Dictionary<string, string> parameters = null)
where T : class
{ {
return await JSONApiCall(method, path, parameters, 0); return JSONApiCall<T>(method, path, parameters, 0);
} }
/// <param name="timeout">The request timeout, in milliseconds. /// <param name="timeout">The request timeout, in milliseconds.
@ -160,18 +182,27 @@ public class DuoApi
/// return a response until an out-of-band authentication process /// return a response until an out-of-band authentication process
/// has completed. In some cases, this may take as much as a /// has completed. In some cases, this may take as much as a
/// small number of minutes.</param> /// small number of minutes.</param>
private async Task<Response> JSONApiCall(string method, string path, Dictionary<string, string> parameters, int timeout) public T JSONApiCall<T>(string method, string path, Dictionary<string, string> 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 try
{ {
var obj = JsonSerializer.Deserialize<DuoResponseModel>(res); // TODO: We should deserialize this into our own DTO and not work on dictionaries.
if (obj.Stat == "OK") var dict = JsonSerializer.Deserialize<Dictionary<string, object>>(res);
if (dict["stat"].ToString() == "OK")
{ {
return obj.Response; return JsonSerializer.Deserialize<T>(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) catch (ApiException)
{ {