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:
parent
90a5862840
commit
1c2acbec3a
@ -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")]
|
||||||
|
@ -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; }
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user