1
0
mirror of https://github.com/bitwarden/server.git synced 2024-11-24 12:35:25 +01:00

[AC-2171] Member modal - limit admin access - editing self (#3893)

* Restrict admins from adding themselves to groups

Updated OrganizationUsersController only, GroupsController to be updated
separately

* Delete unused api method
This commit is contained in:
Thomas Rittson 2024-03-22 12:37:30 +10:00 committed by GitHub
parent 90a5862840
commit 1c2acbec3a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 130 additions and 34 deletions

View File

@ -4,6 +4,7 @@ using Bit.Api.Models.Request.Organizations;
using Bit.Api.Models.Response; using Bit.Api.Models.Response;
using Bit.Api.Utilities; using Bit.Api.Utilities;
using Bit.Api.Vault.AuthorizationHandlers.OrganizationUsers; using Bit.Api.Vault.AuthorizationHandlers.OrganizationUsers;
using Bit.Core;
using Bit.Core.AdminConsole.Enums; using Bit.Core.AdminConsole.Enums;
using Bit.Core.AdminConsole.Models.Data.Organizations.Policies; using Bit.Core.AdminConsole.Models.Data.Organizations.Policies;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces; using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces;
@ -41,6 +42,7 @@ public class OrganizationUsersController : Controller
private readonly IAcceptOrgUserCommand _acceptOrgUserCommand; private readonly IAcceptOrgUserCommand _acceptOrgUserCommand;
private readonly IAuthorizationService _authorizationService; private readonly IAuthorizationService _authorizationService;
private readonly IApplicationCacheService _applicationCacheService; private readonly IApplicationCacheService _applicationCacheService;
private readonly IFeatureService _featureService;
public OrganizationUsersController( public OrganizationUsersController(
IOrganizationRepository organizationRepository, IOrganizationRepository organizationRepository,
@ -56,7 +58,8 @@ public class OrganizationUsersController : Controller
IUpdateOrganizationUserGroupsCommand updateOrganizationUserGroupsCommand, IUpdateOrganizationUserGroupsCommand updateOrganizationUserGroupsCommand,
IAcceptOrgUserCommand acceptOrgUserCommand, IAcceptOrgUserCommand acceptOrgUserCommand,
IAuthorizationService authorizationService, IAuthorizationService authorizationService,
IApplicationCacheService applicationCacheService) IApplicationCacheService applicationCacheService,
IFeatureService featureService)
{ {
_organizationRepository = organizationRepository; _organizationRepository = organizationRepository;
_organizationUserRepository = organizationUserRepository; _organizationUserRepository = organizationUserRepository;
@ -72,6 +75,7 @@ public class OrganizationUsersController : Controller
_acceptOrgUserCommand = acceptOrgUserCommand; _acceptOrgUserCommand = acceptOrgUserCommand;
_authorizationService = authorizationService; _authorizationService = authorizationService;
_applicationCacheService = applicationCacheService; _applicationCacheService = applicationCacheService;
_featureService = featureService;
} }
[HttpGet("{id}")] [HttpGet("{id}")]
@ -305,43 +309,34 @@ public class OrganizationUsersController : Controller
[HttpPut("{id}")] [HttpPut("{id}")]
[HttpPost("{id}")] [HttpPost("{id}")]
public async Task Put(string orgId, string id, [FromBody] OrganizationUserUpdateRequestModel model) public async Task Put(Guid orgId, Guid id, [FromBody] OrganizationUserUpdateRequestModel model)
{ {
var orgGuidId = new Guid(orgId); if (!await _currentContext.ManageUsers(orgId))
if (!await _currentContext.ManageUsers(orgGuidId))
{ {
throw new NotFoundException(); throw new NotFoundException();
} }
var organizationUser = await _organizationUserRepository.GetByIdAsync(new Guid(id)); var organizationUser = await _organizationUserRepository.GetByIdAsync(id);
if (organizationUser == null || organizationUser.OrganizationId != orgGuidId) if (organizationUser == null || organizationUser.OrganizationId != orgId)
{ {
throw new NotFoundException(); throw new NotFoundException();
} }
var userId = _userService.GetProperUserId(User); // If admins are not allowed access to all collections, you cannot add yourself to a group
await _organizationService.SaveUserAsync(model.ToOrganizationUser(organizationUser), userId.Value, // In this case we just don't update groups
model.Collections?.Select(c => c.ToSelectionReadOnly()).ToList(), model.Groups); var userId = _userService.GetProperUserId(User).Value;
} var organizationAbility = await _applicationCacheService.GetOrganizationAbilityAsync(orgId);
var restrictEditingGroups = _featureService.IsEnabled(FeatureFlagKeys.FlexibleCollectionsV1) &&
organizationAbility.FlexibleCollections &&
userId == organizationUser.UserId &&
!organizationAbility.AllowAdminAccessToAllCollectionItems;
[HttpPut("{id}/groups")] var groups = restrictEditingGroups
[HttpPost("{id}/groups")] ? null
public async Task PutGroups(string orgId, string id, [FromBody] OrganizationUserUpdateGroupsRequestModel model) : model.Groups;
{
var orgGuidId = new Guid(orgId);
if (!await _currentContext.ManageUsers(orgGuidId))
{
throw new NotFoundException();
}
var organizationUser = await _organizationUserRepository.GetByIdAsync(new Guid(id)); await _organizationService.SaveUserAsync(model.ToOrganizationUser(organizationUser), userId,
if (organizationUser == null || organizationUser.OrganizationId != orgGuidId) model.Collections?.Select(c => c.ToSelectionReadOnly()).ToList(), groups);
{
throw new NotFoundException();
}
var loggedInUserId = _userService.GetProperUserId(User);
await _updateOrganizationUserGroupsCommand.UpdateUserGroupsAsync(organizationUser, model.GroupIds.Select(g => new Guid(g)), loggedInUserId);
} }
[HttpPut("{userId}/reset-password-enrollment")] [HttpPut("{userId}/reset-password-enrollment")]

View File

@ -102,12 +102,6 @@ public class OrganizationUserUpdateRequestModel
} }
} }
public class OrganizationUserUpdateGroupsRequestModel
{
[Required]
public IEnumerable<string> GroupIds { get; set; }
}
public class OrganizationUserResetPasswordEnrollmentRequestModel public class OrganizationUserResetPasswordEnrollmentRequestModel
{ {
public string ResetPasswordKey { get; set; } public string ResetPasswordKey { get; set; }

View File

@ -1,10 +1,15 @@
using Bit.Api.AdminConsole.Controllers; using System.Security.Claims;
using Bit.Api.AdminConsole.Controllers;
using Bit.Api.AdminConsole.Models.Request.Organizations; using Bit.Api.AdminConsole.Models.Request.Organizations;
using Bit.Core;
using Bit.Core.AdminConsole.Entities; using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.Enums; using Bit.Core.AdminConsole.Enums;
using Bit.Core.AdminConsole.Models.Data.Organizations.Policies; using Bit.Core.AdminConsole.Models.Data.Organizations.Policies;
using Bit.Core.AdminConsole.Repositories; using Bit.Core.AdminConsole.Repositories;
using Bit.Core.Context;
using Bit.Core.Entities; using Bit.Core.Entities;
using Bit.Core.Models.Data;
using Bit.Core.Models.Data.Organizations;
using Bit.Core.OrganizationFeatures.OrganizationUsers.Interfaces; using Bit.Core.OrganizationFeatures.OrganizationUsers.Interfaces;
using Bit.Core.Repositories; using Bit.Core.Repositories;
using Bit.Core.Services; using Bit.Core.Services;
@ -94,4 +99,106 @@ public class OrganizationUsersControllerTests
await sutProvider.GetDependency<IOrganizationService>().Received(1) await sutProvider.GetDependency<IOrganizationService>().Received(1)
.UpdateUserResetPasswordEnrollmentAsync(orgId, user.Id, model.ResetPasswordKey, user.Id); .UpdateUserResetPasswordEnrollmentAsync(orgId, user.Id, model.ResetPasswordKey, user.Id);
} }
[Theory]
[BitAutoData]
public async Task Put_Success(OrganizationUserUpdateRequestModel model,
OrganizationUser organizationUser, OrganizationAbility organizationAbility,
SutProvider<OrganizationUsersController> sutProvider, Guid savingUserId)
{
var orgId = organizationAbility.Id = organizationUser.OrganizationId;
sutProvider.GetDependency<ICurrentContext>().ManageUsers(orgId).Returns(true);
sutProvider.GetDependency<IOrganizationUserRepository>().GetByIdAsync(organizationUser.Id).Returns(organizationUser);
sutProvider.GetDependency<IApplicationCacheService>().GetOrganizationAbilityAsync(orgId)
.Returns(organizationAbility);
sutProvider.GetDependency<IUserService>().GetProperUserId(Arg.Any<ClaimsPrincipal>()).Returns(savingUserId);
var orgUserId = organizationUser.Id;
var orgUserEmail = organizationUser.Email;
await sutProvider.Sut.Put(orgId, organizationUser.Id, model);
sutProvider.GetDependency<IOrganizationService>().Received(1).SaveUserAsync(Arg.Is<OrganizationUser>(ou =>
ou.Type == model.Type &&
ou.Permissions == CoreHelpers.ClassToJsonData(model.Permissions) &&
ou.AccessSecretsManager == model.AccessSecretsManager &&
ou.Id == orgUserId &&
ou.Email == orgUserEmail),
savingUserId,
Arg.Is<List<CollectionAccessSelection>>(cas =>
cas.All(c => model.Collections.Any(m => m.Id == c.Id))),
model.Groups);
}
[Theory]
[BitAutoData]
public async Task Put_UpdateSelf_WithoutAllowAdminAccessToAllCollectionItems_DoesNotUpdateGroups(OrganizationUserUpdateRequestModel model,
OrganizationUser organizationUser, OrganizationAbility organizationAbility,
SutProvider<OrganizationUsersController> sutProvider, Guid savingUserId)
{
// Updating self
organizationUser.UserId = savingUserId;
organizationAbility.FlexibleCollections = true;
organizationAbility.AllowAdminAccessToAllCollectionItems = false;
sutProvider.GetDependency<IFeatureService>().IsEnabled(FeatureFlagKeys.FlexibleCollectionsV1).Returns(true);
var orgId = organizationAbility.Id = organizationUser.OrganizationId;
sutProvider.GetDependency<ICurrentContext>().ManageUsers(orgId).Returns(true);
sutProvider.GetDependency<IOrganizationUserRepository>().GetByIdAsync(organizationUser.Id).Returns(organizationUser);
sutProvider.GetDependency<IApplicationCacheService>().GetOrganizationAbilityAsync(orgId)
.Returns(organizationAbility);
sutProvider.GetDependency<IUserService>().GetProperUserId(Arg.Any<ClaimsPrincipal>()).Returns(savingUserId);
var orgUserId = organizationUser.Id;
var orgUserEmail = organizationUser.Email;
await sutProvider.Sut.Put(orgId, organizationUser.Id, model);
sutProvider.GetDependency<IOrganizationService>().Received(1).SaveUserAsync(Arg.Is<OrganizationUser>(ou =>
ou.Type == model.Type &&
ou.Permissions == CoreHelpers.ClassToJsonData(model.Permissions) &&
ou.AccessSecretsManager == model.AccessSecretsManager &&
ou.Id == orgUserId &&
ou.Email == orgUserEmail),
savingUserId,
Arg.Is<List<CollectionAccessSelection>>(cas =>
cas.All(c => model.Collections.Any(m => m.Id == c.Id))),
null);
}
[Theory]
[BitAutoData]
public async Task Put_UpdateSelf_WithAllowAdminAccessToAllCollectionItems_DoesUpdateGroups(OrganizationUserUpdateRequestModel model,
OrganizationUser organizationUser, OrganizationAbility organizationAbility,
SutProvider<OrganizationUsersController> sutProvider, Guid savingUserId)
{
// Updating self
organizationUser.UserId = savingUserId;
organizationAbility.FlexibleCollections = true;
organizationAbility.AllowAdminAccessToAllCollectionItems = true;
sutProvider.GetDependency<IFeatureService>().IsEnabled(FeatureFlagKeys.FlexibleCollectionsV1).Returns(true);
var orgId = organizationAbility.Id = organizationUser.OrganizationId;
sutProvider.GetDependency<ICurrentContext>().ManageUsers(orgId).Returns(true);
sutProvider.GetDependency<IOrganizationUserRepository>().GetByIdAsync(organizationUser.Id).Returns(organizationUser);
sutProvider.GetDependency<IApplicationCacheService>().GetOrganizationAbilityAsync(orgId)
.Returns(organizationAbility);
sutProvider.GetDependency<IUserService>().GetProperUserId(Arg.Any<ClaimsPrincipal>()).Returns(savingUserId);
var orgUserId = organizationUser.Id;
var orgUserEmail = organizationUser.Email;
await sutProvider.Sut.Put(orgId, organizationUser.Id, model);
sutProvider.GetDependency<IOrganizationService>().Received(1).SaveUserAsync(Arg.Is<OrganizationUser>(ou =>
ou.Type == model.Type &&
ou.Permissions == CoreHelpers.ClassToJsonData(model.Permissions) &&
ou.AccessSecretsManager == model.AccessSecretsManager &&
ou.Id == orgUserId &&
ou.Email == orgUserEmail),
savingUserId,
Arg.Is<List<CollectionAccessSelection>>(cas =>
cas.All(c => model.Collections.Any(m => m.Id == c.Id))),
model.Groups);
}
} }