mirror of
https://github.com/bitwarden/server.git
synced 2025-07-01 16:12:49 -05:00

* [PM-1203] feat: allow verification for all passwordless accounts (#3038) * [PM-1033] Org invite user creation flow 1 (#3028) * [PM-1033] feat: remove user verification from password enrollment * [PM-1033] feat: auto accept invitation when enrolling into password reset * [PM-1033] fix: controller tests * [PM-1033] refactor: `UpdateUserResetPasswordEnrollmentCommand` * [PM-1033] refactor(wip): make `AcceptUserCommand` * Revert "[PM-1033] refactor(wip): make `AcceptUserCommand`" This reverts commitdc1319e7fa
. * Revert "[PM-1033] refactor: `UpdateUserResetPasswordEnrollmentCommand`" This reverts commit43df689c7f
. * [PM-1033] refactor: move invite accept to controller This avoids creating yet another method that depends on having `IUserService` passed in as a parameter * [PM-1033] fix: add missing changes * [PM-1381] Add Trusted Device Keys to Auth Response (#3066) * Return Keys for Trusted Device - Check whether the current logging in device is trusted - Return their keys on successful login * Formatting * Address PR Feedback * Add Remarks Comment * [PM-1338] `AuthRequest` Event Logs (#3046) * Update AuthRequestController - Only allow AdminApproval Requests to be created from authed endpoint - Add endpoint that has authentication to be able to create admin approval * Add PasswordlessAuthSettings - Add settings for customizing expiration times * Add new EventTypes * Add Logic for AdminApproval Type - Add logic for validating AdminApproval expiration - Add event logging for Approval/Disapproval of AdminApproval - Add logic for creating AdminApproval types * Add Test Helpers - Change BitAutoData to allow you to use string representations of common types. * Add/Update AuthRequestService Tests * Run Formatting * Switch to 7 Days * Add Test Covering ResponseDate Being Set * Address PR Feedback - Create helper for checking if date is expired - Move validation logic into smaller methods * Switch to User Event Type - Make RequestDeviceApproval user type - User types will log for each org user is in * [PM-2998] Move Approving Device Check (#3101) * Move Check for Approving Devices - Exclude currently logging in device - Remove old way of checking - Add tests asserting behavior * Update DeviceType list * Update Naming & Address PR Feedback * Fix Tests * Address PR Feedback * Formatting * Now Fully Update Naming? * Feature/auth/pm 2759/add can reset password to user decryption options (#3113) * PM-2759 - BaseRequestValidator.cs - CreateUserDecryptionOptionsAsync - Add new hasManageResetPasswordPermission for post SSO redirect logic required on client. * PM-2759 - Update IdentityServerSsoTests.cs to all pass based on the addition of HasManageResetPasswordPermission to TrustedDeviceUserDecryptionOption * IdentityServerSsoTests.cs - fix typo in test name: LoggingApproval --> LoginApproval * PM1259 - Add test case for verifying that TrustedDeviceOption.hasManageResetPasswordPermission is set properly based on user permission * dotnet format run * Feature/auth/pm 2759/add can reset password to user decryption options fix jit users (#3120) * PM-2759 - IdentityServer - CreateUserDecryptionOptionsAsync - hasManageResetPasswordPermission set logic was broken for JIT provisioned users as I assumed we would always have a list of at least 1 org during the SSO process. Added TODO for future test addition but getting this out there now as QA is blocked by being unable to create JIT provisioned users. * dotnet format * Tiny tweak * [PM-1339] Allow Rotating Device Keys (#3096) * Allow Rotation of Trusted Device Keys - Add endpoint for getting keys relating to rotation - Add endpoint for rotating your current device - In the same endpoint allow a list of other devices to rotate * Formatting * Use Extension Method * Add Tests from PR Co-authored-by: Jared Snider <jsnider@bitwarden.com> --------- Co-authored-by: Jared Snider <jsnider@bitwarden.com> * Check the user directly if they have the ResetPasswordKey (#3153) * PM-3327 - UpdateKeyAsync must exempt the currently calling device from the logout notification in order to prevent prematurely logging the user out before the client side key rotation process can complete. The calling device will log itself out once it is done. (#3170) * Allow OTP Requests When Users Are On TDE (#3184) * [PM-3356][PM-3292] Allow OTP For All (#3188) * Allow OTP For All - On a trusted device isn't a good check because a user might be using a trusted device locally but not trusted it long term - The logic wasn't working for KC users anyways * Remove Old Comment * [AC-1601] Added RequireSso policy as a dependency of TDE (#3209) * Added RequireSso policy as a dependency of TDE. * Added test for RequireSso for TDE. * Added save. * Fixed policy name. --------- Co-authored-by: Andreas Coroiu <acoroiu@bitwarden.com> Co-authored-by: Justin Baur <19896123+justindbaur@users.noreply.github.com> Co-authored-by: Vincent Salucci <vincesalucci21@gmail.com> Co-authored-by: Jared Snider <116684653+JaredSnider-Bitwarden@users.noreply.github.com> Co-authored-by: Jared Snider <jsnider@bitwarden.com>
499 lines
20 KiB
C#
499 lines
20 KiB
C#
using Bit.Api.Models.Request.Organizations;
|
|
using Bit.Api.Models.Response;
|
|
using Bit.Api.Models.Response.Organizations;
|
|
using Bit.Core.Context;
|
|
using Bit.Core.Enums;
|
|
using Bit.Core.Exceptions;
|
|
using Bit.Core.Models.Business;
|
|
using Bit.Core.Models.Data.Organizations.OrganizationUsers;
|
|
using Bit.Core.Models.Data.Organizations.Policies;
|
|
using Bit.Core.OrganizationFeatures.OrganizationSubscriptions.Interface;
|
|
using Bit.Core.OrganizationFeatures.OrganizationUsers.Interfaces;
|
|
using Bit.Core.Repositories;
|
|
using Bit.Core.Services;
|
|
using Microsoft.AspNetCore.Authorization;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
|
|
namespace Bit.Api.Controllers;
|
|
|
|
[Route("organizations/{orgId}/users")]
|
|
[Authorize("Application")]
|
|
public class OrganizationUsersController : Controller
|
|
{
|
|
private readonly IOrganizationRepository _organizationRepository;
|
|
private readonly IOrganizationUserRepository _organizationUserRepository;
|
|
private readonly IOrganizationService _organizationService;
|
|
private readonly ICollectionRepository _collectionRepository;
|
|
private readonly IGroupRepository _groupRepository;
|
|
private readonly IUserService _userService;
|
|
private readonly IPolicyRepository _policyRepository;
|
|
private readonly ICurrentContext _currentContext;
|
|
private readonly ICountNewSmSeatsRequiredQuery _countNewSmSeatsRequiredQuery;
|
|
private readonly IUpdateSecretsManagerSubscriptionCommand _updateSecretsManagerSubscriptionCommand;
|
|
|
|
public OrganizationUsersController(
|
|
IOrganizationRepository organizationRepository,
|
|
IOrganizationUserRepository organizationUserRepository,
|
|
IOrganizationService organizationService,
|
|
ICollectionRepository collectionRepository,
|
|
IGroupRepository groupRepository,
|
|
IUserService userService,
|
|
IPolicyRepository policyRepository,
|
|
ICurrentContext currentContext,
|
|
ICountNewSmSeatsRequiredQuery countNewSmSeatsRequiredQuery,
|
|
IUpdateSecretsManagerSubscriptionCommand updateSecretsManagerSubscriptionCommand)
|
|
{
|
|
_organizationRepository = organizationRepository;
|
|
_organizationUserRepository = organizationUserRepository;
|
|
_organizationService = organizationService;
|
|
_collectionRepository = collectionRepository;
|
|
_groupRepository = groupRepository;
|
|
_userService = userService;
|
|
_policyRepository = policyRepository;
|
|
_currentContext = currentContext;
|
|
_countNewSmSeatsRequiredQuery = countNewSmSeatsRequiredQuery;
|
|
_updateSecretsManagerSubscriptionCommand = updateSecretsManagerSubscriptionCommand;
|
|
}
|
|
|
|
[HttpGet("{id}")]
|
|
public async Task<OrganizationUserDetailsResponseModel> Get(string id, bool includeGroups = false)
|
|
{
|
|
var organizationUser = await _organizationUserRepository.GetDetailsByIdWithCollectionsAsync(new Guid(id));
|
|
if (organizationUser == null || !await _currentContext.ManageUsers(organizationUser.Item1.OrganizationId))
|
|
{
|
|
throw new NotFoundException();
|
|
}
|
|
|
|
var response = new OrganizationUserDetailsResponseModel(organizationUser.Item1, organizationUser.Item2);
|
|
|
|
if (includeGroups)
|
|
{
|
|
response.Groups = await _groupRepository.GetManyIdsByUserIdAsync(organizationUser.Item1.Id);
|
|
}
|
|
|
|
return response;
|
|
}
|
|
|
|
[HttpGet("")]
|
|
public async Task<ListResponseModel<OrganizationUserUserDetailsResponseModel>> Get(string orgId, bool includeGroups = false, bool includeCollections = false)
|
|
{
|
|
var orgGuidId = new Guid(orgId);
|
|
if (!await _currentContext.ViewAllCollections(orgGuidId) &&
|
|
!await _currentContext.ViewAssignedCollections(orgGuidId) &&
|
|
!await _currentContext.ManageGroups(orgGuidId) &&
|
|
!await _currentContext.ManageUsers(orgGuidId))
|
|
{
|
|
throw new NotFoundException();
|
|
}
|
|
|
|
var organizationUsers = await _organizationUserRepository.GetManyDetailsByOrganizationAsync(orgGuidId, includeGroups, includeCollections);
|
|
var responseTasks = organizationUsers.Select(async o => new OrganizationUserUserDetailsResponseModel(o,
|
|
await _userService.TwoFactorIsEnabledAsync(o)));
|
|
var responses = await Task.WhenAll(responseTasks);
|
|
return new ListResponseModel<OrganizationUserUserDetailsResponseModel>(responses);
|
|
}
|
|
|
|
[HttpGet("{id}/groups")]
|
|
public async Task<IEnumerable<string>> GetGroups(string orgId, string id)
|
|
{
|
|
var organizationUser = await _organizationUserRepository.GetByIdAsync(new Guid(id));
|
|
if (organizationUser == null || (!await _currentContext.ManageGroups(organizationUser.OrganizationId) &&
|
|
!await _currentContext.ManageUsers(organizationUser.OrganizationId)))
|
|
{
|
|
throw new NotFoundException();
|
|
}
|
|
|
|
var groupIds = await _groupRepository.GetManyIdsByUserIdAsync(organizationUser.Id);
|
|
var responses = groupIds.Select(g => g.ToString());
|
|
return responses;
|
|
}
|
|
|
|
[HttpGet("{id}/reset-password-details")]
|
|
public async Task<OrganizationUserResetPasswordDetailsResponseModel> GetResetPasswordDetails(string orgId, string id)
|
|
{
|
|
// Make sure the calling user can reset passwords for this org
|
|
var orgGuidId = new Guid(orgId);
|
|
if (!await _currentContext.ManageResetPassword(orgGuidId))
|
|
{
|
|
throw new NotFoundException();
|
|
}
|
|
|
|
var organizationUser = await _organizationUserRepository.GetByIdAsync(new Guid(id));
|
|
if (organizationUser == null || !organizationUser.UserId.HasValue)
|
|
{
|
|
throw new NotFoundException();
|
|
}
|
|
|
|
// Retrieve data necessary for response (KDF, KDF Iterations, ResetPasswordKey)
|
|
// TODO Reset Password - Revisit this and create SPROC to reduce DB calls
|
|
var user = await _userService.GetUserByIdAsync(organizationUser.UserId.Value);
|
|
if (user == null)
|
|
{
|
|
throw new NotFoundException();
|
|
}
|
|
|
|
// Retrieve Encrypted Private Key from organization
|
|
var org = await _organizationRepository.GetByIdAsync(orgGuidId);
|
|
if (org == null)
|
|
{
|
|
throw new NotFoundException();
|
|
}
|
|
|
|
return new OrganizationUserResetPasswordDetailsResponseModel(new OrganizationUserResetPasswordDetails(organizationUser, user, org));
|
|
}
|
|
|
|
[HttpPost("invite")]
|
|
public async Task Invite(string orgId, [FromBody] OrganizationUserInviteRequestModel model)
|
|
{
|
|
var orgGuidId = new Guid(orgId);
|
|
if (!await _currentContext.ManageUsers(orgGuidId))
|
|
{
|
|
throw new NotFoundException();
|
|
}
|
|
|
|
var userId = _userService.GetProperUserId(User);
|
|
var result = await _organizationService.InviteUsersAsync(orgGuidId, userId.Value,
|
|
new (OrganizationUserInvite, string)[] { (new OrganizationUserInvite(model.ToData()), null) });
|
|
}
|
|
|
|
[HttpPost("reinvite")]
|
|
public async Task<ListResponseModel<OrganizationUserBulkResponseModel>> BulkReinvite(string orgId, [FromBody] OrganizationUserBulkRequestModel model)
|
|
{
|
|
var orgGuidId = new Guid(orgId);
|
|
if (!await _currentContext.ManageUsers(orgGuidId))
|
|
{
|
|
throw new NotFoundException();
|
|
}
|
|
|
|
var userId = _userService.GetProperUserId(User);
|
|
var result = await _organizationService.ResendInvitesAsync(orgGuidId, userId.Value, model.Ids);
|
|
return new ListResponseModel<OrganizationUserBulkResponseModel>(
|
|
result.Select(t => new OrganizationUserBulkResponseModel(t.Item1.Id, t.Item2)));
|
|
}
|
|
|
|
[HttpPost("{id}/reinvite")]
|
|
public async Task Reinvite(string orgId, string id)
|
|
{
|
|
var orgGuidId = new Guid(orgId);
|
|
if (!await _currentContext.ManageUsers(orgGuidId))
|
|
{
|
|
throw new NotFoundException();
|
|
}
|
|
|
|
var userId = _userService.GetProperUserId(User);
|
|
await _organizationService.ResendInviteAsync(orgGuidId, userId.Value, new Guid(id));
|
|
}
|
|
|
|
[HttpPost("{organizationUserId}/accept-init")]
|
|
public async Task AcceptInit(Guid orgId, Guid organizationUserId, [FromBody] OrganizationUserAcceptInitRequestModel model)
|
|
{
|
|
var user = await _userService.GetUserByPrincipalAsync(User);
|
|
if (user == null)
|
|
{
|
|
throw new UnauthorizedAccessException();
|
|
}
|
|
|
|
await _organizationService.InitPendingOrganization(user.Id, orgId, model.Keys.PublicKey, model.Keys.EncryptedPrivateKey, model.CollectionName);
|
|
await _organizationService.AcceptUserAsync(organizationUserId, user, model.Token, _userService);
|
|
await _organizationService.ConfirmUserAsync(orgId, organizationUserId, model.Key, user.Id, _userService);
|
|
}
|
|
|
|
[HttpPost("{organizationUserId}/accept")]
|
|
public async Task Accept(Guid orgId, Guid organizationUserId, [FromBody] OrganizationUserAcceptRequestModel model)
|
|
{
|
|
var user = await _userService.GetUserByPrincipalAsync(User);
|
|
if (user == null)
|
|
{
|
|
throw new UnauthorizedAccessException();
|
|
}
|
|
|
|
var masterPasswordPolicy = await _policyRepository.GetByOrganizationIdTypeAsync(orgId, PolicyType.ResetPassword);
|
|
var useMasterPasswordPolicy = masterPasswordPolicy != null &&
|
|
masterPasswordPolicy.Enabled &&
|
|
masterPasswordPolicy.GetDataModel<ResetPasswordDataModel>().AutoEnrollEnabled;
|
|
if (useMasterPasswordPolicy && string.IsNullOrWhiteSpace(model.ResetPasswordKey))
|
|
{
|
|
throw new BadRequestException(string.Empty, "Master Password reset is required, but not provided.");
|
|
}
|
|
|
|
await _organizationService.AcceptUserAsync(organizationUserId, user, model.Token, _userService);
|
|
|
|
if (useMasterPasswordPolicy)
|
|
{
|
|
await _organizationService.UpdateUserResetPasswordEnrollmentAsync(orgId, user.Id, model.ResetPasswordKey, user.Id);
|
|
}
|
|
}
|
|
|
|
[HttpPost("{id}/confirm")]
|
|
public async Task Confirm(string orgId, string id, [FromBody] OrganizationUserConfirmRequestModel model)
|
|
{
|
|
var orgGuidId = new Guid(orgId);
|
|
if (!await _currentContext.ManageUsers(orgGuidId))
|
|
{
|
|
throw new NotFoundException();
|
|
}
|
|
|
|
var userId = _userService.GetProperUserId(User);
|
|
var result = await _organizationService.ConfirmUserAsync(orgGuidId, new Guid(id), model.Key, userId.Value,
|
|
_userService);
|
|
}
|
|
|
|
[HttpPost("confirm")]
|
|
public async Task<ListResponseModel<OrganizationUserBulkResponseModel>> BulkConfirm(string orgId,
|
|
[FromBody] OrganizationUserBulkConfirmRequestModel model)
|
|
{
|
|
var orgGuidId = new Guid(orgId);
|
|
if (!await _currentContext.ManageUsers(orgGuidId))
|
|
{
|
|
throw new NotFoundException();
|
|
}
|
|
|
|
var userId = _userService.GetProperUserId(User);
|
|
var results = await _organizationService.ConfirmUsersAsync(orgGuidId, model.ToDictionary(), userId.Value,
|
|
_userService);
|
|
|
|
return new ListResponseModel<OrganizationUserBulkResponseModel>(results.Select(r =>
|
|
new OrganizationUserBulkResponseModel(r.Item1.Id, r.Item2)));
|
|
}
|
|
|
|
[HttpPost("public-keys")]
|
|
public async Task<ListResponseModel<OrganizationUserPublicKeyResponseModel>> UserPublicKeys(string orgId, [FromBody] OrganizationUserBulkRequestModel model)
|
|
{
|
|
var orgGuidId = new Guid(orgId);
|
|
if (!await _currentContext.ManageUsers(orgGuidId))
|
|
{
|
|
throw new NotFoundException();
|
|
}
|
|
|
|
var result = await _organizationUserRepository.GetManyPublicKeysByOrganizationUserAsync(orgGuidId, model.Ids);
|
|
var responses = result.Select(r => new OrganizationUserPublicKeyResponseModel(r.Id, r.UserId, r.PublicKey)).ToList();
|
|
return new ListResponseModel<OrganizationUserPublicKeyResponseModel>(responses);
|
|
}
|
|
|
|
[HttpPut("{id}")]
|
|
[HttpPost("{id}")]
|
|
public async Task Put(string orgId, string id, [FromBody] OrganizationUserUpdateRequestModel model)
|
|
{
|
|
var orgGuidId = new Guid(orgId);
|
|
if (!await _currentContext.ManageUsers(orgGuidId))
|
|
{
|
|
throw new NotFoundException();
|
|
}
|
|
|
|
var organizationUser = await _organizationUserRepository.GetByIdAsync(new Guid(id));
|
|
if (organizationUser == null || organizationUser.OrganizationId != orgGuidId)
|
|
{
|
|
throw new NotFoundException();
|
|
}
|
|
|
|
var userId = _userService.GetProperUserId(User);
|
|
await _organizationService.SaveUserAsync(model.ToOrganizationUser(organizationUser), userId.Value,
|
|
model.Collections?.Select(c => c.ToSelectionReadOnly()), model.Groups);
|
|
}
|
|
|
|
[HttpPut("{id}/groups")]
|
|
[HttpPost("{id}/groups")]
|
|
public async Task PutGroups(string orgId, string id, [FromBody] OrganizationUserUpdateGroupsRequestModel model)
|
|
{
|
|
var orgGuidId = new Guid(orgId);
|
|
if (!await _currentContext.ManageUsers(orgGuidId))
|
|
{
|
|
throw new NotFoundException();
|
|
}
|
|
|
|
var organizationUser = await _organizationUserRepository.GetByIdAsync(new Guid(id));
|
|
if (organizationUser == null || organizationUser.OrganizationId != orgGuidId)
|
|
{
|
|
throw new NotFoundException();
|
|
}
|
|
|
|
var loggedInUserId = _userService.GetProperUserId(User);
|
|
await _organizationService.UpdateUserGroupsAsync(organizationUser, model.GroupIds.Select(g => new Guid(g)), loggedInUserId);
|
|
}
|
|
|
|
[HttpPut("{userId}/reset-password-enrollment")]
|
|
public async Task PutResetPasswordEnrollment(Guid orgId, Guid userId, [FromBody] OrganizationUserResetPasswordEnrollmentRequestModel model)
|
|
{
|
|
var user = await _userService.GetUserByPrincipalAsync(User);
|
|
if (user == null)
|
|
{
|
|
throw new UnauthorizedAccessException();
|
|
}
|
|
|
|
var callingUserId = user.Id;
|
|
await _organizationService.UpdateUserResetPasswordEnrollmentAsync(
|
|
orgId, userId, model.ResetPasswordKey, callingUserId);
|
|
|
|
var orgUser = await _organizationUserRepository.GetByOrganizationAsync(orgId, user.Id);
|
|
if (orgUser.Status == OrganizationUserStatusType.Invited)
|
|
{
|
|
await _organizationService.AcceptUserAsync(orgId, user, _userService);
|
|
}
|
|
}
|
|
|
|
[HttpPut("{id}/reset-password")]
|
|
public async Task PutResetPassword(string orgId, string id, [FromBody] OrganizationUserResetPasswordRequestModel model)
|
|
{
|
|
|
|
var orgGuidId = new Guid(orgId);
|
|
|
|
// Calling user must have Manage Reset Password permission
|
|
if (!await _currentContext.ManageResetPassword(orgGuidId))
|
|
{
|
|
throw new NotFoundException();
|
|
}
|
|
|
|
// Get the users role, since provider users aren't a member of the organization we use the owner check
|
|
var orgUserType = await _currentContext.OrganizationOwner(orgGuidId)
|
|
? OrganizationUserType.Owner
|
|
: _currentContext.Organizations?.FirstOrDefault(o => o.Id == orgGuidId)?.Type;
|
|
if (orgUserType == null)
|
|
{
|
|
throw new NotFoundException();
|
|
}
|
|
|
|
var result = await _userService.AdminResetPasswordAsync(orgUserType.Value, orgGuidId, new Guid(id), model.NewMasterPasswordHash, model.Key);
|
|
if (result.Succeeded)
|
|
{
|
|
return;
|
|
}
|
|
|
|
foreach (var error in result.Errors)
|
|
{
|
|
ModelState.AddModelError(string.Empty, error.Description);
|
|
}
|
|
|
|
await Task.Delay(2000);
|
|
throw new BadRequestException(ModelState);
|
|
}
|
|
|
|
[HttpDelete("{id}")]
|
|
[HttpPost("{id}/delete")]
|
|
public async Task Delete(string orgId, string id)
|
|
{
|
|
var orgGuidId = new Guid(orgId);
|
|
if (!await _currentContext.ManageUsers(orgGuidId))
|
|
{
|
|
throw new NotFoundException();
|
|
}
|
|
|
|
var userId = _userService.GetProperUserId(User);
|
|
await _organizationService.DeleteUserAsync(orgGuidId, new Guid(id), userId.Value);
|
|
}
|
|
|
|
[HttpDelete("")]
|
|
[HttpPost("delete")]
|
|
public async Task<ListResponseModel<OrganizationUserBulkResponseModel>> BulkDelete(string orgId, [FromBody] OrganizationUserBulkRequestModel model)
|
|
{
|
|
var orgGuidId = new Guid(orgId);
|
|
if (!await _currentContext.ManageUsers(orgGuidId))
|
|
{
|
|
throw new NotFoundException();
|
|
}
|
|
|
|
var userId = _userService.GetProperUserId(User);
|
|
var result = await _organizationService.DeleteUsersAsync(orgGuidId, model.Ids, userId.Value);
|
|
return new ListResponseModel<OrganizationUserBulkResponseModel>(result.Select(r =>
|
|
new OrganizationUserBulkResponseModel(r.Item1.Id, r.Item2)));
|
|
}
|
|
|
|
[HttpPatch("{id}/revoke")]
|
|
[HttpPut("{id}/revoke")]
|
|
public async Task RevokeAsync(Guid orgId, Guid id)
|
|
{
|
|
await RestoreOrRevokeUserAsync(orgId, id, _organizationService.RevokeUserAsync);
|
|
}
|
|
|
|
[HttpPatch("revoke")]
|
|
[HttpPut("revoke")]
|
|
public async Task<ListResponseModel<OrganizationUserBulkResponseModel>> BulkRevokeAsync(Guid orgId, [FromBody] OrganizationUserBulkRequestModel model)
|
|
{
|
|
return await RestoreOrRevokeUsersAsync(orgId, model, _organizationService.RevokeUsersAsync);
|
|
}
|
|
|
|
[HttpPatch("{id}/restore")]
|
|
[HttpPut("{id}/restore")]
|
|
public async Task RestoreAsync(Guid orgId, Guid id)
|
|
{
|
|
await RestoreOrRevokeUserAsync(orgId, id, (orgUser, userId) => _organizationService.RestoreUserAsync(orgUser, userId, _userService));
|
|
}
|
|
|
|
[HttpPatch("restore")]
|
|
[HttpPut("restore")]
|
|
public async Task<ListResponseModel<OrganizationUserBulkResponseModel>> BulkRestoreAsync(Guid orgId, [FromBody] OrganizationUserBulkRequestModel model)
|
|
{
|
|
return await RestoreOrRevokeUsersAsync(orgId, model, (orgId, orgUserIds, restoringUserId) => _organizationService.RestoreUsersAsync(orgId, orgUserIds, restoringUserId, _userService));
|
|
}
|
|
|
|
[HttpPatch("enable-secrets-manager")]
|
|
[HttpPut("enable-secrets-manager")]
|
|
public async Task BulkEnableSecretsManagerAsync(Guid orgId,
|
|
[FromBody] OrganizationUserBulkRequestModel model)
|
|
{
|
|
if (!await _currentContext.ManageUsers(orgId))
|
|
{
|
|
throw new NotFoundException();
|
|
}
|
|
|
|
var orgUsers = (await _organizationUserRepository.GetManyAsync(model.Ids))
|
|
.Where(ou => ou.OrganizationId == orgId && !ou.AccessSecretsManager).ToList();
|
|
if (orgUsers.Count == 0)
|
|
{
|
|
throw new BadRequestException("Users invalid.");
|
|
}
|
|
|
|
var additionalSmSeatsRequired = await _countNewSmSeatsRequiredQuery.CountNewSmSeatsRequiredAsync(orgId,
|
|
orgUsers.Count);
|
|
if (additionalSmSeatsRequired > 0)
|
|
{
|
|
var organization = await _organizationRepository.GetByIdAsync(orgId);
|
|
var update = new SecretsManagerSubscriptionUpdate(organization, true);
|
|
update.AdjustSeats(additionalSmSeatsRequired);
|
|
await _updateSecretsManagerSubscriptionCommand.UpdateSubscriptionAsync(update);
|
|
}
|
|
|
|
foreach (var orgUser in orgUsers)
|
|
{
|
|
orgUser.AccessSecretsManager = true;
|
|
}
|
|
|
|
await _organizationUserRepository.ReplaceManyAsync(orgUsers);
|
|
}
|
|
|
|
private async Task RestoreOrRevokeUserAsync(
|
|
Guid orgId,
|
|
Guid id,
|
|
Func<Core.Entities.OrganizationUser, Guid?, Task> statusAction)
|
|
{
|
|
if (!await _currentContext.ManageUsers(orgId))
|
|
{
|
|
throw new NotFoundException();
|
|
}
|
|
|
|
var userId = _userService.GetProperUserId(User);
|
|
var orgUser = await _organizationUserRepository.GetByIdAsync(id);
|
|
if (orgUser == null || orgUser.OrganizationId != orgId)
|
|
{
|
|
throw new NotFoundException();
|
|
}
|
|
|
|
await statusAction(orgUser, userId);
|
|
}
|
|
|
|
private async Task<ListResponseModel<OrganizationUserBulkResponseModel>> RestoreOrRevokeUsersAsync(
|
|
Guid orgId,
|
|
OrganizationUserBulkRequestModel model,
|
|
Func<Guid, IEnumerable<Guid>, Guid?, Task<List<Tuple<Core.Entities.OrganizationUser, string>>>> statusAction)
|
|
{
|
|
if (!await _currentContext.ManageUsers(orgId))
|
|
{
|
|
throw new NotFoundException();
|
|
}
|
|
|
|
var userId = _userService.GetProperUserId(User);
|
|
var result = await statusAction(orgId, model.Ids, userId.Value);
|
|
return new ListResponseModel<OrganizationUserBulkResponseModel>(result.Select(r =>
|
|
new OrganizationUserBulkResponseModel(r.Item1.Id, r.Item2)));
|
|
}
|
|
}
|