mirror of
https://github.com/bitwarden/server.git
synced 2025-07-02 16:42:50 -05: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:
@ -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(
|
||||
|
Reference in New Issue
Block a user