From ab4cb18145a8df117c334ec9e5de1ca227145d3f Mon Sep 17 00:00:00 2001 From: Ike Kottlowski Date: Thu, 5 Jun 2025 18:11:54 -0400 Subject: [PATCH] test: adding tests for AuthRequestsController; docs: added comments for the AuthRequestService. --- .../Controllers/AuthRequestsController.cs | 4 +- src/Core/Auth/Services/IAuthRequestService.cs | 34 ++- .../Implementations/AuthRequestService.cs | 10 +- test/Api.Test/Api.Test.csproj | 5 - .../AuthRequestsControllerTests.cs | 257 ++++++++++++++++++ 5 files changed, 295 insertions(+), 15 deletions(-) create mode 100644 test/Api.Test/Auth/Controllers/AuthRequestsControllerTests.cs diff --git a/src/Api/Auth/Controllers/AuthRequestsController.cs b/src/Api/Auth/Controllers/AuthRequestsController.cs index 62f7c289d2..142ff26df6 100644 --- a/src/Api/Auth/Controllers/AuthRequestsController.cs +++ b/src/Api/Auth/Controllers/AuthRequestsController.cs @@ -20,14 +20,12 @@ public class AuthRequestsController( IUserService userService, IAuthRequestRepository authRequestRepository, IGlobalSettings globalSettings, - IAuthRequestService authRequestService, - IFeatureService featureService) : Controller + IAuthRequestService authRequestService) : Controller { private readonly IUserService _userService = userService; private readonly IAuthRequestRepository _authRequestRepository = authRequestRepository; private readonly IGlobalSettings _globalSettings = globalSettings; private readonly IAuthRequestService _authRequestService = authRequestService; - private readonly IFeatureService _featureService = featureService; [HttpGet("")] public async Task> Get() diff --git a/src/Core/Auth/Services/IAuthRequestService.cs b/src/Core/Auth/Services/IAuthRequestService.cs index 181b7ab9bf..7ea4503f59 100644 --- a/src/Core/Auth/Services/IAuthRequestService.cs +++ b/src/Core/Auth/Services/IAuthRequestService.cs @@ -1,6 +1,9 @@ using Bit.Core.Auth.Entities; +using Bit.Core.Auth.Exceptions; using Bit.Core.Auth.Models.Api.Request.AuthRequest; using Bit.Core.Context; +using Bit.Core.Exceptions; +using Bit.Core.Settings; #nullable enable @@ -8,8 +11,24 @@ namespace Bit.Core.Auth.Services; public interface IAuthRequestService { - Task GetAuthRequestAsync(Guid id, Guid userId); - Task GetValidatedAuthRequestAsync(Guid id, string code); + /// + /// fetches an authRequest by Id. Returns AuthRequest if AuthRequest.UserId mateches + /// userId. Returns null if the user doesn't match or if the AuthRequest is not found. + /// + /// Authrequest Id being fetched + /// user who owns AuthRequest + /// An AuthRequest or null + Task GetAuthRequestAsync(Guid authRequestId, Guid userId); + /// + /// Fetches the authrequest from the database with the id provided. Then checks + /// the accessCode against the AuthRequest.AccessCode from the database. accessCodes + /// must match the found authRequest, and the AuthRequest must not be expired. Expiration + /// is configured in + /// + /// AuthRequest being acted on + /// Access code of the authrequest, must match saved database value + /// A valid AuthRequest or null + Task GetValidatedAuthRequestAsync(Guid authRequestId, string accessCode); /// /// Validates and Creates an in the database, as well as pushes it through notifications services /// @@ -17,5 +36,16 @@ public interface IAuthRequestService /// This method can only be called inside of an HTTP call because of it's reliance on /// Task CreateAuthRequestAsync(AuthRequestCreateRequestModel model); + /// + /// Updates the AuthRequest per the AuthRequestUpdateRequestModel context. This approves + /// or rejects the login request. + /// + /// AuthRequest being acted on. + /// User acting on AuthRequest + /// Update context for the AuthRequest + /// retuns an AuthRequest or throws an exception + /// Thows if the AuthRequest has already been Approved/Rejected + /// Throws if the AuthRequest as expired or the userId doesn't match + /// Throws if the device isn't associated with the UserId Task UpdateAuthRequestAsync(Guid authRequestId, Guid userId, AuthRequestUpdateRequestModel model); } diff --git a/src/Core/Auth/Services/Implementations/AuthRequestService.cs b/src/Core/Auth/Services/Implementations/AuthRequestService.cs index 9f59242466..11682b524f 100644 --- a/src/Core/Auth/Services/Implementations/AuthRequestService.cs +++ b/src/Core/Auth/Services/Implementations/AuthRequestService.cs @@ -58,9 +58,9 @@ public class AuthRequestService : IAuthRequestService _logger = logger; } - public async Task GetAuthRequestAsync(Guid id, Guid userId) + public async Task GetAuthRequestAsync(Guid authRequestId, Guid userId) { - var authRequest = await _authRequestRepository.GetByIdAsync(id); + var authRequest = await _authRequestRepository.GetByIdAsync(authRequestId); if (authRequest == null || authRequest.UserId != userId) { return null; @@ -69,10 +69,10 @@ public class AuthRequestService : IAuthRequestService return authRequest; } - public async Task GetValidatedAuthRequestAsync(Guid id, string code) + public async Task GetValidatedAuthRequestAsync(Guid authRequestId, string accessCode) { - var authRequest = await _authRequestRepository.GetByIdAsync(id); - if (authRequest == null || !CoreHelpers.FixedTimeEquals(authRequest.AccessCode, code)) + var authRequest = await _authRequestRepository.GetByIdAsync(authRequestId); + if (authRequest == null || !CoreHelpers.FixedTimeEquals(authRequest.AccessCode, accessCode)) { return null; } diff --git a/test/Api.Test/Api.Test.csproj b/test/Api.Test/Api.Test.csproj index d6b31ce930..fb75246d4f 100644 --- a/test/Api.Test/Api.Test.csproj +++ b/test/Api.Test/Api.Test.csproj @@ -25,9 +25,4 @@ - - - - - diff --git a/test/Api.Test/Auth/Controllers/AuthRequestsControllerTests.cs b/test/Api.Test/Auth/Controllers/AuthRequestsControllerTests.cs new file mode 100644 index 0000000000..20f391c609 --- /dev/null +++ b/test/Api.Test/Auth/Controllers/AuthRequestsControllerTests.cs @@ -0,0 +1,257 @@ +using System.Security.Claims; +using Bit.Api.Auth.Controllers; +using Bit.Api.Auth.Models.Response; +using Bit.Api.Models.Response; +using Bit.Core.Auth.Entities; +using Bit.Core.Auth.Enums; +using Bit.Core.Auth.Models.Api.Request.AuthRequest; +using Bit.Core.Auth.Services; +using Bit.Core.Entities; +using Bit.Core.Exceptions; +using Bit.Core.Repositories; +using Bit.Core.Services; +using Bit.Core.Settings; +using Bit.Test.Common.AutoFixture; +using Bit.Test.Common.AutoFixture.Attributes; +using NSubstitute; +using Xunit; + +namespace Bit.Api.Test.Auth.Controllers; + +[ControllerCustomize(typeof(AuthRequestsController))] +[SutProviderCustomize] +public class AuthRequestsControllerTests +{ + const string _testGlobalSettingsBaseUri = "https://vault.test.dev"; + + [Theory, BitAutoData] + public async Task Get_ReturnsExpectedResult( + SutProvider sutProvider, + User user, + AuthRequest authRequest) + { + // Arrange + SetBaseServiceUri(sutProvider); + + sutProvider.GetDependency() + .GetProperUserId(Arg.Any()) + .Returns(user.Id); + + sutProvider.GetDependency() + .GetManyByUserIdAsync(user.Id) + .Returns([authRequest]); + + // Act + var result = await sutProvider.Sut.Get(); + + // Assert + Assert.NotNull(result); + var expectedCount = 1; + Assert.Equal(result.Data.Count(), expectedCount); + Assert.IsType>(result); + } + + [Theory, BitAutoData] + public async Task GetById_ThrowsNotFoundException( + SutProvider sutProvider, + User user, + AuthRequest authRequest) + { + // Arrange + sutProvider.GetDependency() + .GetProperUserId(Arg.Any()) + .Returns(user.Id); + + sutProvider.GetDependency() + .GetAuthRequestAsync(authRequest.Id, user.Id) + .Returns((AuthRequest)null); + + // Act + // Assert + var exception = await Assert.ThrowsAsync( + () => sutProvider.Sut.Get(authRequest.Id)); + } + + [Theory, BitAutoData] + public async Task GetById_ReturnsAuthRequest( + SutProvider sutProvider, + User user, + AuthRequest authRequest) + { + // Arrange + SetBaseServiceUri(sutProvider); + sutProvider.GetDependency() + .GetProperUserId(Arg.Any()) + .Returns(user.Id); + + sutProvider.GetDependency() + .GetAuthRequestAsync(authRequest.Id, user.Id) + .Returns(authRequest); + + // Act + var result = await sutProvider.Sut.Get(authRequest.Id); + + // Assert + Assert.NotNull(result); + Assert.IsType(result); + } + + [Theory, BitAutoData] + public async Task GetPending_ReturnsExpectedResult( + SutProvider sutProvider, + User user, + AuthRequest authRequest) + { + // Arrange + SetBaseServiceUri(sutProvider); + + sutProvider.GetDependency() + .GetProperUserId(Arg.Any()) + .Returns(user.Id); + + sutProvider.GetDependency() + .GetManyPendingAuthRequestByUserId(user.Id) + .Returns([authRequest]); + + // Act + var result = await sutProvider.Sut.GetPendingAuthRequestsAsync(); + + // Assert + Assert.NotNull(result); + var expectedCount = 1; + Assert.Equal(result.Data.Count(), expectedCount); + Assert.IsType>(result); + } + + [Theory, BitAutoData] + public async Task GetResponseById_ThrowsNotFoundException( + SutProvider sutProvider, + AuthRequest authRequest) + { + // Arrange + sutProvider.GetDependency() + .GetValidatedAuthRequestAsync(authRequest.Id, authRequest.AccessCode) + .Returns((AuthRequest)null); + + // Act + // Assert + var exception = await Assert.ThrowsAsync( + () => sutProvider.Sut.GetResponse(authRequest.Id, authRequest.AccessCode)); + } + + [Theory, BitAutoData] + public async Task GetResponseById_ReturnsAuthRequest( + SutProvider sutProvider, + AuthRequest authRequest) + { + // Arrange + SetBaseServiceUri(sutProvider); + + sutProvider.GetDependency() + .GetValidatedAuthRequestAsync(authRequest.Id, authRequest.AccessCode) + .Returns(authRequest); + + // Act + var result = await sutProvider.Sut.GetResponse(authRequest.Id, authRequest.AccessCode); + + // Assert + Assert.NotNull(result); + Assert.IsType(result); + } + + [Theory, BitAutoData] + public async Task Post_AdminApprovalRequest_ThrowsBadRequestException( + SutProvider sutProvider, + AuthRequestCreateRequestModel authRequest) + { + // Arrange + authRequest.Type = AuthRequestType.AdminApproval; + + // Act + // Assert + var exception = await Assert.ThrowsAsync( + () => sutProvider.Sut.Post(authRequest)); + + var expectedMessage = "You must be authenticated to create a request of that type."; + Assert.Equal(exception.Message, expectedMessage); + } + + [Theory, BitAutoData] + public async Task Post_ReturnsAuthRequest( + SutProvider sutProvider, + AuthRequestCreateRequestModel requestModel, + AuthRequest authRequest) + { + // Arrange + SetBaseServiceUri(sutProvider); + + requestModel.Type = AuthRequestType.AuthenticateAndUnlock; + sutProvider.GetDependency() + .CreateAuthRequestAsync(requestModel) + .Returns(authRequest); + + // Act + var result = await sutProvider.Sut.Post(requestModel); + + // Assert + Assert.NotNull(result); + Assert.IsType(result); + } + + [Theory, BitAutoData] + public async Task PostAdminRequest_ReturnsAuthRequest( + SutProvider sutProvider, + AuthRequestCreateRequestModel requestModel, + AuthRequest authRequest) + { + // Arrange + SetBaseServiceUri(sutProvider); + + requestModel.Type = AuthRequestType.AuthenticateAndUnlock; + sutProvider.GetDependency() + .CreateAuthRequestAsync(requestModel) + .Returns(authRequest); + + // Act + var result = await sutProvider.Sut.PostAdminRequest(requestModel); + + // Assert + Assert.NotNull(result); + Assert.IsType(result); + } + + [Theory, BitAutoData] + public async Task Put_ReturnsAuthRequest( + SutProvider sutProvider, + User user, + AuthRequestUpdateRequestModel requestModel, + AuthRequest authRequest) + { + // Arrange + SetBaseServiceUri(sutProvider); + + sutProvider.GetDependency() + .GetProperUserId(Arg.Any()) + .Returns(user.Id); + + sutProvider.GetDependency() + .UpdateAuthRequestAsync(authRequest.Id, user.Id, requestModel) + .Returns(authRequest); + + // Act + var result = await sutProvider.Sut + .Put(authRequest.Id, requestModel); + + // Assert + Assert.NotNull(result); + Assert.IsType(result); + } + + private void SetBaseServiceUri(SutProvider sutProvider) + { + sutProvider.GetDependency() + .BaseServiceUri + .Vault + .Returns(_testGlobalSettingsBaseUri); + } +}