mirror of
https://github.com/bitwarden/server
synced 2025-12-06 00:03:34 +00:00
* Adding auto confirm endpoint and initial command work. * Adding validator * Finished command implementation. * Enabled the feature renomved used method. Enabled the policy in the tests. * Added extension functions to allow for railroad programming. * Removed guid from route template. Added xml docs * Added validation for command. * Added default collection creation to command. * formatting. * Added additional error types and mapped to appropriate results. * Added tests for auto confirm validator * Adding tests * fixing file name * Cleaned up OrgUserController. Added integration tests. * Consolidated CommandResult and validation result stuff into a v2 directory. * changing result to match handle method. * Moves validation thenasync method. * Added brackets. * Updated XML comment * Adding idempotency comment. * Fixed up merge problems. Fixed return types for handle. * Renamed to ValidationRequest * I added some methods for CommandResult to cover some future use cases. Added ApplyAsync method to execute multiple functions against CommandResult without an error stopping the workflow for side-effects. * Fixed up logic around should create default colleciton. Added more methods for chaining ValidationResult together. Added logic for user type. * Clearing nullable enable. * Fixed up validator tests. * Tests for auto confirm command * Fixed up command result and AutoConfirmCommand. * Removed some unused methods. * Moved autoconfirm tests to their own class. * Moved some stuff around. Need to clean up creation of accepted org user yet. * Moved some more code around. Folded Key into accepted constructor. removed unneeded tests since key and accepted are now a part of AcceptedOrgUser Creation. * Clean up clean up everybody everywhere. Clean up clean up everybody do your share. * Another quick one * Removed aggregate Errors.cs * Cleaned up validator and fixed up tests. * Fixed auto confirm repo * Cleaned up command tests. * Unused method. * Restoring Bulk command back to what it was. deleted handle method for bulk. * Remove unused method. * removed unnecssary lines and comments * fixed layout. * Fixed test. * fixed spelling mistake. removed unused import. * Update test/Core.Test/AdminConsole/OrganizationFeatures/OrganizationUsers/AutoConfirmUsers/AutomaticallyConfirmUsersCommandTests.cs Co-authored-by: Rui Tomé <108268980+r-tome@users.noreply.github.com> * Ensuring collection is created before full sync. Cleaning up tests and added a few more. Added check that the policy is enabled. * Added org cleanup * Lowering to 5 to see if that helps the runner. * 🤷 * Trying this * Maybe this time will be different. * seeing if awaiting and checking independently will work in ci * I figured it out. Locally, it would be fast enough to all return NoContent, however in CI, its slow enough for it to return 400 due to the user already being confirmed via validation. * Updated tests and validator * Fixed name --------- Co-authored-by: Rui Tomé <108268980+r-tome@users.noreply.github.com>
1599 lines
63 KiB
C#
1599 lines
63 KiB
C#
using Bit.Core.AdminConsole.Entities;
|
|
using Bit.Core.AdminConsole.Models.Data.OrganizationUsers;
|
|
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Models;
|
|
using Bit.Core.AdminConsole.Repositories;
|
|
using Bit.Core.Auth.Entities;
|
|
using Bit.Core.Auth.Enums;
|
|
using Bit.Core.Auth.Models.Data;
|
|
using Bit.Core.Auth.Repositories;
|
|
using Bit.Core.Entities;
|
|
using Bit.Core.Enums;
|
|
using Bit.Core.Models.Data;
|
|
using Bit.Core.Repositories;
|
|
using Bit.Core.Utilities;
|
|
using Xunit;
|
|
|
|
namespace Bit.Infrastructure.IntegrationTest.AdminConsole.Repositories.OrganizationUserRepository;
|
|
|
|
public class OrganizationUserRepositoryTests
|
|
{
|
|
[DatabaseTheory, DatabaseData]
|
|
public async Task DeleteAsync_Works(IUserRepository userRepository,
|
|
IOrganizationRepository organizationRepository,
|
|
IOrganizationUserRepository organizationUserRepository)
|
|
{
|
|
var user = await userRepository.CreateAsync(new User
|
|
{
|
|
Name = "Test User",
|
|
Email = $"test+{Guid.NewGuid()}@example.com",
|
|
ApiKey = "TEST",
|
|
SecurityStamp = "stamp",
|
|
});
|
|
|
|
var organization = await organizationRepository.CreateAsync(new Organization
|
|
{
|
|
Name = "Test Org",
|
|
BillingEmail = user.Email, // TODO: EF does not enforce this being NOT NULL
|
|
Plan = "Test", // TODO: EF does not enforce this being NOT NULL
|
|
});
|
|
|
|
var orgUser = await organizationUserRepository.CreateAsync(new OrganizationUser
|
|
{
|
|
OrganizationId = organization.Id,
|
|
UserId = user.Id,
|
|
Status = OrganizationUserStatusType.Confirmed,
|
|
Email = user.Email
|
|
});
|
|
|
|
await organizationUserRepository.DeleteAsync(orgUser);
|
|
|
|
var newUser = await userRepository.GetByIdAsync(user.Id);
|
|
Assert.NotNull(newUser);
|
|
Assert.NotEqual(newUser.AccountRevisionDate, user.AccountRevisionDate);
|
|
}
|
|
|
|
[DatabaseTheory, DatabaseData]
|
|
public async Task DeleteManyAsync_Migrates_UserDefaultCollection(IUserRepository userRepository,
|
|
ICollectionRepository collectionRepository,
|
|
IOrganizationRepository organizationRepository,
|
|
IOrganizationUserRepository organizationUserRepository
|
|
)
|
|
{
|
|
var user1 = await userRepository.CreateAsync(new User
|
|
{
|
|
Name = "Test User",
|
|
Email = $"test+{Guid.NewGuid()}@example.com",
|
|
ApiKey = "TEST",
|
|
SecurityStamp = "stamp",
|
|
});
|
|
|
|
var user2 = await userRepository.CreateAsync(new User
|
|
{
|
|
Name = "Test User",
|
|
Email = $"test+{Guid.NewGuid()}@example.com",
|
|
ApiKey = "TEST",
|
|
SecurityStamp = "stamp",
|
|
});
|
|
|
|
var organization = await organizationRepository.CreateAsync(new Organization
|
|
{
|
|
Name = "Test Org",
|
|
BillingEmail = user1.Email, // TODO: EF does not enforce this being NOT NULL
|
|
Plan = "Test", // TODO: EF does not enforce this being NOT NULL
|
|
});
|
|
|
|
var orgUser1 = await organizationUserRepository.CreateAsync(new OrganizationUser
|
|
{
|
|
OrganizationId = organization.Id,
|
|
UserId = user1.Id,
|
|
Status = OrganizationUserStatusType.Confirmed,
|
|
Email = user1.Email
|
|
});
|
|
|
|
var orgUser2 = await organizationUserRepository.CreateAsync(new OrganizationUser
|
|
{
|
|
OrganizationId = organization.Id,
|
|
UserId = user2.Id,
|
|
Status = OrganizationUserStatusType.Confirmed,
|
|
Email = user2.Email
|
|
});
|
|
|
|
var defaultUserCollection1 = await collectionRepository.CreateAsync(new Collection
|
|
{
|
|
Name = "Test Collection 1",
|
|
Id = user1.Id,
|
|
Type = CollectionType.DefaultUserCollection,
|
|
OrganizationId = organization.Id
|
|
});
|
|
|
|
var defaultUserCollection2 = await collectionRepository.CreateAsync(new Collection
|
|
{
|
|
Name = "Test Collection 2",
|
|
Id = user2.Id,
|
|
Type = CollectionType.DefaultUserCollection,
|
|
OrganizationId = organization.Id
|
|
});
|
|
|
|
// Create the CollectionUser entry for the defaultUserCollection
|
|
await collectionRepository.UpdateUsersAsync(defaultUserCollection1.Id, new List<CollectionAccessSelection>()
|
|
{
|
|
new CollectionAccessSelection
|
|
{
|
|
Id = orgUser1.Id,
|
|
HidePasswords = false,
|
|
ReadOnly = false,
|
|
Manage = true
|
|
},
|
|
});
|
|
|
|
await collectionRepository.UpdateUsersAsync(defaultUserCollection2.Id, new List<CollectionAccessSelection>()
|
|
{
|
|
new CollectionAccessSelection
|
|
{
|
|
Id = orgUser2.Id,
|
|
HidePasswords = false,
|
|
ReadOnly = false,
|
|
Manage = true
|
|
},
|
|
});
|
|
|
|
await organizationUserRepository.DeleteManyAsync(new List<Guid> { orgUser1.Id, orgUser2.Id });
|
|
|
|
var newUser = await userRepository.GetByIdAsync(user1.Id);
|
|
Assert.NotNull(newUser);
|
|
Assert.NotEqual(newUser.AccountRevisionDate, user1.AccountRevisionDate);
|
|
|
|
var updatedCollection1 = await collectionRepository.GetByIdAsync(defaultUserCollection1.Id);
|
|
Assert.NotNull(updatedCollection1);
|
|
Assert.Equal(CollectionType.SharedCollection, updatedCollection1.Type);
|
|
Assert.Equal(user1.Email, updatedCollection1.DefaultUserCollectionEmail);
|
|
|
|
var updatedCollection2 = await collectionRepository.GetByIdAsync(defaultUserCollection2.Id);
|
|
Assert.NotNull(updatedCollection2);
|
|
Assert.Equal(CollectionType.SharedCollection, updatedCollection2.Type);
|
|
Assert.Equal(user2.Email, updatedCollection2.DefaultUserCollectionEmail);
|
|
}
|
|
|
|
[DatabaseTheory, DatabaseData]
|
|
public async Task DeleteAsync_Migrates_UserDefaultCollection(IUserRepository userRepository,
|
|
ICollectionRepository collectionRepository,
|
|
IOrganizationRepository organizationRepository,
|
|
IOrganizationUserRepository organizationUserRepository
|
|
)
|
|
{
|
|
var user = await userRepository.CreateAsync(new User
|
|
{
|
|
Name = "Test User",
|
|
Email = $"test+{Guid.NewGuid()}@example.com",
|
|
ApiKey = "TEST",
|
|
SecurityStamp = "stamp",
|
|
});
|
|
|
|
var organization = await organizationRepository.CreateAsync(new Organization
|
|
{
|
|
Name = "Test Org",
|
|
BillingEmail = user.Email, // TODO: EF does not enforce this being NOT NULL
|
|
Plan = "Test", // TODO: EF does not enforce this being NOT NULL
|
|
});
|
|
|
|
var orgUser = await organizationUserRepository.CreateAsync(new OrganizationUser
|
|
{
|
|
OrganizationId = organization.Id,
|
|
UserId = user.Id,
|
|
Status = OrganizationUserStatusType.Confirmed,
|
|
Email = user.Email
|
|
});
|
|
|
|
var defaultUserCollection = await collectionRepository.CreateAsync(new Collection
|
|
{
|
|
Name = "Test Collection",
|
|
Id = user.Id,
|
|
Type = CollectionType.DefaultUserCollection,
|
|
OrganizationId = organization.Id
|
|
});
|
|
|
|
// Create the CollectionUser entry for the defaultUserCollection
|
|
await collectionRepository.UpdateUsersAsync(defaultUserCollection.Id, new List<CollectionAccessSelection>()
|
|
{
|
|
new CollectionAccessSelection
|
|
{
|
|
Id = orgUser.Id,
|
|
HidePasswords = false,
|
|
ReadOnly = false,
|
|
Manage = true
|
|
},
|
|
});
|
|
|
|
await organizationUserRepository.DeleteAsync(orgUser);
|
|
|
|
var newUser = await userRepository.GetByIdAsync(user.Id);
|
|
Assert.NotNull(newUser);
|
|
Assert.NotEqual(newUser.AccountRevisionDate, user.AccountRevisionDate);
|
|
|
|
var updatedCollection = await collectionRepository.GetByIdAsync(defaultUserCollection.Id);
|
|
Assert.NotNull(updatedCollection);
|
|
Assert.Equal(CollectionType.SharedCollection, updatedCollection.Type);
|
|
Assert.Equal(user.Email, updatedCollection.DefaultUserCollectionEmail);
|
|
}
|
|
|
|
|
|
[DatabaseTheory, DatabaseData]
|
|
public async Task DeleteManyAsync_Works(IUserRepository userRepository,
|
|
IOrganizationRepository organizationRepository,
|
|
IOrganizationUserRepository organizationUserRepository)
|
|
{
|
|
var user1 = await userRepository.CreateAsync(new User
|
|
{
|
|
Name = "Test User 1",
|
|
Email = $"test+{Guid.NewGuid()}@email.com",
|
|
ApiKey = "TEST",
|
|
SecurityStamp = "stamp",
|
|
});
|
|
|
|
var user2 = await userRepository.CreateAsync(new User
|
|
{
|
|
Name = "Test User 2",
|
|
Email = $"test+{Guid.NewGuid()}@email.com",
|
|
ApiKey = "TEST",
|
|
SecurityStamp = "stamp",
|
|
});
|
|
|
|
var organization = await organizationRepository.CreateAsync(new Organization
|
|
{
|
|
Name = "Test Org",
|
|
BillingEmail = user1.Email, // TODO: EF does not enforce this being NOT NULL
|
|
Plan = "Test", // TODO: EF does not enforce this being NOT NULL
|
|
});
|
|
|
|
var orgUser1 = await organizationUserRepository.CreateAsync(new OrganizationUser
|
|
{
|
|
OrganizationId = organization.Id,
|
|
UserId = user1.Id,
|
|
Status = OrganizationUserStatusType.Confirmed,
|
|
Email = user1.Email
|
|
});
|
|
|
|
var orgUser2 = await organizationUserRepository.CreateAsync(new OrganizationUser
|
|
{
|
|
OrganizationId = organization.Id,
|
|
UserId = user2.Id,
|
|
Status = OrganizationUserStatusType.Confirmed,
|
|
Email = user2.Email
|
|
});
|
|
|
|
await organizationUserRepository.DeleteManyAsync(new List<Guid>
|
|
{
|
|
orgUser1.Id,
|
|
orgUser2.Id,
|
|
});
|
|
|
|
var updatedUser1 = await userRepository.GetByIdAsync(user1.Id);
|
|
Assert.NotNull(updatedUser1);
|
|
var updatedUser2 = await userRepository.GetByIdAsync(user2.Id);
|
|
Assert.NotNull(updatedUser2);
|
|
|
|
Assert.NotEqual(updatedUser1.AccountRevisionDate, user1.AccountRevisionDate);
|
|
Assert.NotEqual(updatedUser2.AccountRevisionDate, user2.AccountRevisionDate);
|
|
}
|
|
|
|
[DatabaseTheory, DatabaseData]
|
|
public async Task GetManyAccountRecoveryDetailsByOrganizationUserAsync_Works(IUserRepository userRepository,
|
|
IOrganizationRepository organizationRepository,
|
|
IOrganizationUserRepository organizationUserRepository)
|
|
{
|
|
var user1 = await userRepository.CreateAsync(new User
|
|
{
|
|
Name = "Test User 1",
|
|
Email = $"test+{Guid.NewGuid()}@example.com",
|
|
ApiKey = "TEST",
|
|
SecurityStamp = "stamp",
|
|
Kdf = KdfType.PBKDF2_SHA256,
|
|
KdfIterations = 1,
|
|
KdfMemory = 2,
|
|
KdfParallelism = 3
|
|
});
|
|
|
|
var user2 = await userRepository.CreateAsync(new User
|
|
{
|
|
Name = "Test User 2",
|
|
Email = $"test+{Guid.NewGuid()}@example.com",
|
|
ApiKey = "TEST",
|
|
SecurityStamp = "stamp",
|
|
Kdf = KdfType.Argon2id,
|
|
KdfIterations = 4,
|
|
KdfMemory = 5,
|
|
KdfParallelism = 6
|
|
});
|
|
|
|
var organization = await organizationRepository.CreateAsync(new Organization
|
|
{
|
|
Name = "Test Org",
|
|
BillingEmail = user1.Email, // TODO: EF does not enforce this being NOT NULL
|
|
Plan = "Test", // TODO: EF does not enforce this being NOT NULL
|
|
PrivateKey = "privatekey",
|
|
});
|
|
|
|
var orgUser1 = await organizationUserRepository.CreateAsync(new OrganizationUser
|
|
{
|
|
Id = CoreHelpers.GenerateComb(),
|
|
OrganizationId = organization.Id,
|
|
UserId = user1.Id,
|
|
Status = OrganizationUserStatusType.Confirmed,
|
|
Type = OrganizationUserType.Owner,
|
|
ResetPasswordKey = "resetpasswordkey1",
|
|
AccessSecretsManager = false
|
|
});
|
|
|
|
var orgUser2 = await organizationUserRepository.CreateAsync(new OrganizationUser
|
|
{
|
|
Id = CoreHelpers.GenerateComb(),
|
|
OrganizationId = organization.Id,
|
|
UserId = user2.Id,
|
|
Status = OrganizationUserStatusType.Invited,
|
|
Type = OrganizationUserType.User,
|
|
ResetPasswordKey = "resetpasswordkey2",
|
|
AccessSecretsManager = true
|
|
});
|
|
|
|
var recoveryDetails = await organizationUserRepository.GetManyAccountRecoveryDetailsByOrganizationUserAsync(
|
|
organization.Id,
|
|
new[]
|
|
{
|
|
orgUser1.Id,
|
|
orgUser2.Id,
|
|
});
|
|
|
|
Assert.NotNull(recoveryDetails);
|
|
Assert.Equal(2, recoveryDetails.Count());
|
|
Assert.Contains(recoveryDetails, r =>
|
|
r.OrganizationUserId == orgUser1.Id &&
|
|
r.Kdf == KdfType.PBKDF2_SHA256 &&
|
|
r.KdfIterations == 1 &&
|
|
r.KdfMemory == 2 &&
|
|
r.KdfParallelism == 3 &&
|
|
r.ResetPasswordKey == "resetpasswordkey1" &&
|
|
r.EncryptedPrivateKey == "privatekey");
|
|
Assert.Contains(recoveryDetails, r =>
|
|
r.OrganizationUserId == orgUser2.Id &&
|
|
r.Kdf == KdfType.Argon2id &&
|
|
r.KdfIterations == 4 &&
|
|
r.KdfMemory == 5 &&
|
|
r.KdfParallelism == 6 &&
|
|
r.ResetPasswordKey == "resetpasswordkey2" &&
|
|
r.EncryptedPrivateKey == "privatekey");
|
|
}
|
|
|
|
[DatabaseTheory, DatabaseData]
|
|
public async Task GetManyDetailsByOrganizationAsync_WithIncludeCollections_ExcludesDefaultCollections(
|
|
IUserRepository userRepository,
|
|
IOrganizationRepository organizationRepository,
|
|
IOrganizationUserRepository organizationUserRepository,
|
|
ICollectionRepository collectionRepository)
|
|
{
|
|
var user = await userRepository.CreateAsync(new User
|
|
{
|
|
Name = "Test User",
|
|
Email = $"test+{Guid.NewGuid()}@example.com",
|
|
ApiKey = "TEST",
|
|
SecurityStamp = "stamp",
|
|
});
|
|
|
|
var organization = await organizationRepository.CreateAsync(new Organization
|
|
{
|
|
Name = "Test Org",
|
|
BillingEmail = user.Email,
|
|
Plan = "Test",
|
|
});
|
|
|
|
var orgUser = await organizationUserRepository.CreateAsync(new OrganizationUser
|
|
{
|
|
OrganizationId = organization.Id,
|
|
UserId = user.Id,
|
|
Status = OrganizationUserStatusType.Confirmed,
|
|
});
|
|
|
|
// Create a regular collection
|
|
var regularCollection = await collectionRepository.CreateAsync(new Collection
|
|
{
|
|
OrganizationId = organization.Id,
|
|
Name = "Regular Collection",
|
|
Type = CollectionType.SharedCollection
|
|
});
|
|
|
|
// Create a default user collection
|
|
var defaultCollection = await collectionRepository.CreateAsync(new Collection
|
|
{
|
|
OrganizationId = organization.Id,
|
|
Name = "Default Collection",
|
|
Type = CollectionType.DefaultUserCollection,
|
|
DefaultUserCollectionEmail = user.Email
|
|
});
|
|
|
|
// Assign the organization user to both collections
|
|
await organizationUserRepository.ReplaceAsync(orgUser, new List<CollectionAccessSelection>
|
|
{
|
|
new CollectionAccessSelection
|
|
{
|
|
Id = regularCollection.Id,
|
|
ReadOnly = false,
|
|
HidePasswords = false,
|
|
Manage = true
|
|
},
|
|
new CollectionAccessSelection
|
|
{
|
|
Id = defaultCollection.Id,
|
|
ReadOnly = false,
|
|
HidePasswords = false,
|
|
Manage = true
|
|
}
|
|
});
|
|
|
|
// Get organization users with collections included
|
|
var organizationUsers = await organizationUserRepository.GetManyDetailsByOrganizationAsync(
|
|
organization.Id, includeGroups: false, includeCollections: true);
|
|
|
|
Assert.NotNull(organizationUsers);
|
|
Assert.Single(organizationUsers);
|
|
|
|
var orgUserWithCollections = organizationUsers.First();
|
|
Assert.NotNull(orgUserWithCollections.Collections);
|
|
|
|
// Should only include the regular collection, not the default collection
|
|
Assert.Single(orgUserWithCollections.Collections);
|
|
Assert.Equal(regularCollection.Id, orgUserWithCollections.Collections.First().Id);
|
|
Assert.DoesNotContain(orgUserWithCollections.Collections, c => c.Id == defaultCollection.Id);
|
|
}
|
|
|
|
[DatabaseTheory, DatabaseData]
|
|
public async Task GetManyDetailsByUserAsync_Works(IUserRepository userRepository,
|
|
IOrganizationRepository organizationRepository,
|
|
IOrganizationUserRepository organizationUserRepository,
|
|
ISsoConfigRepository ssoConfigRepository)
|
|
{
|
|
var user1 = await userRepository.CreateAsync(new User
|
|
{
|
|
Name = "Test User 1",
|
|
Email = $"test+{Guid.NewGuid()}@example.com",
|
|
ApiKey = "TEST",
|
|
SecurityStamp = "stamp",
|
|
Kdf = KdfType.PBKDF2_SHA256,
|
|
KdfIterations = 1,
|
|
KdfMemory = 2,
|
|
KdfParallelism = 3
|
|
});
|
|
|
|
var organization = await organizationRepository.CreateTestOrganizationAsync();
|
|
|
|
var orgUser1 = await organizationUserRepository.CreateAsync(new OrganizationUser
|
|
{
|
|
Id = CoreHelpers.GenerateComb(),
|
|
OrganizationId = organization.Id,
|
|
UserId = user1.Id,
|
|
Status = OrganizationUserStatusType.Confirmed,
|
|
Type = OrganizationUserType.Owner,
|
|
ResetPasswordKey = "resetpasswordkey1",
|
|
AccessSecretsManager = false
|
|
});
|
|
|
|
var ssoConfigData = new SsoConfigurationData
|
|
{
|
|
MemberDecryptionType = MemberDecryptionType.TrustedDeviceEncryption
|
|
};
|
|
|
|
var ssoConfig = await ssoConfigRepository.CreateAsync(new SsoConfig
|
|
{
|
|
OrganizationId = organization.Id,
|
|
Enabled = true,
|
|
Data = ssoConfigData.Serialize()
|
|
});
|
|
|
|
var responseModel = await organizationUserRepository.GetManyDetailsByUserAsync(user1.Id);
|
|
|
|
Assert.NotNull(responseModel);
|
|
Assert.Single(responseModel);
|
|
var result = responseModel.Single();
|
|
Assert.Equal(organization.Id, result.OrganizationId);
|
|
Assert.Equal(user1.Id, result.UserId);
|
|
Assert.Equal(orgUser1.Id, result.OrganizationUserId);
|
|
Assert.Equal(organization.Name, result.Name);
|
|
Assert.Equal(organization.UsePolicies, result.UsePolicies);
|
|
Assert.Equal(organization.UseSso, result.UseSso);
|
|
Assert.Equal(organization.UseKeyConnector, result.UseKeyConnector);
|
|
Assert.Equal(ssoConfig.Enabled, result.SsoEnabled);
|
|
Assert.Equal(ssoConfig.Data, result.SsoConfig);
|
|
Assert.Equal(organization.UseScim, result.UseScim);
|
|
Assert.Equal(organization.UseGroups, result.UseGroups);
|
|
Assert.Equal(organization.UseDirectory, result.UseDirectory);
|
|
Assert.Equal(organization.UseEvents, result.UseEvents);
|
|
Assert.Equal(organization.UseTotp, result.UseTotp);
|
|
Assert.Equal(organization.Use2fa, result.Use2fa);
|
|
Assert.Equal(organization.UseApi, result.UseApi);
|
|
Assert.Equal(organization.UseResetPassword, result.UseResetPassword);
|
|
Assert.Equal(organization.UseSecretsManager, result.UseSecretsManager);
|
|
Assert.Equal(organization.UsePasswordManager, result.UsePasswordManager);
|
|
Assert.Equal(organization.UsersGetPremium, result.UsersGetPremium);
|
|
Assert.Equal(organization.UseCustomPermissions, result.UseCustomPermissions);
|
|
Assert.Equal(organization.SelfHost, result.SelfHost);
|
|
Assert.Equal(organization.Seats, result.Seats);
|
|
Assert.Equal(organization.MaxCollections, result.MaxCollections);
|
|
Assert.Equal(organization.MaxStorageGb, result.MaxStorageGb);
|
|
Assert.Equal(organization.Identifier, result.Identifier);
|
|
Assert.Equal(orgUser1.Key, result.Key);
|
|
Assert.Equal(orgUser1.ResetPasswordKey, result.ResetPasswordKey);
|
|
Assert.Equal(organization.PublicKey, result.PublicKey);
|
|
Assert.Equal(organization.PrivateKey, result.PrivateKey);
|
|
Assert.Equal(orgUser1.Status, result.Status);
|
|
Assert.Equal(orgUser1.Type, result.Type);
|
|
Assert.Equal(organization.Enabled, result.Enabled);
|
|
Assert.Equal(organization.PlanType, result.PlanType);
|
|
Assert.Equal(orgUser1.Permissions, result.Permissions);
|
|
Assert.Equal(organization.SmSeats, result.SmSeats);
|
|
Assert.Equal(organization.SmServiceAccounts, result.SmServiceAccounts);
|
|
Assert.Equal(organization.LimitCollectionCreation, result.LimitCollectionCreation);
|
|
Assert.Equal(organization.LimitCollectionDeletion, result.LimitCollectionDeletion);
|
|
Assert.Equal(organization.LimitItemDeletion, result.LimitItemDeletion);
|
|
Assert.Equal(organization.AllowAdminAccessToAllCollectionItems, result.AllowAdminAccessToAllCollectionItems);
|
|
Assert.Equal(organization.UseRiskInsights, result.UseRiskInsights);
|
|
Assert.Equal(organization.UseOrganizationDomains, result.UseOrganizationDomains);
|
|
Assert.Equal(organization.UseAdminSponsoredFamilies, result.UseAdminSponsoredFamilies);
|
|
Assert.Equal(organization.UseAutomaticUserConfirmation, result.UseAutomaticUserConfirmation);
|
|
}
|
|
|
|
[Theory, DatabaseData]
|
|
public async Task GetManyDetailsByUserAsync_ShouldPopulateSsoPropertiesCorrectly(
|
|
IUserRepository userRepository,
|
|
IOrganizationRepository organizationRepository,
|
|
IOrganizationUserRepository organizationUserRepository,
|
|
ISsoConfigRepository ssoConfigRepository)
|
|
{
|
|
var user = await userRepository.CreateTestUserAsync();
|
|
var organizationWithSso = await organizationRepository.CreateTestOrganizationAsync();
|
|
var organizationWithoutSso = await organizationRepository.CreateTestOrganizationAsync();
|
|
|
|
var orgUserWithSso = await organizationUserRepository.CreateAsync(new OrganizationUser
|
|
{
|
|
OrganizationId = organizationWithSso.Id,
|
|
UserId = user.Id,
|
|
Status = OrganizationUserStatusType.Confirmed,
|
|
Type = OrganizationUserType.Owner,
|
|
Email = user.Email
|
|
});
|
|
|
|
var orgUserWithoutSso = await organizationUserRepository.CreateAsync(new OrganizationUser
|
|
{
|
|
OrganizationId = organizationWithoutSso.Id,
|
|
UserId = user.Id,
|
|
Status = OrganizationUserStatusType.Confirmed,
|
|
Type = OrganizationUserType.User,
|
|
Email = user.Email
|
|
});
|
|
|
|
// Create SSO configuration for first organization only
|
|
var serializedSsoConfigData = new SsoConfigurationData
|
|
{
|
|
MemberDecryptionType = MemberDecryptionType.KeyConnector,
|
|
KeyConnectorUrl = "https://keyconnector.example.com"
|
|
}.Serialize();
|
|
|
|
var ssoConfig = await ssoConfigRepository.CreateAsync(new SsoConfig
|
|
{
|
|
OrganizationId = organizationWithSso.Id,
|
|
Enabled = true,
|
|
Data = serializedSsoConfigData
|
|
});
|
|
|
|
var results = (await organizationUserRepository.GetManyDetailsByUserAsync(user.Id)).ToList();
|
|
|
|
Assert.Equal(2, results.Count);
|
|
|
|
var orgWithSsoDetails = results.Single(r => r.OrganizationId == organizationWithSso.Id);
|
|
var orgWithoutSsoDetails = results.Single(r => r.OrganizationId == organizationWithoutSso.Id);
|
|
|
|
// Organization with SSO should have SSO properties populated
|
|
Assert.True(orgWithSsoDetails.SsoEnabled);
|
|
Assert.NotNull(orgWithSsoDetails.SsoConfig);
|
|
Assert.Equal(serializedSsoConfigData, orgWithSsoDetails.SsoConfig);
|
|
|
|
// Organization without SSO should have null SSO properties
|
|
Assert.Null(orgWithoutSsoDetails.SsoEnabled);
|
|
Assert.Null(orgWithoutSsoDetails.SsoConfig);
|
|
}
|
|
|
|
[DatabaseTheory, DatabaseData]
|
|
public async Task GetManyByOrganizationWithClaimedDomainsAsync_WithVerifiedDomain_WithOneMatchingEmailDomain_ReturnsSingle(
|
|
IUserRepository userRepository,
|
|
IOrganizationRepository organizationRepository,
|
|
IOrganizationUserRepository organizationUserRepository,
|
|
IOrganizationDomainRepository organizationDomainRepository)
|
|
{
|
|
var id = Guid.NewGuid();
|
|
var domainName = $"{id}.example.com";
|
|
|
|
var user1 = await userRepository.CreateAsync(new User
|
|
{
|
|
Name = "Test User 1",
|
|
Email = $"test+{id}@{domainName}",
|
|
ApiKey = "TEST",
|
|
SecurityStamp = "stamp",
|
|
Kdf = KdfType.PBKDF2_SHA256,
|
|
KdfIterations = 1,
|
|
KdfMemory = 2,
|
|
KdfParallelism = 3
|
|
});
|
|
|
|
var user2 = await userRepository.CreateAsync(new User
|
|
{
|
|
Name = "Test User 2",
|
|
Email = $"test+{id}@x-{domainName}", // Different domain
|
|
ApiKey = "TEST",
|
|
SecurityStamp = "stamp",
|
|
Kdf = KdfType.PBKDF2_SHA256,
|
|
KdfIterations = 1,
|
|
KdfMemory = 2,
|
|
KdfParallelism = 3
|
|
});
|
|
|
|
var user3 = await userRepository.CreateAsync(new User
|
|
{
|
|
Name = "Test User 2",
|
|
Email = $"test+{id}@{domainName}.example.com", // Different domain
|
|
ApiKey = "TEST",
|
|
SecurityStamp = "stamp",
|
|
Kdf = KdfType.PBKDF2_SHA256,
|
|
KdfIterations = 1,
|
|
KdfMemory = 2,
|
|
KdfParallelism = 3
|
|
});
|
|
|
|
var organization = await organizationRepository.CreateAsync(new Organization
|
|
{
|
|
Name = $"Test Org {id}",
|
|
BillingEmail = user1.Email, // TODO: EF does not enforce this being NOT NULL
|
|
Plan = "Test", // TODO: EF does not enforce this being NOT NULL
|
|
PrivateKey = "privatekey",
|
|
UsePolicies = false,
|
|
UseSso = false,
|
|
UseKeyConnector = false,
|
|
UseScim = false,
|
|
UseGroups = false,
|
|
UseDirectory = false,
|
|
UseEvents = false,
|
|
UseTotp = false,
|
|
Use2fa = false,
|
|
UseApi = false,
|
|
UseResetPassword = false,
|
|
UseSecretsManager = false,
|
|
SelfHost = false,
|
|
UsersGetPremium = false,
|
|
UseCustomPermissions = false,
|
|
Enabled = true,
|
|
UsePasswordManager = false,
|
|
LimitCollectionCreation = false,
|
|
LimitCollectionDeletion = false,
|
|
LimitItemDeletion = false,
|
|
AllowAdminAccessToAllCollectionItems = false,
|
|
UseRiskInsights = false,
|
|
UseAdminSponsoredFamilies = false
|
|
});
|
|
|
|
var organizationDomain = new OrganizationDomain
|
|
{
|
|
OrganizationId = organization.Id,
|
|
DomainName = domainName,
|
|
Txt = "btw+12345",
|
|
};
|
|
organizationDomain.SetVerifiedDate();
|
|
organizationDomain.SetNextRunDate(12);
|
|
organizationDomain.SetJobRunCount();
|
|
await organizationDomainRepository.CreateAsync(organizationDomain);
|
|
|
|
var orgUser1 = await organizationUserRepository.CreateAsync(new OrganizationUser
|
|
{
|
|
Id = CoreHelpers.GenerateComb(),
|
|
OrganizationId = organization.Id,
|
|
UserId = user1.Id,
|
|
Status = OrganizationUserStatusType.Confirmed,
|
|
Type = OrganizationUserType.Owner,
|
|
ResetPasswordKey = "resetpasswordkey1",
|
|
AccessSecretsManager = false
|
|
});
|
|
|
|
await organizationUserRepository.CreateAsync(new OrganizationUser
|
|
{
|
|
Id = CoreHelpers.GenerateComb(),
|
|
OrganizationId = organization.Id,
|
|
UserId = user2.Id,
|
|
Status = OrganizationUserStatusType.Confirmed,
|
|
Type = OrganizationUserType.User,
|
|
ResetPasswordKey = "resetpasswordkey1",
|
|
AccessSecretsManager = false
|
|
});
|
|
|
|
await organizationUserRepository.CreateAsync(new OrganizationUser
|
|
{
|
|
Id = CoreHelpers.GenerateComb(),
|
|
OrganizationId = organization.Id,
|
|
UserId = user3.Id,
|
|
Status = OrganizationUserStatusType.Confirmed,
|
|
Type = OrganizationUserType.User,
|
|
ResetPasswordKey = "resetpasswordkey1",
|
|
AccessSecretsManager = false
|
|
});
|
|
|
|
var responseModel = await organizationUserRepository.GetManyByOrganizationWithClaimedDomainsAsync(organization.Id);
|
|
|
|
Assert.NotNull(responseModel);
|
|
Assert.Single(responseModel);
|
|
Assert.Equal(orgUser1.Id, responseModel.Single().Id);
|
|
}
|
|
|
|
[DatabaseTheory, DatabaseData]
|
|
public async Task CreateManyAsync_NoId_Works(IOrganizationRepository organizationRepository,
|
|
IUserRepository userRepository,
|
|
IOrganizationUserRepository organizationUserRepository)
|
|
{
|
|
// Arrange
|
|
var user1 = await userRepository.CreateTestUserAsync("user1");
|
|
var user2 = await userRepository.CreateTestUserAsync("user2");
|
|
var user3 = await userRepository.CreateTestUserAsync("user3");
|
|
List<User> users = [user1, user2, user3];
|
|
|
|
var org = await organizationRepository.CreateAsync(new Organization
|
|
{
|
|
Name = $"test-{Guid.NewGuid()}",
|
|
BillingEmail = "billing@example.com", // TODO: EF does not enforce this being NOT NULL
|
|
Plan = "Test", // TODO: EF does not enforce this being NOT NULL
|
|
});
|
|
|
|
var orgUsers = users.Select(u => new OrganizationUser
|
|
{
|
|
OrganizationId = org.Id,
|
|
UserId = u.Id,
|
|
Status = OrganizationUserStatusType.Confirmed,
|
|
Type = OrganizationUserType.Owner
|
|
});
|
|
|
|
var createdOrgUserIds = await organizationUserRepository.CreateManyAsync(orgUsers);
|
|
|
|
var readOrgUsers = await organizationUserRepository.GetManyByOrganizationAsync(org.Id, null);
|
|
var readOrgUserIds = readOrgUsers.Select(ou => ou.Id);
|
|
|
|
Assert.Equal(createdOrgUserIds.ToHashSet(), readOrgUserIds.ToHashSet());
|
|
}
|
|
|
|
[DatabaseTheory, DatabaseData]
|
|
public async Task CreateManyAsync_WithId_Works(IOrganizationRepository organizationRepository,
|
|
IUserRepository userRepository,
|
|
IOrganizationUserRepository organizationUserRepository)
|
|
{
|
|
// Arrange
|
|
var user1 = await userRepository.CreateTestUserAsync("user1");
|
|
var user2 = await userRepository.CreateTestUserAsync("user2");
|
|
var user3 = await userRepository.CreateTestUserAsync("user3");
|
|
List<User> users = [user1, user2, user3];
|
|
|
|
var org = await organizationRepository.CreateAsync(new Organization
|
|
{
|
|
Name = $"test-{Guid.NewGuid()}",
|
|
BillingEmail = "billing@example.com", // TODO: EF does not enforce this being NOT NULL
|
|
Plan = "Test", // TODO: EF does not enforce this being NOT NULL
|
|
});
|
|
|
|
var orgUsers = users.Select(u => new OrganizationUser
|
|
{
|
|
Id = CoreHelpers.GenerateComb(), // generate ID ahead of time
|
|
OrganizationId = org.Id,
|
|
UserId = u.Id,
|
|
Status = OrganizationUserStatusType.Confirmed,
|
|
Type = OrganizationUserType.Owner
|
|
});
|
|
|
|
var createdOrgUserIds = await organizationUserRepository.CreateManyAsync(orgUsers);
|
|
|
|
var readOrgUsers = await organizationUserRepository.GetManyByOrganizationAsync(org.Id, null);
|
|
var readOrgUserIds = readOrgUsers.Select(ou => ou.Id);
|
|
|
|
Assert.Equal(createdOrgUserIds.ToHashSet(), readOrgUserIds.ToHashSet());
|
|
}
|
|
|
|
[DatabaseTheory, DatabaseData]
|
|
public async Task CreateManyAsync_WithCollectionAndGroup_SaveSuccessfully(
|
|
IOrganizationUserRepository organizationUserRepository,
|
|
IOrganizationRepository organizationRepository,
|
|
ICollectionRepository collectionRepository,
|
|
IGroupRepository groupRepository)
|
|
{
|
|
var requestTime = DateTime.UtcNow;
|
|
|
|
var organization = await organizationRepository.CreateAsync(new Organization
|
|
{
|
|
Name = "Test Org",
|
|
BillingEmail = "billing@test.com", // TODO: EF does not enforce this being NOT NULL
|
|
Plan = "Test", // TODO: EF does not enforce this being NOT NULL,
|
|
CreationDate = requestTime
|
|
});
|
|
|
|
var collection1 = await collectionRepository.CreateAsync(new Collection
|
|
{
|
|
Id = CoreHelpers.GenerateComb(),
|
|
OrganizationId = organization.Id,
|
|
Name = "Test Collection",
|
|
ExternalId = "external-collection-1",
|
|
CreationDate = requestTime,
|
|
RevisionDate = requestTime
|
|
});
|
|
var collection2 = await collectionRepository.CreateAsync(new Collection
|
|
{
|
|
Id = CoreHelpers.GenerateComb(),
|
|
OrganizationId = organization.Id,
|
|
Name = "Test Collection",
|
|
ExternalId = "external-collection-1",
|
|
CreationDate = requestTime,
|
|
RevisionDate = requestTime
|
|
});
|
|
var collection3 = await collectionRepository.CreateAsync(new Collection
|
|
{
|
|
Id = CoreHelpers.GenerateComb(),
|
|
OrganizationId = organization.Id,
|
|
Name = "Test Collection",
|
|
ExternalId = "external-collection-1",
|
|
CreationDate = requestTime,
|
|
RevisionDate = requestTime
|
|
});
|
|
|
|
// Create a default user collection that should be excluded from admin results
|
|
var defaultCollection = await collectionRepository.CreateAsync(new Collection
|
|
{
|
|
Id = CoreHelpers.GenerateComb(),
|
|
OrganizationId = organization.Id,
|
|
Name = "My Items",
|
|
Type = CollectionType.DefaultUserCollection,
|
|
CreationDate = requestTime,
|
|
RevisionDate = requestTime
|
|
});
|
|
|
|
var group1 = await groupRepository.CreateAsync(new Group
|
|
{
|
|
Id = CoreHelpers.GenerateComb(),
|
|
OrganizationId = organization.Id,
|
|
Name = "Test Group",
|
|
ExternalId = "external-group-1"
|
|
});
|
|
var group2 = await groupRepository.CreateAsync(new Group
|
|
{
|
|
Id = CoreHelpers.GenerateComb(),
|
|
OrganizationId = organization.Id,
|
|
Name = "Test Group",
|
|
ExternalId = "external-group-1"
|
|
});
|
|
var group3 = await groupRepository.CreateAsync(new Group
|
|
{
|
|
Id = CoreHelpers.GenerateComb(),
|
|
OrganizationId = organization.Id,
|
|
Name = "Test Group",
|
|
ExternalId = "external-group-1"
|
|
});
|
|
|
|
|
|
var orgUserCollection = new List<CreateOrganizationUser>
|
|
{
|
|
new()
|
|
{
|
|
OrganizationUser = new OrganizationUser
|
|
{
|
|
Id = CoreHelpers.GenerateComb(),
|
|
OrganizationId = organization.Id,
|
|
Email = "test-user@test.com",
|
|
Status = OrganizationUserStatusType.Invited,
|
|
Type = OrganizationUserType.Owner,
|
|
ExternalId = "externalid-1",
|
|
Permissions = CoreHelpers.ClassToJsonData(new Permissions()),
|
|
AccessSecretsManager = false
|
|
},
|
|
Collections =
|
|
[
|
|
new CollectionAccessSelection
|
|
{
|
|
Id = collection1.Id,
|
|
ReadOnly = true,
|
|
HidePasswords = false,
|
|
Manage = false
|
|
},
|
|
new CollectionAccessSelection
|
|
{
|
|
Id = defaultCollection.Id,
|
|
ReadOnly = false,
|
|
HidePasswords = false,
|
|
Manage = true
|
|
}
|
|
],
|
|
Groups = [group1.Id]
|
|
},
|
|
new()
|
|
{
|
|
OrganizationUser = new OrganizationUser
|
|
{
|
|
Id = CoreHelpers.GenerateComb(),
|
|
OrganizationId = organization.Id,
|
|
Email = "test-user@test.com",
|
|
Status = OrganizationUserStatusType.Invited,
|
|
Type = OrganizationUserType.Owner,
|
|
ExternalId = "externalid-1",
|
|
Permissions = CoreHelpers.ClassToJsonData(new Permissions()),
|
|
AccessSecretsManager = false
|
|
},
|
|
Collections =
|
|
[
|
|
new CollectionAccessSelection
|
|
{
|
|
Id = collection2.Id,
|
|
ReadOnly = true,
|
|
HidePasswords = false,
|
|
Manage = false
|
|
}
|
|
],
|
|
Groups = [group2.Id]
|
|
},
|
|
new()
|
|
{
|
|
OrganizationUser = new OrganizationUser
|
|
{
|
|
Id = CoreHelpers.GenerateComb(),
|
|
OrganizationId = organization.Id,
|
|
Email = "test-user@test.com",
|
|
Status = OrganizationUserStatusType.Invited,
|
|
Type = OrganizationUserType.Owner,
|
|
ExternalId = "externalid-1",
|
|
Permissions = CoreHelpers.ClassToJsonData(new Permissions()),
|
|
AccessSecretsManager = false
|
|
},
|
|
Collections =
|
|
[
|
|
new CollectionAccessSelection
|
|
{
|
|
Id = collection3.Id,
|
|
ReadOnly = true,
|
|
HidePasswords = false,
|
|
Manage = false
|
|
}
|
|
],
|
|
Groups = [group3.Id]
|
|
}
|
|
};
|
|
|
|
await organizationUserRepository.CreateManyAsync(orgUserCollection);
|
|
|
|
var orgUser1 = await organizationUserRepository.GetDetailsByIdWithCollectionsAsync(orgUserCollection[0].OrganizationUser.Id);
|
|
var group1Database = await groupRepository.GetManyIdsByUserIdAsync(orgUserCollection[0].OrganizationUser.Id);
|
|
Assert.Equal(orgUserCollection[0].OrganizationUser.Id, orgUser1.OrganizationUser.Id);
|
|
|
|
// Should only return the regular collection, not the default collection (even though both were assigned)
|
|
Assert.Single(orgUser1.Collections);
|
|
Assert.Equal(collection1.Id, orgUser1.Collections.First().Id);
|
|
Assert.DoesNotContain(orgUser1.Collections, c => c.Id == defaultCollection.Id);
|
|
Assert.Equal(group1.Id, group1Database.First());
|
|
|
|
|
|
var orgUser2 = await organizationUserRepository.GetDetailsByIdWithCollectionsAsync(orgUserCollection[1].OrganizationUser.Id);
|
|
var group2Database = await groupRepository.GetManyIdsByUserIdAsync(orgUserCollection[1].OrganizationUser.Id);
|
|
Assert.Equal(orgUserCollection[1].OrganizationUser.Id, orgUser2.OrganizationUser.Id);
|
|
Assert.Equal(collection2.Id, orgUser2.Collections.First().Id);
|
|
Assert.Equal(group2.Id, group2Database.First());
|
|
|
|
var orgUser3 = await organizationUserRepository.GetDetailsByIdWithCollectionsAsync(orgUserCollection[2].OrganizationUser.Id);
|
|
var group3Database = await groupRepository.GetManyIdsByUserIdAsync(orgUserCollection[2].OrganizationUser.Id);
|
|
Assert.Equal(orgUserCollection[2].OrganizationUser.Id, orgUser3.OrganizationUser.Id);
|
|
Assert.Equal(collection3.Id, orgUser3.Collections.First().Id);
|
|
Assert.Equal(group3.Id, group3Database.First());
|
|
}
|
|
|
|
[DatabaseTheory, DatabaseData]
|
|
public async Task GetManyDetailsByOrganizationAsync_vNext_WithoutGroupsAndCollections_ReturnsBasicUserDetails(
|
|
IUserRepository userRepository,
|
|
IOrganizationRepository organizationRepository,
|
|
IOrganizationUserRepository organizationUserRepository)
|
|
{
|
|
var id = Guid.NewGuid();
|
|
|
|
var user1 = await userRepository.CreateAsync(new User
|
|
{
|
|
Id = CoreHelpers.GenerateComb(),
|
|
Name = "Test User 1",
|
|
Email = $"test1+{id}@example.com",
|
|
ApiKey = "TEST",
|
|
SecurityStamp = "stamp",
|
|
Kdf = KdfType.PBKDF2_SHA256,
|
|
KdfIterations = 1,
|
|
KdfMemory = 2,
|
|
KdfParallelism = 3
|
|
});
|
|
|
|
var user2 = await userRepository.CreateAsync(new User
|
|
{
|
|
Id = CoreHelpers.GenerateComb(),
|
|
Name = "Test User 2",
|
|
Email = $"test2+{id}@example.com",
|
|
ApiKey = "TEST",
|
|
SecurityStamp = "stamp",
|
|
Kdf = KdfType.Argon2id,
|
|
KdfIterations = 4,
|
|
KdfMemory = 5,
|
|
KdfParallelism = 6
|
|
});
|
|
|
|
var organization = await organizationRepository.CreateAsync(new Organization
|
|
{
|
|
Id = CoreHelpers.GenerateComb(),
|
|
Name = $"Test Org {id}",
|
|
BillingEmail = user1.Email,
|
|
Plan = "Test",
|
|
PrivateKey = "privatekey",
|
|
PublicKey = "publickey",
|
|
UseGroups = true,
|
|
Enabled = true,
|
|
UsePasswordManager = true
|
|
});
|
|
|
|
var orgUser1 = await organizationUserRepository.CreateAsync(new OrganizationUser
|
|
{
|
|
Id = CoreHelpers.GenerateComb(),
|
|
OrganizationId = organization.Id,
|
|
UserId = user1.Id,
|
|
Status = OrganizationUserStatusType.Confirmed,
|
|
Type = OrganizationUserType.Owner,
|
|
ResetPasswordKey = "resetpasswordkey1",
|
|
AccessSecretsManager = false
|
|
});
|
|
|
|
var orgUser2 = await organizationUserRepository.CreateAsync(new OrganizationUser
|
|
{
|
|
Id = CoreHelpers.GenerateComb(),
|
|
OrganizationId = organization.Id,
|
|
UserId = user2.Id,
|
|
Status = OrganizationUserStatusType.Invited,
|
|
Type = OrganizationUserType.User,
|
|
ResetPasswordKey = "resetpasswordkey2",
|
|
AccessSecretsManager = true
|
|
});
|
|
|
|
var responseModel = await organizationUserRepository.GetManyDetailsByOrganizationAsync_vNext(organization.Id, includeGroups: false, includeCollections: false);
|
|
|
|
Assert.NotNull(responseModel);
|
|
Assert.Equal(2, responseModel.Count);
|
|
|
|
var user1Result = responseModel.FirstOrDefault(u => u.Id == orgUser1.Id);
|
|
Assert.NotNull(user1Result);
|
|
Assert.Equal(user1.Name, user1Result.Name);
|
|
Assert.Equal(user1.Email, user1Result.Email);
|
|
Assert.Equal(orgUser1.Status, user1Result.Status);
|
|
Assert.Equal(orgUser1.Type, user1Result.Type);
|
|
Assert.Equal(organization.Id, user1Result.OrganizationId);
|
|
Assert.Equal(user1.Id, user1Result.UserId);
|
|
Assert.Empty(user1Result.Groups);
|
|
Assert.Empty(user1Result.Collections);
|
|
|
|
var user2Result = responseModel.FirstOrDefault(u => u.Id == orgUser2.Id);
|
|
Assert.NotNull(user2Result);
|
|
Assert.Equal(user2.Name, user2Result.Name);
|
|
Assert.Equal(user2.Email, user2Result.Email);
|
|
Assert.Equal(orgUser2.Status, user2Result.Status);
|
|
Assert.Equal(orgUser2.Type, user2Result.Type);
|
|
Assert.Equal(organization.Id, user2Result.OrganizationId);
|
|
Assert.Equal(user2.Id, user2Result.UserId);
|
|
Assert.Empty(user2Result.Groups);
|
|
Assert.Empty(user2Result.Collections);
|
|
}
|
|
|
|
[DatabaseTheory, DatabaseData]
|
|
public async Task GetManyDetailsByOrganizationAsync_vNext_WithGroupsAndCollections_ReturnsUserDetailsWithBoth(
|
|
IUserRepository userRepository,
|
|
IOrganizationRepository organizationRepository,
|
|
IOrganizationUserRepository organizationUserRepository,
|
|
IGroupRepository groupRepository,
|
|
ICollectionRepository collectionRepository)
|
|
{
|
|
var id = Guid.NewGuid();
|
|
var requestTime = DateTime.UtcNow;
|
|
|
|
var user1 = await userRepository.CreateAsync(new User
|
|
{
|
|
Id = CoreHelpers.GenerateComb(),
|
|
Name = "Test User 1",
|
|
Email = $"test1+{id}@example.com",
|
|
ApiKey = "TEST",
|
|
SecurityStamp = "stamp",
|
|
Kdf = KdfType.PBKDF2_SHA256,
|
|
KdfIterations = 1,
|
|
KdfMemory = 2,
|
|
KdfParallelism = 3
|
|
});
|
|
|
|
var organization = await organizationRepository.CreateAsync(new Organization
|
|
{
|
|
Id = CoreHelpers.GenerateComb(),
|
|
Name = $"Test Org {id}",
|
|
BillingEmail = user1.Email,
|
|
Plan = "Test",
|
|
PrivateKey = "privatekey",
|
|
PublicKey = "publickey",
|
|
UseGroups = true,
|
|
Enabled = true
|
|
});
|
|
|
|
var group1 = await groupRepository.CreateAsync(new Group
|
|
{
|
|
Id = CoreHelpers.GenerateComb(),
|
|
OrganizationId = organization.Id,
|
|
Name = "Test Group 1",
|
|
ExternalId = "external-group-1"
|
|
});
|
|
|
|
var group2 = await groupRepository.CreateAsync(new Group
|
|
{
|
|
Id = CoreHelpers.GenerateComb(),
|
|
OrganizationId = organization.Id,
|
|
Name = "Test Group 2",
|
|
ExternalId = "external-group-2"
|
|
});
|
|
|
|
var collection1 = await collectionRepository.CreateAsync(new Collection
|
|
{
|
|
Id = CoreHelpers.GenerateComb(),
|
|
OrganizationId = organization.Id,
|
|
Name = "Test Collection 1",
|
|
ExternalId = "external-collection-1",
|
|
CreationDate = requestTime,
|
|
RevisionDate = requestTime
|
|
});
|
|
|
|
var collection2 = await collectionRepository.CreateAsync(new Collection
|
|
{
|
|
Id = CoreHelpers.GenerateComb(),
|
|
OrganizationId = organization.Id,
|
|
Name = "Test Collection 2",
|
|
ExternalId = "external-collection-2",
|
|
CreationDate = requestTime,
|
|
RevisionDate = requestTime
|
|
});
|
|
|
|
var defaultUserCollection = await collectionRepository.CreateAsync(new Collection
|
|
{
|
|
Id = CoreHelpers.GenerateComb(),
|
|
OrganizationId = organization.Id,
|
|
Name = "My Items",
|
|
Type = CollectionType.DefaultUserCollection,
|
|
DefaultUserCollectionEmail = user1.Email,
|
|
CreationDate = requestTime,
|
|
RevisionDate = requestTime
|
|
});
|
|
|
|
// Create organization user with both groups and collections using CreateManyAsync
|
|
var createOrgUserWithCollections = new List<CreateOrganizationUser>
|
|
{
|
|
new()
|
|
{
|
|
OrganizationUser = new OrganizationUser
|
|
{
|
|
Id = CoreHelpers.GenerateComb(),
|
|
OrganizationId = organization.Id,
|
|
UserId = user1.Id,
|
|
Status = OrganizationUserStatusType.Confirmed,
|
|
Type = OrganizationUserType.Owner,
|
|
AccessSecretsManager = false
|
|
},
|
|
Collections =
|
|
[
|
|
new CollectionAccessSelection
|
|
{
|
|
Id = collection1.Id,
|
|
ReadOnly = true,
|
|
HidePasswords = false,
|
|
Manage = false
|
|
},
|
|
new CollectionAccessSelection
|
|
{
|
|
Id = collection2.Id,
|
|
ReadOnly = false,
|
|
HidePasswords = true,
|
|
Manage = true
|
|
},
|
|
new CollectionAccessSelection
|
|
{
|
|
Id = defaultUserCollection.Id,
|
|
ReadOnly = false,
|
|
HidePasswords = false,
|
|
Manage = true
|
|
}
|
|
],
|
|
Groups = [group1.Id, group2.Id]
|
|
}
|
|
};
|
|
|
|
await organizationUserRepository.CreateManyAsync(createOrgUserWithCollections);
|
|
|
|
var responseModel = await organizationUserRepository.GetManyDetailsByOrganizationAsync_vNext(organization.Id, includeGroups: true, includeCollections: true);
|
|
|
|
Assert.NotNull(responseModel);
|
|
Assert.Single(responseModel);
|
|
|
|
var user1Result = responseModel.First();
|
|
|
|
Assert.Equal(user1.Name, user1Result.Name);
|
|
Assert.Equal(user1.Email, user1Result.Email);
|
|
Assert.Equal(organization.Id, user1Result.OrganizationId);
|
|
Assert.Equal(user1.Id, user1Result.UserId);
|
|
|
|
Assert.NotNull(user1Result.Groups);
|
|
Assert.Equal(2, user1Result.Groups.Count());
|
|
Assert.Contains(group1.Id, user1Result.Groups);
|
|
Assert.Contains(group2.Id, user1Result.Groups);
|
|
|
|
Assert.NotNull(user1Result.Collections);
|
|
Assert.Equal(2, user1Result.Collections.Count());
|
|
Assert.Contains(user1Result.Collections, c => c.Id == collection1.Id);
|
|
Assert.Contains(user1Result.Collections, c => c.Id == collection2.Id);
|
|
Assert.DoesNotContain(user1Result.Collections, c => c.Id == defaultUserCollection.Id);
|
|
}
|
|
|
|
[DatabaseTheory, DatabaseData]
|
|
public async Task GetManyByOrganizationWithClaimedDomainsAsync_WithNoVerifiedDomain_ReturnsEmpty(
|
|
IUserRepository userRepository,
|
|
IOrganizationRepository organizationRepository,
|
|
IOrganizationUserRepository organizationUserRepository,
|
|
IOrganizationDomainRepository organizationDomainRepository)
|
|
{
|
|
var id = Guid.NewGuid();
|
|
var domainName = $"{id}.example.com";
|
|
var requestTime = DateTime.UtcNow;
|
|
|
|
var user1 = await userRepository.CreateAsync(new User
|
|
{
|
|
Id = CoreHelpers.GenerateComb(),
|
|
Name = "Test User 1",
|
|
Email = $"test+{id}@{domainName}",
|
|
ApiKey = "TEST",
|
|
SecurityStamp = "stamp",
|
|
CreationDate = requestTime,
|
|
RevisionDate = requestTime,
|
|
AccountRevisionDate = requestTime
|
|
});
|
|
|
|
var organization = await organizationRepository.CreateAsync(new Organization
|
|
{
|
|
Id = CoreHelpers.GenerateComb(),
|
|
Name = $"Test Org {id}",
|
|
BillingEmail = user1.Email,
|
|
Plan = "Test",
|
|
Enabled = true,
|
|
CreationDate = requestTime,
|
|
RevisionDate = requestTime
|
|
});
|
|
|
|
// Create domain but do NOT verify it
|
|
var organizationDomain = new OrganizationDomain
|
|
{
|
|
Id = CoreHelpers.GenerateComb(),
|
|
OrganizationId = organization.Id,
|
|
DomainName = domainName,
|
|
Txt = "btw+12345",
|
|
CreationDate = requestTime
|
|
};
|
|
organizationDomain.SetNextRunDate(12);
|
|
// Note: NOT calling SetVerifiedDate()
|
|
await organizationDomainRepository.CreateAsync(organizationDomain);
|
|
|
|
await organizationUserRepository.CreateAsync(new OrganizationUser
|
|
{
|
|
Id = CoreHelpers.GenerateComb(),
|
|
OrganizationId = organization.Id,
|
|
UserId = user1.Id,
|
|
Status = OrganizationUserStatusType.Confirmed,
|
|
Type = OrganizationUserType.Owner,
|
|
CreationDate = requestTime,
|
|
RevisionDate = requestTime
|
|
});
|
|
|
|
var responseModel = await organizationUserRepository.GetManyByOrganizationWithClaimedDomainsAsync(organization.Id);
|
|
|
|
Assert.NotNull(responseModel);
|
|
Assert.Empty(responseModel);
|
|
}
|
|
|
|
[DatabaseTheory, DatabaseData]
|
|
public async Task DeleteAsync_WithNullEmail_DoesNotSetDefaultUserCollectionEmail(IUserRepository userRepository,
|
|
ICollectionRepository collectionRepository,
|
|
IOrganizationRepository organizationRepository,
|
|
IOrganizationUserRepository organizationUserRepository
|
|
)
|
|
{
|
|
var user = await userRepository.CreateAsync(new User
|
|
{
|
|
Name = "Test User",
|
|
Email = $"test+{Guid.NewGuid()}@example.com",
|
|
ApiKey = "TEST",
|
|
SecurityStamp = "stamp",
|
|
});
|
|
|
|
var organization = await organizationRepository.CreateAsync(new Organization
|
|
{
|
|
Name = "Test Org",
|
|
BillingEmail = user.Email,
|
|
Plan = "Test",
|
|
});
|
|
|
|
var orgUser = await organizationUserRepository.CreateAsync(new OrganizationUser
|
|
{
|
|
OrganizationId = organization.Id,
|
|
UserId = user.Id,
|
|
Status = OrganizationUserStatusType.Confirmed,
|
|
Email = null
|
|
});
|
|
|
|
var defaultUserCollection = await collectionRepository.CreateAsync(new Collection
|
|
{
|
|
Name = "Test Collection",
|
|
Id = user.Id,
|
|
Type = CollectionType.DefaultUserCollection,
|
|
OrganizationId = organization.Id
|
|
});
|
|
|
|
await collectionRepository.UpdateUsersAsync(defaultUserCollection.Id, new List<CollectionAccessSelection>()
|
|
{
|
|
new CollectionAccessSelection
|
|
{
|
|
Id = orgUser.Id,
|
|
HidePasswords = false,
|
|
ReadOnly = false,
|
|
Manage = true
|
|
},
|
|
});
|
|
|
|
await organizationUserRepository.DeleteAsync(orgUser);
|
|
|
|
var updatedCollection = await collectionRepository.GetByIdAsync(defaultUserCollection.Id);
|
|
Assert.NotNull(updatedCollection);
|
|
Assert.Equal(CollectionType.SharedCollection, updatedCollection.Type);
|
|
Assert.Equal(user.Email, updatedCollection.DefaultUserCollectionEmail);
|
|
}
|
|
|
|
[DatabaseTheory, DatabaseData]
|
|
public async Task DeleteAsync_WithEmptyEmail_DoesNotSetDefaultUserCollectionEmail(IUserRepository userRepository,
|
|
ICollectionRepository collectionRepository,
|
|
IOrganizationRepository organizationRepository,
|
|
IOrganizationUserRepository organizationUserRepository
|
|
)
|
|
{
|
|
var user = await userRepository.CreateAsync(new User
|
|
{
|
|
Name = "Test User",
|
|
Email = $"test+{Guid.NewGuid()}@example.com",
|
|
ApiKey = "TEST",
|
|
SecurityStamp = "stamp",
|
|
});
|
|
|
|
var organization = await organizationRepository.CreateAsync(new Organization
|
|
{
|
|
Name = "Test Org",
|
|
BillingEmail = user.Email,
|
|
Plan = "Test",
|
|
});
|
|
|
|
var orgUser = await organizationUserRepository.CreateAsync(new OrganizationUser
|
|
{
|
|
OrganizationId = organization.Id,
|
|
UserId = user.Id,
|
|
Status = OrganizationUserStatusType.Confirmed,
|
|
Email = "" // Empty string email
|
|
});
|
|
|
|
var defaultUserCollection = await collectionRepository.CreateAsync(new Collection
|
|
{
|
|
Name = "Test Collection",
|
|
Id = user.Id,
|
|
Type = CollectionType.DefaultUserCollection,
|
|
OrganizationId = organization.Id
|
|
});
|
|
|
|
await collectionRepository.UpdateUsersAsync(defaultUserCollection.Id, new List<CollectionAccessSelection>()
|
|
{
|
|
new CollectionAccessSelection
|
|
{
|
|
Id = orgUser.Id,
|
|
HidePasswords = false,
|
|
ReadOnly = false,
|
|
Manage = true
|
|
},
|
|
});
|
|
|
|
await organizationUserRepository.DeleteAsync(orgUser);
|
|
|
|
var updatedCollection = await collectionRepository.GetByIdAsync(defaultUserCollection.Id);
|
|
Assert.NotNull(updatedCollection);
|
|
Assert.Equal(CollectionType.SharedCollection, updatedCollection.Type);
|
|
Assert.Equal(user.Email, updatedCollection.DefaultUserCollectionEmail);
|
|
}
|
|
|
|
[DatabaseTheory, DatabaseData]
|
|
public async Task ReplaceAsync_PreservesDefaultCollections_WhenUpdatingCollectionAccess(
|
|
IUserRepository userRepository,
|
|
IOrganizationRepository organizationRepository,
|
|
IOrganizationUserRepository organizationUserRepository,
|
|
ICollectionRepository collectionRepository)
|
|
{
|
|
// Arrange
|
|
var organization = await organizationRepository.CreateTestOrganizationAsync();
|
|
var user = await userRepository.CreateTestUserAsync();
|
|
var orgUser = await organizationUserRepository.CreateTestOrganizationUserAsync(organization, user);
|
|
|
|
// Create a regular collection and a default collection
|
|
var regularCollection = await collectionRepository.CreateTestCollectionAsync(organization);
|
|
|
|
// Manually create default collection since CreateTestCollectionAsync doesn't support type parameter
|
|
var defaultCollection = new Collection
|
|
{
|
|
OrganizationId = organization.Id,
|
|
Name = $"Default Collection {Guid.NewGuid()}",
|
|
Type = CollectionType.DefaultUserCollection
|
|
};
|
|
await collectionRepository.CreateAsync(defaultCollection);
|
|
|
|
var newCollection = await collectionRepository.CreateTestCollectionAsync(organization);
|
|
|
|
// Set up initial collection access: user has access to both regular and default collections
|
|
await organizationUserRepository.ReplaceAsync(orgUser, [
|
|
new CollectionAccessSelection { Id = regularCollection.Id, ReadOnly = false, HidePasswords = false, Manage = false },
|
|
new CollectionAccessSelection { Id = defaultCollection.Id, ReadOnly = false, HidePasswords = false, Manage = true }
|
|
]);
|
|
|
|
// Verify initial state
|
|
var (_, initialCollections) = await organizationUserRepository.GetByIdWithCollectionsAsync(orgUser.Id);
|
|
Assert.Equal(2, initialCollections.Count);
|
|
Assert.Contains(initialCollections, c => c.Id == regularCollection.Id);
|
|
Assert.Contains(initialCollections, c => c.Id == defaultCollection.Id);
|
|
|
|
// Act: Update collection access with only the new collection
|
|
// This should preserve the default collection but remove the regular collection
|
|
await organizationUserRepository.ReplaceAsync(orgUser, [
|
|
new CollectionAccessSelection { Id = newCollection.Id, ReadOnly = false, HidePasswords = false, Manage = true }
|
|
]);
|
|
|
|
// Assert
|
|
var (actualOrgUser, actualCollections) = await organizationUserRepository.GetByIdWithCollectionsAsync(orgUser.Id);
|
|
Assert.NotNull(actualOrgUser);
|
|
Assert.Equal(2, actualCollections.Count); // Should have default collection + new collection
|
|
|
|
// Default collection should be preserved
|
|
var preservedDefaultCollection = actualCollections.FirstOrDefault(c => c.Id == defaultCollection.Id);
|
|
Assert.NotNull(preservedDefaultCollection);
|
|
Assert.True(preservedDefaultCollection.Manage); // Original permissions preserved
|
|
|
|
// New collection should be added
|
|
var addedNewCollection = actualCollections.FirstOrDefault(c => c.Id == newCollection.Id);
|
|
Assert.NotNull(addedNewCollection);
|
|
Assert.True(addedNewCollection.Manage);
|
|
|
|
// Regular collection should be removed
|
|
Assert.DoesNotContain(actualCollections, c => c.Id == regularCollection.Id);
|
|
}
|
|
|
|
[Theory, DatabaseData]
|
|
public async Task ConfirmOrganizationUserAsync_WhenUserIsAccepted_ReturnsTrue(IOrganizationUserRepository organizationUserRepository,
|
|
IOrganizationRepository organizationRepository,
|
|
IUserRepository userRepository)
|
|
{
|
|
// Arrange
|
|
var organization = await organizationRepository.CreateTestOrganizationAsync();
|
|
var user = await userRepository.CreateTestUserAsync();
|
|
var orgUser = await organizationUserRepository.CreateAcceptedTestOrganizationUserAsync(organization, user);
|
|
const string key = "test-key";
|
|
orgUser.Key = key;
|
|
|
|
var acceptedOrganizationUser = new AcceptedOrganizationUserToConfirm
|
|
{
|
|
OrganizationUserId = orgUser.Id,
|
|
UserId = user.Id,
|
|
Key = key
|
|
};
|
|
|
|
// Act
|
|
var result = await organizationUserRepository.ConfirmOrganizationUserAsync(acceptedOrganizationUser);
|
|
|
|
// Assert
|
|
Assert.True(result);
|
|
var updatedUser = await organizationUserRepository.GetByIdAsync(orgUser.Id);
|
|
Assert.NotNull(updatedUser);
|
|
Assert.Equal(OrganizationUserStatusType.Confirmed, updatedUser.Status);
|
|
Assert.Equal(key, updatedUser.Key);
|
|
|
|
// Annul
|
|
await organizationRepository.DeleteAsync(organization);
|
|
await userRepository.DeleteAsync(user);
|
|
}
|
|
|
|
[Theory, DatabaseData]
|
|
public async Task ConfirmOrganizationUserAsync_WhenUserIsAlreadyConfirmed_ReturnsFalse(IOrganizationUserRepository organizationUserRepository,
|
|
IOrganizationRepository organizationRepository,
|
|
IUserRepository userRepository)
|
|
{
|
|
// Arrange
|
|
var organization = await organizationRepository.CreateTestOrganizationAsync();
|
|
var user = await userRepository.CreateTestUserAsync();
|
|
var orgUser = await organizationUserRepository.CreateConfirmedTestOrganizationUserAsync(organization, user);
|
|
|
|
orgUser.Status = OrganizationUserStatusType.Accepted; // To simulate a second call to ConfirmOrganizationUserAsync
|
|
|
|
var acceptedOrganizationUser = new AcceptedOrganizationUserToConfirm
|
|
{
|
|
OrganizationUserId = orgUser.Id,
|
|
UserId = user.Id,
|
|
Key = "test-key"
|
|
};
|
|
|
|
// Act
|
|
var result = await organizationUserRepository.ConfirmOrganizationUserAsync(acceptedOrganizationUser);
|
|
|
|
// Assert
|
|
Assert.False(result);
|
|
var unchangedUser = await organizationUserRepository.GetByIdAsync(orgUser.Id);
|
|
Assert.NotNull(unchangedUser);
|
|
Assert.Equal(OrganizationUserStatusType.Confirmed, unchangedUser.Status);
|
|
|
|
// Annul
|
|
await organizationRepository.DeleteAsync(organization);
|
|
await userRepository.DeleteAsync(user);
|
|
}
|
|
|
|
[Theory, DatabaseData]
|
|
public async Task ConfirmOrganizationUserAsync_IsIdempotent_WhenCalledMultipleTimes(
|
|
IOrganizationUserRepository organizationUserRepository,
|
|
IOrganizationRepository organizationRepository,
|
|
IUserRepository userRepository)
|
|
{
|
|
// Arrange
|
|
var organization = await organizationRepository.CreateTestOrganizationAsync();
|
|
var user = await userRepository.CreateTestUserAsync();
|
|
var orgUser = await organizationUserRepository.CreateAcceptedTestOrganizationUserAsync(organization, user);
|
|
|
|
var acceptedOrganizationUser = new AcceptedOrganizationUserToConfirm
|
|
{
|
|
OrganizationUserId = orgUser.Id,
|
|
UserId = user.Id,
|
|
Key = "test-key"
|
|
};
|
|
|
|
// Act - First call should confirm
|
|
var firstResult = await organizationUserRepository.ConfirmOrganizationUserAsync(acceptedOrganizationUser);
|
|
var secondResult = await organizationUserRepository.ConfirmOrganizationUserAsync(acceptedOrganizationUser);
|
|
|
|
// Assert
|
|
Assert.True(firstResult);
|
|
Assert.False(secondResult);
|
|
var finalUser = await organizationUserRepository.GetByIdAsync(orgUser.Id);
|
|
Assert.NotNull(finalUser);
|
|
Assert.Equal(OrganizationUserStatusType.Confirmed, finalUser.Status);
|
|
|
|
// Annul
|
|
await organizationRepository.DeleteAsync(organization);
|
|
await userRepository.DeleteAsync(user);
|
|
}
|
|
|
|
[Theory, DatabaseData]
|
|
public async Task ConfirmOrganizationUserAsync_WhenUserDoesNotExist_ReturnsFalse(
|
|
IOrganizationUserRepository organizationUserRepository)
|
|
{
|
|
// Arrange
|
|
var nonExistentUser = new AcceptedOrganizationUserToConfirm
|
|
{
|
|
OrganizationUserId = Guid.NewGuid(),
|
|
UserId = Guid.NewGuid(),
|
|
Key = "test-key"
|
|
};
|
|
|
|
// Act
|
|
var result = await organizationUserRepository.ConfirmOrganizationUserAsync(nonExistentUser);
|
|
|
|
// Assert
|
|
Assert.False(result);
|
|
}
|
|
}
|