mirror of
https://github.com/bitwarden/server.git
synced 2024-12-22 16:57:36 +01:00
[PM-11404] Account Management: Prevent a verified user from purging their vault (#4853)
* Add check for managed user before purging account * Rename IOrganizationRepository.GetByClaimedUserDomainAsync to GetByVerifiedUserEmailDomainAsync and refactor to return a list. Remove ManagedByOrganizationId from ProfileResponseMode. Add ManagesActiveUser to ProfileOrganizationResponseModel * Rename the property ManagesActiveUser to UserIsManagedByOrganization * Remove whole class #nullable enable and add it to specific places * Remove unnecessary .ToList() * Refactor IUserService methods GetOrganizationsManagingUserAsync and IsManagedByAnyOrganizationAsync to not return nullable objects. Update ProfileOrganizationResponseModel.UserIsManagedByOrganization to not be nullable * Update error message when unable to purge vault for managed account
This commit is contained in:
parent
245e2e4d52
commit
d6cd73cfcc
@ -124,7 +124,11 @@ public class OrganizationsController : Controller
|
||||
var userId = _userService.GetProperUserId(User).Value;
|
||||
var organizations = await _organizationUserRepository.GetManyDetailsByUserAsync(userId,
|
||||
OrganizationUserStatusType.Confirmed);
|
||||
var responses = organizations.Select(o => new ProfileOrganizationResponseModel(o));
|
||||
|
||||
var organizationManagingActiveUser = await _userService.GetOrganizationsManagingUserAsync(userId);
|
||||
var organizationIdsManagingActiveUser = organizationManagingActiveUser.Select(o => o.Id);
|
||||
|
||||
var responses = organizations.Select(o => new ProfileOrganizationResponseModel(o, organizationIdsManagingActiveUser));
|
||||
return new ListResponseModel<ProfileOrganizationResponseModel>(responses);
|
||||
}
|
||||
|
||||
|
@ -15,7 +15,10 @@ public class ProfileOrganizationResponseModel : ResponseModel
|
||||
{
|
||||
public ProfileOrganizationResponseModel(string str) : base(str) { }
|
||||
|
||||
public ProfileOrganizationResponseModel(OrganizationUserOrganizationDetails organization) : this("profileOrganization")
|
||||
public ProfileOrganizationResponseModel(
|
||||
OrganizationUserOrganizationDetails organization,
|
||||
IEnumerable<Guid> organizationIdsManagingUser)
|
||||
: this("profileOrganization")
|
||||
{
|
||||
Id = organization.OrganizationId;
|
||||
Name = organization.Name;
|
||||
@ -64,6 +67,7 @@ public class ProfileOrganizationResponseModel : ResponseModel
|
||||
AccessSecretsManager = organization.AccessSecretsManager;
|
||||
LimitCollectionCreationDeletion = organization.LimitCollectionCreationDeletion;
|
||||
AllowAdminAccessToAllCollectionItems = organization.AllowAdminAccessToAllCollectionItems;
|
||||
UserIsManagedByOrganization = organizationIdsManagingUser.Contains(organization.OrganizationId);
|
||||
|
||||
if (organization.SsoConfig != null)
|
||||
{
|
||||
@ -122,4 +126,15 @@ public class ProfileOrganizationResponseModel : ResponseModel
|
||||
public bool AccessSecretsManager { get; set; }
|
||||
public bool LimitCollectionCreationDeletion { get; set; }
|
||||
public bool AllowAdminAccessToAllCollectionItems { get; set; }
|
||||
/// <summary>
|
||||
/// Indicates if the organization manages the user.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// An organization manages a user if the user's email domain is verified by the organization and the user is a member of it.
|
||||
/// The organization must be enabled and able to have verified domains.
|
||||
/// </remarks>
|
||||
/// <returns>
|
||||
/// False if the Account Deprovisioning feature flag is disabled.
|
||||
/// </returns>
|
||||
public bool UserIsManagedByOrganization { get; set; }
|
||||
}
|
||||
|
@ -443,11 +443,11 @@ public class AccountsController : Controller
|
||||
|
||||
var twoFactorEnabled = await _userService.TwoFactorIsEnabledAsync(user);
|
||||
var hasPremiumFromOrg = await _userService.HasPremiumFromOrganization(user);
|
||||
var managedByOrganizationId = await GetManagedByOrganizationIdAsync(user);
|
||||
var organizationIdsManagingActiveUser = await GetOrganizationIdsManagingUserAsync(user.Id);
|
||||
|
||||
var response = new ProfileResponseModel(user, organizationUserDetails, providerUserDetails,
|
||||
providerUserOrganizationDetails, twoFactorEnabled,
|
||||
hasPremiumFromOrg, managedByOrganizationId);
|
||||
hasPremiumFromOrg, organizationIdsManagingActiveUser);
|
||||
return response;
|
||||
}
|
||||
|
||||
@ -457,7 +457,9 @@ public class AccountsController : Controller
|
||||
var userId = _userService.GetProperUserId(User);
|
||||
var organizationUserDetails = await _organizationUserRepository.GetManyDetailsByUserAsync(userId.Value,
|
||||
OrganizationUserStatusType.Confirmed);
|
||||
var responseData = organizationUserDetails.Select(o => new ProfileOrganizationResponseModel(o));
|
||||
var organizationIdsManagingActiveUser = await GetOrganizationIdsManagingUserAsync(userId.Value);
|
||||
|
||||
var responseData = organizationUserDetails.Select(o => new ProfileOrganizationResponseModel(o, organizationIdsManagingActiveUser));
|
||||
return new ListResponseModel<ProfileOrganizationResponseModel>(responseData);
|
||||
}
|
||||
|
||||
@ -475,9 +477,9 @@ public class AccountsController : Controller
|
||||
|
||||
var twoFactorEnabled = await _userService.TwoFactorIsEnabledAsync(user);
|
||||
var hasPremiumFromOrg = await _userService.HasPremiumFromOrganization(user);
|
||||
var managedByOrganizationId = await GetManagedByOrganizationIdAsync(user);
|
||||
var organizationIdsManagingActiveUser = await GetOrganizationIdsManagingUserAsync(user.Id);
|
||||
|
||||
var response = new ProfileResponseModel(user, null, null, null, twoFactorEnabled, hasPremiumFromOrg, managedByOrganizationId);
|
||||
var response = new ProfileResponseModel(user, null, null, null, twoFactorEnabled, hasPremiumFromOrg, organizationIdsManagingActiveUser);
|
||||
return response;
|
||||
}
|
||||
|
||||
@ -494,9 +496,9 @@ public class AccountsController : Controller
|
||||
|
||||
var userTwoFactorEnabled = await _userService.TwoFactorIsEnabledAsync(user);
|
||||
var userHasPremiumFromOrganization = await _userService.HasPremiumFromOrganization(user);
|
||||
var managedByOrganizationId = await GetManagedByOrganizationIdAsync(user);
|
||||
var organizationIdsManagingActiveUser = await GetOrganizationIdsManagingUserAsync(user.Id);
|
||||
|
||||
var response = new ProfileResponseModel(user, null, null, null, userTwoFactorEnabled, userHasPremiumFromOrganization, managedByOrganizationId);
|
||||
var response = new ProfileResponseModel(user, null, null, null, userTwoFactorEnabled, userHasPremiumFromOrganization, organizationIdsManagingActiveUser);
|
||||
return response;
|
||||
}
|
||||
|
||||
@ -647,9 +649,9 @@ public class AccountsController : Controller
|
||||
|
||||
var userTwoFactorEnabled = await _userService.TwoFactorIsEnabledAsync(user);
|
||||
var userHasPremiumFromOrganization = await _userService.HasPremiumFromOrganization(user);
|
||||
var managedByOrganizationId = await GetManagedByOrganizationIdAsync(user);
|
||||
var organizationIdsManagingActiveUser = await GetOrganizationIdsManagingUserAsync(user.Id);
|
||||
|
||||
var profile = new ProfileResponseModel(user, null, null, null, userTwoFactorEnabled, userHasPremiumFromOrganization, managedByOrganizationId);
|
||||
var profile = new ProfileResponseModel(user, null, null, null, userTwoFactorEnabled, userHasPremiumFromOrganization, organizationIdsManagingActiveUser);
|
||||
return new PaymentResponseModel
|
||||
{
|
||||
UserProfile = profile,
|
||||
@ -937,14 +939,9 @@ public class AccountsController : Controller
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<Guid?> GetManagedByOrganizationIdAsync(User user)
|
||||
private async Task<IEnumerable<Guid>> GetOrganizationIdsManagingUserAsync(Guid userId)
|
||||
{
|
||||
if (!_featureService.IsEnabled(FeatureFlagKeys.AccountDeprovisioning))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var organizationManagingUser = await _userService.GetOrganizationManagingUserAsync(user.Id);
|
||||
return organizationManagingUser?.Id;
|
||||
var organizationManagingUser = await _userService.GetOrganizationsManagingUserAsync(userId);
|
||||
return organizationManagingUser.Select(o => o.Id);
|
||||
}
|
||||
}
|
||||
|
@ -201,7 +201,10 @@ public class OrganizationsController(
|
||||
var organizationDetails = await organizationUserRepository.GetDetailsByUserAsync(userId, organization.Id,
|
||||
OrganizationUserStatusType.Confirmed);
|
||||
|
||||
return new ProfileOrganizationResponseModel(organizationDetails);
|
||||
var organizationManagingActiveUser = await userService.GetOrganizationsManagingUserAsync(userId);
|
||||
var organizationIdsManagingActiveUser = organizationManagingActiveUser.Select(o => o.Id);
|
||||
|
||||
return new ProfileOrganizationResponseModel(organizationDetails, organizationIdsManagingActiveUser);
|
||||
}
|
||||
|
||||
[HttpPost("{id:guid}/seat")]
|
||||
|
@ -15,7 +15,7 @@ public class ProfileResponseModel : ResponseModel
|
||||
IEnumerable<ProviderUserOrganizationDetails> providerUserOrganizationDetails,
|
||||
bool twoFactorEnabled,
|
||||
bool premiumFromOrganization,
|
||||
Guid? managedByOrganizationId) : base("profile")
|
||||
IEnumerable<Guid> organizationIdsManagingUser) : base("profile")
|
||||
{
|
||||
if (user == null)
|
||||
{
|
||||
@ -37,11 +37,10 @@ public class ProfileResponseModel : ResponseModel
|
||||
UsesKeyConnector = user.UsesKeyConnector;
|
||||
AvatarColor = user.AvatarColor;
|
||||
CreationDate = user.CreationDate;
|
||||
Organizations = organizationsUserDetails?.Select(o => new ProfileOrganizationResponseModel(o));
|
||||
Organizations = organizationsUserDetails?.Select(o => new ProfileOrganizationResponseModel(o, organizationIdsManagingUser));
|
||||
Providers = providerUserDetails?.Select(p => new ProfileProviderResponseModel(p));
|
||||
ProviderOrganizations =
|
||||
providerUserOrganizationDetails?.Select(po => new ProfileProviderOrganizationResponseModel(po));
|
||||
ManagedByOrganizationId = managedByOrganizationId;
|
||||
}
|
||||
|
||||
public ProfileResponseModel() : base("profile")
|
||||
@ -63,7 +62,6 @@ public class ProfileResponseModel : ResponseModel
|
||||
public bool UsesKeyConnector { get; set; }
|
||||
public string AvatarColor { get; set; }
|
||||
public DateTime CreationDate { get; set; }
|
||||
public Guid? ManagedByOrganizationId { get; set; }
|
||||
public IEnumerable<ProfileOrganizationResponseModel> Organizations { get; set; }
|
||||
public IEnumerable<ProfileProviderResponseModel> Providers { get; set; }
|
||||
public IEnumerable<ProfileProviderOrganizationResponseModel> ProviderOrganizations { get; set; }
|
||||
|
@ -910,6 +910,13 @@ public class CiphersController : Controller
|
||||
throw new BadRequestException(ModelState);
|
||||
}
|
||||
|
||||
// If Account Deprovisioning is enabled, we need to check if the user is managed by any organization.
|
||||
if (_featureService.IsEnabled(FeatureFlagKeys.AccountDeprovisioning)
|
||||
&& await _userService.IsManagedByAnyOrganizationAsync(user.Id))
|
||||
{
|
||||
throw new BadRequestException("Cannot purge accounts owned by an organization. Contact your organization administrator for additional details.");
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(organizationId))
|
||||
{
|
||||
await _cipherRepository.DeleteByUserIdAsync(user.Id);
|
||||
|
@ -1,5 +1,4 @@
|
||||
using Bit.Api.Vault.Models.Response;
|
||||
using Bit.Core;
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.AdminConsole.Enums.Provider;
|
||||
using Bit.Core.AdminConsole.Repositories;
|
||||
@ -7,7 +6,6 @@ using Bit.Core.Entities;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Models.Data;
|
||||
using Bit.Core.Models.Data.Organizations.OrganizationUsers;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Settings;
|
||||
@ -95,23 +93,12 @@ public class SyncController : Controller
|
||||
|
||||
var userTwoFactorEnabled = await _userService.TwoFactorIsEnabledAsync(user);
|
||||
var userHasPremiumFromOrganization = await _userService.HasPremiumFromOrganization(user);
|
||||
var managedByOrganizationId = await GetManagedByOrganizationIdAsync(user, organizationUserDetails);
|
||||
var organizationManagingActiveUser = await _userService.GetOrganizationsManagingUserAsync(user.Id);
|
||||
var organizationIdsManagingActiveUser = organizationManagingActiveUser.Select(o => o.Id);
|
||||
|
||||
var response = new SyncResponseModel(_globalSettings, user, userTwoFactorEnabled, userHasPremiumFromOrganization,
|
||||
managedByOrganizationId, organizationUserDetails, providerUserDetails, providerUserOrganizationDetails,
|
||||
organizationIdsManagingActiveUser, organizationUserDetails, providerUserDetails, providerUserOrganizationDetails,
|
||||
folders, collections, ciphers, collectionCiphersGroupDict, excludeDomains, policies, sends);
|
||||
return response;
|
||||
}
|
||||
|
||||
private async Task<Guid?> GetManagedByOrganizationIdAsync(User user, IEnumerable<OrganizationUserOrganizationDetails> organizationUserDetails)
|
||||
{
|
||||
if (!_featureService.IsEnabled(FeatureFlagKeys.AccountDeprovisioning) ||
|
||||
!organizationUserDetails.Any(o => o.Enabled && o.UseSso))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var organizationManagingUser = await _userService.GetOrganizationManagingUserAsync(user.Id);
|
||||
return organizationManagingUser?.Id;
|
||||
}
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ public class SyncResponseModel : ResponseModel
|
||||
User user,
|
||||
bool userTwoFactorEnabled,
|
||||
bool userHasPremiumFromOrganization,
|
||||
Guid? managedByOrganizationId,
|
||||
IEnumerable<Guid> organizationIdsManagingUser,
|
||||
IEnumerable<OrganizationUserOrganizationDetails> organizationUserDetails,
|
||||
IEnumerable<ProviderUserProviderDetails> providerUserDetails,
|
||||
IEnumerable<ProviderUserOrganizationDetails> providerUserOrganizationDetails,
|
||||
@ -35,7 +35,7 @@ public class SyncResponseModel : ResponseModel
|
||||
: base("sync")
|
||||
{
|
||||
Profile = new ProfileResponseModel(user, organizationUserDetails, providerUserDetails,
|
||||
providerUserOrganizationDetails, userTwoFactorEnabled, userHasPremiumFromOrganization, managedByOrganizationId);
|
||||
providerUserOrganizationDetails, userTwoFactorEnabled, userHasPremiumFromOrganization, organizationIdsManagingUser);
|
||||
Folders = folders.Select(f => new FolderResponseModel(f));
|
||||
Ciphers = ciphers.Select(c => new CipherDetailsResponseModel(c, globalSettings, collectionCiphersDict));
|
||||
Collections = collections?.Select(
|
||||
|
@ -19,7 +19,7 @@ public interface IOrganizationRepository : IRepository<Organization, Guid>
|
||||
Task<IEnumerable<string>> GetOwnerEmailAddressesById(Guid organizationId);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the organization that has a claimed domain matching the user's email domain.
|
||||
/// Gets the organizations that have a verified domain matching the user's email domain.
|
||||
/// </summary>
|
||||
Task<Organization> GetByClaimedUserDomainAsync(Guid userId);
|
||||
Task<ICollection<Organization>> GetByVerifiedUserEmailDomainAsync(Guid userId);
|
||||
}
|
||||
|
@ -90,14 +90,20 @@ public interface IUserService
|
||||
/// Indicates if the user is managed by any organization.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// A managed user is a user whose email domain matches one of the Organization's verified domains.
|
||||
/// The organization must be enabled and be on an Enterprise plan.
|
||||
/// A user is considered managed by an organization if their email domain matches one of the verified domains of that organization, and the user is a member of it.
|
||||
/// The organization must be enabled and able to have verified domains.
|
||||
/// </remarks>
|
||||
/// <returns>
|
||||
/// False if the Account Deprovisioning feature flag is disabled.
|
||||
/// </returns>
|
||||
Task<bool> IsManagedByAnyOrganizationAsync(Guid userId);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the organization that manages the user.
|
||||
/// Gets the organizations that manage the user.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// An empty collection if the Account Deprovisioning feature flag is disabled.
|
||||
/// </returns>
|
||||
/// <inheritdoc cref="IsManagedByAnyOrganizationAsync(Guid)"/>
|
||||
Task<Organization> GetOrganizationManagingUserAsync(Guid userId);
|
||||
Task<IEnumerable<Organization>> GetOrganizationsManagingUserAsync(Guid userId);
|
||||
}
|
||||
|
@ -1267,18 +1267,24 @@ public class UserService : UserManager<User>, IUserService, IDisposable
|
||||
|
||||
public async Task<bool> IsManagedByAnyOrganizationAsync(Guid userId)
|
||||
{
|
||||
var managingOrganization = await GetOrganizationManagingUserAsync(userId);
|
||||
return managingOrganization != null;
|
||||
var managingOrganizations = await GetOrganizationsManagingUserAsync(userId);
|
||||
return managingOrganizations.Any();
|
||||
}
|
||||
|
||||
public async Task<Organization> GetOrganizationManagingUserAsync(Guid userId)
|
||||
public async Task<IEnumerable<Organization>> GetOrganizationsManagingUserAsync(Guid userId)
|
||||
{
|
||||
// Users can only be managed by an Organization that is enabled and can have organization domains
|
||||
var organization = await _organizationRepository.GetByClaimedUserDomainAsync(userId);
|
||||
if (!_featureService.IsEnabled(FeatureFlagKeys.AccountDeprovisioning))
|
||||
{
|
||||
return Enumerable.Empty<Organization>();
|
||||
}
|
||||
|
||||
// Get all organizations that have verified the user's email domain.
|
||||
var organizationsWithVerifiedUserEmailDomain = await _organizationRepository.GetByVerifiedUserEmailDomainAsync(userId);
|
||||
|
||||
// Organizations must be enabled and able to have verified domains.
|
||||
// TODO: Replace "UseSso" with a new organization ability like "UseOrganizationDomains" (PM-11622).
|
||||
// Verified domains were tied to SSO, so we currently check the "UseSso" organization ability.
|
||||
return (organization is { Enabled: true, UseSso: true }) ? organization : null;
|
||||
return organizationsWithVerifiedUserEmailDomain.Where(organization => organization is { Enabled: true, UseSso: true });
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="IsLegacyUser(string)"/>
|
||||
|
@ -168,7 +168,7 @@ public class OrganizationRepository : Repository<Organization, Guid>, IOrganizat
|
||||
commandType: CommandType.StoredProcedure);
|
||||
}
|
||||
|
||||
public async Task<Organization> GetByClaimedUserDomainAsync(Guid userId)
|
||||
public async Task<ICollection<Organization>> GetByVerifiedUserEmailDomainAsync(Guid userId)
|
||||
{
|
||||
using (var connection = new SqlConnection(ConnectionString))
|
||||
{
|
||||
@ -177,7 +177,7 @@ public class OrganizationRepository : Repository<Organization, Guid>, IOrganizat
|
||||
new { UserId = userId },
|
||||
commandType: CommandType.StoredProcedure);
|
||||
|
||||
return result.SingleOrDefault();
|
||||
return result.ToList();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -276,7 +276,7 @@ public class OrganizationRepository : Repository<Core.AdminConsole.Entities.Orga
|
||||
return await query.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<Core.AdminConsole.Entities.Organization> GetByClaimedUserDomainAsync(Guid userId)
|
||||
public async Task<ICollection<Core.AdminConsole.Entities.Organization>> GetByVerifiedUserEmailDomainAsync(Guid userId)
|
||||
{
|
||||
using (var scope = ServiceScopeFactory.CreateScope())
|
||||
{
|
||||
@ -291,7 +291,7 @@ public class OrganizationRepository : Repository<Core.AdminConsole.Entities.Orga
|
||||
&& u.Email.ToLower().EndsWith("@" + od.DomainName.ToLower())
|
||||
select o;
|
||||
|
||||
return await query.FirstOrDefaultAsync();
|
||||
return await query.ToArrayAsync();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -27,7 +27,6 @@ using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using NSubstitute;
|
||||
using NSubstitute.ReceivedExtensions;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Core.Test.Services;
|
||||
@ -282,45 +281,69 @@ public class UserServiceTests
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task IsManagedByAnyOrganizationAsync_WithManagingEnabledOrganization_ReturnsTrue(
|
||||
SutProvider<UserService> sutProvider, Guid userId, Organization organization)
|
||||
public async Task IsManagedByAnyOrganizationAsync_WithAccountDeprovisioningDisabled_ReturnsFalse(
|
||||
SutProvider<UserService> sutProvider, Guid userId)
|
||||
{
|
||||
organization.Enabled = true;
|
||||
organization.UseSso = true;
|
||||
|
||||
sutProvider.GetDependency<IOrganizationRepository>()
|
||||
.GetByClaimedUserDomainAsync(userId)
|
||||
.Returns(organization);
|
||||
|
||||
var result = await sutProvider.Sut.IsManagedByAnyOrganizationAsync(userId);
|
||||
Assert.True(result);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task IsManagedByAnyOrganizationAsync_WithManagingDisabledOrganization_ReturnsFalse(
|
||||
SutProvider<UserService> sutProvider, Guid userId, Organization organization)
|
||||
{
|
||||
organization.Enabled = false;
|
||||
organization.UseSso = true;
|
||||
|
||||
sutProvider.GetDependency<IOrganizationRepository>()
|
||||
.GetByClaimedUserDomainAsync(userId)
|
||||
.Returns(organization);
|
||||
sutProvider.GetDependency<IFeatureService>()
|
||||
.IsEnabled(FeatureFlagKeys.AccountDeprovisioning)
|
||||
.Returns(false);
|
||||
|
||||
var result = await sutProvider.Sut.IsManagedByAnyOrganizationAsync(userId);
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task IsManagedByAnyOrganizationAsync_WithOrganizationUseSsoFalse_ReturnsFalse(
|
||||
public async Task IsManagedByAnyOrganizationAsync_WithAccountDeprovisioningEnabled_WithManagingEnabledOrganization_ReturnsTrue(
|
||||
SutProvider<UserService> sutProvider, Guid userId, Organization organization)
|
||||
{
|
||||
organization.Enabled = true;
|
||||
organization.UseSso = true;
|
||||
|
||||
sutProvider.GetDependency<IFeatureService>()
|
||||
.IsEnabled(FeatureFlagKeys.AccountDeprovisioning)
|
||||
.Returns(true);
|
||||
|
||||
sutProvider.GetDependency<IOrganizationRepository>()
|
||||
.GetByVerifiedUserEmailDomainAsync(userId)
|
||||
.Returns(new[] { organization });
|
||||
|
||||
var result = await sutProvider.Sut.IsManagedByAnyOrganizationAsync(userId);
|
||||
Assert.True(result);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task IsManagedByAnyOrganizationAsync_WithAccountDeprovisioningEnabled_WithManagingDisabledOrganization_ReturnsFalse(
|
||||
SutProvider<UserService> sutProvider, Guid userId, Organization organization)
|
||||
{
|
||||
organization.Enabled = false;
|
||||
organization.UseSso = true;
|
||||
|
||||
sutProvider.GetDependency<IFeatureService>()
|
||||
.IsEnabled(FeatureFlagKeys.AccountDeprovisioning)
|
||||
.Returns(true);
|
||||
|
||||
sutProvider.GetDependency<IOrganizationRepository>()
|
||||
.GetByVerifiedUserEmailDomainAsync(userId)
|
||||
.Returns(new[] { organization });
|
||||
|
||||
var result = await sutProvider.Sut.IsManagedByAnyOrganizationAsync(userId);
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task IsManagedByAnyOrganizationAsync_WithAccountDeprovisioningEnabled_WithOrganizationUseSsoFalse_ReturnsFalse(
|
||||
SutProvider<UserService> sutProvider, Guid userId, Organization organization)
|
||||
{
|
||||
organization.Enabled = true;
|
||||
organization.UseSso = false;
|
||||
|
||||
sutProvider.GetDependency<IFeatureService>()
|
||||
.IsEnabled(FeatureFlagKeys.AccountDeprovisioning)
|
||||
.Returns(true);
|
||||
|
||||
sutProvider.GetDependency<IOrganizationRepository>()
|
||||
.GetByClaimedUserDomainAsync(userId)
|
||||
.Returns(organization);
|
||||
.GetByVerifiedUserEmailDomainAsync(userId)
|
||||
.Returns(new[] { organization });
|
||||
|
||||
var result = await sutProvider.Sut.IsManagedByAnyOrganizationAsync(userId);
|
||||
Assert.False(result);
|
||||
|
@ -97,13 +97,160 @@ public class OrganizationRepositoryTests
|
||||
ResetPasswordKey = "resetpasswordkey1",
|
||||
});
|
||||
|
||||
var user1Response = await organizationRepository.GetByClaimedUserDomainAsync(user1.Id);
|
||||
var user2Response = await organizationRepository.GetByClaimedUserDomainAsync(user2.Id);
|
||||
var user3Response = await organizationRepository.GetByClaimedUserDomainAsync(user3.Id);
|
||||
var user1Response = await organizationRepository.GetByVerifiedUserEmailDomainAsync(user1.Id);
|
||||
var user2Response = await organizationRepository.GetByVerifiedUserEmailDomainAsync(user2.Id);
|
||||
var user3Response = await organizationRepository.GetByVerifiedUserEmailDomainAsync(user3.Id);
|
||||
|
||||
Assert.NotNull(user1Response);
|
||||
Assert.Equal(organization.Id, user1Response.Id);
|
||||
Assert.Null(user2Response);
|
||||
Assert.Null(user3Response);
|
||||
Assert.NotEmpty(user1Response);
|
||||
Assert.Equal(organization.Id, user1Response.First().Id);
|
||||
Assert.Empty(user2Response);
|
||||
Assert.Empty(user3Response);
|
||||
}
|
||||
|
||||
[DatabaseTheory, DatabaseData]
|
||||
public async Task GetByVerifiedUserEmailDomainAsync_WithUnverifiedDomains_ReturnsEmpty(
|
||||
IUserRepository userRepository,
|
||||
IOrganizationRepository organizationRepository,
|
||||
IOrganizationUserRepository organizationUserRepository,
|
||||
IOrganizationDomainRepository organizationDomainRepository)
|
||||
{
|
||||
var id = Guid.NewGuid();
|
||||
var domainName = $"{id}.example.com";
|
||||
|
||||
var user = await userRepository.CreateAsync(new User
|
||||
{
|
||||
Name = "Test User",
|
||||
Email = $"test+{id}@{domainName}",
|
||||
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 = user.Email,
|
||||
Plan = "Test",
|
||||
PrivateKey = "privatekey",
|
||||
});
|
||||
|
||||
var organizationDomain = new OrganizationDomain
|
||||
{
|
||||
OrganizationId = organization.Id,
|
||||
DomainName = domainName,
|
||||
Txt = "btw+12345",
|
||||
};
|
||||
organizationDomain.SetNextRunDate(12);
|
||||
organizationDomain.SetJobRunCount();
|
||||
await organizationDomainRepository.CreateAsync(organizationDomain);
|
||||
|
||||
await organizationUserRepository.CreateAsync(new OrganizationUser
|
||||
{
|
||||
OrganizationId = organization.Id,
|
||||
UserId = user.Id,
|
||||
Status = OrganizationUserStatusType.Confirmed,
|
||||
ResetPasswordKey = "resetpasswordkey",
|
||||
});
|
||||
|
||||
var result = await organizationRepository.GetByVerifiedUserEmailDomainAsync(user.Id);
|
||||
|
||||
Assert.Empty(result);
|
||||
}
|
||||
|
||||
[DatabaseTheory, DatabaseData]
|
||||
public async Task GetByVerifiedUserEmailDomainAsync_WithMultipleVerifiedDomains_ReturnsAllMatchingOrganizations(
|
||||
IUserRepository userRepository,
|
||||
IOrganizationRepository organizationRepository,
|
||||
IOrganizationUserRepository organizationUserRepository,
|
||||
IOrganizationDomainRepository organizationDomainRepository)
|
||||
{
|
||||
var id = Guid.NewGuid();
|
||||
var domainName = $"{id}.example.com";
|
||||
|
||||
var user = await userRepository.CreateAsync(new User
|
||||
{
|
||||
Name = "Test User",
|
||||
Email = $"test+{id}@{domainName}",
|
||||
ApiKey = "TEST",
|
||||
SecurityStamp = "stamp",
|
||||
Kdf = KdfType.PBKDF2_SHA256,
|
||||
KdfIterations = 1,
|
||||
KdfMemory = 2,
|
||||
KdfParallelism = 3
|
||||
});
|
||||
|
||||
var organization1 = await organizationRepository.CreateAsync(new Organization
|
||||
{
|
||||
Name = $"Test Org 1 {id}",
|
||||
BillingEmail = user.Email,
|
||||
Plan = "Test",
|
||||
PrivateKey = "privatekey1",
|
||||
});
|
||||
|
||||
var organization2 = await organizationRepository.CreateAsync(new Organization
|
||||
{
|
||||
Name = $"Test Org 2 {id}",
|
||||
BillingEmail = user.Email,
|
||||
Plan = "Test",
|
||||
PrivateKey = "privatekey2",
|
||||
});
|
||||
|
||||
var organizationDomain1 = new OrganizationDomain
|
||||
{
|
||||
OrganizationId = organization1.Id,
|
||||
DomainName = domainName,
|
||||
Txt = "btw+12345",
|
||||
};
|
||||
organizationDomain1.SetNextRunDate(12);
|
||||
organizationDomain1.SetJobRunCount();
|
||||
organizationDomain1.SetVerifiedDate();
|
||||
await organizationDomainRepository.CreateAsync(organizationDomain1);
|
||||
|
||||
var organizationDomain2 = new OrganizationDomain
|
||||
{
|
||||
OrganizationId = organization2.Id,
|
||||
DomainName = domainName,
|
||||
Txt = "btw+67890",
|
||||
};
|
||||
organizationDomain2.SetNextRunDate(12);
|
||||
organizationDomain2.SetJobRunCount();
|
||||
organizationDomain2.SetVerifiedDate();
|
||||
await organizationDomainRepository.CreateAsync(organizationDomain2);
|
||||
|
||||
await organizationUserRepository.CreateAsync(new OrganizationUser
|
||||
{
|
||||
OrganizationId = organization1.Id,
|
||||
UserId = user.Id,
|
||||
Status = OrganizationUserStatusType.Confirmed,
|
||||
ResetPasswordKey = "resetpasswordkey1",
|
||||
});
|
||||
|
||||
await organizationUserRepository.CreateAsync(new OrganizationUser
|
||||
{
|
||||
OrganizationId = organization2.Id,
|
||||
UserId = user.Id,
|
||||
Status = OrganizationUserStatusType.Confirmed,
|
||||
ResetPasswordKey = "resetpasswordkey2",
|
||||
});
|
||||
|
||||
var result = await organizationRepository.GetByVerifiedUserEmailDomainAsync(user.Id);
|
||||
|
||||
Assert.Equal(2, result.Count);
|
||||
Assert.Contains(result, org => org.Id == organization1.Id);
|
||||
Assert.Contains(result, org => org.Id == organization2.Id);
|
||||
}
|
||||
|
||||
[DatabaseTheory, DatabaseData]
|
||||
public async Task GetByVerifiedUserEmailDomainAsync_WithNonExistentUser_ReturnsEmpty(
|
||||
IOrganizationRepository organizationRepository)
|
||||
{
|
||||
var nonExistentUserId = Guid.NewGuid();
|
||||
|
||||
var result = await organizationRepository.GetByVerifiedUserEmailDomainAsync(nonExistentUserId);
|
||||
|
||||
Assert.Empty(result);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user