using Bit.Core.AdminConsole.Entities; using Bit.Core.AdminConsole.Enums; using Bit.Core.AdminConsole.Models.Data.Organizations.Policies; using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers; using Bit.Core.AdminConsole.OrganizationFeatures.Policies; using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements; using Bit.Core.AdminConsole.Services; using Bit.Core.Auth.Models.Business.Tokenables; using Bit.Core.Auth.UserFeatures.TwoFactorAuth.Interfaces; using Bit.Core.Billing.Enums; using Bit.Core.Entities; using Bit.Core.Enums; using Bit.Core.Exceptions; using Bit.Core.Models.Data; using Bit.Core.Repositories; using Bit.Core.Services; using Bit.Core.Tokens; using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture.Attributes; using Bit.Test.Common.Fakes; using NSubstitute; using Xunit; namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.Organizations; [SutProviderCustomize] public class InitPendingOrganizationCommandTests { private readonly IOrgUserInviteTokenableFactory _orgUserInviteTokenableFactory = Substitute.For(); private readonly IDataProtectorTokenFactory _orgUserInviteTokenDataFactory = new FakeDataProtectorTokenFactory(); [Theory, BitAutoData] public async Task Init_Organization_Success(User user, Guid orgId, Guid orgUserId, string publicKey, string privateKey, SutProvider sutProvider, Organization org, OrganizationUser orgUser) { var token = CreateToken(orgUser, orgUserId, sutProvider); org.PrivateKey = null; org.PublicKey = null; var organizationRepository = sutProvider.GetDependency(); organizationRepository.GetByIdAsync(orgId).Returns(org); var organizationServcie = sutProvider.GetDependency(); var collectionRepository = sutProvider.GetDependency(); await sutProvider.Sut.InitPendingOrganizationAsync(user, orgId, orgUserId, publicKey, privateKey, "", token); await organizationRepository.Received().GetByIdAsync(orgId); await organizationServcie.Received().UpdateAsync(org); await collectionRepository.DidNotReceiveWithAnyArgs().CreateAsync(default); } [Theory, BitAutoData] public async Task Init_Organization_With_CollectionName_Success(User user, Guid orgId, Guid orgUserId, string publicKey, string privateKey, SutProvider sutProvider, Organization org, string collectionName, OrganizationUser orgUser) { var token = CreateToken(orgUser, orgUserId, sutProvider); org.PrivateKey = null; org.PublicKey = null; org.Id = orgId; var organizationRepository = sutProvider.GetDependency(); organizationRepository.GetByIdAsync(orgId).Returns(org); var organizationServcie = sutProvider.GetDependency(); var collectionRepository = sutProvider.GetDependency(); await sutProvider.Sut.InitPendingOrganizationAsync(user, orgId, orgUserId, publicKey, privateKey, collectionName, token); await organizationRepository.Received().GetByIdAsync(orgId); await organizationServcie.Received().UpdateAsync(org); await collectionRepository.Received().CreateAsync( Arg.Any(), Arg.Is>(l => l == null), Arg.Is>(l => l.Any(i => i.Manage == true))); } [Theory, BitAutoData] public async Task Init_Organization_When_Organization_Is_Enabled(User user, Guid orgId, Guid orgUserId, string publicKey, string privateKey, SutProvider sutProvider, Organization org, OrganizationUser orgUser) { var token = CreateToken(orgUser, orgUserId, sutProvider); org.Enabled = true; var organizationRepository = sutProvider.GetDependency(); organizationRepository.GetByIdAsync(orgId).Returns(org); var exception = await Assert.ThrowsAsync(() => sutProvider.Sut.InitPendingOrganizationAsync(user, orgId, orgUserId, publicKey, privateKey, "", token)); Assert.Equal("Organization is already enabled.", exception.Message); } [Theory, BitAutoData] public async Task Init_Organization_When_Organization_Is_Not_Pending(User user, Guid orgId, Guid orgUserId, string publicKey, string privateKey, SutProvider sutProvider, Organization org, OrganizationUser orgUser) { var token = CreateToken(orgUser, orgUserId, sutProvider); org.Status = Enums.OrganizationStatusType.Created; var organizationRepository = sutProvider.GetDependency(); organizationRepository.GetByIdAsync(orgId).Returns(org); var exception = await Assert.ThrowsAsync(() => sutProvider.Sut.InitPendingOrganizationAsync(user, orgId, orgUserId, publicKey, privateKey, "", token)); Assert.Equal("Organization is not on a Pending status.", exception.Message); } [Theory, BitAutoData] public async Task Init_Organization_When_Organization_Has_Public_Key(User user, Guid orgId, Guid orgUserId, string publicKey, string privateKey, SutProvider sutProvider, Organization org, OrganizationUser orgUser) { var token = CreateToken(orgUser, orgUserId, sutProvider); org.PublicKey = publicKey; var organizationRepository = sutProvider.GetDependency(); organizationRepository.GetByIdAsync(orgId).Returns(org); var exception = await Assert.ThrowsAsync(() => sutProvider.Sut.InitPendingOrganizationAsync(user, orgId, orgUserId, publicKey, privateKey, "", token)); Assert.Equal("Organization already has a Public Key.", exception.Message); } [Theory, BitAutoData] public async Task Init_Organization_When_Organization_Has_Private_Key(User user, Guid orgId, Guid orgUserId, string publicKey, string privateKey, SutProvider sutProvider, Organization org, OrganizationUser orgUser) { var token = CreateToken(orgUser, orgUserId, sutProvider); org.PublicKey = null; org.PrivateKey = privateKey; org.Enabled = false; var organizationRepository = sutProvider.GetDependency(); organizationRepository.GetByIdAsync(orgId).Returns(org); var exception = await Assert.ThrowsAsync(() => sutProvider.Sut.InitPendingOrganizationAsync(user, orgId, orgUserId, publicKey, privateKey, "", token)); Assert.Equal("Organization already has a Private Key.", exception.Message); } public string CreateToken(OrganizationUser orgUser, Guid orgUserId, SutProvider sutProvider) { sutProvider.SetDependency(_orgUserInviteTokenDataFactory, "orgUserInviteTokenDataFactory"); sutProvider.Create(); _orgUserInviteTokenableFactory.CreateToken(orgUser).Returns(new OrgUserInviteTokenable(orgUser) { ExpirationDate = DateTime.UtcNow.Add(TimeSpan.FromDays(5)) }); var orgUserInviteTokenable = _orgUserInviteTokenableFactory.CreateToken(orgUser); var protectedToken = _orgUserInviteTokenDataFactory.Protect(orgUserInviteTokenable); sutProvider.GetDependency().GetByIdAsync(orgUserId).Returns(orgUser); return protectedToken; } [Theory, BitAutoData] public async Task InitPendingOrganizationVNextAsync_WithValidData_InitializesOrgAndConfirmsUser( User user, Guid orgId, Guid orgUserId, string publicKey, string privateKey, string userKey, SutProvider sutProvider, Organization org, OrganizationUser orgUser) { // Arrange orgUser.Email = user.Email; orgUser.OrganizationId = orgId; var token = CreateToken(orgUser, orgUserId, sutProvider); org.Id = orgId; org.Enabled = false; org.Status = OrganizationStatusType.Pending; org.PrivateKey = null; org.PublicKey = null; sutProvider.GetDependency() .GetByIdAsync(orgId) .Returns(org); sutProvider.GetDependency() .TwoFactorIsEnabledAsync(user) .Returns(true); var autoConfirmReq = new AutomaticUserConfirmationPolicyRequirement(new List()); var twoFactorReq = new RequireTwoFactorPolicyRequirement(new List()); sutProvider.GetDependency() .GetAsync(user.Id) .Returns(autoConfirmReq); sutProvider.GetDependency() .GetAsync(user.Id) .Returns(twoFactorReq); // Act var result = await sutProvider.Sut.InitPendingOrganizationVNextAsync( user, orgId, orgUserId, publicKey, privateKey, "", token, userKey); // Assert Assert.True(result.IsSuccess); Assert.True(org.Enabled); Assert.Equal(OrganizationStatusType.Created, org.Status); Assert.Equal(publicKey, org.PublicKey); Assert.Equal(OrganizationUserStatusType.Confirmed, orgUser.Status); Assert.Equal(user.Id, orgUser.UserId); Assert.Equal(userKey, orgUser.Key); Assert.Null(orgUser.Email); await sutProvider.GetDependency() .Received(1) .UpdateAsync(org); await sutProvider.GetDependency() .Received(1) .ReplaceAsync(orgUser); await sutProvider.GetDependency() .Received(1) .LogOrganizationUserEventAsync(orgUser, EventType.OrganizationUser_Confirmed); } [Theory, BitAutoData] public async Task InitPendingOrganizationVNextAsync_WithNullOrgUser_ReturnsOrganizationUserNotFoundError( User user, Guid orgId, Guid orgUserId, string publicKey, string privateKey, string userKey, SutProvider sutProvider) { // Arrange sutProvider.GetDependency() .GetByIdAsync(orgUserId) .Returns((OrganizationUser)null); // Act var result = await sutProvider.Sut.InitPendingOrganizationVNextAsync( user, orgId, orgUserId, publicKey, privateKey, "", "token", userKey); // Assert Assert.True(result.IsError); Assert.IsType(result.AsError); } [Theory, BitAutoData] public async Task InitPendingOrganizationVNextAsync_WithInvalidToken_ReturnsInvalidTokenError( User user, Guid orgId, Guid orgUserId, string publicKey, string privateKey, string userKey, SutProvider sutProvider, OrganizationUser orgUser) { // Arrange sutProvider.GetDependency() .GetByIdAsync(orgUserId) .Returns(orgUser); // Act var result = await sutProvider.Sut.InitPendingOrganizationVNextAsync( user, orgId, orgUserId, publicKey, privateKey, "", "invalid-token", userKey); // Assert Assert.True(result.IsError); Assert.IsType(result.AsError); } [Theory, BitAutoData] public async Task InitPendingOrganizationVNextAsync_WithEnabledOrg_ReturnsOrganizationAlreadyEnabledError( User user, Guid orgId, Guid orgUserId, string publicKey, string privateKey, string userKey, SutProvider sutProvider, Organization org, OrganizationUser orgUser) { // Arrange orgUser.Email = user.Email; orgUser.OrganizationId = orgId; var token = CreateToken(orgUser, orgUserId, sutProvider); org.Enabled = true; sutProvider.GetDependency() .GetByIdAsync(orgId) .Returns(org); // Act var result = await sutProvider.Sut.InitPendingOrganizationVNextAsync( user, orgId, orgUserId, publicKey, privateKey, "", token, userKey); // Assert Assert.True(result.IsError); Assert.IsType(result.AsError); } [Theory, BitAutoData] public async Task InitPendingOrganizationVNextAsync_WithNonPendingOrg_ReturnsOrganizationNotPendingError( User user, Guid orgId, Guid orgUserId, string publicKey, string privateKey, string userKey, SutProvider sutProvider, Organization org, OrganizationUser orgUser) { // Arrange orgUser.Email = user.Email; orgUser.OrganizationId = orgId; var token = CreateToken(orgUser, orgUserId, sutProvider); org.Enabled = false; org.Status = OrganizationStatusType.Created; sutProvider.GetDependency() .GetByIdAsync(orgId) .Returns(org); // Act var result = await sutProvider.Sut.InitPendingOrganizationVNextAsync( user, orgId, orgUserId, publicKey, privateKey, "", token, userKey); // Assert Assert.True(result.IsError); Assert.IsType(result.AsError); } [Theory, BitAutoData] public async Task InitPendingOrganizationVNextAsync_WithExistingKeys_ReturnsOrganizationHasKeysError( User user, Guid orgId, Guid orgUserId, string publicKey, string privateKey, string userKey, SutProvider sutProvider, Organization org, OrganizationUser orgUser) { // Arrange orgUser.Email = user.Email; orgUser.OrganizationId = orgId; var token = CreateToken(orgUser, orgUserId, sutProvider); org.Enabled = false; org.Status = OrganizationStatusType.Pending; org.PublicKey = "existing-key"; sutProvider.GetDependency() .GetByIdAsync(orgId) .Returns(org); // Act var result = await sutProvider.Sut.InitPendingOrganizationVNextAsync( user, orgId, orgUserId, publicKey, privateKey, "", token, userKey); // Assert Assert.True(result.IsError); Assert.IsType(result.AsError); } [Theory, BitAutoData] public async Task InitPendingOrganizationVNextAsync_WithNullOrganization_ReturnsOrganizationNotFoundError( User user, Guid orgId, Guid orgUserId, string publicKey, string privateKey, string userKey, SutProvider sutProvider, OrganizationUser orgUser) { // Arrange orgUser.Email = user.Email; orgUser.OrganizationId = orgId; var token = CreateToken(orgUser, orgUserId, sutProvider); sutProvider.GetDependency() .GetByIdAsync(orgId) .Returns((Organization)null); // Act var result = await sutProvider.Sut.InitPendingOrganizationVNextAsync( user, orgId, orgUserId, publicKey, privateKey, "", token, userKey); // Assert Assert.True(result.IsError); Assert.IsType(result.AsError); } [Theory, BitAutoData] public async Task InitPendingOrganizationVNextAsync_WithEmailMismatch_ReturnsEmailMismatchError( User user, Guid orgId, Guid orgUserId, string publicKey, string privateKey, string userKey, SutProvider sutProvider, Organization org, OrganizationUser orgUser) { // Arrange orgUser.Email = "different@example.com"; orgUser.OrganizationId = orgId; var token = CreateToken(orgUser, orgUserId, sutProvider); sutProvider.GetDependency() .GetByIdAsync(orgId) .Returns(org); // Act var result = await sutProvider.Sut.InitPendingOrganizationVNextAsync( user, orgId, orgUserId, publicKey, privateKey, "", token, userKey); // Assert Assert.True(result.IsError); Assert.IsType(result.AsError); } [Theory, BitAutoData] public async Task InitPendingOrganizationVNextAsync_WithCollectionName_CreatesDefaultCollection( User user, Guid orgId, Guid orgUserId, string publicKey, string privateKey, string userKey, string collectionName, SutProvider sutProvider, Organization org, OrganizationUser orgUser) { // Arrange orgUser.Email = user.Email; orgUser.OrganizationId = orgId; var token = CreateToken(orgUser, orgUserId, sutProvider); org.Id = orgId; org.Enabled = false; org.Status = OrganizationStatusType.Pending; org.PrivateKey = null; org.PublicKey = null; sutProvider.GetDependency() .GetByIdAsync(orgId) .Returns(org); sutProvider.GetDependency() .TwoFactorIsEnabledAsync(user) .Returns(true); var autoConfirmReq = new AutomaticUserConfirmationPolicyRequirement(new List()); var twoFactorReq = new RequireTwoFactorPolicyRequirement(new List()); sutProvider.GetDependency() .GetAsync(user.Id) .Returns(autoConfirmReq); sutProvider.GetDependency() .GetAsync(user.Id) .Returns(twoFactorReq); // Act var result = await sutProvider.Sut.InitPendingOrganizationVNextAsync( user, orgId, orgUserId, publicKey, privateKey, collectionName, token, userKey); // Assert Assert.True(result.IsSuccess); await sutProvider.GetDependency().Received().CreateAsync( Arg.Is(c => c.Name == collectionName && c.OrganizationId == orgId), Arg.Is>(l => l == null), Arg.Is>(l => l.Any(i => i.Manage == true))); } [Theory, BitAutoData] public async Task InitPendingOrganizationVNextAsync_WithTwoFactorRequired_UserDoesntHave_ReturnsTwoFactorRequiredError( User user, Guid orgId, Guid orgUserId, string publicKey, string privateKey, string userKey, SutProvider sutProvider, Organization org, OrganizationUser orgUser) { // Arrange orgUser.Email = user.Email; orgUser.OrganizationId = orgId; var token = CreateToken(orgUser, orgUserId, sutProvider); org.Enabled = false; org.Status = OrganizationStatusType.Pending; org.PrivateKey = null; org.PublicKey = null; sutProvider.GetDependency() .GetByIdAsync(orgId) .Returns(org); sutProvider.GetDependency() .TwoFactorIsEnabledAsync(user) .Returns(false); var autoConfirmReq = new AutomaticUserConfirmationPolicyRequirement(new List()); var twoFactorReq = new RequireTwoFactorPolicyRequirement( new List { new PolicyDetails { OrganizationId = orgId } }); sutProvider.GetDependency() .GetAsync(user.Id) .Returns(autoConfirmReq); sutProvider.GetDependency() .GetAsync(user.Id) .Returns(twoFactorReq); // Act var result = await sutProvider.Sut.InitPendingOrganizationVNextAsync( user, orgId, orgUserId, publicKey, privateKey, "", token, userKey); // Assert Assert.True(result.IsError); Assert.IsType(result.AsError); } [Theory, BitAutoData] public async Task InitPendingOrganizationVNextAsync_WithUserAlreadyAdminOfFreeOrg_ReturnsFreeOrgAdminLimitError( User user, Guid orgId, Guid orgUserId, string publicKey, string privateKey, string userKey, SutProvider sutProvider, Organization org, OrganizationUser orgUser) { // Arrange orgUser.Email = user.Email; orgUser.OrganizationId = orgId; orgUser.Type = OrganizationUserType.Owner; var token = CreateToken(orgUser, orgUserId, sutProvider); org.Enabled = false; org.Status = OrganizationStatusType.Pending; org.PrivateKey = null; org.PublicKey = null; org.PlanType = PlanType.Free; sutProvider.GetDependency() .GetByIdAsync(orgId) .Returns(org); sutProvider.GetDependency() .GetCountByFreeOrganizationAdminUserAsync(user.Id) .Returns(1); var autoConfirmReq = new AutomaticUserConfirmationPolicyRequirement(new List()); var twoFactorReq = new RequireTwoFactorPolicyRequirement(new List()); sutProvider.GetDependency() .GetAsync(user.Id) .Returns(autoConfirmReq); sutProvider.GetDependency() .GetAsync(user.Id) .Returns(twoFactorReq); // Act var result = await sutProvider.Sut.InitPendingOrganizationVNextAsync( user, orgId, orgUserId, publicKey, privateKey, "", token, userKey); // Assert Assert.True(result.IsError); Assert.IsType(result.AsError); } [Theory, BitAutoData] public async Task InitPendingOrganizationVNextAsync_WithAutomaticUserConfirmationPolicy_ReturnsSingleOrgPolicyViolationError( User user, Guid orgId, Guid orgUserId, Guid otherOrgId, string publicKey, string privateKey, string userKey, SutProvider sutProvider, Organization org, OrganizationUser orgUser) { // Arrange orgUser.Email = user.Email; orgUser.OrganizationId = orgId; var token = CreateToken(orgUser, orgUserId, sutProvider); org.Enabled = false; org.Status = OrganizationStatusType.Pending; org.PrivateKey = null; org.PublicKey = null; sutProvider.GetDependency().GetByIdAsync(orgId).Returns(org); // Enable AutomaticConfirmUsers feature flag sutProvider.GetDependency() .IsEnabled(FeatureFlagKeys.AutomaticConfirmUsers) .Returns(true); // User is subject to AutomaticUserConfirmation policy from another organization var autoConfirmReq = new AutomaticUserConfirmationPolicyRequirement( new List { new PolicyDetails { OrganizationId = otherOrgId } }); var twoFactorReq = new RequireTwoFactorPolicyRequirement(new List()); sutProvider.GetDependency() .GetAsync(user.Id) .Returns(autoConfirmReq); sutProvider.GetDependency() .GetAsync(user.Id) .Returns(twoFactorReq); // No legacy SingleOrg policy sutProvider.GetDependency() .AnyPoliciesApplicableToUserAsync(user.Id, PolicyType.SingleOrg).Returns(false); // Act var result = await sutProvider.Sut.InitPendingOrganizationVNextAsync( user, orgId, orgUserId, publicKey, privateKey, "", token, userKey); // Assert Assert.True(result.IsError); Assert.IsType(result.AsError); } [Theory, BitAutoData] public async Task InitPendingOrganizationVNextAsync_WithSingleOrgPolicy_ReturnsSingleOrgPolicyViolationError( User user, Guid orgId, Guid orgUserId, string publicKey, string privateKey, string userKey, SutProvider sutProvider, Organization org, OrganizationUser orgUser) { // Arrange orgUser.Email = user.Email; orgUser.OrganizationId = orgId; var token = CreateToken(orgUser, orgUserId, sutProvider); org.Enabled = false; org.Status = OrganizationStatusType.Pending; org.PrivateKey = null; org.PublicKey = null; sutProvider.GetDependency().GetByIdAsync(orgId).Returns(org); var autoConfirmReq = new AutomaticUserConfirmationPolicyRequirement(new List()); var twoFactorReq = new RequireTwoFactorPolicyRequirement(new List()); sutProvider.GetDependency() .GetAsync(user.Id) .Returns(autoConfirmReq); sutProvider.GetDependency() .GetAsync(user.Id) .Returns(twoFactorReq); // User is subject to SingleOrg policy from another organization sutProvider.GetDependency() .AnyPoliciesApplicableToUserAsync(user.Id, PolicyType.SingleOrg) .Returns(true); // Act var result = await sutProvider.Sut.InitPendingOrganizationVNextAsync( user, orgId, orgUserId, publicKey, privateKey, "", token, userKey); // Assert Assert.True(result.IsError); Assert.IsType(result.AsError); } [Theory, BitAutoData] public async Task InitPendingOrganizationVNextAsync_WithMismatchedOrganizationId_ReturnsOrganizationMismatchError( User user, Guid orgId, Guid differentOrgId, Guid orgUserId, string publicKey, string privateKey, string userKey, SutProvider sutProvider, Organization org, OrganizationUser orgUser) { // Arrange orgUser.Email = user.Email; orgUser.OrganizationId = differentOrgId; var token = CreateToken(orgUser, orgUserId, sutProvider); org.Status = OrganizationStatusType.Pending; org.Enabled = false; org.PrivateKey = null; org.PublicKey = null; sutProvider.GetDependency() .GetByIdAsync(orgId) .Returns(org); // Act var result = await sutProvider.Sut.InitPendingOrganizationVNextAsync( user, orgId, orgUserId, publicKey, privateKey, "", token, userKey); // Assert Assert.True(result.IsError); Assert.IsType(result.AsError); } }