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 hasPremiumFromOrg = await _userService.HasPremiumFromOrganization(user);
var managedByOrganizationId = await GetManagedByOrganizationIdAsync(user);
var response = new ProfileResponseModel(user, organizationUserDetails, providerUserDetails,
providerUserOrganizationDetails, twoFactorEnabled,
hasPremiumFromOrg);
hasPremiumFromOrg, managedByOrganizationId);
return response;
}
@ -471,7 +472,12 @@ public class AccountsController : Controller
}
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;
}
@ -485,7 +491,12 @@ public class AccountsController : Controller
throw new UnauthorizedAccessException();
}
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;
}
@ -633,7 +644,12 @@ public class AccountsController : Controller
BillingAddressCountry = model.Country,
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
{
UserProfile = profile,
@ -920,4 +936,15 @@ public class AccountsController : Controller
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<ProviderUserOrganizationDetails> providerUserOrganizationDetails,
bool twoFactorEnabled,
bool premiumFromOrganization) : base("profile")
bool premiumFromOrganization,
Guid? managedByOrganizationId) : base("profile")
{
if (user == null)
{
@ -40,6 +41,7 @@ public class ProfileResponseModel : ResponseModel
Providers = providerUserDetails?.Select(p => new ProfileProviderResponseModel(p));
ProviderOrganizations =
providerUserOrganizationDetails?.Select(po => new ProfileProviderOrganizationResponseModel(po));
ManagedByOrganizationId = managedByOrganizationId;
}
public ProfileResponseModel() : base("profile")
@ -61,6 +63,7 @@ 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; }

View File

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

View File

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

View File

@ -1,4 +1,5 @@
using System.Security.Claims;
using Bit.Core.AdminConsole.Entities;
using Bit.Core.Auth.Enums;
using Bit.Core.Auth.Models;
using Bit.Core.Entities;
@ -95,4 +96,10 @@ public interface IUserService
/// The organization must be enabled and be on an Enterprise plan.
/// </remarks>
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 Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.Enums;
using Bit.Core.AdminConsole.Repositories;
using Bit.Core.AdminConsole.Services;
@ -1245,13 +1246,19 @@ public class UserService : UserManager<User>, IUserService, IDisposable
}
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
var organization = await _organizationRepository.GetByClaimedUserDomainAsync(userId);
// 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 };
return (organization is { Enabled: true, UseSso: true }) ? organization : null;
}
/// <inheritdoc cref="IsLegacyUser(string)"/>