From b21e89f2647fc4ded46297c7414cfc880f368b1b Mon Sep 17 00:00:00 2001 From: Kyle Spearrin Date: Mon, 22 Jul 2019 21:23:09 -0400 Subject: [PATCH] hibp api v3 --- src/Api/Controllers/HibpController.cs | 37 +++++++++++++++++++++------ src/Core/GlobalSettings.cs | 2 +- 2 files changed, 30 insertions(+), 9 deletions(-) diff --git a/src/Api/Controllers/HibpController.cs b/src/Api/Controllers/HibpController.cs index b4ff0c5f6..6ec80913c 100644 --- a/src/Api/Controllers/HibpController.cs +++ b/src/Api/Controllers/HibpController.cs @@ -8,6 +8,7 @@ using Bit.Core.Services; using Bit.Core; using System.Net; using Bit.Core.Exceptions; +using System.Linq; namespace Bit.Api.Controllers { @@ -15,17 +16,18 @@ namespace Bit.Api.Controllers [Authorize("Application")] public class HibpController : Controller { - private const string HibpBreachApi = "https://haveibeenpwned.com/api/v2/breachedaccount/{0}"; + private const string HibpBreachApi = "https://haveibeenpwned.com/api/v3/breachedaccount/{0}" + + "?truncateResponse=false&includeUnverified=false"; private static HttpClient _httpClient; private readonly IUserService _userService; private readonly CurrentContext _currentContext; private readonly GlobalSettings _globalSettings; + private readonly string _userAgent; static HibpController() { _httpClient = new HttpClient(); - _httpClient.DefaultRequestHeaders.Add("User-Agent", "Bitwarden"); } public HibpController( @@ -36,19 +38,24 @@ namespace Bit.Api.Controllers _userService = userService; _currentContext = currentContext; _globalSettings = globalSettings; + _userAgent = _globalSettings.SelfHosted ? "Bitwarden Self-Hosted" : "Bitwarden"; } [HttpGet("breach")] public async Task Get(string username) { - var encodedUsername = WebUtility.UrlEncode(username); - var request = new HttpRequestMessage(HttpMethod.Get, string.Format(HibpBreachApi, encodedUsername)); - if(!string.IsNullOrWhiteSpace(_globalSettings.HibpBreachApiKey)) + return await SendAsync(WebUtility.UrlEncode(username), true); + } + + private async Task SendAsync(string username, bool retry) + { + var request = new HttpRequestMessage(HttpMethod.Get, string.Format(HibpBreachApi, username)); + if(!string.IsNullOrWhiteSpace(_globalSettings.HibpApiKey)) { - request.Headers.Add("Authorization", $"Basic {_globalSettings.HibpBreachApiKey}"); + request.Headers.Add("hibp-api-key", _globalSettings.HibpApiKey); } - request.Headers.Add("Client-Id", GetClientId()); - request.Headers.Add("Client-Ip", _currentContext.IpAddress); + request.Headers.Add("hibp-client-id", GetClientId()); + request.Headers.Add("User-Agent", _userAgent); var response = await _httpClient.SendAsync(request); if(response.IsSuccessStatusCode) { @@ -59,6 +66,20 @@ namespace Bit.Api.Controllers { return new NotFoundResult(); } + else if(response.StatusCode == HttpStatusCode.TooManyRequests && retry) + { + var delay = 2000; + if(response.Headers.Contains("retry-after")) + { + var vals = response.Headers.GetValues("retry-after"); + if(vals.Any() && int.TryParse(vals.FirstOrDefault(), out var secDelay)) + { + delay = (secDelay * 1000) + 200; + } + } + await Task.Delay(delay); + return await SendAsync(username, false); + } else { throw new BadRequestException("Request failed. Status code: " + response.StatusCode); diff --git a/src/Core/GlobalSettings.cs b/src/Core/GlobalSettings.cs index e6bc27af6..d4a4a0a36 100644 --- a/src/Core/GlobalSettings.cs +++ b/src/Core/GlobalSettings.cs @@ -14,7 +14,7 @@ namespace Bit.Core public string LicenseCertificatePassword { get; set; } public virtual string PushRelayBaseUri { get; set; } public virtual string InternalIdentityKey { get; set; } - public virtual string HibpBreachApiKey { get; set; } + public virtual string HibpApiKey { get; set; } public virtual bool DisableUserRegistration { get; set; } public virtual bool DisableEmailNewDevice { get; set; } public virtual int OrganizationInviteExpirationHours { get; set; } = 120; // 5 days