mirror of
https://github.com/bitwarden/server
synced 2026-01-04 09:33:40 +00:00
Merge branch 'main' of github.com:bitwarden/server into arch/seeder-sdk
# Conflicts: # .gitignore # bitwarden-server.sln
This commit is contained in:
@@ -0,0 +1,477 @@
|
||||
using System.Net;
|
||||
using Bit.Api.AdminConsole.Models.Request.Organizations;
|
||||
using Bit.Api.AdminConsole.Models.Response.Organizations;
|
||||
using Bit.Api.IntegrationTest.Factories;
|
||||
using Bit.Api.IntegrationTest.Helpers;
|
||||
using Bit.Api.Models.Request;
|
||||
using Bit.Api.Models.Response;
|
||||
using Bit.Core;
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.DeleteClaimedAccount;
|
||||
using Bit.Core.AdminConsole.Repositories;
|
||||
using Bit.Core.Billing.Enums;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Models.Data;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Api.IntegrationTest.AdminConsole.Controllers;
|
||||
|
||||
public class OrganizationUserControllerTests : IClassFixture<ApiApplicationFactory>, IAsyncLifetime
|
||||
{
|
||||
private static readonly string _mockEncryptedString =
|
||||
"2.AOs41Hd8OQiCPXjyJKCiDA==|O6OHgt2U2hJGBSNGnimJmg==|iD33s8B69C8JhYYhSa4V1tArjvLr8eEaGqOV7BRo5Jk=";
|
||||
|
||||
public OrganizationUserControllerTests(ApiApplicationFactory apiFactory)
|
||||
{
|
||||
_factory = apiFactory;
|
||||
_factory.SubstituteService<IFeatureService>(featureService =>
|
||||
{
|
||||
featureService
|
||||
.IsEnabled(FeatureFlagKeys.CreateDefaultLocation)
|
||||
.Returns(true);
|
||||
});
|
||||
_client = _factory.CreateClient();
|
||||
_loginHelper = new LoginHelper(_factory, _client);
|
||||
}
|
||||
|
||||
private readonly HttpClient _client;
|
||||
private readonly ApiApplicationFactory _factory;
|
||||
private readonly LoginHelper _loginHelper;
|
||||
|
||||
private Organization _organization = null!;
|
||||
private string _ownerEmail = null!;
|
||||
|
||||
[Fact]
|
||||
public async Task BulkDeleteAccount_Success()
|
||||
{
|
||||
var (userEmail, _) = await OrganizationTestHelpers.CreateNewUserWithAccountAsync(_factory,
|
||||
_organization.Id, OrganizationUserType.Owner);
|
||||
|
||||
await _loginHelper.LoginAsync(userEmail);
|
||||
|
||||
var (_, orgUserToDelete) = await OrganizationTestHelpers.CreateNewUserWithAccountAsync(_factory, _organization.Id, OrganizationUserType.User);
|
||||
await OrganizationTestHelpers.CreateVerifiedDomainAsync(_factory, _organization.Id, "bitwarden.com");
|
||||
|
||||
var userRepository = _factory.GetService<IUserRepository>();
|
||||
var organizationUserRepository = _factory.GetService<IOrganizationUserRepository>();
|
||||
|
||||
Assert.NotNull(orgUserToDelete.UserId);
|
||||
Assert.NotNull(await userRepository.GetByIdAsync(orgUserToDelete.UserId.Value));
|
||||
Assert.NotNull(await organizationUserRepository.GetByIdAsync(orgUserToDelete.Id));
|
||||
|
||||
var request = new OrganizationUserBulkRequestModel
|
||||
{
|
||||
Ids = [orgUserToDelete.Id]
|
||||
};
|
||||
|
||||
var httpResponse = await _client.PostAsJsonAsync($"organizations/{_organization.Id}/users/delete-account", request);
|
||||
var content = await httpResponse.Content.ReadFromJsonAsync<ListResponseModel<OrganizationUserBulkResponseModel>>();
|
||||
Assert.Single(content.Data, r => r.Id == orgUserToDelete.Id && r.Error == string.Empty);
|
||||
|
||||
Assert.Equal(HttpStatusCode.OK, httpResponse.StatusCode);
|
||||
Assert.Null(await userRepository.GetByIdAsync(orgUserToDelete.UserId.Value));
|
||||
Assert.Null(await organizationUserRepository.GetByIdAsync(orgUserToDelete.Id));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BulkDeleteAccount_MixedResults()
|
||||
{
|
||||
var (userEmail, _) = await OrganizationTestHelpers.CreateNewUserWithAccountAsync(_factory,
|
||||
_organization.Id, OrganizationUserType.Admin);
|
||||
|
||||
await _loginHelper.LoginAsync(userEmail);
|
||||
|
||||
// Can delete users
|
||||
var (_, validOrgUser) = await OrganizationTestHelpers.CreateNewUserWithAccountAsync(_factory, _organization.Id, OrganizationUserType.User);
|
||||
// Cannot delete owners
|
||||
var (_, invalidOrgUser) = await OrganizationTestHelpers.CreateNewUserWithAccountAsync(_factory, _organization.Id, OrganizationUserType.Owner);
|
||||
await OrganizationTestHelpers.CreateVerifiedDomainAsync(_factory, _organization.Id, "bitwarden.com");
|
||||
|
||||
var userRepository = _factory.GetService<IUserRepository>();
|
||||
var organizationUserRepository = _factory.GetService<IOrganizationUserRepository>();
|
||||
|
||||
Assert.NotNull(validOrgUser.UserId);
|
||||
Assert.NotNull(invalidOrgUser.UserId);
|
||||
|
||||
var arrangedUsers =
|
||||
await userRepository.GetManyAsync([validOrgUser.UserId.Value, invalidOrgUser.UserId.Value]);
|
||||
Assert.Equal(2, arrangedUsers.Count());
|
||||
|
||||
var arrangedOrgUsers =
|
||||
await organizationUserRepository.GetManyAsync([validOrgUser.Id, invalidOrgUser.Id]);
|
||||
Assert.Equal(2, arrangedOrgUsers.Count);
|
||||
|
||||
var request = new OrganizationUserBulkRequestModel
|
||||
{
|
||||
Ids = [validOrgUser.Id, invalidOrgUser.Id]
|
||||
};
|
||||
|
||||
var httpResponse = await _client.PostAsJsonAsync($"organizations/{_organization.Id}/users/delete-account", request);
|
||||
|
||||
Assert.Equal(HttpStatusCode.OK, httpResponse.StatusCode);
|
||||
var debug = await httpResponse.Content.ReadAsStringAsync();
|
||||
var content = await httpResponse.Content.ReadFromJsonAsync<ListResponseModel<OrganizationUserBulkResponseModel>>();
|
||||
Assert.Equal(2, content.Data.Count());
|
||||
Assert.Contains(content.Data, r => r.Id == validOrgUser.Id && r.Error == string.Empty);
|
||||
Assert.Contains(content.Data, r =>
|
||||
r.Id == invalidOrgUser.Id &&
|
||||
string.Equals(r.Error, new CannotDeleteOwnersError().Message, StringComparison.Ordinal));
|
||||
|
||||
var actualUsers =
|
||||
await userRepository.GetManyAsync([validOrgUser.UserId.Value, invalidOrgUser.UserId.Value]);
|
||||
Assert.Single(actualUsers, u => u.Id == invalidOrgUser.UserId.Value);
|
||||
|
||||
var actualOrgUsers =
|
||||
await organizationUserRepository.GetManyAsync([validOrgUser.Id, invalidOrgUser.Id]);
|
||||
Assert.Single(actualOrgUsers, ou => ou.Id == invalidOrgUser.Id);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(OrganizationUserType.User)]
|
||||
[InlineData(OrganizationUserType.Custom)]
|
||||
public async Task BulkDeleteAccount_WhenUserCannotManageUsers_ReturnsForbiddenResponse(OrganizationUserType organizationUserType)
|
||||
{
|
||||
var (userEmail, _) = await OrganizationTestHelpers.CreateNewUserWithAccountAsync(_factory,
|
||||
_organization.Id, organizationUserType, new Permissions { ManageUsers = false });
|
||||
|
||||
await _loginHelper.LoginAsync(userEmail);
|
||||
|
||||
var request = new OrganizationUserBulkRequestModel
|
||||
{
|
||||
Ids = new List<Guid> { Guid.NewGuid() }
|
||||
};
|
||||
|
||||
var httpResponse = await _client.PostAsJsonAsync($"organizations/{_organization.Id}/users/delete-account", request);
|
||||
|
||||
Assert.Equal(HttpStatusCode.Forbidden, httpResponse.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task DeleteAccount_Success()
|
||||
{
|
||||
var (userEmail, _) = await OrganizationTestHelpers.CreateNewUserWithAccountAsync(_factory,
|
||||
_organization.Id, OrganizationUserType.Owner);
|
||||
|
||||
await _loginHelper.LoginAsync(userEmail);
|
||||
|
||||
var (_, orgUserToDelete) = await OrganizationTestHelpers.CreateNewUserWithAccountAsync(_factory, _organization.Id, OrganizationUserType.User);
|
||||
await OrganizationTestHelpers.CreateVerifiedDomainAsync(_factory, _organization.Id, "bitwarden.com");
|
||||
|
||||
var userRepository = _factory.GetService<IUserRepository>();
|
||||
var organizationUserRepository = _factory.GetService<IOrganizationUserRepository>();
|
||||
|
||||
Assert.NotNull(orgUserToDelete.UserId);
|
||||
Assert.NotNull(await userRepository.GetByIdAsync(orgUserToDelete.UserId.Value));
|
||||
Assert.NotNull(await organizationUserRepository.GetByIdAsync(orgUserToDelete.Id));
|
||||
|
||||
var httpResponse = await _client.DeleteAsync($"organizations/{_organization.Id}/users/{orgUserToDelete.Id}/delete-account");
|
||||
|
||||
Assert.Equal(HttpStatusCode.OK, httpResponse.StatusCode);
|
||||
Assert.Null(await userRepository.GetByIdAsync(orgUserToDelete.UserId.Value));
|
||||
Assert.Null(await organizationUserRepository.GetByIdAsync(orgUserToDelete.Id));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(OrganizationUserType.User)]
|
||||
[InlineData(OrganizationUserType.Custom)]
|
||||
public async Task DeleteAccount_WhenUserCannotManageUsers_ReturnsForbiddenResponse(OrganizationUserType organizationUserType)
|
||||
{
|
||||
var (userEmail, _) = await OrganizationTestHelpers.CreateNewUserWithAccountAsync(_factory,
|
||||
_organization.Id, organizationUserType, new Permissions { ManageUsers = false });
|
||||
|
||||
await _loginHelper.LoginAsync(userEmail);
|
||||
|
||||
var userToRemove = Guid.NewGuid();
|
||||
|
||||
var httpResponse = await _client.DeleteAsync($"organizations/{_organization.Id}/users/{userToRemove}/delete-account");
|
||||
|
||||
Assert.Equal(HttpStatusCode.Forbidden, httpResponse.StatusCode);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(OrganizationUserType.User)]
|
||||
[InlineData(OrganizationUserType.Custom)]
|
||||
public async Task GetAccountRecoveryDetails_WithoutManageResetPasswordPermission_ReturnsForbiddenResponse(OrganizationUserType organizationUserType)
|
||||
{
|
||||
var (userEmail, _) = await OrganizationTestHelpers.CreateNewUserWithAccountAsync(_factory,
|
||||
_organization.Id, organizationUserType, new Permissions { ManageUsers = false });
|
||||
|
||||
await _loginHelper.LoginAsync(userEmail);
|
||||
|
||||
var request = new OrganizationUserBulkRequestModel
|
||||
{
|
||||
Ids = []
|
||||
};
|
||||
|
||||
var httpResponse =
|
||||
await _client.PostAsJsonAsync($"organizations/{_organization.Id}/users/account-recovery-details", request);
|
||||
|
||||
Assert.Equal(HttpStatusCode.Forbidden, httpResponse.StatusCode);
|
||||
}
|
||||
|
||||
public async Task InitializeAsync()
|
||||
{
|
||||
_ownerEmail = $"org-user-integration-test-{Guid.NewGuid()}@bitwarden.com";
|
||||
await _factory.LoginWithNewAccount(_ownerEmail);
|
||||
|
||||
(_organization, _) = await OrganizationTestHelpers.SignUpAsync(_factory, plan: PlanType.EnterpriseAnnually2023,
|
||||
ownerEmail: _ownerEmail, passwordManagerSeats: 5, paymentMethod: PaymentMethodType.Card);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Confirm_WithValidUser_ReturnsSuccess()
|
||||
{
|
||||
await OrganizationTestHelpers.EnableOrganizationDataOwnershipPolicyAsync(_factory, _organization.Id);
|
||||
|
||||
var acceptedOrgUser = (await CreateAcceptedUsersAsync(new[] { ("test1@bitwarden.com", OrganizationUserType.User) })).First();
|
||||
|
||||
await _loginHelper.LoginAsync(_ownerEmail);
|
||||
|
||||
var confirmModel = new OrganizationUserConfirmRequestModel
|
||||
{
|
||||
Key = "test-key",
|
||||
DefaultUserCollectionName = _mockEncryptedString
|
||||
};
|
||||
var confirmResponse = await _client.PostAsJsonAsync($"organizations/{_organization.Id}/users/{acceptedOrgUser.Id}/confirm", confirmModel);
|
||||
|
||||
Assert.Equal(HttpStatusCode.OK, confirmResponse.StatusCode);
|
||||
|
||||
await VerifyUserConfirmedAsync(acceptedOrgUser, "test-key");
|
||||
await VerifyDefaultCollectionCountAsync(acceptedOrgUser, 1);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Confirm_WithValidOwner_ReturnsSuccess()
|
||||
{
|
||||
await OrganizationTestHelpers.EnableOrganizationDataOwnershipPolicyAsync(_factory, _organization.Id);
|
||||
|
||||
var acceptedOrgUser = (await CreateAcceptedUsersAsync(new[] { ("owner1@bitwarden.com", OrganizationUserType.Owner) })).First();
|
||||
|
||||
await _loginHelper.LoginAsync(_ownerEmail);
|
||||
|
||||
var confirmModel = new OrganizationUserConfirmRequestModel
|
||||
{
|
||||
Key = "test-key",
|
||||
DefaultUserCollectionName = _mockEncryptedString
|
||||
};
|
||||
var confirmResponse = await _client.PostAsJsonAsync($"organizations/{_organization.Id}/users/{acceptedOrgUser.Id}/confirm", confirmModel);
|
||||
|
||||
Assert.Equal(HttpStatusCode.OK, confirmResponse.StatusCode);
|
||||
|
||||
await VerifyUserConfirmedAsync(acceptedOrgUser, "test-key");
|
||||
await VerifyDefaultCollectionCountAsync(acceptedOrgUser, 0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BulkConfirm_WithValidUsers_ReturnsSuccess()
|
||||
{
|
||||
const string testKeyFormat = "test-key-{0}";
|
||||
await OrganizationTestHelpers.EnableOrganizationDataOwnershipPolicyAsync(_factory, _organization.Id);
|
||||
|
||||
var acceptedUsers = await CreateAcceptedUsersAsync([
|
||||
("test1@example.com", OrganizationUserType.User),
|
||||
("test2@example.com", OrganizationUserType.Owner),
|
||||
("test3@example.com", OrganizationUserType.User)
|
||||
]);
|
||||
|
||||
await _loginHelper.LoginAsync(_ownerEmail);
|
||||
|
||||
var bulkConfirmModel = new OrganizationUserBulkConfirmRequestModel
|
||||
{
|
||||
Keys = acceptedUsers.Select((organizationUser, index) => new OrganizationUserBulkConfirmRequestModelEntry
|
||||
{
|
||||
Id = organizationUser.Id,
|
||||
Key = string.Format(testKeyFormat, index)
|
||||
}),
|
||||
DefaultUserCollectionName = _mockEncryptedString
|
||||
};
|
||||
|
||||
var bulkConfirmResponse = await _client.PostAsJsonAsync($"organizations/{_organization.Id}/users/confirm", bulkConfirmModel);
|
||||
|
||||
Assert.Equal(HttpStatusCode.OK, bulkConfirmResponse.StatusCode);
|
||||
|
||||
await VerifyMultipleUsersConfirmedAsync(acceptedUsers.Select((organizationUser, index) =>
|
||||
(organizationUser, string.Format(testKeyFormat, index))).ToList());
|
||||
await VerifyDefaultCollectionCountAsync(acceptedUsers.ElementAt(0), 1);
|
||||
await VerifyDefaultCollectionCountAsync(acceptedUsers.ElementAt(1), 0); // Owner does not get a default collection
|
||||
await VerifyDefaultCollectionCountAsync(acceptedUsers.ElementAt(2), 1);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Put_WithExistingDefaultCollection_Success()
|
||||
{
|
||||
// Arrange
|
||||
await _loginHelper.LoginAsync(_ownerEmail);
|
||||
|
||||
var (userEmail, organizationUser) = await OrganizationTestHelpers.CreateNewUserWithAccountAsync(_factory,
|
||||
_organization.Id, OrganizationUserType.User);
|
||||
|
||||
var (group, sharedCollection, defaultCollection) = await CreateTestDataAsync();
|
||||
await AssignDefaultCollectionToUserAsync(organizationUser, defaultCollection);
|
||||
|
||||
// Act
|
||||
var updateRequest = CreateUpdateRequest(sharedCollection, group);
|
||||
var httpResponse = await _client.PutAsJsonAsync($"organizations/{_organization.Id}/users/{organizationUser.Id}", updateRequest);
|
||||
|
||||
Assert.Equal(HttpStatusCode.OK, httpResponse.StatusCode);
|
||||
|
||||
// Assert
|
||||
await VerifyUserWasUpdatedCorrectlyAsync(organizationUser, expectedType: OrganizationUserType.Custom, expectedManageGroups: true);
|
||||
await VerifyGroupAccessWasAddedAsync(organizationUser, [group]);
|
||||
await VerifyCollectionAccessWasUpdatedCorrectlyAsync(organizationUser, sharedCollection.Id, defaultCollection.Id);
|
||||
}
|
||||
|
||||
public Task DisposeAsync()
|
||||
{
|
||||
_client.Dispose();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private async Task<(Group group, Collection sharedCollection, Collection defaultCollection)> CreateTestDataAsync()
|
||||
{
|
||||
var groupRepository = _factory.GetService<IGroupRepository>();
|
||||
var group = await groupRepository.CreateAsync(new Group
|
||||
{
|
||||
OrganizationId = _organization.Id,
|
||||
Name = $"Test Group {Guid.NewGuid()}"
|
||||
});
|
||||
|
||||
var collectionRepository = _factory.GetService<ICollectionRepository>();
|
||||
var sharedCollection = await collectionRepository.CreateAsync(new Collection
|
||||
{
|
||||
OrganizationId = _organization.Id,
|
||||
Name = $"Test Collection {Guid.NewGuid()}",
|
||||
Type = CollectionType.SharedCollection
|
||||
});
|
||||
|
||||
var defaultCollection = await collectionRepository.CreateAsync(new Collection
|
||||
{
|
||||
OrganizationId = _organization.Id,
|
||||
Name = $"My Items {Guid.NewGuid()}",
|
||||
Type = CollectionType.DefaultUserCollection
|
||||
});
|
||||
|
||||
return (group, sharedCollection, defaultCollection);
|
||||
}
|
||||
|
||||
private async Task AssignDefaultCollectionToUserAsync(OrganizationUser organizationUser, Collection defaultCollection)
|
||||
{
|
||||
var organizationUserRepository = _factory.GetService<IOrganizationUserRepository>();
|
||||
await organizationUserRepository.ReplaceAsync(organizationUser,
|
||||
new List<CollectionAccessSelection>
|
||||
{
|
||||
new CollectionAccessSelection
|
||||
{
|
||||
Id = defaultCollection.Id,
|
||||
ReadOnly = false,
|
||||
HidePasswords = false,
|
||||
Manage = true
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static OrganizationUserUpdateRequestModel CreateUpdateRequest(Collection sharedCollection, Group group)
|
||||
{
|
||||
return new OrganizationUserUpdateRequestModel
|
||||
{
|
||||
Type = OrganizationUserType.Custom,
|
||||
Permissions = new Permissions
|
||||
{
|
||||
ManageGroups = true
|
||||
},
|
||||
Collections = new List<SelectionReadOnlyRequestModel>
|
||||
{
|
||||
new SelectionReadOnlyRequestModel
|
||||
{
|
||||
Id = sharedCollection.Id,
|
||||
ReadOnly = true,
|
||||
HidePasswords = false,
|
||||
Manage = false
|
||||
}
|
||||
},
|
||||
Groups = new List<Guid> { group.Id }
|
||||
};
|
||||
}
|
||||
|
||||
private async Task VerifyUserWasUpdatedCorrectlyAsync(
|
||||
OrganizationUser organizationUser,
|
||||
OrganizationUserType expectedType,
|
||||
bool expectedManageGroups)
|
||||
{
|
||||
var organizationUserRepository = _factory.GetService<IOrganizationUserRepository>();
|
||||
var updatedOrgUser = await organizationUserRepository.GetByIdAsync(organizationUser.Id);
|
||||
Assert.NotNull(updatedOrgUser);
|
||||
Assert.Equal(expectedType, updatedOrgUser.Type);
|
||||
Assert.Equal(expectedManageGroups, updatedOrgUser.GetPermissions().ManageGroups);
|
||||
}
|
||||
|
||||
private async Task VerifyGroupAccessWasAddedAsync(
|
||||
OrganizationUser organizationUser, IEnumerable<Group> groups)
|
||||
{
|
||||
var groupRepository = _factory.GetService<IGroupRepository>();
|
||||
var userGroups = await groupRepository.GetManyIdsByUserIdAsync(organizationUser.Id);
|
||||
Assert.All(groups, group => Assert.Contains(group.Id, userGroups));
|
||||
}
|
||||
|
||||
private async Task VerifyCollectionAccessWasUpdatedCorrectlyAsync(
|
||||
OrganizationUser organizationUser, Guid sharedCollectionId, Guid defaultCollectionId)
|
||||
{
|
||||
var organizationUserRepository = _factory.GetService<IOrganizationUserRepository>();
|
||||
var (_, collectionAccess) = await organizationUserRepository.GetByIdWithCollectionsAsync(organizationUser.Id);
|
||||
var collectionIds = collectionAccess.Select(c => c.Id).ToHashSet();
|
||||
|
||||
Assert.Contains(defaultCollectionId, collectionIds);
|
||||
Assert.Contains(sharedCollectionId, collectionIds);
|
||||
|
||||
var newCollectionAccess = collectionAccess.First(c => c.Id == sharedCollectionId);
|
||||
Assert.True(newCollectionAccess.ReadOnly);
|
||||
Assert.False(newCollectionAccess.HidePasswords);
|
||||
Assert.False(newCollectionAccess.Manage);
|
||||
}
|
||||
|
||||
private async Task<List<OrganizationUser>> CreateAcceptedUsersAsync(
|
||||
IEnumerable<(string email, OrganizationUserType userType)> newUsers)
|
||||
{
|
||||
var acceptedUsers = new List<OrganizationUser>();
|
||||
|
||||
foreach (var (email, userType) in newUsers)
|
||||
{
|
||||
await _factory.LoginWithNewAccount(email);
|
||||
|
||||
var acceptedOrgUser = await OrganizationTestHelpers.CreateUserAsync(
|
||||
_factory, _organization.Id, email,
|
||||
userType, userStatusType: OrganizationUserStatusType.Accepted);
|
||||
|
||||
acceptedUsers.Add(acceptedOrgUser);
|
||||
}
|
||||
|
||||
return acceptedUsers;
|
||||
}
|
||||
|
||||
private async Task VerifyDefaultCollectionCountAsync(OrganizationUser orgUser, int expectedCount)
|
||||
{
|
||||
var collectionRepository = _factory.GetService<ICollectionRepository>();
|
||||
var collections = await collectionRepository.GetManyByUserIdAsync(orgUser.UserId!.Value);
|
||||
Assert.Equal(expectedCount, collections.Count);
|
||||
}
|
||||
|
||||
private async Task VerifyUserConfirmedAsync(OrganizationUser orgUser, string expectedKey)
|
||||
{
|
||||
await VerifyMultipleUsersConfirmedAsync(new List<(OrganizationUser orgUser, string key)> { (orgUser, expectedKey) });
|
||||
}
|
||||
|
||||
private async Task VerifyMultipleUsersConfirmedAsync(List<(OrganizationUser orgUser, string key)> acceptedOrganizationUsers)
|
||||
{
|
||||
var orgUserRepository = _factory.GetService<IOrganizationUserRepository>();
|
||||
for (int i = 0; i < acceptedOrganizationUsers.Count; i++)
|
||||
{
|
||||
var confirmedUser = await orgUserRepository.GetByIdAsync(acceptedOrganizationUsers[i].orgUser.Id);
|
||||
Assert.Equal(OrganizationUserStatusType.Confirmed, confirmedUser.Status);
|
||||
Assert.Equal(acceptedOrganizationUsers[i].key, confirmedUser.Key);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,214 @@
|
||||
using System.Net;
|
||||
using System.Text.Json;
|
||||
using Bit.Api.AdminConsole.Models.Request;
|
||||
using Bit.Api.AdminConsole.Models.Response.Organizations;
|
||||
using Bit.Api.IntegrationTest.Factories;
|
||||
using Bit.Api.IntegrationTest.Helpers;
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.AdminConsole.Enums;
|
||||
using Bit.Core.AdminConsole.Models.Data.Organizations.Policies;
|
||||
using Bit.Core.AdminConsole.Repositories;
|
||||
using Bit.Core.Billing.Enums;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Test.Common.Helpers;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Api.IntegrationTest.AdminConsole.Controllers;
|
||||
|
||||
public class PoliciesControllerTests : IClassFixture<ApiApplicationFactory>, IAsyncLifetime
|
||||
{
|
||||
private readonly HttpClient _client;
|
||||
private readonly ApiApplicationFactory _factory;
|
||||
private readonly LoginHelper _loginHelper;
|
||||
|
||||
private Organization _organization = null!;
|
||||
private string _ownerEmail = null!;
|
||||
|
||||
public PoliciesControllerTests(ApiApplicationFactory factory)
|
||||
{
|
||||
_factory = factory;
|
||||
_factory.SubstituteService<Core.Services.IFeatureService>(featureService =>
|
||||
{
|
||||
featureService
|
||||
.IsEnabled("pm-19467-create-default-location")
|
||||
.Returns(true);
|
||||
});
|
||||
_client = factory.CreateClient();
|
||||
_loginHelper = new LoginHelper(_factory, _client);
|
||||
}
|
||||
|
||||
public async Task InitializeAsync()
|
||||
{
|
||||
_ownerEmail = $"integration-test{Guid.NewGuid()}@bitwarden.com";
|
||||
await _factory.LoginWithNewAccount(_ownerEmail);
|
||||
|
||||
(_organization, _) = await OrganizationTestHelpers.SignUpAsync(_factory, plan: PlanType.EnterpriseAnnually,
|
||||
ownerEmail: _ownerEmail, passwordManagerSeats: 10, paymentMethod: PaymentMethodType.Card);
|
||||
|
||||
await _loginHelper.LoginAsync(_ownerEmail);
|
||||
}
|
||||
|
||||
public Task DisposeAsync()
|
||||
{
|
||||
_client.Dispose();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task PutVNext_OrganizationDataOwnershipPolicy_Success()
|
||||
{
|
||||
// Arrange
|
||||
const PolicyType policyType = PolicyType.OrganizationDataOwnership;
|
||||
|
||||
const string defaultCollectionName = "Test Default Collection";
|
||||
var request = new SavePolicyRequest
|
||||
{
|
||||
Policy = new PolicyRequestModel
|
||||
{
|
||||
Type = policyType,
|
||||
Enabled = true,
|
||||
},
|
||||
Metadata = new Dictionary<string, object>
|
||||
{
|
||||
{ "defaultUserCollectionName", defaultCollectionName }
|
||||
}
|
||||
};
|
||||
|
||||
var (_, admin) = await OrganizationTestHelpers.CreateNewUserWithAccountAsync(_factory,
|
||||
_organization.Id, OrganizationUserType.Admin);
|
||||
|
||||
var (_, user) = await OrganizationTestHelpers.CreateNewUserWithAccountAsync(_factory,
|
||||
_organization.Id, OrganizationUserType.User);
|
||||
|
||||
// Act
|
||||
var response = await _client.PutAsync($"/organizations/{_organization.Id}/policies/{policyType}/vnext",
|
||||
JsonContent.Create(request));
|
||||
|
||||
// Assert
|
||||
await AssertResponse();
|
||||
|
||||
await AssertPolicy();
|
||||
|
||||
await AssertDefaultCollectionCreatedOnlyForUserTypeAsync();
|
||||
return;
|
||||
|
||||
async Task AssertDefaultCollectionCreatedOnlyForUserTypeAsync()
|
||||
{
|
||||
var collectionRepository = _factory.GetService<ICollectionRepository>();
|
||||
await AssertUserExpectations(collectionRepository);
|
||||
await AssertAdminExpectations(collectionRepository);
|
||||
}
|
||||
|
||||
async Task AssertUserExpectations(ICollectionRepository collectionRepository)
|
||||
{
|
||||
var collections = await collectionRepository.GetManyByUserIdAsync(user.UserId!.Value);
|
||||
var defaultCollection = collections.FirstOrDefault(c => c.Name == defaultCollectionName);
|
||||
Assert.NotNull(defaultCollection);
|
||||
Assert.Equal(_organization.Id, defaultCollection.OrganizationId);
|
||||
}
|
||||
|
||||
async Task AssertAdminExpectations(ICollectionRepository collectionRepository)
|
||||
{
|
||||
var collections = await collectionRepository.GetManyByUserIdAsync(admin.UserId!.Value);
|
||||
var defaultCollection = collections.FirstOrDefault(c => c.Name == defaultCollectionName);
|
||||
Assert.Null(defaultCollection);
|
||||
}
|
||||
|
||||
async Task AssertResponse()
|
||||
{
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
var content = await response.Content.ReadFromJsonAsync<PolicyResponseModel>();
|
||||
|
||||
Assert.True(content.Enabled);
|
||||
Assert.Equal(policyType, content.Type);
|
||||
Assert.Equal(_organization.Id, content.OrganizationId);
|
||||
}
|
||||
|
||||
async Task AssertPolicy()
|
||||
{
|
||||
var policyRepository = _factory.GetService<IPolicyRepository>();
|
||||
var policy = await policyRepository.GetByOrganizationIdTypeAsync(_organization.Id, policyType);
|
||||
|
||||
Assert.NotNull(policy);
|
||||
Assert.True(policy.Enabled);
|
||||
Assert.Equal(policyType, policy.Type);
|
||||
Assert.Null(policy.Data);
|
||||
Assert.Equal(_organization.Id, policy.OrganizationId);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task PutVNext_MasterPasswordPolicy_Success()
|
||||
{
|
||||
// Arrange
|
||||
var policyType = PolicyType.MasterPassword;
|
||||
var request = new SavePolicyRequest
|
||||
{
|
||||
Policy = new PolicyRequestModel
|
||||
{
|
||||
Type = policyType,
|
||||
Enabled = true,
|
||||
Data = new Dictionary<string, object>
|
||||
{
|
||||
{ "minComplexity", 10 },
|
||||
{ "minLength", 12 },
|
||||
{ "requireUpper", true },
|
||||
{ "requireLower", false },
|
||||
{ "requireNumbers", true },
|
||||
{ "requireSpecial", false },
|
||||
{ "enforceOnLogin", true }
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Act
|
||||
var response = await _client.PutAsync($"/organizations/{_organization.Id}/policies/{policyType}/vnext",
|
||||
JsonContent.Create(request));
|
||||
|
||||
// Assert
|
||||
await AssertResponse();
|
||||
|
||||
await AssertPolicyDataForMasterPasswordPolicy();
|
||||
return;
|
||||
|
||||
async Task AssertPolicyDataForMasterPasswordPolicy()
|
||||
{
|
||||
var policyRepository = _factory.GetService<IPolicyRepository>();
|
||||
var policy = await policyRepository.GetByOrganizationIdTypeAsync(_organization.Id, policyType);
|
||||
|
||||
AssertPolicy(policy);
|
||||
AssertMasterPasswordPolicyData(policy);
|
||||
}
|
||||
|
||||
async Task AssertResponse()
|
||||
{
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
var content = await response.Content.ReadFromJsonAsync<PolicyResponseModel>();
|
||||
|
||||
Assert.True(content.Enabled);
|
||||
Assert.Equal(policyType, content.Type);
|
||||
Assert.Equal(_organization.Id, content.OrganizationId);
|
||||
}
|
||||
|
||||
void AssertPolicy(Policy policy)
|
||||
{
|
||||
Assert.NotNull(policy);
|
||||
Assert.True(policy.Enabled);
|
||||
Assert.Equal(policyType, policy.Type);
|
||||
Assert.Equal(_organization.Id, policy.OrganizationId);
|
||||
Assert.NotNull(policy.Data);
|
||||
}
|
||||
|
||||
void AssertMasterPasswordPolicyData(Policy policy)
|
||||
{
|
||||
var resultData = policy.GetDataModel<MasterPasswordPolicyData>();
|
||||
|
||||
var json = JsonSerializer.Serialize(request.Policy.Data);
|
||||
var expectedData = JsonSerializer.Deserialize<MasterPasswordPolicyData>(json);
|
||||
AssertHelper.AssertPropertyEqual(resultData, expectedData);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,331 @@
|
||||
using System.Net;
|
||||
using Bit.Api.AdminConsole.Public.Models.Request;
|
||||
using Bit.Api.IntegrationTest.Factories;
|
||||
using Bit.Api.IntegrationTest.Helpers;
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.AdminConsole.Repositories;
|
||||
using Bit.Core.Billing.Enums;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Repositories;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Api.IntegrationTest.AdminConsole.Import;
|
||||
|
||||
public class ImportOrganizationUsersAndGroupsCommandTests : IClassFixture<ApiApplicationFactory>, IAsyncLifetime
|
||||
{
|
||||
private readonly HttpClient _client;
|
||||
private readonly ApiApplicationFactory _factory;
|
||||
private readonly LoginHelper _loginHelper;
|
||||
private Organization _organization = null!;
|
||||
private string _ownerEmail = null!;
|
||||
|
||||
public ImportOrganizationUsersAndGroupsCommandTests(ApiApplicationFactory factory)
|
||||
{
|
||||
_factory = factory;
|
||||
_client = _factory.CreateClient();
|
||||
_loginHelper = new LoginHelper(_factory, _client);
|
||||
}
|
||||
|
||||
public async Task InitializeAsync()
|
||||
{
|
||||
// Create the owner account
|
||||
_ownerEmail = $"integration-test{Guid.NewGuid()}@bitwarden.com";
|
||||
await _factory.LoginWithNewAccount(_ownerEmail);
|
||||
|
||||
// Create the organization
|
||||
(_organization, _) = await OrganizationTestHelpers.SignUpAsync(_factory, plan: PlanType.EnterpriseAnnually2023,
|
||||
ownerEmail: _ownerEmail, passwordManagerSeats: 10, paymentMethod: PaymentMethodType.Card);
|
||||
|
||||
// Authorize with the organization api key
|
||||
await _loginHelper.LoginWithOrganizationApiKeyAsync(_organization.Id);
|
||||
}
|
||||
|
||||
public Task DisposeAsync()
|
||||
{
|
||||
_client.Dispose();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Import_Existing_Organization_User_Succeeds()
|
||||
{
|
||||
var (email, ou) = await OrganizationTestHelpers.CreateNewUserWithAccountAsync(_factory, _organization.Id,
|
||||
OrganizationUserType.User);
|
||||
|
||||
var externalId = Guid.NewGuid().ToString();
|
||||
var request = new OrganizationImportRequestModel();
|
||||
request.LargeImport = false;
|
||||
request.OverwriteExisting = false;
|
||||
request.Groups = [];
|
||||
request.Members = [
|
||||
new OrganizationImportRequestModel.OrganizationImportMemberRequestModel
|
||||
{
|
||||
Email = email,
|
||||
ExternalId = externalId,
|
||||
Deleted = false
|
||||
}
|
||||
];
|
||||
|
||||
var response = await _client.PostAsync($"/public/organization/import", JsonContent.Create(request));
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
|
||||
// Assert against the database values
|
||||
var organizationUserRepository = _factory.GetService<IOrganizationUserRepository>();
|
||||
var orgUser = await organizationUserRepository.GetByIdAsync(ou.Id);
|
||||
|
||||
Assert.NotNull(orgUser);
|
||||
Assert.Equal(ou.Id, orgUser.Id);
|
||||
Assert.Equal(email, orgUser.Email);
|
||||
Assert.Equal(OrganizationUserType.User, orgUser.Type);
|
||||
Assert.Equal(externalId, orgUser.ExternalId);
|
||||
Assert.Equal(OrganizationUserStatusType.Confirmed, orgUser.Status);
|
||||
Assert.Equal(_organization.Id, orgUser.OrganizationId);
|
||||
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Import_New_Organization_User_Succeeds()
|
||||
{
|
||||
var email = $"integration-test{Guid.NewGuid()}@bitwarden.com";
|
||||
await _factory.LoginWithNewAccount(email);
|
||||
|
||||
var externalId = Guid.NewGuid().ToString();
|
||||
var request = new OrganizationImportRequestModel();
|
||||
request.LargeImport = false;
|
||||
request.OverwriteExisting = false;
|
||||
request.Groups = [];
|
||||
request.Members = [
|
||||
new OrganizationImportRequestModel.OrganizationImportMemberRequestModel
|
||||
{
|
||||
Email = email,
|
||||
ExternalId = externalId,
|
||||
Deleted = false
|
||||
}
|
||||
];
|
||||
|
||||
var response = await _client.PostAsync($"/public/organization/import", JsonContent.Create(request));
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
|
||||
// Assert against the database values
|
||||
var organizationUserRepository = _factory.GetService<IOrganizationUserRepository>();
|
||||
var orgUser = await organizationUserRepository.GetByOrganizationEmailAsync(_organization.Id, email);
|
||||
|
||||
Assert.NotNull(orgUser);
|
||||
Assert.Equal(email, orgUser.Email);
|
||||
Assert.Equal(OrganizationUserType.User, orgUser.Type);
|
||||
Assert.Equal(externalId, orgUser.ExternalId);
|
||||
Assert.Equal(OrganizationUserStatusType.Invited, orgUser.Status);
|
||||
Assert.Equal(_organization.Id, orgUser.OrganizationId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Import_New_And_Existing_Organization_Users_Succeeds()
|
||||
{
|
||||
// Existing organization user
|
||||
var (existingEmail, ou) = await OrganizationTestHelpers.CreateNewUserWithAccountAsync(_factory, _organization.Id,
|
||||
OrganizationUserType.User);
|
||||
var existingExternalId = Guid.NewGuid().ToString();
|
||||
|
||||
// New organization user
|
||||
var newEmail = $"integration-test{Guid.NewGuid()}@bitwarden.com";
|
||||
await _factory.LoginWithNewAccount(newEmail);
|
||||
var newExternalId = Guid.NewGuid().ToString();
|
||||
|
||||
var request = new OrganizationImportRequestModel();
|
||||
request.LargeImport = false;
|
||||
request.OverwriteExisting = false;
|
||||
request.Groups = [];
|
||||
request.Members = [
|
||||
new OrganizationImportRequestModel.OrganizationImportMemberRequestModel
|
||||
{
|
||||
Email = existingEmail,
|
||||
ExternalId = existingExternalId,
|
||||
Deleted = false
|
||||
},
|
||||
new OrganizationImportRequestModel.OrganizationImportMemberRequestModel
|
||||
{
|
||||
Email = newEmail,
|
||||
ExternalId = newExternalId,
|
||||
Deleted = false
|
||||
}
|
||||
];
|
||||
|
||||
var response = await _client.PostAsync($"/public/organization/import", JsonContent.Create(request));
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
|
||||
// Assert against the database values
|
||||
var organizationUserRepository = _factory.GetService<IOrganizationUserRepository>();
|
||||
|
||||
// Existing user
|
||||
var existingOrgUser = await organizationUserRepository.GetByIdAsync(ou.Id);
|
||||
Assert.NotNull(existingOrgUser);
|
||||
Assert.Equal(existingEmail, existingOrgUser.Email);
|
||||
Assert.Equal(OrganizationUserType.User, existingOrgUser.Type);
|
||||
Assert.Equal(existingExternalId, existingOrgUser.ExternalId);
|
||||
Assert.Equal(OrganizationUserStatusType.Confirmed, existingOrgUser.Status);
|
||||
Assert.Equal(_organization.Id, existingOrgUser.OrganizationId);
|
||||
|
||||
// New User
|
||||
var newOrgUser = await organizationUserRepository.GetByOrganizationEmailAsync(_organization.Id, newEmail);
|
||||
Assert.NotNull(newOrgUser);
|
||||
Assert.Equal(newEmail, newOrgUser.Email);
|
||||
Assert.Equal(OrganizationUserType.User, newOrgUser.Type);
|
||||
Assert.Equal(newExternalId, newOrgUser.ExternalId);
|
||||
Assert.Equal(OrganizationUserStatusType.Invited, newOrgUser.Status);
|
||||
Assert.Equal(_organization.Id, newOrgUser.OrganizationId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Import_Existing_Groups_Succeeds()
|
||||
{
|
||||
var organizationUserRepository = _factory.GetService<IOrganizationUserRepository>();
|
||||
var group = await OrganizationTestHelpers.CreateGroup(_factory, _organization.Id);
|
||||
var request = new OrganizationImportRequestModel();
|
||||
var addedMember = new OrganizationImportRequestModel.OrganizationImportMemberRequestModel
|
||||
{
|
||||
Email = "test@test.com",
|
||||
ExternalId = "bwtest-externalId",
|
||||
Deleted = false
|
||||
};
|
||||
|
||||
request.LargeImport = false;
|
||||
request.OverwriteExisting = false;
|
||||
request.Groups = [
|
||||
new OrganizationImportRequestModel.OrganizationImportGroupRequestModel
|
||||
{
|
||||
Name = "new-name",
|
||||
ExternalId = "bwtest-externalId",
|
||||
MemberExternalIds = []
|
||||
}
|
||||
];
|
||||
request.Members = [addedMember];
|
||||
|
||||
var response = await _client.PostAsync($"/public/organization/import", JsonContent.Create(request));
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
|
||||
// Assert against the database values
|
||||
var groupRepository = _factory.GetService<IGroupRepository>();
|
||||
var existingGroups = (await groupRepository.GetManyByOrganizationIdAsync(_organization.Id)).ToArray();
|
||||
|
||||
// Assert that we are actually updating the existing group, not adding a new one.
|
||||
Assert.Single(existingGroups);
|
||||
Assert.NotNull(existingGroups[0]);
|
||||
Assert.Equal(group.Id, existingGroups[0].Id);
|
||||
Assert.Equal("new-name", existingGroups[0].Name);
|
||||
Assert.Equal(group.ExternalId, existingGroups[0].ExternalId);
|
||||
|
||||
var addedOrgUser = await organizationUserRepository.GetByOrganizationEmailAsync(_organization.Id, addedMember.Email);
|
||||
Assert.NotNull(addedOrgUser);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Import_New_Groups_Succeeds()
|
||||
{
|
||||
var group = new Group
|
||||
{
|
||||
OrganizationId = _organization.Id,
|
||||
ExternalId = new Guid().ToString(),
|
||||
Name = "bwtest1"
|
||||
};
|
||||
|
||||
var request = new OrganizationImportRequestModel();
|
||||
request.LargeImport = false;
|
||||
request.OverwriteExisting = false;
|
||||
request.Groups = [
|
||||
new OrganizationImportRequestModel.OrganizationImportGroupRequestModel
|
||||
{
|
||||
Name = group.Name,
|
||||
ExternalId = group.ExternalId,
|
||||
MemberExternalIds = []
|
||||
}
|
||||
];
|
||||
request.Members = [];
|
||||
|
||||
var response = await _client.PostAsync($"/public/organization/import", JsonContent.Create(request));
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
|
||||
// Assert against the database values
|
||||
var groupRepository = _factory.GetService<IGroupRepository>();
|
||||
var existingGroups = await groupRepository.GetManyByOrganizationIdAsync(_organization.Id);
|
||||
var existingGroup = existingGroups.Where(g => g.ExternalId == group.ExternalId).FirstOrDefault();
|
||||
|
||||
Assert.NotNull(existingGroup);
|
||||
Assert.Equal(existingGroup.Name, group.Name);
|
||||
Assert.Equal(existingGroup.ExternalId, group.ExternalId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Import_New_And_Existing_Groups_Succeeds()
|
||||
{
|
||||
var existingGroup = await OrganizationTestHelpers.CreateGroup(_factory, _organization.Id);
|
||||
|
||||
var newGroup = new Group
|
||||
{
|
||||
OrganizationId = _organization.Id,
|
||||
ExternalId = "test",
|
||||
Name = "bwtest1"
|
||||
};
|
||||
|
||||
var request = new OrganizationImportRequestModel();
|
||||
request.LargeImport = false;
|
||||
request.OverwriteExisting = false;
|
||||
request.Groups = [
|
||||
new OrganizationImportRequestModel.OrganizationImportGroupRequestModel
|
||||
{
|
||||
Name = "new-name",
|
||||
ExternalId = existingGroup.ExternalId,
|
||||
MemberExternalIds = []
|
||||
},
|
||||
new OrganizationImportRequestModel.OrganizationImportGroupRequestModel
|
||||
{
|
||||
Name = newGroup.Name,
|
||||
ExternalId = newGroup.ExternalId,
|
||||
MemberExternalIds = []
|
||||
}
|
||||
];
|
||||
request.Members = [];
|
||||
|
||||
var response = await _client.PostAsync($"/public/organization/import", JsonContent.Create(request));
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
|
||||
// Assert against the database values
|
||||
var groupRepository = _factory.GetService<IGroupRepository>();
|
||||
var groups = await groupRepository.GetManyByOrganizationIdAsync(_organization.Id);
|
||||
|
||||
var newGroupInDb = groups.Where(g => g.ExternalId == newGroup.ExternalId).FirstOrDefault();
|
||||
Assert.NotNull(newGroupInDb);
|
||||
Assert.Equal(newGroupInDb.Name, newGroup.Name);
|
||||
Assert.Equal(newGroupInDb.ExternalId, newGroup.ExternalId);
|
||||
|
||||
var existingGroupInDb = groups.Where(g => g.ExternalId == existingGroup.ExternalId).FirstOrDefault();
|
||||
Assert.NotNull(existingGroupInDb);
|
||||
Assert.Equal(existingGroup.Id, existingGroupInDb.Id);
|
||||
Assert.Equal("new-name", existingGroupInDb.Name);
|
||||
Assert.Equal(existingGroup.ExternalId, existingGroupInDb.ExternalId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Import_Remove_Member_Without_Master_Password_Throws_400_Error()
|
||||
{
|
||||
// ARRANGE: a member without a master password
|
||||
await OrganizationTestHelpers.CreateUserWithoutMasterPasswordAsync(_factory, Guid.NewGuid() + "@example.com",
|
||||
_organization.Id);
|
||||
|
||||
// ACT: an import request that would remove that member
|
||||
var request = new OrganizationImportRequestModel
|
||||
{
|
||||
LargeImport = false,
|
||||
OverwriteExisting = true, // removes all members not in the request
|
||||
Groups = [],
|
||||
Members = []
|
||||
};
|
||||
|
||||
var response = await _client.PostAsync($"/public/organization/import", JsonContent.Create(request));
|
||||
|
||||
// ASSERT: that a 400 error is thrown with the correct error message
|
||||
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
|
||||
|
||||
var responseContent = await response.Content.ReadAsStringAsync();
|
||||
Assert.Contains("Sync failed. To proceed, disable the 'Remove and re-add users during next sync' setting and try again.", responseContent);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user