mirror of
https://github.com/bitwarden/server.git
synced 2025-05-09 13:42:21 -05:00
[PM-20092] Refactor OrganizationUsersController Get to return account recovery users (#5756)
* wip * wip * add dict conversion to Get * wip * clean up * clean up * continue refactor * Fix feature flag Co-authored-by: Rui Tomé <108268980+r-tome@users.noreply.github.com> --------- Co-authored-by: Rui Tomé <108268980+r-tome@users.noreply.github.com>
This commit is contained in:
parent
10fcff58b2
commit
28467fc8f6
@ -0,0 +1,20 @@
|
|||||||
|
#nullable enable
|
||||||
|
|
||||||
|
using Bit.Core.Context;
|
||||||
|
using Bit.Core.Enums;
|
||||||
|
|
||||||
|
namespace Bit.Api.AdminConsole.Authorization.Requirements;
|
||||||
|
|
||||||
|
public class ManageAccountRecoveryRequirement : IOrganizationRequirement
|
||||||
|
{
|
||||||
|
public async Task<bool> AuthorizeAsync(
|
||||||
|
CurrentContextOrganization? organizationClaims,
|
||||||
|
Func<Task<bool>> isProviderUserForOrg)
|
||||||
|
=> organizationClaims switch
|
||||||
|
{
|
||||||
|
{ Type: OrganizationUserType.Owner } => true,
|
||||||
|
{ Type: OrganizationUserType.Admin } => true,
|
||||||
|
{ Permissions.ManageResetPassword: true } => true,
|
||||||
|
_ => await isProviderUserForOrg()
|
||||||
|
};
|
||||||
|
}
|
@ -1,4 +1,5 @@
|
|||||||
using Bit.Api.AdminConsole.Models.Request.Organizations;
|
using Bit.Api.AdminConsole.Authorization.Requirements;
|
||||||
|
using Bit.Api.AdminConsole.Models.Request.Organizations;
|
||||||
using Bit.Api.AdminConsole.Models.Response.Organizations;
|
using Bit.Api.AdminConsole.Models.Response.Organizations;
|
||||||
using Bit.Api.Models.Request.Organizations;
|
using Bit.Api.Models.Request.Organizations;
|
||||||
using Bit.Api.Models.Response;
|
using Bit.Api.Models.Response;
|
||||||
@ -162,6 +163,12 @@ public class OrganizationUsersController : Controller
|
|||||||
[HttpGet("")]
|
[HttpGet("")]
|
||||||
public async Task<ListResponseModel<OrganizationUserUserDetailsResponseModel>> Get(Guid orgId, bool includeGroups = false, bool includeCollections = false)
|
public async Task<ListResponseModel<OrganizationUserUserDetailsResponseModel>> Get(Guid orgId, bool includeGroups = false, bool includeCollections = false)
|
||||||
{
|
{
|
||||||
|
|
||||||
|
if (_featureService.IsEnabled(FeatureFlagKeys.SeparateCustomRolePermissions))
|
||||||
|
{
|
||||||
|
return await GetvNextAsync(orgId, includeGroups, includeCollections);
|
||||||
|
}
|
||||||
|
|
||||||
var authorized = (await _authorizationService.AuthorizeAsync(
|
var authorized = (await _authorizationService.AuthorizeAsync(
|
||||||
User, new OrganizationScope(orgId), OrganizationUserUserDetailsOperations.ReadAll)).Succeeded;
|
User, new OrganizationScope(orgId), OrganizationUserUserDetailsOperations.ReadAll)).Succeeded;
|
||||||
if (!authorized)
|
if (!authorized)
|
||||||
@ -191,6 +198,37 @@ public class OrganizationUsersController : Controller
|
|||||||
return new ListResponseModel<OrganizationUserUserDetailsResponseModel>(responses);
|
return new ListResponseModel<OrganizationUserUserDetailsResponseModel>(responses);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task<ListResponseModel<OrganizationUserUserDetailsResponseModel>> GetvNextAsync(Guid orgId, bool includeGroups = false, bool includeCollections = false)
|
||||||
|
{
|
||||||
|
var request = new OrganizationUserUserDetailsQueryRequest
|
||||||
|
{
|
||||||
|
OrganizationId = orgId,
|
||||||
|
IncludeGroups = includeGroups,
|
||||||
|
IncludeCollections = includeCollections,
|
||||||
|
};
|
||||||
|
|
||||||
|
if ((await _authorizationService.AuthorizeAsync(User, new ManageUsersRequirement())).Succeeded)
|
||||||
|
{
|
||||||
|
return GetResultListResponseModel(await _organizationUserUserDetailsQuery.Get(request));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((await _authorizationService.AuthorizeAsync(User, new ManageAccountRecoveryRequirement())).Succeeded)
|
||||||
|
{
|
||||||
|
return GetResultListResponseModel(await _organizationUserUserDetailsQuery.GetAccountRecoveryEnrolledUsers(request));
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new NotFoundException();
|
||||||
|
}
|
||||||
|
|
||||||
|
private ListResponseModel<OrganizationUserUserDetailsResponseModel> GetResultListResponseModel(IEnumerable<(OrganizationUserUserDetails OrgUser,
|
||||||
|
bool TwoFactorEnabled, bool ClaimedByOrganization)> results)
|
||||||
|
{
|
||||||
|
return new ListResponseModel<OrganizationUserUserDetailsResponseModel>(results
|
||||||
|
.Select(result => new OrganizationUserUserDetailsResponseModel(result))
|
||||||
|
.ToList());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
[HttpGet("{id}/groups")]
|
[HttpGet("{id}/groups")]
|
||||||
public async Task<IEnumerable<string>> GetGroups(string orgId, string id)
|
public async Task<IEnumerable<string>> GetGroups(string orgId, string id)
|
||||||
{
|
{
|
||||||
|
@ -126,6 +126,26 @@ public class OrganizationUserUserMiniDetailsResponseModel : ResponseModel
|
|||||||
|
|
||||||
public class OrganizationUserUserDetailsResponseModel : OrganizationUserResponseModel
|
public class OrganizationUserUserDetailsResponseModel : OrganizationUserResponseModel
|
||||||
{
|
{
|
||||||
|
public OrganizationUserUserDetailsResponseModel((OrganizationUserUserDetails OrgUser, bool TwoFactorEnabled, bool ClaimedByOrganization) data, string obj = "organizationUserUserDetails")
|
||||||
|
: base(data.OrgUser, obj)
|
||||||
|
{
|
||||||
|
if (data.OrgUser == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(data.OrgUser));
|
||||||
|
}
|
||||||
|
|
||||||
|
Name = data.OrgUser.Name;
|
||||||
|
Email = data.OrgUser.Email;
|
||||||
|
AvatarColor = data.OrgUser.AvatarColor;
|
||||||
|
TwoFactorEnabled = data.TwoFactorEnabled;
|
||||||
|
SsoBound = !string.IsNullOrWhiteSpace(data.OrgUser.SsoExternalId);
|
||||||
|
Collections = data.OrgUser.Collections.Select(c => new SelectionReadOnlyResponseModel(c));
|
||||||
|
Groups = data.OrgUser.Groups;
|
||||||
|
// Prevent reset password when using key connector.
|
||||||
|
ResetPasswordEnrolled = ResetPasswordEnrolled && !data.OrgUser.UsesKeyConnector;
|
||||||
|
ClaimedByOrganization = data.ClaimedByOrganization;
|
||||||
|
}
|
||||||
|
|
||||||
public OrganizationUserUserDetailsResponseModel(OrganizationUserUserDetails organizationUser,
|
public OrganizationUserUserDetailsResponseModel(OrganizationUserUserDetails organizationUser,
|
||||||
bool twoFactorEnabled, bool claimedByOrganization, string obj = "organizationUserUserDetails")
|
bool twoFactorEnabled, bool claimedByOrganization, string obj = "organizationUserUserDetails")
|
||||||
: base(organizationUser, obj)
|
: base(organizationUser, obj)
|
||||||
|
@ -6,4 +6,8 @@ namespace Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces;
|
|||||||
public interface IOrganizationUserUserDetailsQuery
|
public interface IOrganizationUserUserDetailsQuery
|
||||||
{
|
{
|
||||||
Task<IEnumerable<OrganizationUserUserDetails>> GetOrganizationUserUserDetails(OrganizationUserUserDetailsQueryRequest request);
|
Task<IEnumerable<OrganizationUserUserDetails>> GetOrganizationUserUserDetails(OrganizationUserUserDetailsQueryRequest request);
|
||||||
|
|
||||||
|
Task<IEnumerable<(OrganizationUserUserDetails OrgUser, bool TwoFactorEnabled, bool ClaimedByOrganization)>> Get(OrganizationUserUserDetailsQueryRequest request);
|
||||||
|
|
||||||
|
Task<IEnumerable<(OrganizationUserUserDetails OrgUser, bool TwoFactorEnabled, bool ClaimedByOrganization)>> GetAccountRecoveryEnrolledUsers(OrganizationUserUserDetailsQueryRequest request);
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,9 @@
|
|||||||
using Bit.Core.Models.Data.Organizations.OrganizationUsers;
|
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces;
|
||||||
|
using Bit.Core.Auth.UserFeatures.TwoFactorAuth.Interfaces;
|
||||||
|
using Bit.Core.Enums;
|
||||||
|
using Bit.Core.Models.Data.Organizations.OrganizationUsers;
|
||||||
using Bit.Core.Repositories;
|
using Bit.Core.Repositories;
|
||||||
|
using Bit.Core.Services;
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
using Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces;
|
using Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces;
|
||||||
using Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Requests;
|
using Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Requests;
|
||||||
@ -9,12 +13,21 @@ namespace Core.AdminConsole.OrganizationFeatures.OrganizationUsers;
|
|||||||
public class OrganizationUserUserDetailsQuery : IOrganizationUserUserDetailsQuery
|
public class OrganizationUserUserDetailsQuery : IOrganizationUserUserDetailsQuery
|
||||||
{
|
{
|
||||||
private readonly IOrganizationUserRepository _organizationUserRepository;
|
private readonly IOrganizationUserRepository _organizationUserRepository;
|
||||||
|
private readonly ITwoFactorIsEnabledQuery _twoFactorIsEnabledQuery;
|
||||||
|
private readonly IFeatureService _featureService;
|
||||||
|
private readonly IGetOrganizationUsersClaimedStatusQuery _getOrganizationUsersClaimedStatusQuery;
|
||||||
|
|
||||||
public OrganizationUserUserDetailsQuery(
|
public OrganizationUserUserDetailsQuery(
|
||||||
IOrganizationUserRepository organizationUserRepository
|
IOrganizationUserRepository organizationUserRepository,
|
||||||
|
ITwoFactorIsEnabledQuery twoFactorIsEnabledQuery,
|
||||||
|
IFeatureService featureService,
|
||||||
|
IGetOrganizationUsersClaimedStatusQuery getOrganizationUsersClaimedStatusQuery
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
_organizationUserRepository = organizationUserRepository;
|
_organizationUserRepository = organizationUserRepository;
|
||||||
|
_twoFactorIsEnabledQuery = twoFactorIsEnabledQuery;
|
||||||
|
_featureService = featureService;
|
||||||
|
_getOrganizationUsersClaimedStatusQuery = getOrganizationUsersClaimedStatusQuery;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -37,4 +50,42 @@ public class OrganizationUserUserDetailsQuery : IOrganizationUserUserDetailsQuer
|
|||||||
return o;
|
return o;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get the organization user user details, two factor enabled status, and
|
||||||
|
/// claimed status for the provided request.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="request">Request details for the query</param>
|
||||||
|
/// <returns>List of OrganizationUserUserDetails</returns>
|
||||||
|
public async Task<IEnumerable<(OrganizationUserUserDetails OrgUser, bool TwoFactorEnabled, bool ClaimedByOrganization)>> Get(OrganizationUserUserDetailsQueryRequest request)
|
||||||
|
{
|
||||||
|
var organizationUsers = await GetOrganizationUserUserDetails(request);
|
||||||
|
|
||||||
|
var organizationUsersTwoFactorEnabled = (await _twoFactorIsEnabledQuery.TwoFactorIsEnabledAsync(organizationUsers)).ToDictionary(u => u.user.Id);
|
||||||
|
var organizationUsersClaimedStatus = await _getOrganizationUsersClaimedStatusQuery.GetUsersOrganizationClaimedStatusAsync(request.OrganizationId, organizationUsers.Select(o => o.Id));
|
||||||
|
var responses = organizationUsers.Select(o => (o, organizationUsersTwoFactorEnabled[o.Id].twoFactorIsEnabled, organizationUsersClaimedStatus[o.Id]));
|
||||||
|
|
||||||
|
|
||||||
|
return responses;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get the organization users user details, two factor enabled status, and
|
||||||
|
/// claimed status for confirmed users that are enrolled in account recovery
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="request">Request details for the query</param>
|
||||||
|
/// <returns>List of OrganizationUserUserDetails</returns>
|
||||||
|
public async Task<IEnumerable<(OrganizationUserUserDetails OrgUser, bool TwoFactorEnabled, bool ClaimedByOrganization)>> GetAccountRecoveryEnrolledUsers(OrganizationUserUserDetailsQueryRequest request)
|
||||||
|
{
|
||||||
|
var organizationUsers = (await GetOrganizationUserUserDetails(request))
|
||||||
|
.Where(o => o.Status.Equals(OrganizationUserStatusType.Confirmed) && o.UsesKeyConnector == false && !String.IsNullOrEmpty(o.ResetPasswordKey));
|
||||||
|
|
||||||
|
var organizationUsersTwoFactorEnabled = (await _twoFactorIsEnabledQuery.TwoFactorIsEnabledAsync(organizationUsers)).ToDictionary(u => u.user.Id);
|
||||||
|
var organizationUsersClaimedStatus = await _getOrganizationUsersClaimedStatusQuery.GetUsersOrganizationClaimedStatusAsync(request.OrganizationId, organizationUsers.Select(o => o.Id));
|
||||||
|
var responses = organizationUsers
|
||||||
|
.Select(o => (o, organizationUsersTwoFactorEnabled[o.Id].twoFactorIsEnabled, organizationUsersClaimedStatus[o.Id]));
|
||||||
|
|
||||||
|
return responses;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -205,6 +205,7 @@ public static class FeatureFlagKeys
|
|||||||
public const string DesktopCipherForms = "pm-18520-desktop-cipher-forms";
|
public const string DesktopCipherForms = "pm-18520-desktop-cipher-forms";
|
||||||
public const string PM19941MigrateCipherDomainToSdk = "pm-19941-migrate-cipher-domain-to-sdk";
|
public const string PM19941MigrateCipherDomainToSdk = "pm-19941-migrate-cipher-domain-to-sdk";
|
||||||
public const string EndUserNotifications = "pm-10609-end-user-notifications";
|
public const string EndUserNotifications = "pm-10609-end-user-notifications";
|
||||||
|
public const string SeparateCustomRolePermissions = "pm-19917-separate-custom-role-permissions";
|
||||||
public const string PhishingDetection = "phishing-detection";
|
public const string PhishingDetection = "phishing-detection";
|
||||||
|
|
||||||
public static List<string> GetAllKeys()
|
public static List<string> GetAllKeys()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user