From 700984e97314ec208c3cdf125500c7bd34e3cf42 Mon Sep 17 00:00:00 2001 From: Vincent Salucci Date: Tue, 11 Jul 2023 13:56:08 -0500 Subject: [PATCH 1/7] feat: initial commit, refs AC-1454 --- .../Tools/Controllers/ReportsController.cs | 42 +++++++++ .../InactiveTwoFactorResponseModel.cs | 10 +++ src/Core/Settings/GlobalSettings.cs | 7 ++ src/Core/Settings/IGlobalSettings.cs | 1 + .../Settings/ITwoFactorDirectorySettings.cs | 7 ++ .../TwoFactorDirectoryTotpResponseModel.cs | 15 ++++ .../Queries/GetInactiveTwoFactorQuery.cs | 89 +++++++++++++++++++ .../Interfaces/IGetInactiveTwoFactorQuery.cs | 6 ++ .../Tools/ToolsServiceCollectionExtensions.cs | 19 ++++ .../Utilities/ServiceCollectionExtensions.cs | 2 + .../Queries/GetInactiveTwoFactorQueryTests.cs | 44 +++++++++ 11 files changed, 242 insertions(+) create mode 100644 src/Api/Tools/Controllers/ReportsController.cs create mode 100644 src/Api/Tools/Models/Response/InactiveTwoFactorResponseModel.cs create mode 100644 src/Core/Settings/ITwoFactorDirectorySettings.cs create mode 100644 src/Core/Tools/Models/Api/Response/TwoFactorDirectoryTotpResponseModel.cs create mode 100644 src/Core/Tools/Queries/GetInactiveTwoFactorQuery.cs create mode 100644 src/Core/Tools/Queries/Interfaces/IGetInactiveTwoFactorQuery.cs create mode 100644 src/Core/Tools/ToolsServiceCollectionExtensions.cs create mode 100644 test/Core.Test/Tools/Queries/GetInactiveTwoFactorQueryTests.cs diff --git a/src/Api/Tools/Controllers/ReportsController.cs b/src/Api/Tools/Controllers/ReportsController.cs new file mode 100644 index 000000000000..909e3d6bc8f7 --- /dev/null +++ b/src/Api/Tools/Controllers/ReportsController.cs @@ -0,0 +1,42 @@ +using Bit.Api.Tools.Models.Response; +using Bit.Core.Context; +using Bit.Core.Services; +using Bit.Core.Tools.Queries.Interfaces; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace Bit.Api.Tools.Controllers; + +[Route("reports")] +[Authorize("Application")] +public class ReportsController : Controller +{ + private readonly ICurrentContext _currentContext; + private readonly IGetInactiveTwoFactorQuery _getInactiveTwoFactorQuery; + private readonly IUserService _userService; + + public ReportsController(ICurrentContext currentContext, IGetInactiveTwoFactorQuery getInactiveTwoFactorQuery, IUserService userService) + { + _currentContext = currentContext; + _getInactiveTwoFactorQuery = getInactiveTwoFactorQuery; + _userService = userService; + } + + [HttpGet("inactive-two-factor")] + public async Task GetInactiveTwoFactorAsync() + { + // Premium guarded + var user = await _userService.GetUserByPrincipalAsync(User); + if (!user.Premium) + { + throw new UnauthorizedAccessException("Premium required"); + } + + var services = await _getInactiveTwoFactorQuery.GetInactiveTwoFactorAsync(); + return new InactiveTwoFactorResponseModel() + { + Services = services + }; + + } +} diff --git a/src/Api/Tools/Models/Response/InactiveTwoFactorResponseModel.cs b/src/Api/Tools/Models/Response/InactiveTwoFactorResponseModel.cs new file mode 100644 index 000000000000..d2f368b2e6e1 --- /dev/null +++ b/src/Api/Tools/Models/Response/InactiveTwoFactorResponseModel.cs @@ -0,0 +1,10 @@ +using Bit.Core.Models.Api; + +namespace Bit.Api.Tools.Models.Response; + +public class InactiveTwoFactorResponseModel : ResponseModel +{ + public InactiveTwoFactorResponseModel() : base("inactive-two-factor") { } + + public IReadOnlyDictionary Services { get; set; } +} diff --git a/src/Core/Settings/GlobalSettings.cs b/src/Core/Settings/GlobalSettings.cs index 3c3d3e0c5558..cb3d962cf4a5 100644 --- a/src/Core/Settings/GlobalSettings.cs +++ b/src/Core/Settings/GlobalSettings.cs @@ -80,6 +80,7 @@ public virtual string LicenseDirectory public virtual IPasswordlessAuthSettings PasswordlessAuth { get; set; } = new PasswordlessAuthSettings(); public virtual IDomainVerificationSettings DomainVerification { get; set; } = new DomainVerificationSettings(); public virtual ILaunchDarklySettings LaunchDarkly { get; set; } = new LaunchDarklySettings(); + public virtual ITwoFactorDirectorySettings TwoFactorDirectory { get; set; } = new TwoFactorDirectorySettings(); public string BuildExternalUri(string explicitValue, string name) { @@ -551,4 +552,10 @@ public class LaunchDarklySettings : ILaunchDarklySettings public string FlagDataFilePath { get; set; } = "flags.json"; public Dictionary FlagValues { get; set; } = new Dictionary(); } + + private class TwoFactorDirectorySettings : ITwoFactorDirectorySettings + { + public string Uri { get; set; } = "https://api.2fa.directory/v3/totp.json"; + public int CacheExpirationHours { get; set; } = 24; + } } diff --git a/src/Core/Settings/IGlobalSettings.cs b/src/Core/Settings/IGlobalSettings.cs index 42fc54ef8d7e..9b19b01044d0 100644 --- a/src/Core/Settings/IGlobalSettings.cs +++ b/src/Core/Settings/IGlobalSettings.cs @@ -22,4 +22,5 @@ public interface IGlobalSettings IPasswordlessAuthSettings PasswordlessAuth { get; set; } IDomainVerificationSettings DomainVerification { get; set; } ILaunchDarklySettings LaunchDarkly { get; set; } + ITwoFactorDirectorySettings TwoFactorDirectory { get; set; } } diff --git a/src/Core/Settings/ITwoFactorDirectorySettings.cs b/src/Core/Settings/ITwoFactorDirectorySettings.cs new file mode 100644 index 000000000000..828bd160c018 --- /dev/null +++ b/src/Core/Settings/ITwoFactorDirectorySettings.cs @@ -0,0 +1,7 @@ +namespace Bit.Core.Settings; + +public interface ITwoFactorDirectorySettings +{ + public string Uri { get; set; } + public int CacheExpirationHours { get; set; } +} diff --git a/src/Core/Tools/Models/Api/Response/TwoFactorDirectoryTotpResponseModel.cs b/src/Core/Tools/Models/Api/Response/TwoFactorDirectoryTotpResponseModel.cs new file mode 100644 index 000000000000..b8e844e2d81a --- /dev/null +++ b/src/Core/Tools/Models/Api/Response/TwoFactorDirectoryTotpResponseModel.cs @@ -0,0 +1,15 @@ +using System.ComponentModel.DataAnnotations; +using System.Text.Json.Serialization; + +namespace Bit.Core.Tools.Models.Api.Response; + +public class TwoFactorDirectoryTotpResponseModel +{ + [Required] + [JsonPropertyName("domain")] + public string Domain { get; set; } + [JsonPropertyName("documentation")] + public string Documentation { get; set; } + [JsonPropertyName("additional-domains")] + public IEnumerable AdditionalDomains { get; set; } +} diff --git a/src/Core/Tools/Queries/GetInactiveTwoFactorQuery.cs b/src/Core/Tools/Queries/GetInactiveTwoFactorQuery.cs new file mode 100644 index 000000000000..2298385236f3 --- /dev/null +++ b/src/Core/Tools/Queries/GetInactiveTwoFactorQuery.cs @@ -0,0 +1,89 @@ +using System.Text.Json; +using Bit.Core.Tools.Models.Api.Response; +using Bit.Core.Tools.Queries.Interfaces; +using Bit.Core.Utilities; +using Microsoft.Extensions.Caching.Distributed; +using Bit.Core.Exceptions; +using Bit.Core.Settings; +using Microsoft.Extensions.Logging; + +namespace Bit.Core.Tools.Queries; + +public class GetInactiveTwoFactorQuery : IGetInactiveTwoFactorQuery +{ + private const string _cacheKey = "ReportsInactiveTwoFactor"; + + private readonly IDistributedCache _distributedCache; + private readonly IHttpClientFactory _httpClientFactory; + private readonly IGlobalSettings _globalSettings; + private readonly ILogger _logger; + + public GetInactiveTwoFactorQuery( + IDistributedCache distributedCache, + IHttpClientFactory httpClientFactory, + IGlobalSettings globalSettings, + ILogger logger) + { + _distributedCache = distributedCache; + _httpClientFactory = httpClientFactory; + _globalSettings = globalSettings; + _logger = logger; + } + + public async Task> GetInactiveTwoFactorAsync() + { + _distributedCache.TryGetValue(_cacheKey, out Dictionary services); + if (services != null) + { + return services; + } + + using var client = _httpClientFactory.CreateClient(); + var response = + await client.SendAsync(new HttpRequestMessage(HttpMethod.Get, _globalSettings.TwoFactorDirectory.Uri)); + if (!response.IsSuccessStatusCode) + { + _logger.LogInformation("Request to 2fa.Directory was unsuccessful: {statusCode}", response.StatusCode); + throw new BadRequestException(); + } + + services = new Dictionary(); + var deserializedData = ParseTwoFactorDirectoryTotpResponse(await response.Content.ReadAsStringAsync()); + + foreach (var service in deserializedData.Where(service => !string.IsNullOrEmpty(service.Documentation))) + { + if (service.AdditionalDomains != null) + { + foreach (var additionalDomain in service.AdditionalDomains) + { + // TryAdd used to prevent duplicate keys + services.TryAdd(additionalDomain, service.Documentation); + } + } + + // TryAdd used to prevent duplicate keys + services.TryAdd(service.Domain, service.Documentation); + } + + await _distributedCache.SetAsync(_cacheKey, services, + new DistributedCacheEntryOptions().SetAbsoluteExpiration( + new TimeSpan(_globalSettings.TwoFactorDirectory.CacheExpirationHours, 0, 0))); + return services; + } + + private static IEnumerable ParseTwoFactorDirectoryTotpResponse(string json) + { + var data = new List(); + using var jsonDocument = JsonDocument.Parse(json); + // JSON response object opens with Array notation + if (jsonDocument.RootElement.ValueKind == JsonValueKind.Array) + { + // Each nested array has two values: a floating "name" value [index: 0] and an object with desired data [index: 1] + data.AddRange(from element in jsonDocument.RootElement.EnumerateArray() + where element.ValueKind == JsonValueKind.Array && element.GetArrayLength() == 2 + select element[1].Deserialize()); + } + + return data; + } +} diff --git a/src/Core/Tools/Queries/Interfaces/IGetInactiveTwoFactorQuery.cs b/src/Core/Tools/Queries/Interfaces/IGetInactiveTwoFactorQuery.cs new file mode 100644 index 000000000000..32ef854fa91a --- /dev/null +++ b/src/Core/Tools/Queries/Interfaces/IGetInactiveTwoFactorQuery.cs @@ -0,0 +1,6 @@ +namespace Bit.Core.Tools.Queries.Interfaces; + +public interface IGetInactiveTwoFactorQuery +{ + Task> GetInactiveTwoFactorAsync(); +} diff --git a/src/Core/Tools/ToolsServiceCollectionExtensions.cs b/src/Core/Tools/ToolsServiceCollectionExtensions.cs new file mode 100644 index 000000000000..fa1a637ab17a --- /dev/null +++ b/src/Core/Tools/ToolsServiceCollectionExtensions.cs @@ -0,0 +1,19 @@ +using Bit.Core.Tools.Queries; +using Bit.Core.Tools.Queries.Interfaces; +using Microsoft.Extensions.DependencyInjection; + +namespace Bit.Core.Tools; + +public static class ToolsServiceCollectionExtensions +{ + public static void AddToolsServices(this IServiceCollection services) + { + // TODO Add other Tools services/commands/queries + services.AddReportsQueries(); + } + + private static void AddReportsQueries(this IServiceCollection services) + { + services.AddScoped(); + } +} diff --git a/src/SharedWeb/Utilities/ServiceCollectionExtensions.cs b/src/SharedWeb/Utilities/ServiceCollectionExtensions.cs index c239be969a9c..1ebef4876cb8 100644 --- a/src/SharedWeb/Utilities/ServiceCollectionExtensions.cs +++ b/src/SharedWeb/Utilities/ServiceCollectionExtensions.cs @@ -21,6 +21,7 @@ using Bit.Core.Services; using Bit.Core.Settings; using Bit.Core.Tokens; +using Bit.Core.Tools; using Bit.Core.Tools.Services; using Bit.Core.Utilities; using Bit.Core.Vault.Services; @@ -136,6 +137,7 @@ public static void AddBaseServices(this IServiceCollection services, IGlobalSett services.AddScoped(); services.AddLoginServices(); services.AddScoped(); + services.AddToolsServices(); } public static void AddTokenizers(this IServiceCollection services) diff --git a/test/Core.Test/Tools/Queries/GetInactiveTwoFactorQueryTests.cs b/test/Core.Test/Tools/Queries/GetInactiveTwoFactorQueryTests.cs new file mode 100644 index 000000000000..afb9485fcb9f --- /dev/null +++ b/test/Core.Test/Tools/Queries/GetInactiveTwoFactorQueryTests.cs @@ -0,0 +1,44 @@ +using Bit.Core.Tools.Queries; +using Bit.Core.Utilities; +using Bit.Test.Common.AutoFixture; +using Bit.Test.Common.AutoFixture.Attributes; +using Microsoft.Extensions.Caching.Distributed; +using NSubstitute; +using Xunit; +using System.Text.Json; +using NSubstitute.ReturnsExtensions; + +namespace Bit.Core.Test.Tools.Queries; + +[SutProviderCustomize] +public class GetInactiveTwoFactorQueryTests +{ + [Theory] + [BitAutoData] + public async Task GetInactiveTwoFactor_FromApi_Success(SutProvider sutProvider) + { + // Cache retrieval returns null + sutProvider.GetDependency().Get(Arg.Any()).ReturnsNull(); + //sutProvider.GetDependency() + + await sutProvider.Sut.GetInactiveTwoFactorAsync(); + + // Will return cached values - Should not hit the save method + await sutProvider.GetDependency().DidNotReceive().SetAsync(Arg.Any(), Arg.Any(), Arg.Any()); + } + + [Theory] + [BitAutoData] + public async Task GetInactiveTwoFactor_FromCache_Success(Dictionary dictionary, + SutProvider sutProvider) + { + // Byte array needs to deserialize into dictionary object + var bytes = JsonSerializer.SerializeToUtf8Bytes(dictionary); + sutProvider.GetDependency().Get(Arg.Any()).Returns(bytes); + + await sutProvider.Sut.GetInactiveTwoFactorAsync(); + + await sutProvider.GetDependency().DidNotReceive().SetAsync(Arg.Any(), + Arg.Any(), Arg.Any()); + } +} From 6a1607b94959de44614de796a0509c0ee9511db2 Mon Sep 17 00:00:00 2001 From: Vincent Salucci Date: Wed, 12 Jul 2023 14:38:32 -0500 Subject: [PATCH 2/7] feat: add unit tests and make two factor directory settings uri type-safe, refs AC-1454 --- src/Core/Settings/GlobalSettings.cs | 4 +- .../Settings/ITwoFactorDirectorySettings.cs | 2 +- .../Queries/GetInactiveTwoFactorQueryTests.cs | 72 +++++++++++++++++-- 3 files changed, 69 insertions(+), 9 deletions(-) diff --git a/src/Core/Settings/GlobalSettings.cs b/src/Core/Settings/GlobalSettings.cs index e439b1807e2f..4103a5aaf0ab 100644 --- a/src/Core/Settings/GlobalSettings.cs +++ b/src/Core/Settings/GlobalSettings.cs @@ -554,9 +554,9 @@ public class LaunchDarklySettings : ILaunchDarklySettings public Dictionary FlagValues { get; set; } = new Dictionary(); } - private class TwoFactorDirectorySettings : ITwoFactorDirectorySettings + public class TwoFactorDirectorySettings : ITwoFactorDirectorySettings { - public string Uri { get; set; } = "https://api.2fa.directory/v3/totp.json"; + public Uri Uri { get; set; } = new("https://api.2fa.directory/v3/totp.json"); public int CacheExpirationHours { get; set; } = 24; } } diff --git a/src/Core/Settings/ITwoFactorDirectorySettings.cs b/src/Core/Settings/ITwoFactorDirectorySettings.cs index 828bd160c018..60d988f80394 100644 --- a/src/Core/Settings/ITwoFactorDirectorySettings.cs +++ b/src/Core/Settings/ITwoFactorDirectorySettings.cs @@ -2,6 +2,6 @@ namespace Bit.Core.Settings; public interface ITwoFactorDirectorySettings { - public string Uri { get; set; } + public Uri Uri { get; set; } public int CacheExpirationHours { get; set; } } diff --git a/test/Core.Test/Tools/Queries/GetInactiveTwoFactorQueryTests.cs b/test/Core.Test/Tools/Queries/GetInactiveTwoFactorQueryTests.cs index afb9485fcb9f..0391435c68e4 100644 --- a/test/Core.Test/Tools/Queries/GetInactiveTwoFactorQueryTests.cs +++ b/test/Core.Test/Tools/Queries/GetInactiveTwoFactorQueryTests.cs @@ -1,30 +1,90 @@ +using System.Net; +using System.Net.Mime; +using System.Text; using Bit.Core.Tools.Queries; -using Bit.Core.Utilities; using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture.Attributes; using Microsoft.Extensions.Caching.Distributed; using NSubstitute; using Xunit; using System.Text.Json; +using Bit.Core.Exceptions; +using Bit.Core.Settings; using NSubstitute.ReturnsExtensions; +using GlobalSettings = Bit.Core.Settings.GlobalSettings; namespace Bit.Core.Test.Tools.Queries; [SutProviderCustomize] public class GetInactiveTwoFactorQueryTests { + public class MockHttpMessageHandler : HttpMessageHandler + { + protected override Task SendAsync(HttpRequestMessage request, + CancellationToken cancellationToken) => Send(request, cancellationToken); + + public virtual Task Send(HttpRequestMessage request, CancellationToken token) + { + throw new NotImplementedException(); + } + } + [Theory] [BitAutoData] public async Task GetInactiveTwoFactor_FromApi_Success(SutProvider sutProvider) { - // Cache retrieval returns null sutProvider.GetDependency().Get(Arg.Any()).ReturnsNull(); - //sutProvider.GetDependency() - + + var handler = Substitute.ForPartsOf(); + handler.Send(Arg.Any(), Arg.Any()) + .Returns(new HttpResponseMessage() + { + StatusCode = HttpStatusCode.OK, + Content = new StringContent("{}", Encoding.UTF8, MediaTypeNames.Application.Json) + }); + + var client = new HttpClient(handler); + sutProvider.GetDependency() + .CreateClient() + .Returns(client); + + sutProvider.GetDependency().TwoFactorDirectory.Returns( + new GlobalSettings.TwoFactorDirectorySettings() + { + CacheExpirationHours = 1, Uri = new Uri("http://localhost") + }); + await sutProvider.Sut.GetInactiveTwoFactorAsync(); + + await sutProvider.GetDependency().Received(1).SetAsync(Arg.Any(), + Arg.Any(), Arg.Any()); + } + + [Theory] + [BitAutoData] + public async Task GetInactiveTwoFactor_FromApi_Failure(SutProvider sutProvider) + { + sutProvider.GetDependency().Get(Arg.Any()).ReturnsNull(); + + var handler = Substitute.ForPartsOf(); + handler.Send(Arg.Any(), Arg.Any()) + .Returns(new HttpResponseMessage() + { + StatusCode = HttpStatusCode.Unauthorized + }); + + var client = new HttpClient(handler); + sutProvider.GetDependency() + .CreateClient() + .Returns(client); + + sutProvider.GetDependency().TwoFactorDirectory.Returns( + new GlobalSettings.TwoFactorDirectorySettings() + { + CacheExpirationHours = 1, Uri = new Uri("http://localhost") + }); - // Will return cached values - Should not hit the save method - await sutProvider.GetDependency().DidNotReceive().SetAsync(Arg.Any(), Arg.Any(), Arg.Any()); + await Assert.ThrowsAsync(() => sutProvider.Sut.GetInactiveTwoFactorAsync()); } [Theory] From feebc505a4ba191f547e7c3b7e78988d3478c053 Mon Sep 17 00:00:00 2001 From: Vincent Salucci Date: Fri, 14 Jul 2023 16:33:44 -0500 Subject: [PATCH 3/7] fix: dotnet format, refs AC-1454 --- src/Core/Settings/GlobalSettings.cs | 2 +- .../Settings/ITwoFactorDirectorySettings.cs | 2 +- .../Queries/GetInactiveTwoFactorQuery.cs | 10 +++++----- .../Interfaces/IGetInactiveTwoFactorQuery.cs | 2 +- .../Tools/ToolsServiceCollectionExtensions.cs | 4 ++-- .../Queries/GetInactiveTwoFactorQueryTests.cs | 20 ++++++++++--------- 6 files changed, 21 insertions(+), 19 deletions(-) diff --git a/src/Core/Settings/GlobalSettings.cs b/src/Core/Settings/GlobalSettings.cs index 4103a5aaf0ab..823c815ad4f7 100644 --- a/src/Core/Settings/GlobalSettings.cs +++ b/src/Core/Settings/GlobalSettings.cs @@ -553,7 +553,7 @@ public class LaunchDarklySettings : ILaunchDarklySettings public string FlagDataFilePath { get; set; } = "flags.json"; public Dictionary FlagValues { get; set; } = new Dictionary(); } - + public class TwoFactorDirectorySettings : ITwoFactorDirectorySettings { public Uri Uri { get; set; } = new("https://api.2fa.directory/v3/totp.json"); diff --git a/src/Core/Settings/ITwoFactorDirectorySettings.cs b/src/Core/Settings/ITwoFactorDirectorySettings.cs index 60d988f80394..974d15bd1bff 100644 --- a/src/Core/Settings/ITwoFactorDirectorySettings.cs +++ b/src/Core/Settings/ITwoFactorDirectorySettings.cs @@ -1,4 +1,4 @@ -namespace Bit.Core.Settings; +namespace Bit.Core.Settings; public interface ITwoFactorDirectorySettings { diff --git a/src/Core/Tools/Queries/GetInactiveTwoFactorQuery.cs b/src/Core/Tools/Queries/GetInactiveTwoFactorQuery.cs index 2298385236f3..e08e61a2b12d 100644 --- a/src/Core/Tools/Queries/GetInactiveTwoFactorQuery.cs +++ b/src/Core/Tools/Queries/GetInactiveTwoFactorQuery.cs @@ -1,10 +1,10 @@ -using System.Text.Json; +using System.Text.Json; +using Bit.Core.Exceptions; +using Bit.Core.Settings; using Bit.Core.Tools.Models.Api.Response; using Bit.Core.Tools.Queries.Interfaces; using Bit.Core.Utilities; using Microsoft.Extensions.Caching.Distributed; -using Bit.Core.Exceptions; -using Bit.Core.Settings; using Microsoft.Extensions.Logging; namespace Bit.Core.Tools.Queries; @@ -80,8 +80,8 @@ private static IEnumerable ParseTwoFactorDi { // Each nested array has two values: a floating "name" value [index: 0] and an object with desired data [index: 1] data.AddRange(from element in jsonDocument.RootElement.EnumerateArray() - where element.ValueKind == JsonValueKind.Array && element.GetArrayLength() == 2 - select element[1].Deserialize()); + where element.ValueKind == JsonValueKind.Array && element.GetArrayLength() == 2 + select element[1].Deserialize()); } return data; diff --git a/src/Core/Tools/Queries/Interfaces/IGetInactiveTwoFactorQuery.cs b/src/Core/Tools/Queries/Interfaces/IGetInactiveTwoFactorQuery.cs index 32ef854fa91a..a6652aca95fc 100644 --- a/src/Core/Tools/Queries/Interfaces/IGetInactiveTwoFactorQuery.cs +++ b/src/Core/Tools/Queries/Interfaces/IGetInactiveTwoFactorQuery.cs @@ -1,4 +1,4 @@ -namespace Bit.Core.Tools.Queries.Interfaces; +namespace Bit.Core.Tools.Queries.Interfaces; public interface IGetInactiveTwoFactorQuery { diff --git a/src/Core/Tools/ToolsServiceCollectionExtensions.cs b/src/Core/Tools/ToolsServiceCollectionExtensions.cs index fa1a637ab17a..a9df7b9e4c70 100644 --- a/src/Core/Tools/ToolsServiceCollectionExtensions.cs +++ b/src/Core/Tools/ToolsServiceCollectionExtensions.cs @@ -1,4 +1,4 @@ -using Bit.Core.Tools.Queries; +using Bit.Core.Tools.Queries; using Bit.Core.Tools.Queries.Interfaces; using Microsoft.Extensions.DependencyInjection; @@ -11,7 +11,7 @@ public static void AddToolsServices(this IServiceCollection services) // TODO Add other Tools services/commands/queries services.AddReportsQueries(); } - + private static void AddReportsQueries(this IServiceCollection services) { services.AddScoped(); diff --git a/test/Core.Test/Tools/Queries/GetInactiveTwoFactorQueryTests.cs b/test/Core.Test/Tools/Queries/GetInactiveTwoFactorQueryTests.cs index 0391435c68e4..6caaeead84d6 100644 --- a/test/Core.Test/Tools/Queries/GetInactiveTwoFactorQueryTests.cs +++ b/test/Core.Test/Tools/Queries/GetInactiveTwoFactorQueryTests.cs @@ -1,16 +1,16 @@ -using System.Net; +using System.Net; using System.Net.Mime; using System.Text; +using System.Text.Json; +using Bit.Core.Exceptions; +using Bit.Core.Settings; using Bit.Core.Tools.Queries; using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture.Attributes; using Microsoft.Extensions.Caching.Distributed; using NSubstitute; -using Xunit; -using System.Text.Json; -using Bit.Core.Exceptions; -using Bit.Core.Settings; using NSubstitute.ReturnsExtensions; +using Xunit; using GlobalSettings = Bit.Core.Settings.GlobalSettings; namespace Bit.Core.Test.Tools.Queries; @@ -51,7 +51,8 @@ public async Task GetInactiveTwoFactor_FromApi_Success(SutProvider().TwoFactorDirectory.Returns( new GlobalSettings.TwoFactorDirectorySettings() { - CacheExpirationHours = 1, Uri = new Uri("http://localhost") + CacheExpirationHours = 1, + Uri = new Uri("http://localhost") }); await sutProvider.Sut.GetInactiveTwoFactorAsync(); @@ -59,7 +60,7 @@ public async Task GetInactiveTwoFactor_FromApi_Success(SutProvider().Received(1).SetAsync(Arg.Any(), Arg.Any(), Arg.Any()); } - + [Theory] [BitAutoData] public async Task GetInactiveTwoFactor_FromApi_Failure(SutProvider sutProvider) @@ -81,9 +82,10 @@ public async Task GetInactiveTwoFactor_FromApi_Failure(SutProvider().TwoFactorDirectory.Returns( new GlobalSettings.TwoFactorDirectorySettings() { - CacheExpirationHours = 1, Uri = new Uri("http://localhost") + CacheExpirationHours = 1, + Uri = new Uri("http://localhost") }); - + await Assert.ThrowsAsync(() => sutProvider.Sut.GetInactiveTwoFactorAsync()); } From 8d15389d7bf3eb4a168d2403016fdd2a8c4dfb8e Mon Sep 17 00:00:00 2001 From: Vincent Salucci Date: Fri, 11 Aug 2023 13:26:55 -0500 Subject: [PATCH 4/7] fix: remove TODO, only add Tools Services to API project scope, refs AC-1454 --- src/Api/Startup.cs | 2 ++ src/Core/Tools/ToolsServiceCollectionExtensions.cs | 1 - src/SharedWeb/Utilities/ServiceCollectionExtensions.cs | 1 - 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Api/Startup.cs b/src/Api/Startup.cs index a642dcb10f98..f02a369c7cd3 100644 --- a/src/Api/Startup.cs +++ b/src/Api/Startup.cs @@ -16,6 +16,7 @@ using Microsoft.Extensions.DependencyInjection.Extensions; using Bit.Core.Auth.Identity; using Bit.Core.OrganizationFeatures.OrganizationSubscriptions; +using Bit.Core.Tools; #if !OSS using Bit.Commercial.Core.SecretsManager; @@ -133,6 +134,7 @@ public void ConfigureServices(IServiceCollection services) // Services services.AddBaseServices(globalSettings); + services.AddToolsServices(); services.AddDefaultServices(globalSettings); services.AddOrganizationSubscriptionServices(); services.AddCoreLocalizationServices(); diff --git a/src/Core/Tools/ToolsServiceCollectionExtensions.cs b/src/Core/Tools/ToolsServiceCollectionExtensions.cs index a9df7b9e4c70..54f646bb8b1c 100644 --- a/src/Core/Tools/ToolsServiceCollectionExtensions.cs +++ b/src/Core/Tools/ToolsServiceCollectionExtensions.cs @@ -8,7 +8,6 @@ public static class ToolsServiceCollectionExtensions { public static void AddToolsServices(this IServiceCollection services) { - // TODO Add other Tools services/commands/queries services.AddReportsQueries(); } diff --git a/src/SharedWeb/Utilities/ServiceCollectionExtensions.cs b/src/SharedWeb/Utilities/ServiceCollectionExtensions.cs index 0ad802ab2357..5dd024495b6f 100644 --- a/src/SharedWeb/Utilities/ServiceCollectionExtensions.cs +++ b/src/SharedWeb/Utilities/ServiceCollectionExtensions.cs @@ -139,7 +139,6 @@ public static void AddBaseServices(this IServiceCollection services, IGlobalSett services.AddScoped(); services.AddLoginServices(); services.AddScoped(); - services.AddToolsServices(); } public static void AddTokenizers(this IServiceCollection services) From e3d7bf02ee6bb0bb27d9777f75f4c367b7f6149e Mon Sep 17 00:00:00 2001 From: Vincent Salucci Date: Fri, 11 Aug 2023 14:02:20 -0500 Subject: [PATCH 5/7] fix: removed unused statement, refs AC-1454 --- src/SharedWeb/Utilities/ServiceCollectionExtensions.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/SharedWeb/Utilities/ServiceCollectionExtensions.cs b/src/SharedWeb/Utilities/ServiceCollectionExtensions.cs index 5dd024495b6f..ea97af041958 100644 --- a/src/SharedWeb/Utilities/ServiceCollectionExtensions.cs +++ b/src/SharedWeb/Utilities/ServiceCollectionExtensions.cs @@ -23,7 +23,6 @@ using Bit.Core.Services; using Bit.Core.Settings; using Bit.Core.Tokens; -using Bit.Core.Tools; using Bit.Core.Tools.Services; using Bit.Core.Utilities; using Bit.Core.Vault.Services; From 8a33050ab0ae6ef4302a8e17076cdabedd93e83f Mon Sep 17 00:00:00 2001 From: Vincent Salucci Date: Fri, 19 Jan 2024 15:35:56 -0600 Subject: [PATCH 6/7] feat: move tools team service init to bottom of services section, refs AC-1454 --- src/Api/Startup.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Api/Startup.cs b/src/Api/Startup.cs index 087d6185d6a7..eca1c5471d99 100644 --- a/src/Api/Startup.cs +++ b/src/Api/Startup.cs @@ -168,11 +168,11 @@ public void ConfigureServices(IServiceCollection services) // Services services.AddBaseServices(globalSettings); - services.AddToolsServices(); services.AddDefaultServices(globalSettings); services.AddOrganizationSubscriptionServices(); services.AddCoreLocalizationServices(); services.AddBillingCommands(); + services.AddToolsServices(); // Authorization Handlers services.AddAuthorizationHandlers(); From 2322789dfc3db2bff43d9252b7ff8b68c052ac21 Mon Sep 17 00:00:00 2001 From: Vincent Salucci Date: Fri, 2 Feb 2024 13:10:23 -0600 Subject: [PATCH 7/7] feat: add MigrateTwoFactorDirectory feature flag / api protection, refs AC-1454 --- src/Api/Tools/Controllers/ReportsController.cs | 3 +++ src/Core/Constants.cs | 1 + 2 files changed, 4 insertions(+) diff --git a/src/Api/Tools/Controllers/ReportsController.cs b/src/Api/Tools/Controllers/ReportsController.cs index 909e3d6bc8f7..2b611be9cf49 100644 --- a/src/Api/Tools/Controllers/ReportsController.cs +++ b/src/Api/Tools/Controllers/ReportsController.cs @@ -1,7 +1,9 @@ using Bit.Api.Tools.Models.Response; +using Bit.Core; using Bit.Core.Context; using Bit.Core.Services; using Bit.Core.Tools.Queries.Interfaces; +using Bit.Core.Utilities; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; @@ -23,6 +25,7 @@ public ReportsController(ICurrentContext currentContext, IGetInactiveTwoFactorQu } [HttpGet("inactive-two-factor")] + [RequireFeature(FeatureFlagKeys.MigrateTwoFactorDirectory)] public async Task GetInactiveTwoFactorAsync() { // Premium guarded diff --git a/src/Core/Constants.cs b/src/Core/Constants.cs index 1d5073df6930..ff86888255a6 100644 --- a/src/Core/Constants.cs +++ b/src/Core/Constants.cs @@ -115,6 +115,7 @@ public static class FeatureFlagKeys /// flexible collections /// public const string FlexibleCollectionsMigration = "flexible-collections-migration"; + public const string MigrateTwoFactorDirectory = "migrate-two-factor-directory"; public const string PM5766AutomaticTax = "PM-5766-automatic-tax";