1
0
mirror of https://github.com/bitwarden/server.git synced 2025-06-07 19:50:32 -05:00

test: adding tests for AuthRequestsController; docs: added comments for the AuthRequestService.

This commit is contained in:
Ike Kottlowski 2025-06-05 18:11:54 -04:00
parent ca711cd57e
commit ab4cb18145
No known key found for this signature in database
GPG Key ID: C86308E3DCA6D76F
5 changed files with 295 additions and 15 deletions

View File

@ -20,14 +20,12 @@ public class AuthRequestsController(
IUserService userService, IUserService userService,
IAuthRequestRepository authRequestRepository, IAuthRequestRepository authRequestRepository,
IGlobalSettings globalSettings, IGlobalSettings globalSettings,
IAuthRequestService authRequestService, IAuthRequestService authRequestService) : Controller
IFeatureService featureService) : Controller
{ {
private readonly IUserService _userService = userService; private readonly IUserService _userService = userService;
private readonly IAuthRequestRepository _authRequestRepository = authRequestRepository; private readonly IAuthRequestRepository _authRequestRepository = authRequestRepository;
private readonly IGlobalSettings _globalSettings = globalSettings; private readonly IGlobalSettings _globalSettings = globalSettings;
private readonly IAuthRequestService _authRequestService = authRequestService; private readonly IAuthRequestService _authRequestService = authRequestService;
private readonly IFeatureService _featureService = featureService;
[HttpGet("")] [HttpGet("")]
public async Task<ListResponseModel<AuthRequestResponseModel>> Get() public async Task<ListResponseModel<AuthRequestResponseModel>> Get()

View File

@ -1,6 +1,9 @@
using Bit.Core.Auth.Entities; using Bit.Core.Auth.Entities;
using Bit.Core.Auth.Exceptions;
using Bit.Core.Auth.Models.Api.Request.AuthRequest; using Bit.Core.Auth.Models.Api.Request.AuthRequest;
using Bit.Core.Context; using Bit.Core.Context;
using Bit.Core.Exceptions;
using Bit.Core.Settings;
#nullable enable #nullable enable
@ -8,8 +11,24 @@ namespace Bit.Core.Auth.Services;
public interface IAuthRequestService public interface IAuthRequestService
{ {
Task<AuthRequest?> GetAuthRequestAsync(Guid id, Guid userId); /// <summary>
Task<AuthRequest?> 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.
/// </summary>
/// <param name="authRequestId">Authrequest Id being fetched</param>
/// <param name="userId">user who owns AuthRequest</param>
/// <returns>An AuthRequest or null</returns>
Task<AuthRequest?> GetAuthRequestAsync(Guid authRequestId, Guid userId);
/// <summary>
/// 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 <see cref="GlobalSettings"/>
/// </summary>
/// <param name="authRequestId">AuthRequest being acted on</param>
/// <param name="accessCode">Access code of the authrequest, must match saved database value</param>
/// <returns>A valid AuthRequest or null</returns>
Task<AuthRequest?> GetValidatedAuthRequestAsync(Guid authRequestId, string accessCode);
/// <summary> /// <summary>
/// Validates and Creates an <see cref="AuthRequest" /> in the database, as well as pushes it through notifications services /// Validates and Creates an <see cref="AuthRequest" /> in the database, as well as pushes it through notifications services
/// </summary> /// </summary>
@ -17,5 +36,16 @@ public interface IAuthRequestService
/// This method can only be called inside of an HTTP call because of it's reliance on <see cref="ICurrentContext" /> /// This method can only be called inside of an HTTP call because of it's reliance on <see cref="ICurrentContext" />
/// </remarks> /// </remarks>
Task<AuthRequest> CreateAuthRequestAsync(AuthRequestCreateRequestModel model); Task<AuthRequest> CreateAuthRequestAsync(AuthRequestCreateRequestModel model);
/// <summary>
/// Updates the AuthRequest per the AuthRequestUpdateRequestModel context. This approves
/// or rejects the login request.
/// </summary>
/// <param name="authRequestId">AuthRequest being acted on.</param>
/// <param name="userId">User acting on AuthRequest</param>
/// <param name="model">Update context for the AuthRequest</param>
/// <returns>retuns an AuthRequest or throws an exception</returns>
/// <exception cref="DuplicateAuthRequestException">Thows if the AuthRequest has already been Approved/Rejected</exception>
/// <exception cref="NotFoundException">Throws if the AuthRequest as expired or the userId doesn't match</exception>
/// <exception cref="BadRequestException">Throws if the device isn't associated with the UserId</exception>
Task<AuthRequest> UpdateAuthRequestAsync(Guid authRequestId, Guid userId, AuthRequestUpdateRequestModel model); Task<AuthRequest> UpdateAuthRequestAsync(Guid authRequestId, Guid userId, AuthRequestUpdateRequestModel model);
} }

View File

@ -58,9 +58,9 @@ public class AuthRequestService : IAuthRequestService
_logger = logger; _logger = logger;
} }
public async Task<AuthRequest?> GetAuthRequestAsync(Guid id, Guid userId) public async Task<AuthRequest?> GetAuthRequestAsync(Guid authRequestId, Guid userId)
{ {
var authRequest = await _authRequestRepository.GetByIdAsync(id); var authRequest = await _authRequestRepository.GetByIdAsync(authRequestId);
if (authRequest == null || authRequest.UserId != userId) if (authRequest == null || authRequest.UserId != userId)
{ {
return null; return null;
@ -69,10 +69,10 @@ public class AuthRequestService : IAuthRequestService
return authRequest; return authRequest;
} }
public async Task<AuthRequest?> GetValidatedAuthRequestAsync(Guid id, string code) public async Task<AuthRequest?> GetValidatedAuthRequestAsync(Guid authRequestId, string accessCode)
{ {
var authRequest = await _authRequestRepository.GetByIdAsync(id); var authRequest = await _authRequestRepository.GetByIdAsync(authRequestId);
if (authRequest == null || !CoreHelpers.FixedTimeEquals(authRequest.AccessCode, code)) if (authRequest == null || !CoreHelpers.FixedTimeEquals(authRequest.AccessCode, accessCode))
{ {
return null; return null;
} }

View File

@ -25,9 +25,4 @@
<ProjectReference Include="..\Common\Common.csproj" /> <ProjectReference Include="..\Common\Common.csproj" />
<ProjectReference Include="..\Core.Test\Core.Test.csproj" /> <ProjectReference Include="..\Core.Test\Core.Test.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<Folder Include="Auth\" />
<Folder Include="Auth\Controllers\" />
</ItemGroup>
</Project> </Project>

View File

@ -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<AuthRequestsController> sutProvider,
User user,
AuthRequest authRequest)
{
// Arrange
SetBaseServiceUri(sutProvider);
sutProvider.GetDependency<IUserService>()
.GetProperUserId(Arg.Any<ClaimsPrincipal>())
.Returns(user.Id);
sutProvider.GetDependency<IAuthRequestRepository>()
.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<ListResponseModel<AuthRequestResponseModel>>(result);
}
[Theory, BitAutoData]
public async Task GetById_ThrowsNotFoundException(
SutProvider<AuthRequestsController> sutProvider,
User user,
AuthRequest authRequest)
{
// Arrange
sutProvider.GetDependency<IUserService>()
.GetProperUserId(Arg.Any<ClaimsPrincipal>())
.Returns(user.Id);
sutProvider.GetDependency<IAuthRequestService>()
.GetAuthRequestAsync(authRequest.Id, user.Id)
.Returns((AuthRequest)null);
// Act
// Assert
var exception = await Assert.ThrowsAsync<NotFoundException>(
() => sutProvider.Sut.Get(authRequest.Id));
}
[Theory, BitAutoData]
public async Task GetById_ReturnsAuthRequest(
SutProvider<AuthRequestsController> sutProvider,
User user,
AuthRequest authRequest)
{
// Arrange
SetBaseServiceUri(sutProvider);
sutProvider.GetDependency<IUserService>()
.GetProperUserId(Arg.Any<ClaimsPrincipal>())
.Returns(user.Id);
sutProvider.GetDependency<IAuthRequestService>()
.GetAuthRequestAsync(authRequest.Id, user.Id)
.Returns(authRequest);
// Act
var result = await sutProvider.Sut.Get(authRequest.Id);
// Assert
Assert.NotNull(result);
Assert.IsType<AuthRequestResponseModel>(result);
}
[Theory, BitAutoData]
public async Task GetPending_ReturnsExpectedResult(
SutProvider<AuthRequestsController> sutProvider,
User user,
AuthRequest authRequest)
{
// Arrange
SetBaseServiceUri(sutProvider);
sutProvider.GetDependency<IUserService>()
.GetProperUserId(Arg.Any<ClaimsPrincipal>())
.Returns(user.Id);
sutProvider.GetDependency<IAuthRequestRepository>()
.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<ListResponseModel<AuthRequestResponseModel>>(result);
}
[Theory, BitAutoData]
public async Task GetResponseById_ThrowsNotFoundException(
SutProvider<AuthRequestsController> sutProvider,
AuthRequest authRequest)
{
// Arrange
sutProvider.GetDependency<IAuthRequestService>()
.GetValidatedAuthRequestAsync(authRequest.Id, authRequest.AccessCode)
.Returns((AuthRequest)null);
// Act
// Assert
var exception = await Assert.ThrowsAsync<NotFoundException>(
() => sutProvider.Sut.GetResponse(authRequest.Id, authRequest.AccessCode));
}
[Theory, BitAutoData]
public async Task GetResponseById_ReturnsAuthRequest(
SutProvider<AuthRequestsController> sutProvider,
AuthRequest authRequest)
{
// Arrange
SetBaseServiceUri(sutProvider);
sutProvider.GetDependency<IAuthRequestService>()
.GetValidatedAuthRequestAsync(authRequest.Id, authRequest.AccessCode)
.Returns(authRequest);
// Act
var result = await sutProvider.Sut.GetResponse(authRequest.Id, authRequest.AccessCode);
// Assert
Assert.NotNull(result);
Assert.IsType<AuthRequestResponseModel>(result);
}
[Theory, BitAutoData]
public async Task Post_AdminApprovalRequest_ThrowsBadRequestException(
SutProvider<AuthRequestsController> sutProvider,
AuthRequestCreateRequestModel authRequest)
{
// Arrange
authRequest.Type = AuthRequestType.AdminApproval;
// Act
// Assert
var exception = await Assert.ThrowsAsync<BadRequestException>(
() => 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<AuthRequestsController> sutProvider,
AuthRequestCreateRequestModel requestModel,
AuthRequest authRequest)
{
// Arrange
SetBaseServiceUri(sutProvider);
requestModel.Type = AuthRequestType.AuthenticateAndUnlock;
sutProvider.GetDependency<IAuthRequestService>()
.CreateAuthRequestAsync(requestModel)
.Returns(authRequest);
// Act
var result = await sutProvider.Sut.Post(requestModel);
// Assert
Assert.NotNull(result);
Assert.IsType<AuthRequestResponseModel>(result);
}
[Theory, BitAutoData]
public async Task PostAdminRequest_ReturnsAuthRequest(
SutProvider<AuthRequestsController> sutProvider,
AuthRequestCreateRequestModel requestModel,
AuthRequest authRequest)
{
// Arrange
SetBaseServiceUri(sutProvider);
requestModel.Type = AuthRequestType.AuthenticateAndUnlock;
sutProvider.GetDependency<IAuthRequestService>()
.CreateAuthRequestAsync(requestModel)
.Returns(authRequest);
// Act
var result = await sutProvider.Sut.PostAdminRequest(requestModel);
// Assert
Assert.NotNull(result);
Assert.IsType<AuthRequestResponseModel>(result);
}
[Theory, BitAutoData]
public async Task Put_ReturnsAuthRequest(
SutProvider<AuthRequestsController> sutProvider,
User user,
AuthRequestUpdateRequestModel requestModel,
AuthRequest authRequest)
{
// Arrange
SetBaseServiceUri(sutProvider);
sutProvider.GetDependency<IUserService>()
.GetProperUserId(Arg.Any<ClaimsPrincipal>())
.Returns(user.Id);
sutProvider.GetDependency<IAuthRequestService>()
.UpdateAuthRequestAsync(authRequest.Id, user.Id, requestModel)
.Returns(authRequest);
// Act
var result = await sutProvider.Sut
.Put(authRequest.Id, requestModel);
// Assert
Assert.NotNull(result);
Assert.IsType<AuthRequestResponseModel>(result);
}
private void SetBaseServiceUri(SutProvider<AuthRequestsController> sutProvider)
{
sutProvider.GetDependency<IGlobalSettings>()
.BaseServiceUri
.Vault
.Returns(_testGlobalSettingsBaseUri);
}
}