1
0
mirror of https://github.com/bitwarden/server.git synced 2025-04-05 21:18:13 -05:00

Consolidated account recovery into 1 handler. Both use the same method.

This commit is contained in:
jrmccannon 2025-03-17 08:57:08 -05:00
parent 8aa8e0499c
commit 2c70b3a903
No known key found for this signature in database
GPG Key ID: CF03F3DB01CE96A6
7 changed files with 129 additions and 64 deletions

View File

@ -7,9 +7,9 @@ using Bit.Core;
using Bit.Core.AdminConsole.Enums;
using Bit.Core.AdminConsole.Models.Data.Organizations.Policies;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Authorization;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Authorization.OrganizationUserAccountRecoveryDetails;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Authorization.OrganizationUserDetails;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Authorization.OrganizationUserGroups;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Authorization.OrganizationUsersResetPasswordDetails;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces;
using Bit.Core.AdminConsole.OrganizationFeatures.Shared.Authorization;
using Bit.Core.AdminConsole.Repositories;
@ -214,7 +214,7 @@ public class OrganizationUsersController : Controller
{
var authResult = await _authorizationService.AuthorizeAsync(User,
new OrganizationScope(orgId),
[OrganizationUsersResetPasswordDetailsOperations.Read]);
[OrganizationUsersAccountRecoveryDetailsOperations.Read]);
if (authResult.Succeeded is false)
{
@ -248,8 +248,11 @@ public class OrganizationUsersController : Controller
[HttpPost("account-recovery-details")]
public async Task<ListResponseModel<OrganizationUserResetPasswordDetailsResponseModel>> GetAccountRecoveryDetails(Guid orgId, [FromBody] OrganizationUserBulkRequestModel model)
{
// Make sure the calling user can reset passwords for this org
if (!await _currentContext.ManageResetPassword(orgId))
var authResult = await _authorizationService.AuthorizeAsync(User,
new OrganizationScope(orgId),
[OrganizationUsersAccountRecoveryDetailsOperations.ReadAll]);
if (authResult.Succeeded is false)
{
throw new NotFoundException();
}

View File

@ -2,17 +2,19 @@
using Bit.Core.Context;
using Microsoft.AspNetCore.Authorization;
namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Authorization.OrganizationUsersResetPasswordDetails;
namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Authorization.
OrganizationUserAccountRecoveryDetails;
public class OrganizationUserResetPasswordDetailsAuthorizationHandler(ICurrentContext currentContext)
: AuthorizationHandler<OrganizationUsersResetPasswordDetailsOperationRequirement, OrganizationScope>
public class OrganizationUserAccountRecoveryDetailsAuthorizationHandler(ICurrentContext currentContext)
: AuthorizationHandler<OrganizationUsersAccountRecoveryDetailsOperationRequirement, OrganizationScope>
{
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context,
OrganizationUsersResetPasswordDetailsOperationRequirement requirement, OrganizationScope resource)
OrganizationUsersAccountRecoveryDetailsOperationRequirement requirement, OrganizationScope resource)
{
var authorized = requirement switch
{
not null when requirement.Name == nameof(OrganizationUsersResetPasswordDetailsOperations.Read) =>
not null when requirement.Name is nameof(OrganizationUsersAccountRecoveryDetailsOperations.Read) or
nameof(OrganizationUsersAccountRecoveryDetailsOperations.ReadAll) =>
await currentContext.ManageResetPassword(resource),
_ => false
};

View File

@ -0,0 +1,11 @@
using Microsoft.AspNetCore.Authorization.Infrastructure;
namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Authorization.OrganizationUserAccountRecoveryDetails;
public class OrganizationUsersAccountRecoveryDetailsOperationRequirement : OperationAuthorizationRequirement;
public static class OrganizationUsersAccountRecoveryDetailsOperations
{
public static readonly OrganizationUsersAccountRecoveryDetailsOperationRequirement Read = new() { Name = nameof(Read) };
public static readonly OrganizationUsersAccountRecoveryDetailsOperationRequirement ReadAll = new() { Name = nameof(ReadAll) };
}

View File

@ -12,9 +12,9 @@ using Bit.Core.AdminConsole.OrganizationFeatures.Organizations;
using Bit.Core.AdminConsole.OrganizationFeatures.Organizations.Interfaces;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Authorization;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Authorization.OrganizationUserAccountRecoveryDetails;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Authorization.OrganizationUserDetails;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Authorization.OrganizationUserGroups;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Authorization.OrganizationUsersResetPasswordDetails;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces;
using Bit.Core.Models.Business.Tokenables;
using Bit.Core.OrganizationFeatures.OrganizationCollections;
@ -174,7 +174,7 @@ public static class OrganizationServiceCollectionExtensions
services.AddScoped<IAuthorizationHandler, OrganizationUserUserDetailsAuthorizationHandler>();
services.AddScoped<IAuthorizationHandler, OrganizationUserDetailsAuthorizationHandler>();
services.AddScoped<IAuthorizationHandler, OrganizationUserGroupsAuthorizationHandler>();
services.AddScoped<IAuthorizationHandler, OrganizationUserResetPasswordDetailsAuthorizationHandler>();
services.AddScoped<IAuthorizationHandler, OrganizationUserAccountRecoveryDetailsAuthorizationHandler>();
services.AddScoped<IHasConfirmedOwnersExceptQuery, HasConfirmedOwnersExceptQuery>();
}

View File

@ -6,6 +6,7 @@ using Bit.Core;
using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.Enums;
using Bit.Core.AdminConsole.Models.Data.Organizations.Policies;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Authorization.OrganizationUserAccountRecoveryDetails;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Authorization.OrganizationUserDetails;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces;
using Bit.Core.AdminConsole.OrganizationFeatures.Shared.Authorization;
@ -294,7 +295,13 @@ public class OrganizationUsersControllerTests
ICollection<OrganizationUserResetPasswordDetails> resetPasswordDetails,
SutProvider<OrganizationUsersController> sutProvider)
{
sutProvider.GetDependency<ICurrentContext>().ManageResetPassword(organizationId).Returns(true);
sutProvider.GetDependency<IAuthorizationService>()
.AuthorizeAsync(
user: Arg.Any<ClaimsPrincipal>(),
resource: Arg.Is<OrganizationScope>(x => x == organizationId),
requirements: Arg.Is<IEnumerable<IAuthorizationRequirement>>(x =>
x.Any(y => y == OrganizationUsersAccountRecoveryDetailsOperations.ReadAll)))
.Returns(AuthorizationResult.Success());
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetManyAccountRecoveryDetailsByOrganizationUserAsync(organizationId, bulkRequestModel.Ids)
.Returns(resetPasswordDetails);
@ -320,7 +327,13 @@ public class OrganizationUsersControllerTests
OrganizationUserBulkRequestModel bulkRequestModel,
SutProvider<OrganizationUsersController> sutProvider)
{
sutProvider.GetDependency<ICurrentContext>().ManageResetPassword(organizationId).Returns(false);
sutProvider.GetDependency<IAuthorizationService>()
.AuthorizeAsync(
user: Arg.Any<ClaimsPrincipal>(),
resource: Arg.Is<OrganizationScope>(x => x == organizationId),
requirements: Arg.Is<IEnumerable<IAuthorizationRequirement>>(x =>
x.Any(y => y == OrganizationUsersAccountRecoveryDetailsOperations.ReadAll)))
.Returns(AuthorizationResult.Failed());
await Assert.ThrowsAsync<NotFoundException>(async () => await sutProvider.Sut.GetAccountRecoveryDetails(organizationId, bulkRequestModel));
}

View File

@ -0,0 +1,87 @@
using System.Security.Claims;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Authorization.OrganizationUserAccountRecoveryDetails;
using Bit.Core.AdminConsole.OrganizationFeatures.Shared.Authorization;
using Bit.Core.Context;
using Bit.Test.Common.AutoFixture;
using Bit.Test.Common.AutoFixture.Attributes;
using Microsoft.AspNetCore.Authorization;
using NSubstitute;
using Xunit;
namespace Bit.Core.Test.AdminConsole.Authorization;
[SutProviderCustomize]
public class OrganizationUserAccountRecoveryDetailsAuthorizationHandlerTests
{
[Theory]
[BitAutoData]
public async Task ReadAll_UserCanManageResetPassword_ShouldReturnSuccess(
CurrentContextOrganization contextOrganization,
SutProvider<OrganizationUserAccountRecoveryDetailsAuthorizationHandler> sutProvider)
{
sutProvider.GetDependency<ICurrentContext>().ManageResetPassword(contextOrganization.Id).Returns(true);
var context = new AuthorizationHandlerContext(
[OrganizationUsersAccountRecoveryDetailsOperations.ReadAll],
new ClaimsPrincipal(),
new OrganizationScope(contextOrganization.Id));
await sutProvider.Sut.HandleAsync(context);
Assert.True(context.HasSucceeded);
}
[Theory]
[BitAutoData]
public async Task ReadAll_UserCannotManageResetPassword_ShouldReturnFailure(
CurrentContextOrganization contextOrganization,
SutProvider<OrganizationUserAccountRecoveryDetailsAuthorizationHandler> sutProvider)
{
sutProvider.GetDependency<ICurrentContext>().ManageResetPassword(contextOrganization.Id).Returns(false);
var context = new AuthorizationHandlerContext(
[OrganizationUsersAccountRecoveryDetailsOperations.ReadAll],
new ClaimsPrincipal(),
new OrganizationScope(contextOrganization.Id));
await sutProvider.Sut.HandleAsync(context);
Assert.True(context.HasFailed);
}
[Theory]
[BitAutoData]
public async Task Read_UserCanManageResetPassword_ShouldReturnSuccess(
CurrentContextOrganization contextOrganization,
SutProvider<OrganizationUserAccountRecoveryDetailsAuthorizationHandler> sutProvider)
{
sutProvider.GetDependency<ICurrentContext>().ManageResetPassword(contextOrganization.Id).Returns(true);
var context = new AuthorizationHandlerContext(
[OrganizationUsersAccountRecoveryDetailsOperations.Read],
new ClaimsPrincipal(),
new OrganizationScope(contextOrganization.Id));
await sutProvider.Sut.HandleAsync(context);
Assert.True(context.HasSucceeded);
}
[Theory]
[BitAutoData]
public async Task Read_UserCannotManageResetPassword_ShouldReturnFailure(
CurrentContextOrganization contextOrganization,
SutProvider<OrganizationUserAccountRecoveryDetailsAuthorizationHandler> sutProvider)
{
sutProvider.GetDependency<ICurrentContext>().ManageResetPassword(contextOrganization.Id).Returns(false);
var context = new AuthorizationHandlerContext(
[OrganizationUsersAccountRecoveryDetailsOperations.Read],
new ClaimsPrincipal(),
new OrganizationScope(contextOrganization.Id));
await sutProvider.Sut.HandleAsync(context);
Assert.True(context.HasFailed);
}
}

View File

@ -1,51 +0,0 @@
using System.Security.Claims;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Authorization.OrganizationUsersResetPasswordDetails;
using Bit.Core.AdminConsole.OrganizationFeatures.Shared.Authorization;
using Bit.Core.Context;
using Bit.Test.Common.AutoFixture;
using Bit.Test.Common.AutoFixture.Attributes;
using Microsoft.AspNetCore.Authorization;
using NSubstitute;
using Xunit;
namespace Bit.Core.Test.AdminConsole.Authorization;
[SutProviderCustomize]
public class OrganizationUserResetPasswordDetailsAuthorizationHandlerTests
{
[Theory]
[BitAutoData]
public async Task Read_UserCanManageResetPassword_ShouldReturnSuccess(
CurrentContextOrganization contextOrganization,
SutProvider<OrganizationUserResetPasswordDetailsAuthorizationHandler> sutProvider)
{
sutProvider.GetDependency<ICurrentContext>().ManageResetPassword(contextOrganization.Id).Returns(true);
var context = new AuthorizationHandlerContext(
[OrganizationUsersResetPasswordDetailsOperations.Read],
new ClaimsPrincipal(),
new OrganizationScope(contextOrganization.Id));
await sutProvider.Sut.HandleAsync(context);
Assert.True(context.HasSucceeded);
}
[Theory]
[BitAutoData]
public async Task Read_UserCannotManageResetPassword_ShouldReturnFailure(
CurrentContextOrganization contextOrganization,
SutProvider<OrganizationUserResetPasswordDetailsAuthorizationHandler> sutProvider)
{
sutProvider.GetDependency<ICurrentContext>().ManageResetPassword(contextOrganization.Id).Returns(false);
var context = new AuthorizationHandlerContext(
[OrganizationUsersResetPasswordDetailsOperations.Read],
new ClaimsPrincipal(),
new OrganizationScope(contextOrganization.Id));
await sutProvider.Sut.HandleAsync(context);
Assert.True(context.HasFailed);
}
}