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.Utilities;
using Bit.Api.Vault.AuthorizationHandlers.OrganizationUsers;
using Bit.Core;
using Bit.Core.AdminConsole.Enums;
using Bit.Core.AdminConsole.Models.Data.Organizations.Policies;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces;
@ -41,6 +42,7 @@ public class OrganizationUsersController : Controller
private readonly IAcceptOrgUserCommand _acceptOrgUserCommand;
private readonly IAuthorizationService _authorizationService;
private readonly IApplicationCacheService _applicationCacheService;
private readonly IFeatureService _featureService;
public OrganizationUsersController(
IOrganizationRepository organizationRepository,
@ -56,7 +58,8 @@ public class OrganizationUsersController : Controller
IUpdateOrganizationUserGroupsCommand updateOrganizationUserGroupsCommand,
IAcceptOrgUserCommand acceptOrgUserCommand,
IAuthorizationService authorizationService,
IApplicationCacheService applicationCacheService)
IApplicationCacheService applicationCacheService,
IFeatureService featureService)
{
_organizationRepository = organizationRepository;
_organizationUserRepository = organizationUserRepository;
@ -72,6 +75,7 @@ public class OrganizationUsersController : Controller
_acceptOrgUserCommand = acceptOrgUserCommand;
_authorizationService = authorizationService;
_applicationCacheService = applicationCacheService;
_featureService = featureService;
}
[HttpGet("{id}")]
@ -305,43 +309,34 @@ public class OrganizationUsersController : Controller
[HttpPut("{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(orgGuidId))
if (!await _currentContext.ManageUsers(orgId))
{
throw new NotFoundException();
}
var organizationUser = await _organizationUserRepository.GetByIdAsync(new Guid(id));
if (organizationUser == null || organizationUser.OrganizationId != orgGuidId)
var organizationUser = await _organizationUserRepository.GetByIdAsync(id);
if (organizationUser == null || organizationUser.OrganizationId != orgId)
{
throw new NotFoundException();
}
var userId = _userService.GetProperUserId(User);
await _organizationService.SaveUserAsync(model.ToOrganizationUser(organizationUser), userId.Value,
model.Collections?.Select(c => c.ToSelectionReadOnly()).ToList(), model.Groups);
}
// If admins are not allowed access to all collections, you cannot add yourself to a group
// In this case we just don't update 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")]
[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 groups = restrictEditingGroups
? null
: model.Groups;
var organizationUser = await _organizationUserRepository.GetByIdAsync(new Guid(id));
if (organizationUser == null || organizationUser.OrganizationId != orgGuidId)
{
throw new NotFoundException();
}
var loggedInUserId = _userService.GetProperUserId(User);
await _updateOrganizationUserGroupsCommand.UpdateUserGroupsAsync(organizationUser, model.GroupIds.Select(g => new Guid(g)), loggedInUserId);
await _organizationService.SaveUserAsync(model.ToOrganizationUser(organizationUser), userId,
model.Collections?.Select(c => c.ToSelectionReadOnly()).ToList(), groups);
}
[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 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.Core;
using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.Enums;
using Bit.Core.AdminConsole.Models.Data.Organizations.Policies;
using Bit.Core.AdminConsole.Repositories;
using Bit.Core.Context;
using Bit.Core.Entities;
using Bit.Core.Models.Data;
using Bit.Core.Models.Data.Organizations;
using Bit.Core.OrganizationFeatures.OrganizationUsers.Interfaces;
using Bit.Core.Repositories;
using Bit.Core.Services;
@ -94,4 +99,106 @@ public class OrganizationUsersControllerTests
await sutProvider.GetDependency<IOrganizationService>().Received(1)
.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);
}
}