1
0
mirror of https://github.com/bitwarden/server.git synced 2024-11-21 12:05:42 +01:00

[PM-11334] Add managed status to sync data (#4791)

* Refactor UserService to add GetOrganizationManagingUserAsync method to retrive the organization that manages a user

* Refactor SyncController and AccountsController to include ManagedByOrganizationId in profile response
This commit is contained in:
Rui Tomé 2024-09-26 11:21:51 +01:00 committed by GitHub
parent 2e072aebe3
commit 3f629e0a5a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 75 additions and 11 deletions

View File

@ -443,10 +443,11 @@ public class AccountsController : Controller
var twoFactorEnabled = await _userService.TwoFactorIsEnabledAsync(user); var twoFactorEnabled = await _userService.TwoFactorIsEnabledAsync(user);
var hasPremiumFromOrg = await _userService.HasPremiumFromOrganization(user); var hasPremiumFromOrg = await _userService.HasPremiumFromOrganization(user);
var managedByOrganizationId = await GetManagedByOrganizationIdAsync(user);
var response = new ProfileResponseModel(user, organizationUserDetails, providerUserDetails, var response = new ProfileResponseModel(user, organizationUserDetails, providerUserDetails,
providerUserOrganizationDetails, twoFactorEnabled, providerUserOrganizationDetails, twoFactorEnabled,
hasPremiumFromOrg); hasPremiumFromOrg, managedByOrganizationId);
return response; return response;
} }
@ -471,7 +472,12 @@ public class AccountsController : Controller
} }
await _userService.SaveUserAsync(model.ToUser(user)); await _userService.SaveUserAsync(model.ToUser(user));
var response = new ProfileResponseModel(user, null, null, null, await _userService.TwoFactorIsEnabledAsync(user), await _userService.HasPremiumFromOrganization(user));
var twoFactorEnabled = await _userService.TwoFactorIsEnabledAsync(user);
var hasPremiumFromOrg = await _userService.HasPremiumFromOrganization(user);
var managedByOrganizationId = await GetManagedByOrganizationIdAsync(user);
var response = new ProfileResponseModel(user, null, null, null, twoFactorEnabled, hasPremiumFromOrg, managedByOrganizationId);
return response; return response;
} }
@ -485,7 +491,12 @@ public class AccountsController : Controller
throw new UnauthorizedAccessException(); throw new UnauthorizedAccessException();
} }
await _userService.SaveUserAsync(model.ToUser(user), true); await _userService.SaveUserAsync(model.ToUser(user), true);
var response = new ProfileResponseModel(user, null, null, null, await _userService.TwoFactorIsEnabledAsync(user), await _userService.HasPremiumFromOrganization(user));
var userTwoFactorEnabled = await _userService.TwoFactorIsEnabledAsync(user);
var userHasPremiumFromOrganization = await _userService.HasPremiumFromOrganization(user);
var managedByOrganizationId = await GetManagedByOrganizationIdAsync(user);
var response = new ProfileResponseModel(user, null, null, null, userTwoFactorEnabled, userHasPremiumFromOrganization, managedByOrganizationId);
return response; return response;
} }
@ -633,7 +644,12 @@ public class AccountsController : Controller
BillingAddressCountry = model.Country, BillingAddressCountry = model.Country,
BillingAddressPostalCode = model.PostalCode, BillingAddressPostalCode = model.PostalCode,
}); });
var profile = new ProfileResponseModel(user, null, null, null, await _userService.TwoFactorIsEnabledAsync(user), await _userService.HasPremiumFromOrganization(user));
var userTwoFactorEnabled = await _userService.TwoFactorIsEnabledAsync(user);
var userHasPremiumFromOrganization = await _userService.HasPremiumFromOrganization(user);
var managedByOrganizationId = await GetManagedByOrganizationIdAsync(user);
var profile = new ProfileResponseModel(user, null, null, null, userTwoFactorEnabled, userHasPremiumFromOrganization, managedByOrganizationId);
return new PaymentResponseModel return new PaymentResponseModel
{ {
UserProfile = profile, UserProfile = profile,
@ -920,4 +936,15 @@ public class AccountsController : Controller
throw new BadRequestException("Token", "Invalid token"); throw new BadRequestException("Token", "Invalid token");
} }
} }
private async Task<Guid?> GetManagedByOrganizationIdAsync(User user)
{
if (!_featureService.IsEnabled(FeatureFlagKeys.AccountDeprovisioning))
{
return null;
}
var organizationManagingUser = await _userService.GetOrganizationManagingUserAsync(user.Id);
return organizationManagingUser?.Id;
}
} }

View File

@ -14,7 +14,8 @@ public class ProfileResponseModel : ResponseModel
IEnumerable<ProviderUserProviderDetails> providerUserDetails, IEnumerable<ProviderUserProviderDetails> providerUserDetails,
IEnumerable<ProviderUserOrganizationDetails> providerUserOrganizationDetails, IEnumerable<ProviderUserOrganizationDetails> providerUserOrganizationDetails,
bool twoFactorEnabled, bool twoFactorEnabled,
bool premiumFromOrganization) : base("profile") bool premiumFromOrganization,
Guid? managedByOrganizationId) : base("profile")
{ {
if (user == null) if (user == null)
{ {
@ -40,6 +41,7 @@ public class ProfileResponseModel : ResponseModel
Providers = providerUserDetails?.Select(p => new ProfileProviderResponseModel(p)); Providers = providerUserDetails?.Select(p => new ProfileProviderResponseModel(p));
ProviderOrganizations = ProviderOrganizations =
providerUserOrganizationDetails?.Select(po => new ProfileProviderOrganizationResponseModel(po)); providerUserOrganizationDetails?.Select(po => new ProfileProviderOrganizationResponseModel(po));
ManagedByOrganizationId = managedByOrganizationId;
} }
public ProfileResponseModel() : base("profile") public ProfileResponseModel() : base("profile")
@ -61,6 +63,7 @@ public class ProfileResponseModel : ResponseModel
public bool UsesKeyConnector { get; set; } public bool UsesKeyConnector { get; set; }
public string AvatarColor { get; set; } public string AvatarColor { get; set; }
public DateTime CreationDate { get; set; } public DateTime CreationDate { get; set; }
public Guid? ManagedByOrganizationId { get; set; }
public IEnumerable<ProfileOrganizationResponseModel> Organizations { get; set; } public IEnumerable<ProfileOrganizationResponseModel> Organizations { get; set; }
public IEnumerable<ProfileProviderResponseModel> Providers { get; set; } public IEnumerable<ProfileProviderResponseModel> Providers { get; set; }
public IEnumerable<ProfileProviderOrganizationResponseModel> ProviderOrganizations { get; set; } public IEnumerable<ProfileProviderOrganizationResponseModel> ProviderOrganizations { get; set; }

View File

@ -1,4 +1,5 @@
using Bit.Api.Vault.Models.Response; using Bit.Api.Vault.Models.Response;
using Bit.Core;
using Bit.Core.AdminConsole.Entities; using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.Enums.Provider; using Bit.Core.AdminConsole.Enums.Provider;
using Bit.Core.AdminConsole.Repositories; using Bit.Core.AdminConsole.Repositories;
@ -6,6 +7,7 @@ using Bit.Core.Entities;
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.Exceptions; using Bit.Core.Exceptions;
using Bit.Core.Models.Data; using Bit.Core.Models.Data;
using Bit.Core.Models.Data.Organizations.OrganizationUsers;
using Bit.Core.Repositories; using Bit.Core.Repositories;
using Bit.Core.Services; using Bit.Core.Services;
using Bit.Core.Settings; using Bit.Core.Settings;
@ -30,6 +32,7 @@ public class SyncController : Controller
private readonly IPolicyRepository _policyRepository; private readonly IPolicyRepository _policyRepository;
private readonly ISendRepository _sendRepository; private readonly ISendRepository _sendRepository;
private readonly GlobalSettings _globalSettings; private readonly GlobalSettings _globalSettings;
private readonly IFeatureService _featureService;
public SyncController( public SyncController(
IUserService userService, IUserService userService,
@ -41,7 +44,8 @@ public class SyncController : Controller
IProviderUserRepository providerUserRepository, IProviderUserRepository providerUserRepository,
IPolicyRepository policyRepository, IPolicyRepository policyRepository,
ISendRepository sendRepository, ISendRepository sendRepository,
GlobalSettings globalSettings) GlobalSettings globalSettings,
IFeatureService featureService)
{ {
_userService = userService; _userService = userService;
_folderRepository = folderRepository; _folderRepository = folderRepository;
@ -53,6 +57,7 @@ public class SyncController : Controller
_policyRepository = policyRepository; _policyRepository = policyRepository;
_sendRepository = sendRepository; _sendRepository = sendRepository;
_globalSettings = globalSettings; _globalSettings = globalSettings;
_featureService = featureService;
} }
[HttpGet("")] [HttpGet("")]
@ -90,9 +95,23 @@ public class SyncController : Controller
var userTwoFactorEnabled = await _userService.TwoFactorIsEnabledAsync(user); var userTwoFactorEnabled = await _userService.TwoFactorIsEnabledAsync(user);
var userHasPremiumFromOrganization = await _userService.HasPremiumFromOrganization(user); var userHasPremiumFromOrganization = await _userService.HasPremiumFromOrganization(user);
var response = new SyncResponseModel(_globalSettings, user, userTwoFactorEnabled, userHasPremiumFromOrganization, organizationUserDetails, var managedByOrganizationId = await GetManagedByOrganizationIdAsync(user, organizationUserDetails);
providerUserDetails, providerUserOrganizationDetails, folders, collections, ciphers,
collectionCiphersGroupDict, excludeDomains, policies, sends); var response = new SyncResponseModel(_globalSettings, user, userTwoFactorEnabled, userHasPremiumFromOrganization,
managedByOrganizationId, organizationUserDetails, providerUserDetails, providerUserOrganizationDetails,
folders, collections, ciphers, collectionCiphersGroupDict, excludeDomains, policies, sends);
return response; 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;
}
} }

View File

@ -21,6 +21,7 @@ public class SyncResponseModel : ResponseModel
User user, User user,
bool userTwoFactorEnabled, bool userTwoFactorEnabled,
bool userHasPremiumFromOrganization, bool userHasPremiumFromOrganization,
Guid? managedByOrganizationId,
IEnumerable<OrganizationUserOrganizationDetails> organizationUserDetails, IEnumerable<OrganizationUserOrganizationDetails> organizationUserDetails,
IEnumerable<ProviderUserProviderDetails> providerUserDetails, IEnumerable<ProviderUserProviderDetails> providerUserDetails,
IEnumerable<ProviderUserOrganizationDetails> providerUserOrganizationDetails, IEnumerable<ProviderUserOrganizationDetails> providerUserOrganizationDetails,
@ -34,7 +35,7 @@ public class SyncResponseModel : ResponseModel
: base("sync") : base("sync")
{ {
Profile = new ProfileResponseModel(user, organizationUserDetails, providerUserDetails, Profile = new ProfileResponseModel(user, organizationUserDetails, providerUserDetails,
providerUserOrganizationDetails, userTwoFactorEnabled, userHasPremiumFromOrganization); providerUserOrganizationDetails, userTwoFactorEnabled, userHasPremiumFromOrganization, managedByOrganizationId);
Folders = folders.Select(f => new FolderResponseModel(f)); Folders = folders.Select(f => new FolderResponseModel(f));
Ciphers = ciphers.Select(c => new CipherDetailsResponseModel(c, globalSettings, collectionCiphersDict)); Ciphers = ciphers.Select(c => new CipherDetailsResponseModel(c, globalSettings, collectionCiphersDict));
Collections = collections?.Select( Collections = collections?.Select(

View File

@ -1,4 +1,5 @@
using System.Security.Claims; using System.Security.Claims;
using Bit.Core.AdminConsole.Entities;
using Bit.Core.Auth.Enums; using Bit.Core.Auth.Enums;
using Bit.Core.Auth.Models; using Bit.Core.Auth.Models;
using Bit.Core.Entities; using Bit.Core.Entities;
@ -95,4 +96,10 @@ public interface IUserService
/// The organization must be enabled and be on an Enterprise plan. /// The organization must be enabled and be on an Enterprise plan.
/// </remarks> /// </remarks>
Task<bool> IsManagedByAnyOrganizationAsync(Guid userId); Task<bool> IsManagedByAnyOrganizationAsync(Guid userId);
/// <summary>
/// Gets the organization that manages the user.
/// </summary>
/// <inheritdoc cref="IsManagedByAnyOrganizationAsync(Guid)"/>
Task<Organization> GetOrganizationManagingUserAsync(Guid userId);
} }

View File

@ -1,4 +1,5 @@
using System.Security.Claims; using System.Security.Claims;
using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.Enums; using Bit.Core.AdminConsole.Enums;
using Bit.Core.AdminConsole.Repositories; using Bit.Core.AdminConsole.Repositories;
using Bit.Core.AdminConsole.Services; using Bit.Core.AdminConsole.Services;
@ -1245,13 +1246,19 @@ public class UserService : UserManager<User>, IUserService, IDisposable
} }
public async Task<bool> IsManagedByAnyOrganizationAsync(Guid userId) public async Task<bool> IsManagedByAnyOrganizationAsync(Guid userId)
{
var managingOrganization = await GetOrganizationManagingUserAsync(userId);
return managingOrganization != null;
}
public async Task<Organization> GetOrganizationManagingUserAsync(Guid userId)
{ {
// Users can only be managed by an Organization that is enabled and can have organization domains // Users can only be managed by an Organization that is enabled and can have organization domains
var organization = await _organizationRepository.GetByClaimedUserDomainAsync(userId); var organization = await _organizationRepository.GetByClaimedUserDomainAsync(userId);
// TODO: Replace "UseSso" with a new organization ability like "UseOrganizationDomains" (PM-11622). // 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. // Verified domains were tied to SSO, so we currently check the "UseSso" organization ability.
return organization is { Enabled: true, UseSso: true }; return (organization is { Enabled: true, UseSso: true }) ? organization : null;
} }
/// <inheritdoc cref="IsLegacyUser(string)"/> /// <inheritdoc cref="IsLegacyUser(string)"/>