using System.Security.Claims; using Bit.Api.AdminConsole.Controllers; using Bit.Api.AdminConsole.Models.Request.Organizations; using Bit.Api.Auth.Models.Request.Accounts; using Bit.Api.Vault.AuthorizationHandlers.Collections; 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.Interfaces; using Bit.Core.AdminConsole.Repositories; using Bit.Core.Auth.Entities; using Bit.Core.Auth.Repositories; using Bit.Core.Context; using Bit.Core.Entities; using Bit.Core.Enums; using Bit.Core.Exceptions; using Bit.Core.Models.Business; using Bit.Core.Models.Data; using Bit.Core.Models.Data.Organizations; using Bit.Core.Models.Data.Organizations.OrganizationUsers; using Bit.Core.OrganizationFeatures.OrganizationUsers.Interfaces; using Bit.Core.Repositories; using Bit.Core.Services; using Bit.Core.Utilities; using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture.Attributes; using Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces; using Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Requests; using Microsoft.AspNetCore.Authorization; using NSubstitute; using Xunit; namespace Bit.Api.Test.AdminConsole.Controllers; [ControllerCustomize(typeof(OrganizationUsersController))] [SutProviderCustomize] public class OrganizationUsersControllerTests { [Theory] [BitAutoData] public async Task PutResetPasswordEnrollment_InvitedUser_AcceptsInvite(Guid orgId, Guid userId, OrganizationUserResetPasswordEnrollmentRequestModel model, User user, OrganizationUser orgUser, SutProvider sutProvider) { orgUser.Status = Core.Enums.OrganizationUserStatusType.Invited; sutProvider.GetDependency().GetUserByPrincipalAsync(default).ReturnsForAnyArgs(user); sutProvider.GetDependency().VerifySecretAsync(default, default).ReturnsForAnyArgs(true); sutProvider.GetDependency().GetByOrganizationAsync(default, default).ReturnsForAnyArgs(orgUser); await sutProvider.Sut.PutResetPasswordEnrollment(orgId, userId, model); await sutProvider.GetDependency().Received(1).AcceptOrgUserByOrgIdAsync(orgId, user, sutProvider.GetDependency()); } [Theory] [BitAutoData] public async Task PutResetPasswordEnrollment_ConfirmedUser_AcceptsInvite(Guid orgId, Guid userId, OrganizationUserResetPasswordEnrollmentRequestModel model, User user, OrganizationUser orgUser, SutProvider sutProvider) { orgUser.Status = Core.Enums.OrganizationUserStatusType.Confirmed; sutProvider.GetDependency().GetUserByPrincipalAsync(default).ReturnsForAnyArgs(user); sutProvider.GetDependency().VerifySecretAsync(default, default).ReturnsForAnyArgs(true); sutProvider.GetDependency().GetByOrganizationAsync(default, default).ReturnsForAnyArgs(orgUser); await sutProvider.Sut.PutResetPasswordEnrollment(orgId, userId, model); await sutProvider.GetDependency().Received(0).AcceptOrgUserByOrgIdAsync(orgId, user, sutProvider.GetDependency()); } [Theory] [BitAutoData] public async Task PutResetPasswordEnrollment_PasswordValidationFails_Throws(Guid orgId, Guid userId, OrganizationUserResetPasswordEnrollmentRequestModel model, User user, SutProvider sutProvider, OrganizationUser orgUser) { orgUser.Status = OrganizationUserStatusType.Confirmed; model.MasterPasswordHash = "NotThePassword"; sutProvider.GetDependency().GetUserByPrincipalAsync(default).ReturnsForAnyArgs(user); sutProvider.GetDependency().GetByOrganizationIdAsync(default).ReturnsForAnyArgs((SsoConfig)null); await Assert.ThrowsAsync(async () => await sutProvider.Sut.PutResetPasswordEnrollment(orgId, userId, model)); } [Theory] [BitAutoData] public async Task PutResetPasswordEnrollment_PasswordValidationPasses_Continues(Guid orgId, Guid userId, OrganizationUserResetPasswordEnrollmentRequestModel model, User user, OrganizationUser orgUser, SutProvider sutProvider) { sutProvider.GetDependency().GetUserByPrincipalAsync(default).ReturnsForAnyArgs(user); sutProvider.GetDependency().VerifySecretAsync(user, model.MasterPasswordHash).Returns(true); sutProvider.GetDependency().GetByOrganizationIdAsync(default).ReturnsForAnyArgs((SsoConfig)null); sutProvider.GetDependency().GetByOrganizationAsync(default, default).ReturnsForAnyArgs(orgUser); await sutProvider.Sut.PutResetPasswordEnrollment(orgId, userId, model); await sutProvider.GetDependency().Received(1).UpdateUserResetPasswordEnrollmentAsync( orgId, userId, model.ResetPasswordKey, user.Id ); } [Theory] [BitAutoData] public async Task Accept_RequiresKnownUser(Guid orgId, Guid orgUserId, OrganizationUserAcceptRequestModel model, SutProvider sutProvider) { sutProvider.GetDependency().GetUserByPrincipalAsync(default).ReturnsForAnyArgs((User)null); await Assert.ThrowsAsync(() => sutProvider.Sut.Accept(orgId, orgUserId, model)); } [Theory] [BitAutoData] public async Task Accept_NoMasterPasswordReset(Guid orgId, Guid orgUserId, OrganizationUserAcceptRequestModel model, User user, SutProvider sutProvider) { sutProvider.GetDependency().GetUserByPrincipalAsync(default).ReturnsForAnyArgs(user); await sutProvider.Sut.Accept(orgId, orgUserId, model); await sutProvider.GetDependency().Received(1) .AcceptOrgUserByEmailTokenAsync(orgUserId, user, model.Token, sutProvider.GetDependency()); await sutProvider.GetDependency().DidNotReceiveWithAnyArgs() .UpdateUserResetPasswordEnrollmentAsync(default, default, default, default); } [Theory] [BitAutoData] public async Task Accept_RequireMasterPasswordReset(Guid orgId, Guid orgUserId, OrganizationUserAcceptRequestModel model, User user, SutProvider sutProvider) { var policy = new Policy { Enabled = true, Data = CoreHelpers.ClassToJsonData(new ResetPasswordDataModel { AutoEnrollEnabled = true, }), }; sutProvider.GetDependency().GetUserByPrincipalAsync(default).ReturnsForAnyArgs(user); sutProvider.GetDependency().GetByOrganizationIdTypeAsync(orgId, PolicyType.ResetPassword).Returns(policy); await sutProvider.Sut.Accept(orgId, orgUserId, model); await sutProvider.GetDependency().Received(1) .AcceptOrgUserByEmailTokenAsync(orgUserId, user, model.Token, sutProvider.GetDependency()); await sutProvider.GetDependency().Received(1) .UpdateUserResetPasswordEnrollmentAsync(orgId, user.Id, model.ResetPasswordKey, user.Id); } [Theory] [BitAutoData] public async Task Invite_Success(OrganizationAbility organizationAbility, OrganizationUserInviteRequestModel model, Guid userId, SutProvider sutProvider) { sutProvider.GetDependency().ManageUsers(organizationAbility.Id).Returns(true); sutProvider.GetDependency().GetOrganizationAbilityAsync(organizationAbility.Id) .Returns(organizationAbility); sutProvider.GetDependency() .AuthorizeAsync(Arg.Any(), Arg.Any>(), Arg.Is>(reqs => reqs.Contains(BulkCollectionOperations.ModifyUserAccess))) .Returns(AuthorizationResult.Success()); sutProvider.GetDependency().GetProperUserId(Arg.Any()).Returns(userId); await sutProvider.Sut.Invite(organizationAbility.Id, model); await sutProvider.GetDependency().Received(1).InviteUsersAsync(organizationAbility.Id, userId, systemUser: null, Arg.Is>(invites => invites.Count() == 1 && invites.First().Item1.Emails.SequenceEqual(model.Emails) && invites.First().Item1.Type == model.Type && invites.First().Item1.AccessSecretsManager == model.AccessSecretsManager)); } [Theory] [BitAutoData] public async Task Invite_NotAuthorizedToGiveAccessToCollections_Throws(OrganizationAbility organizationAbility, OrganizationUserInviteRequestModel model, Guid userId, SutProvider sutProvider) { sutProvider.GetDependency().ManageUsers(organizationAbility.Id).Returns(true); sutProvider.GetDependency().GetOrganizationAbilityAsync(organizationAbility.Id) .Returns(organizationAbility); sutProvider.GetDependency() .AuthorizeAsync(Arg.Any(), Arg.Any>(), Arg.Is>(reqs => reqs.Contains(BulkCollectionOperations.ModifyUserAccess))) .Returns(AuthorizationResult.Failed()); sutProvider.GetDependency().GetProperUserId(Arg.Any()).Returns(userId); await Assert.ThrowsAsync(() => sutProvider.Sut.Invite(organizationAbility.Id, model)); } [Theory] [BitAutoData(true)] [BitAutoData(false)] public async Task Get_ReturnsUser( bool accountDeprovisioningEnabled, OrganizationUserUserDetails organizationUser, ICollection collections, SutProvider sutProvider) { organizationUser.Permissions = null; sutProvider.GetDependency() .IsEnabled(FeatureFlagKeys.AccountDeprovisioning) .Returns(accountDeprovisioningEnabled); sutProvider.GetDependency() .ManageUsers(organizationUser.OrganizationId) .Returns(true); sutProvider.GetDependency() .GetDetailsByIdWithCollectionsAsync(organizationUser.Id) .Returns((organizationUser, collections)); sutProvider.GetDependency() .GetUsersOrganizationManagementStatusAsync(organizationUser.OrganizationId, Arg.Is>(ids => ids.Contains(organizationUser.Id))) .Returns(new Dictionary { { organizationUser.Id, true } }); var response = await sutProvider.Sut.Get(organizationUser.Id, false); Assert.Equal(organizationUser.Id, response.Id); Assert.Equal(accountDeprovisioningEnabled, response.ManagedByOrganization); } [Theory] [BitAutoData] public async Task GetMany_ReturnsUsers( ICollection organizationUsers, OrganizationAbility organizationAbility, SutProvider sutProvider) { GetMany_Setup(organizationAbility, organizationUsers, sutProvider); var response = await sutProvider.Sut.Get(organizationAbility.Id, false, false); Assert.True(response.Data.All(r => organizationUsers.Any(ou => ou.Id == r.Id))); } [Theory] [BitAutoData] public async Task GetAccountRecoveryDetails_ReturnsDetails( Guid organizationId, OrganizationUserBulkRequestModel bulkRequestModel, ICollection resetPasswordDetails, SutProvider sutProvider) { sutProvider.GetDependency().ManageResetPassword(organizationId).Returns(true); sutProvider.GetDependency() .GetManyAccountRecoveryDetailsByOrganizationUserAsync(organizationId, bulkRequestModel.Ids) .Returns(resetPasswordDetails); var response = await sutProvider.Sut.GetAccountRecoveryDetails(organizationId, bulkRequestModel); Assert.Equal(resetPasswordDetails.Count, response.Data.Count()); Assert.True(response.Data.All(r => resetPasswordDetails.Any(ou => ou.OrganizationUserId == r.OrganizationUserId && ou.Kdf == r.Kdf && ou.KdfIterations == r.KdfIterations && ou.KdfMemory == r.KdfMemory && ou.KdfParallelism == r.KdfParallelism && ou.ResetPasswordKey == r.ResetPasswordKey && ou.EncryptedPrivateKey == r.EncryptedPrivateKey))); } [Theory] [BitAutoData] public async Task GetAccountRecoveryDetails_WithoutManageResetPasswordPermission_Throws( Guid organizationId, OrganizationUserBulkRequestModel bulkRequestModel, SutProvider sutProvider) { sutProvider.GetDependency().ManageResetPassword(organizationId).Returns(false); await Assert.ThrowsAsync(async () => await sutProvider.Sut.GetAccountRecoveryDetails(organizationId, bulkRequestModel)); } [Theory] [BitAutoData] public async Task DeleteAccount_WhenUserCanManageUsers_Success( Guid orgId, Guid id, SecretVerificationRequestModel model, User currentUser, SutProvider sutProvider) { sutProvider.GetDependency().ManageUsers(orgId).Returns(true); sutProvider.GetDependency().GetUserByPrincipalAsync(default).ReturnsForAnyArgs(currentUser); sutProvider.GetDependency().VerifySecretAsync(currentUser, model.Secret).Returns(true); await sutProvider.Sut.DeleteAccount(orgId, id, model); await sutProvider.GetDependency() .Received(1) .DeleteUserAsync(orgId, id, currentUser.Id); } [Theory] [BitAutoData] public async Task DeleteAccount_WhenUserCannotManageUsers_ThrowsNotFoundException( Guid orgId, Guid id, SecretVerificationRequestModel model, SutProvider sutProvider) { sutProvider.GetDependency().ManageUsers(orgId).Returns(false); await Assert.ThrowsAsync(() => sutProvider.Sut.DeleteAccount(orgId, id, model)); } [Theory] [BitAutoData] public async Task DeleteAccount_WhenCurrentUserNotFound_ThrowsUnauthorizedAccessException( Guid orgId, Guid id, SecretVerificationRequestModel model, SutProvider sutProvider) { sutProvider.GetDependency().ManageUsers(orgId).Returns(true); sutProvider.GetDependency().GetUserByPrincipalAsync(default).ReturnsForAnyArgs((User)null); await Assert.ThrowsAsync(() => sutProvider.Sut.DeleteAccount(orgId, id, model)); } [Theory] [BitAutoData] public async Task DeleteAccount_WhenSecretVerificationFails_ThrowsBadRequestException( Guid orgId, Guid id, SecretVerificationRequestModel model, User currentUser, SutProvider sutProvider) { sutProvider.GetDependency().ManageUsers(orgId).Returns(true); sutProvider.GetDependency().GetUserByPrincipalAsync(default).ReturnsForAnyArgs(currentUser); sutProvider.GetDependency().VerifySecretAsync(currentUser, model.Secret).Returns(false); await Assert.ThrowsAsync(() => sutProvider.Sut.DeleteAccount(orgId, id, model)); } [Theory] [BitAutoData] public async Task BulkDeleteAccount_WhenUserCanManageUsers_Success( Guid orgId, SecureOrganizationUserBulkRequestModel model, User currentUser, List<(Guid, string)> deleteResults, SutProvider sutProvider) { sutProvider.GetDependency().ManageUsers(orgId).Returns(true); sutProvider.GetDependency().GetUserByPrincipalAsync(default).ReturnsForAnyArgs(currentUser); sutProvider.GetDependency().VerifySecretAsync(currentUser, model.Secret).Returns(true); sutProvider.GetDependency() .DeleteManyUsersAsync(orgId, model.Ids, currentUser.Id) .Returns(deleteResults); var response = await sutProvider.Sut.BulkDeleteAccount(orgId, model); Assert.Equal(deleteResults.Count, response.Data.Count()); Assert.True(response.Data.All(r => deleteResults.Any(res => res.Item1 == r.Id && res.Item2 == r.Error))); await sutProvider.GetDependency() .Received(1) .DeleteManyUsersAsync(orgId, model.Ids, currentUser.Id); } [Theory] [BitAutoData] public async Task BulkDeleteAccount_WhenUserCannotManageUsers_ThrowsNotFoundException( Guid orgId, SecureOrganizationUserBulkRequestModel model, SutProvider sutProvider) { sutProvider.GetDependency().ManageUsers(orgId).Returns(false); await Assert.ThrowsAsync(() => sutProvider.Sut.BulkDeleteAccount(orgId, model)); } [Theory] [BitAutoData] public async Task BulkDeleteAccount_WhenCurrentUserNotFound_ThrowsUnauthorizedAccessException( Guid orgId, SecureOrganizationUserBulkRequestModel model, SutProvider sutProvider) { sutProvider.GetDependency().ManageUsers(orgId).Returns(true); sutProvider.GetDependency().GetUserByPrincipalAsync(default).ReturnsForAnyArgs((User)null); await Assert.ThrowsAsync(() => sutProvider.Sut.BulkDeleteAccount(orgId, model)); } [Theory] [BitAutoData] public async Task BulkDeleteAccount_WhenSecretVerificationFails_ThrowsBadRequestException( Guid orgId, SecureOrganizationUserBulkRequestModel model, User currentUser, SutProvider sutProvider) { sutProvider.GetDependency().ManageUsers(orgId).Returns(true); sutProvider.GetDependency().GetUserByPrincipalAsync(default).ReturnsForAnyArgs(currentUser); sutProvider.GetDependency().VerifySecretAsync(currentUser, model.Secret).Returns(false); await Assert.ThrowsAsync(() => sutProvider.Sut.BulkDeleteAccount(orgId, model)); } private void GetMany_Setup(OrganizationAbility organizationAbility, ICollection organizationUsers, SutProvider sutProvider) { foreach (var orgUser in organizationUsers) { orgUser.Permissions = null; } sutProvider.GetDependency().GetOrganizationAbilityAsync(organizationAbility.Id) .Returns(organizationAbility); sutProvider.GetDependency().GetOrganizationUserUserDetails(Arg.Any()).Returns(organizationUsers); sutProvider.GetDependency().AuthorizeAsync( user: Arg.Any(), resource: Arg.Any(), requirements: Arg.Any>()) .Returns(AuthorizationResult.Success()); sutProvider.GetDependency() .GetManyDetailsByOrganizationAsync(organizationAbility.Id, Arg.Any(), Arg.Any()) .Returns(organizationUsers); } }