Skip to content

Commit

Permalink
[PM-11334] Add managed status to sync data (#4791)
Browse files Browse the repository at this point in the history
* Refactor UserService to add GetOrganizationManagingUserAsync method to retrive the organization that manages a user

* Refactor SyncController and AccountsController to include ManagedByOrganizationId in profile response
  • Loading branch information
r-tome authored Sep 26, 2024
1 parent 2e072ae commit 3f629e0
Show file tree
Hide file tree
Showing 6 changed files with 75 additions and 11 deletions.
35 changes: 31 additions & 4 deletions src/Api/Auth/Controllers/AccountsController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -443,10 +443,11 @@ await _providerUserRepository.GetManyOrganizationDetailsByUserAsync(user.Id,

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;
}

Expand All @@ -471,7 +472,12 @@ public async Task<ProfileResponseModel> PutProfile([FromBody] UpdateProfileReque
}

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;
}

Expand All @@ -485,7 +491,12 @@ public async Task<ProfileResponseModel> PutAvatar([FromBody] UpdateAvatarRequest
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;
}

Expand Down Expand Up @@ -633,7 +644,12 @@ public async Task<PaymentResponseModel> PostPremium(PremiumRequestModel model)
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,
Expand Down Expand Up @@ -920,4 +936,15 @@ public async Task VerifyOTP([FromBody] VerifyOTPRequestModel model)
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;
}
}
5 changes: 4 additions & 1 deletion src/Api/Models/Response/ProfileResponseModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ public ProfileResponseModel(User user,
IEnumerable<ProviderUserProviderDetails> providerUserDetails,
IEnumerable<ProviderUserOrganizationDetails> providerUserOrganizationDetails,
bool twoFactorEnabled,
bool premiumFromOrganization) : base("profile")
bool premiumFromOrganization,
Guid? managedByOrganizationId) : base("profile")
{
if (user == null)
{
Expand All @@ -40,6 +41,7 @@ public ProfileResponseModel(User user,
Providers = providerUserDetails?.Select(p => new ProfileProviderResponseModel(p));
ProviderOrganizations =
providerUserOrganizationDetails?.Select(po => new ProfileProviderOrganizationResponseModel(po));
ManagedByOrganizationId = managedByOrganizationId;
}

public ProfileResponseModel() : base("profile")
Expand All @@ -61,6 +63,7 @@ public ProfileResponseModel() : base("profile")
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; }
Expand Down
27 changes: 23 additions & 4 deletions src/Api/Vault/Controllers/SyncController.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
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;
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;
Expand All @@ -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,
Expand All @@ -41,7 +44,8 @@ public SyncController(
IProviderUserRepository providerUserRepository,
IPolicyRepository policyRepository,
ISendRepository sendRepository,
GlobalSettings globalSettings)
GlobalSettings globalSettings,
IFeatureService featureService)
{
_userService = userService;
_folderRepository = folderRepository;
Expand All @@ -53,6 +57,7 @@ public SyncController(
_policyRepository = policyRepository;
_sendRepository = sendRepository;
_globalSettings = globalSettings;
_featureService = featureService;
}

[HttpGet("")]
Expand Down Expand Up @@ -90,9 +95,23 @@ await _providerUserRepository.GetManyOrganizationDetailsByUserAsync(user.Id,

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;
}
}
3 changes: 2 additions & 1 deletion src/Api/Vault/Models/Response/SyncResponseModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ public SyncResponseModel(
User user,
bool userTwoFactorEnabled,
bool userHasPremiumFromOrganization,
Guid? managedByOrganizationId,
IEnumerable<OrganizationUserOrganizationDetails> organizationUserDetails,
IEnumerable<ProviderUserProviderDetails> providerUserDetails,
IEnumerable<ProviderUserOrganizationDetails> providerUserOrganizationDetails,
Expand All @@ -34,7 +35,7 @@ public SyncResponseModel(
: 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(
Expand Down
7 changes: 7 additions & 0 deletions src/Core/Services/IUserService.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -95,4 +96,10 @@ Task<IdentityResult> UpdatePasswordHash(User user, string newPassword,
/// 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);
}
9 changes: 8 additions & 1 deletion src/Core/Services/Implementations/UserService.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -1245,13 +1246,19 @@ public async Task<bool> IsLegacyUser(string 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
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)"/>
Expand Down

0 comments on commit 3f629e0

Please sign in to comment.