mirror of
https://github.com/bitwarden/server
synced 2025-12-12 22:33:45 +00:00
[PM-12474] Move to authorization to attibutes/handlers/requirements (#6001)
* Created ReadAllOrganizationUsersBasicInformationRequirement for use with Authorize attribute. * Removed unused req and Handler and tests. Moved to new auth attribute * Moved tests to integration tests with new response. * Removed tests that were migrated to integration tests. * Made string params Guids instead of parsing them manually in methods. * Admin and Owner added to requirement. * Added XML docs for basic get endpoint. Removed unused. Added another auth check. Inverted if check. * Removed unused endpoint * Added tests for requirement * Added checks for both User and Custom * Added org id check to validate the user being requested belongs to the org in the route. * typo
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
// FIXME: Update this file to be null safe and then delete the line below
|
||||
#nullable disable
|
||||
|
||||
using Bit.Api.AdminConsole.Authorization;
|
||||
using Bit.Api.AdminConsole.Authorization.Requirements;
|
||||
using Bit.Api.AdminConsole.Models.Request.Organizations;
|
||||
using Bit.Api.AdminConsole.Models.Response.Organizations;
|
||||
@@ -126,10 +127,11 @@ public class OrganizationUsersController : Controller
|
||||
}
|
||||
|
||||
[HttpGet("{id}")]
|
||||
public async Task<OrganizationUserDetailsResponseModel> Get(Guid id, bool includeGroups = false)
|
||||
[Authorize<ManageUsersRequirement>]
|
||||
public async Task<OrganizationUserDetailsResponseModel> Get(Guid orgId, Guid id, bool includeGroups = false)
|
||||
{
|
||||
var (organizationUser, collections) = await _organizationUserRepository.GetDetailsByIdWithCollectionsAsync(id);
|
||||
if (organizationUser == null || !await _currentContext.ManageUsers(organizationUser.OrganizationId))
|
||||
if (organizationUser == null || organizationUser.OrganizationId != orgId)
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
@@ -148,16 +150,17 @@ public class OrganizationUsersController : Controller
|
||||
return response;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a set of basic information about all members of the organization. This is available to all members of
|
||||
/// the organization to manage collections. For this reason, it contains as little information as possible and no
|
||||
/// cryptographic keys or other sensitive data.
|
||||
/// </summary>
|
||||
/// <param name="orgId">Organization identifier</param>
|
||||
/// <returns>List of users for the organization.</returns>
|
||||
[HttpGet("mini-details")]
|
||||
[Authorize<MemberOrProviderRequirement>]
|
||||
public async Task<ListResponseModel<OrganizationUserUserMiniDetailsResponseModel>> GetMiniDetails(Guid orgId)
|
||||
{
|
||||
var authorizationResult = await _authorizationService.AuthorizeAsync(User, new OrganizationScope(orgId),
|
||||
OrganizationUserUserMiniDetailsOperations.ReadAll);
|
||||
if (!authorizationResult.Succeeded)
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
var organizationUserUserDetails = await _organizationUserRepository.GetManyDetailsByOrganizationAsync(orgId);
|
||||
return new ListResponseModel<OrganizationUserUserMiniDetailsResponseModel>(
|
||||
organizationUserUserDetails.Select(ou => new OrganizationUserUserMiniDetailsResponseModel(ou)));
|
||||
@@ -207,7 +210,7 @@ public class OrganizationUsersController : Controller
|
||||
{
|
||||
OrganizationId = orgId,
|
||||
IncludeGroups = includeGroups,
|
||||
IncludeCollections = includeCollections,
|
||||
IncludeCollections = includeCollections
|
||||
};
|
||||
|
||||
if ((await _authorizationService.AuthorizeAsync(User, new ManageUsersRequirement())).Succeeded)
|
||||
@@ -231,34 +234,12 @@ public class OrganizationUsersController : Controller
|
||||
.ToList());
|
||||
}
|
||||
|
||||
|
||||
[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)
|
||||
[Authorize<ManageAccountRecoveryRequirement>]
|
||||
public async Task<OrganizationUserResetPasswordDetailsResponseModel> GetResetPasswordDetails(Guid orgId, Guid 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)
|
||||
var organizationUser = await _organizationUserRepository.GetByIdAsync(id);
|
||||
if (organizationUser is null || organizationUser.UserId is null)
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
@@ -272,7 +253,7 @@ public class OrganizationUsersController : Controller
|
||||
}
|
||||
|
||||
// Retrieve Encrypted Private Key from organization
|
||||
var org = await _organizationRepository.GetByIdAsync(orgGuidId);
|
||||
var org = await _organizationRepository.GetByIdAsync(orgId);
|
||||
if (org == null)
|
||||
{
|
||||
throw new NotFoundException();
|
||||
@@ -282,26 +263,17 @@ public class OrganizationUsersController : Controller
|
||||
}
|
||||
|
||||
[HttpPost("account-recovery-details")]
|
||||
[Authorize<ManageAccountRecoveryRequirement>]
|
||||
public async Task<ListResponseModel<OrganizationUserResetPasswordDetailsResponseModel>> GetAccountRecoveryDetails(Guid orgId, [FromBody] OrganizationUserBulkRequestModel model)
|
||||
{
|
||||
// Make sure the calling user can reset passwords for this org
|
||||
if (!await _currentContext.ManageResetPassword(orgId))
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
var responses = await _organizationUserRepository.GetManyAccountRecoveryDetailsByOrganizationUserAsync(orgId, model.Ids);
|
||||
return new ListResponseModel<OrganizationUserResetPasswordDetailsResponseModel>(responses.Select(r => new OrganizationUserResetPasswordDetailsResponseModel(r)));
|
||||
}
|
||||
|
||||
[HttpPost("invite")]
|
||||
[Authorize<ManageUsersRequirement>]
|
||||
public async Task Invite(Guid orgId, [FromBody] OrganizationUserInviteRequestModel model)
|
||||
{
|
||||
if (!await _currentContext.ManageUsers(orgId))
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
// Check the user has permission to grant access to the collections for the new user
|
||||
if (model.Collections?.Any() == true)
|
||||
{
|
||||
@@ -317,35 +289,25 @@ public class OrganizationUsersController : Controller
|
||||
|
||||
var userId = _userService.GetProperUserId(User);
|
||||
await _organizationService.InviteUsersAsync(orgId, userId.Value, systemUser: null,
|
||||
new (OrganizationUserInvite, string)[] { (new OrganizationUserInvite(model.ToData()), null) });
|
||||
[(new OrganizationUserInvite(model.ToData()), null)]);
|
||||
}
|
||||
|
||||
[HttpPost("reinvite")]
|
||||
public async Task<ListResponseModel<OrganizationUserBulkResponseModel>> BulkReinvite(string orgId, [FromBody] OrganizationUserBulkRequestModel model)
|
||||
[Authorize<ManageUsersRequirement>]
|
||||
public async Task<ListResponseModel<OrganizationUserBulkResponseModel>> BulkReinvite(Guid 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);
|
||||
var result = await _organizationService.ResendInvitesAsync(orgId, 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)
|
||||
[Authorize<ManageUsersRequirement>]
|
||||
public async Task Reinvite(Guid orgId, Guid 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));
|
||||
await _organizationService.ResendInviteAsync(orgId, userId.Value, id);
|
||||
}
|
||||
|
||||
[HttpPost("{organizationUserId}/accept-init")]
|
||||
@@ -406,57 +368,39 @@ public class OrganizationUsersController : Controller
|
||||
}
|
||||
|
||||
[HttpPost("{id}/confirm")]
|
||||
[Authorize<ManageUsersRequirement>]
|
||||
public async Task Confirm(Guid orgId, Guid id, [FromBody] OrganizationUserConfirmRequestModel model)
|
||||
{
|
||||
if (!await _currentContext.ManageUsers(orgId))
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
var userId = _userService.GetProperUserId(User);
|
||||
var result = await _confirmOrganizationUserCommand.ConfirmUserAsync(orgId, id, model.Key, userId.Value, model.DefaultUserCollectionName);
|
||||
_ = await _confirmOrganizationUserCommand.ConfirmUserAsync(orgId, id, model.Key, userId.Value, model.DefaultUserCollectionName);
|
||||
}
|
||||
|
||||
[HttpPost("confirm")]
|
||||
public async Task<ListResponseModel<OrganizationUserBulkResponseModel>> BulkConfirm(string orgId,
|
||||
[Authorize<ManageUsersRequirement>]
|
||||
public async Task<ListResponseModel<OrganizationUserBulkResponseModel>> BulkConfirm(Guid 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 _confirmOrganizationUserCommand.ConfirmUsersAsync(orgGuidId, model.ToDictionary(), userId.Value);
|
||||
var results = await _confirmOrganizationUserCommand.ConfirmUsersAsync(orgId, model.ToDictionary(), userId.Value);
|
||||
|
||||
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)
|
||||
[Authorize<ManageUsersRequirement>]
|
||||
public async Task<ListResponseModel<OrganizationUserPublicKeyResponseModel>> UserPublicKeys(Guid 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 result = await _organizationUserRepository.GetManyPublicKeysByOrganizationUserAsync(orgId, 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}")]
|
||||
[Authorize<ManageUsersRequirement>]
|
||||
public async Task Put(Guid orgId, Guid id, [FromBody] OrganizationUserUpdateRequestModel model)
|
||||
{
|
||||
if (!await _currentContext.ManageUsers(orgId))
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
var (organizationUser, currentAccess) = await _organizationUserRepository.GetByIdWithCollectionsAsync(id);
|
||||
if (organizationUser == null || organizationUser.OrganizationId != orgId)
|
||||
{
|
||||
@@ -557,27 +501,19 @@ public class OrganizationUsersController : Controller
|
||||
}
|
||||
|
||||
[HttpPut("{id}/reset-password")]
|
||||
public async Task PutResetPassword(string orgId, string id, [FromBody] OrganizationUserResetPasswordRequestModel model)
|
||||
[Authorize<ManageAccountRecoveryRequirement>]
|
||||
public async Task PutResetPassword(Guid orgId, Guid 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)
|
||||
var orgUserType = await _currentContext.OrganizationOwner(orgId)
|
||||
? OrganizationUserType.Owner
|
||||
: _currentContext.Organizations?.FirstOrDefault(o => o.Id == orgGuidId)?.Type;
|
||||
: _currentContext.Organizations?.FirstOrDefault(o => o.Id == orgId)?.Type;
|
||||
if (orgUserType == null)
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
var result = await _userService.AdminResetPasswordAsync(orgUserType.Value, orgGuidId, new Guid(id), model.NewMasterPasswordHash, model.Key);
|
||||
var result = await _userService.AdminResetPasswordAsync(orgUserType.Value, orgId, id, model.NewMasterPasswordHash, model.Key);
|
||||
if (result.Succeeded)
|
||||
{
|
||||
return;
|
||||
@@ -594,26 +530,18 @@ public class OrganizationUsersController : Controller
|
||||
|
||||
[HttpDelete("{id}")]
|
||||
[HttpPost("{id}/remove")]
|
||||
[Authorize<ManageUsersRequirement>]
|
||||
public async Task Remove(Guid orgId, Guid id)
|
||||
{
|
||||
if (!await _currentContext.ManageUsers(orgId))
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
var userId = _userService.GetProperUserId(User);
|
||||
await _removeOrganizationUserCommand.RemoveUserAsync(orgId, id, userId.Value);
|
||||
}
|
||||
|
||||
[HttpDelete("")]
|
||||
[HttpPost("remove")]
|
||||
[Authorize<ManageUsersRequirement>]
|
||||
public async Task<ListResponseModel<OrganizationUserBulkResponseModel>> BulkRemove(Guid orgId, [FromBody] OrganizationUserBulkRequestModel model)
|
||||
{
|
||||
if (!await _currentContext.ManageUsers(orgId))
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
var userId = _userService.GetProperUserId(User);
|
||||
var result = await _removeOrganizationUserCommand.RemoveUsersAsync(orgId, model.Ids, userId.Value);
|
||||
return new ListResponseModel<OrganizationUserBulkResponseModel>(result.Select(r =>
|
||||
@@ -622,13 +550,9 @@ public class OrganizationUsersController : Controller
|
||||
|
||||
[HttpDelete("{id}/delete-account")]
|
||||
[HttpPost("{id}/delete-account")]
|
||||
[Authorize<ManageUsersRequirement>]
|
||||
public async Task DeleteAccount(Guid orgId, Guid id)
|
||||
{
|
||||
if (!await _currentContext.ManageUsers(orgId))
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
var currentUser = await _userService.GetUserByPrincipalAsync(User);
|
||||
if (currentUser == null)
|
||||
{
|
||||
@@ -640,13 +564,9 @@ public class OrganizationUsersController : Controller
|
||||
|
||||
[HttpDelete("delete-account")]
|
||||
[HttpPost("delete-account")]
|
||||
[Authorize<ManageUsersRequirement>]
|
||||
public async Task<ListResponseModel<OrganizationUserBulkResponseModel>> BulkDeleteAccount(Guid orgId, [FromBody] OrganizationUserBulkRequestModel model)
|
||||
{
|
||||
if (!await _currentContext.ManageUsers(orgId))
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
var currentUser = await _userService.GetUserByPrincipalAsync(User);
|
||||
if (currentUser == null)
|
||||
{
|
||||
@@ -661,6 +581,7 @@ public class OrganizationUsersController : Controller
|
||||
|
||||
[HttpPatch("{id}/revoke")]
|
||||
[HttpPut("{id}/revoke")]
|
||||
[Authorize<ManageUsersRequirement>]
|
||||
public async Task RevokeAsync(Guid orgId, Guid id)
|
||||
{
|
||||
await RestoreOrRevokeUserAsync(orgId, id, _organizationService.RevokeUserAsync);
|
||||
@@ -668,6 +589,7 @@ public class OrganizationUsersController : Controller
|
||||
|
||||
[HttpPatch("revoke")]
|
||||
[HttpPut("revoke")]
|
||||
[Authorize<ManageUsersRequirement>]
|
||||
public async Task<ListResponseModel<OrganizationUserBulkResponseModel>> BulkRevokeAsync(Guid orgId, [FromBody] OrganizationUserBulkRequestModel model)
|
||||
{
|
||||
return await RestoreOrRevokeUsersAsync(orgId, model, _organizationService.RevokeUsersAsync);
|
||||
@@ -675,6 +597,7 @@ public class OrganizationUsersController : Controller
|
||||
|
||||
[HttpPatch("{id}/restore")]
|
||||
[HttpPut("{id}/restore")]
|
||||
[Authorize<ManageUsersRequirement>]
|
||||
public async Task RestoreAsync(Guid orgId, Guid id)
|
||||
{
|
||||
await RestoreOrRevokeUserAsync(orgId, id, (orgUser, userId) => _restoreOrganizationUserCommand.RestoreUserAsync(orgUser, userId));
|
||||
@@ -682,6 +605,7 @@ public class OrganizationUsersController : Controller
|
||||
|
||||
[HttpPatch("restore")]
|
||||
[HttpPut("restore")]
|
||||
[Authorize<ManageUsersRequirement>]
|
||||
public async Task<ListResponseModel<OrganizationUserBulkResponseModel>> BulkRestoreAsync(Guid orgId, [FromBody] OrganizationUserBulkRequestModel model)
|
||||
{
|
||||
return await RestoreOrRevokeUsersAsync(orgId, model, (orgId, orgUserIds, restoringUserId) => _restoreOrganizationUserCommand.RestoreUsersAsync(orgId, orgUserIds, restoringUserId, _userService));
|
||||
@@ -689,14 +613,10 @@ public class OrganizationUsersController : Controller
|
||||
|
||||
[HttpPatch("enable-secrets-manager")]
|
||||
[HttpPut("enable-secrets-manager")]
|
||||
[Authorize<ManageUsersRequirement>]
|
||||
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)
|
||||
@@ -729,11 +649,6 @@ public class OrganizationUsersController : Controller
|
||||
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)
|
||||
@@ -749,11 +664,6 @@ public class OrganizationUsersController : Controller
|
||||
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 =>
|
||||
|
||||
Reference in New Issue
Block a user