diff --git a/bitwarden_license/src/Commercial.Infrastructure.EntityFramework/SecretsManager/Repositories/AccessPolicyRepository.cs b/bitwarden_license/src/Commercial.Infrastructure.EntityFramework/SecretsManager/Repositories/AccessPolicyRepository.cs index b45630905..805922ec1 100644 --- a/bitwarden_license/src/Commercial.Infrastructure.EntityFramework/SecretsManager/Repositories/AccessPolicyRepository.cs +++ b/bitwarden_license/src/Commercial.Infrastructure.EntityFramework/SecretsManager/Repositories/AccessPolicyRepository.cs @@ -157,7 +157,7 @@ public class AccessPolicyRepository : BaseEntityFrameworkRepository, IAccessPoli } } - public async Task> GetManyByGrantedProjectIdAsync(Guid id) + public async Task> GetManyByGrantedProjectIdAsync(Guid id, Guid userId) { using var scope = ServiceScopeFactory.CreateScope(); var dbContext = GetDatabaseContext(scope); @@ -169,11 +169,19 @@ public class AccessPolicyRepository : BaseEntityFrameworkRepository, IAccessPoli .Include(ap => ((UserProjectAccessPolicy)ap).OrganizationUser.User) .Include(ap => ((GroupProjectAccessPolicy)ap).Group) .Include(ap => ((ServiceAccountProjectAccessPolicy)ap).ServiceAccount) + .Select(ap => new + { + ap, + CurrentUserInGroup = ap is GroupProjectAccessPolicy && + ((GroupProjectAccessPolicy)ap).Group.GroupUsers.Any(g => + g.OrganizationUser.User.Id == userId), + }) .ToListAsync(); - return entities.Select(MapToCore); + + return entities.Select(e => MapToCore(e.ap, e.CurrentUserInGroup)); } - public async Task> GetManyByGrantedServiceAccountIdAsync(Guid id) + public async Task> GetManyByGrantedServiceAccountIdAsync(Guid id, Guid userId) { using var scope = ServiceScopeFactory.CreateScope(); var dbContext = GetDatabaseContext(scope); @@ -183,9 +191,16 @@ public class AccessPolicyRepository : BaseEntityFrameworkRepository, IAccessPoli ((GroupServiceAccountAccessPolicy)ap).GrantedServiceAccountId == id) .Include(ap => ((UserServiceAccountAccessPolicy)ap).OrganizationUser.User) .Include(ap => ((GroupServiceAccountAccessPolicy)ap).Group) + .Select(ap => new + { + ap, + CurrentUserInGroup = ap is GroupServiceAccountAccessPolicy && + ((GroupServiceAccountAccessPolicy)ap).Group.GroupUsers.Any(g => + g.OrganizationUser.User.Id == userId), + }) .ToListAsync(); - return entities.Select(MapToCore); + return entities.Select(e => MapToCore(e.ap, e.CurrentUserInGroup)); } public async Task DeleteAsync(Guid id) @@ -237,4 +252,26 @@ public class AccessPolicyRepository : BaseEntityFrameworkRepository, IAccessPoli .Map(ap), _ => throw new ArgumentException("Unsupported access policy type"), }; + + private Core.SecretsManager.Entities.BaseAccessPolicy MapToCore( + BaseAccessPolicy baseAccessPolicyEntity, bool currentUserInGroup) + { + switch (baseAccessPolicyEntity) + { + case GroupProjectAccessPolicy ap: + { + var mapped = Mapper.Map(ap); + mapped.CurrentUserInGroup = currentUserInGroup; + return mapped; + } + case GroupServiceAccountAccessPolicy ap: + { + var mapped = Mapper.Map(ap); + mapped.CurrentUserInGroup = currentUserInGroup; + return mapped; + } + default: + return MapToCore(baseAccessPolicyEntity); + } + } } diff --git a/src/Api/SecretsManager/Controllers/AccessPoliciesController.cs b/src/Api/SecretsManager/Controllers/AccessPoliciesController.cs index 876a71fa3..e4f6f6869 100644 --- a/src/Api/SecretsManager/Controllers/AccessPoliciesController.cs +++ b/src/Api/SecretsManager/Controllers/AccessPoliciesController.cs @@ -73,7 +73,7 @@ public class AccessPoliciesController : Controller var (accessClient, userId) = await GetAccessClientTypeAsync(project.OrganizationId); var policies = request.ToBaseAccessPoliciesForProject(id); await _createAccessPoliciesCommand.CreateManyAsync(policies, userId, accessClient); - var results = await _accessPolicyRepository.GetManyByGrantedProjectIdAsync(id); + var results = await _accessPolicyRepository.GetManyByGrantedProjectIdAsync(id, userId); return new ProjectAccessPoliciesResponseModel(results); } @@ -81,9 +81,8 @@ public class AccessPoliciesController : Controller public async Task GetProjectAccessPoliciesAsync([FromRoute] Guid id) { var project = await _projectRepository.GetByIdAsync(id); - await CheckUserHasWriteAccessToProjectAsync(project); - - var results = await _accessPolicyRepository.GetManyByGrantedProjectIdAsync(id); + var (_, userId) = await CheckUserHasWriteAccessToProjectAsync(project); + var results = await _accessPolicyRepository.GetManyByGrantedProjectIdAsync(id, userId); return new ProjectAccessPoliciesResponseModel(results); } @@ -106,7 +105,7 @@ public class AccessPoliciesController : Controller var (accessClient, userId) = await GetAccessClientTypeAsync(serviceAccount.OrganizationId); var policies = request.ToBaseAccessPoliciesForServiceAccount(id); await _createAccessPoliciesCommand.CreateManyAsync(policies, userId, accessClient); - var results = await _accessPolicyRepository.GetManyByGrantedServiceAccountIdAsync(id); + var results = await _accessPolicyRepository.GetManyByGrantedServiceAccountIdAsync(id, userId); return new ServiceAccountAccessPoliciesResponseModel(results); } @@ -115,9 +114,8 @@ public class AccessPoliciesController : Controller [FromRoute] Guid id) { var serviceAccount = await _serviceAccountRepository.GetByIdAsync(id); - await CheckUserHasWriteAccessToServiceAccountAsync(serviceAccount); - - var results = await _accessPolicyRepository.GetManyByGrantedServiceAccountIdAsync(id); + var (_, userId) = await CheckUserHasWriteAccessToServiceAccountAsync(serviceAccount); + var results = await _accessPolicyRepository.GetManyByGrantedServiceAccountIdAsync(id, userId); return new ServiceAccountAccessPoliciesResponseModel(results); } @@ -244,7 +242,7 @@ public class AccessPoliciesController : Controller return new ListResponseModel(projectResponses); } - private async Task CheckUserHasWriteAccessToProjectAsync(Project project) + private async Task<(AccessClientType AccessClientType, Guid UserId)> CheckUserHasWriteAccessToProjectAsync(Project project) { if (project == null) { @@ -263,9 +261,10 @@ public class AccessPoliciesController : Controller { throw new NotFoundException(); } + return (accessClient, userId); } - private async Task CheckUserHasWriteAccessToServiceAccountAsync(ServiceAccount serviceAccount) + private async Task<(AccessClientType AccessClientType, Guid UserId)> CheckUserHasWriteAccessToServiceAccountAsync(ServiceAccount serviceAccount) { if (serviceAccount == null) { @@ -285,6 +284,7 @@ public class AccessPoliciesController : Controller { throw new NotFoundException(); } + return (accessClient, userId); } private async Task<(AccessClientType AccessClientType, Guid UserId)> GetAccessClientTypeAsync(Guid organizationId) diff --git a/src/Api/SecretsManager/Models/Response/AccessPolicyResponseModel.cs b/src/Api/SecretsManager/Models/Response/AccessPolicyResponseModel.cs index a298e15b3..cedaf66f4 100644 --- a/src/Api/SecretsManager/Models/Response/AccessPolicyResponseModel.cs +++ b/src/Api/SecretsManager/Models/Response/AccessPolicyResponseModel.cs @@ -37,6 +37,7 @@ public class UserProjectAccessPolicyResponseModel : BaseAccessPolicyResponseMode OrganizationUserId = accessPolicy.OrganizationUserId; GrantedProjectId = accessPolicy.GrantedProjectId; OrganizationUserName = GetUserDisplayName(accessPolicy.User); + UserId = accessPolicy.User?.Id; } public UserProjectAccessPolicyResponseModel() : base(new UserProjectAccessPolicy(), _objectName) @@ -45,6 +46,7 @@ public class UserProjectAccessPolicyResponseModel : BaseAccessPolicyResponseMode public Guid? OrganizationUserId { get; set; } public string? OrganizationUserName { get; set; } + public Guid? UserId { get; set; } public Guid? GrantedProjectId { get; set; } } @@ -58,6 +60,7 @@ public class UserServiceAccountAccessPolicyResponseModel : BaseAccessPolicyRespo OrganizationUserId = accessPolicy.OrganizationUserId; GrantedServiceAccountId = accessPolicy.GrantedServiceAccountId; OrganizationUserName = GetUserDisplayName(accessPolicy.User); + UserId = accessPolicy.User?.Id; } public UserServiceAccountAccessPolicyResponseModel() : base(new UserServiceAccountAccessPolicy(), _objectName) @@ -66,6 +69,7 @@ public class UserServiceAccountAccessPolicyResponseModel : BaseAccessPolicyRespo public Guid? OrganizationUserId { get; set; } public string? OrganizationUserName { get; set; } + public Guid? UserId { get; set; } public Guid? GrantedServiceAccountId { get; set; } } @@ -79,6 +83,7 @@ public class GroupProjectAccessPolicyResponseModel : BaseAccessPolicyResponseMod GroupId = accessPolicy.GroupId; GrantedProjectId = accessPolicy.GrantedProjectId; GroupName = accessPolicy.Group?.Name; + CurrentUserInGroup = accessPolicy.CurrentUserInGroup; } public GroupProjectAccessPolicyResponseModel() : base(new GroupProjectAccessPolicy(), _objectName) @@ -87,6 +92,7 @@ public class GroupProjectAccessPolicyResponseModel : BaseAccessPolicyResponseMod public Guid? GroupId { get; set; } public string? GroupName { get; set; } + public bool? CurrentUserInGroup { get; set; } public Guid? GrantedProjectId { get; set; } } @@ -100,6 +106,7 @@ public class GroupServiceAccountAccessPolicyResponseModel : BaseAccessPolicyResp GroupId = accessPolicy.GroupId; GroupName = accessPolicy.Group?.Name; GrantedServiceAccountId = accessPolicy.GrantedServiceAccountId; + CurrentUserInGroup = accessPolicy.CurrentUserInGroup; } public GroupServiceAccountAccessPolicyResponseModel() : base(new GroupServiceAccountAccessPolicy(), _objectName) @@ -109,6 +116,7 @@ public class GroupServiceAccountAccessPolicyResponseModel : BaseAccessPolicyResp public Guid? GroupId { get; set; } public string? GroupName { get; set; } public Guid? GrantedServiceAccountId { get; set; } + public bool? CurrentUserInGroup { get; set; } } public class ServiceAccountProjectAccessPolicyResponseModel : BaseAccessPolicyResponseModel diff --git a/src/Core/SecretsManager/Entities/AccessPolicy.cs b/src/Core/SecretsManager/Entities/AccessPolicy.cs index bce9de062..6de856d8d 100644 --- a/src/Core/SecretsManager/Entities/AccessPolicy.cs +++ b/src/Core/SecretsManager/Entities/AccessPolicy.cs @@ -41,6 +41,7 @@ public class GroupProjectAccessPolicy : BaseAccessPolicy { public Guid? GroupId { get; set; } public Group? Group { get; set; } + public bool? CurrentUserInGroup { get; set; } public Guid? GrantedProjectId { get; set; } public Project? GrantedProject { get; set; } } @@ -49,6 +50,7 @@ public class GroupServiceAccountAccessPolicy : BaseAccessPolicy { public Guid? GroupId { get; set; } public Group? Group { get; set; } + public bool? CurrentUserInGroup { get; set; } public Guid? GrantedServiceAccountId { get; set; } public ServiceAccount? GrantedServiceAccount { get; set; } } diff --git a/src/Core/SecretsManager/Repositories/IAccessPolicyRepository.cs b/src/Core/SecretsManager/Repositories/IAccessPolicyRepository.cs index 7241c81eb..198ea7614 100644 --- a/src/Core/SecretsManager/Repositories/IAccessPolicyRepository.cs +++ b/src/Core/SecretsManager/Repositories/IAccessPolicyRepository.cs @@ -9,8 +9,8 @@ public interface IAccessPolicyRepository Task> CreateManyAsync(List baseAccessPolicies); Task AccessPolicyExists(BaseAccessPolicy baseAccessPolicy); Task GetByIdAsync(Guid id); - Task> GetManyByGrantedProjectIdAsync(Guid id); - Task> GetManyByGrantedServiceAccountIdAsync(Guid id); + Task> GetManyByGrantedProjectIdAsync(Guid id, Guid userId); + Task> GetManyByGrantedServiceAccountIdAsync(Guid id, Guid userId); Task> GetManyByServiceAccountIdAsync(Guid id, Guid userId, AccessClientType accessType); Task ReplaceAsync(BaseAccessPolicy baseAccessPolicy); diff --git a/test/Api.IntegrationTest/SecretsManager/Controllers/ProjectsControllerTest.cs b/test/Api.IntegrationTest/SecretsManager/Controllers/ProjectsControllerTest.cs index 5665bee8a..d855465e6 100644 --- a/test/Api.IntegrationTest/SecretsManager/Controllers/ProjectsControllerTest.cs +++ b/test/Api.IntegrationTest/SecretsManager/Controllers/ProjectsControllerTest.cs @@ -123,12 +123,14 @@ public class ProjectsControllerTest : IClassFixture, IAsy var (org, adminOrgUser) = await _organizationHelper.Initialize(true, true); await LoginAsync(_email); var orgUserId = adminOrgUser.Id; + var currentUserId = adminOrgUser.UserId!.Value; if (permissionType == PermissionType.RunAsUserWithPermission) { var (email, orgUser) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true); await LoginAsync(email); orgUserId = orgUser.Id; + currentUserId = orgUser.UserId!.Value; } var request = new ProjectCreateRequestModel { Name = _mockEncryptedString }; @@ -150,7 +152,7 @@ public class ProjectsControllerTest : IClassFixture, IAsy Assert.Null(createdProject.DeletedDate); // Check permissions have been bootstrapped. - var accessPolicies = await _accessPolicyRepository.GetManyByGrantedProjectIdAsync(createdProject.Id); + var accessPolicies = await _accessPolicyRepository.GetManyByGrantedProjectIdAsync(createdProject.Id, currentUserId); Assert.NotNull(accessPolicies); var ap = (UserProjectAccessPolicy)accessPolicies.First(); Assert.Equal(createdProject.Id, ap.GrantedProjectId); diff --git a/test/Api.IntegrationTest/SecretsManager/Controllers/ServiceAccountsControllerTests.cs b/test/Api.IntegrationTest/SecretsManager/Controllers/ServiceAccountsControllerTests.cs index 32cae2503..b2501161b 100644 --- a/test/Api.IntegrationTest/SecretsManager/Controllers/ServiceAccountsControllerTests.cs +++ b/test/Api.IntegrationTest/SecretsManager/Controllers/ServiceAccountsControllerTests.cs @@ -138,7 +138,7 @@ public class ServiceAccountsControllerTest : IClassFixture().Received(1) - .GetManyByGrantedProjectIdAsync(Arg.Is(AssertHelper.AssertPropertyEqual(id))); + .GetManyByGrantedProjectIdAsync(Arg.Is(AssertHelper.AssertPropertyEqual(id)), Arg.Any()); Assert.Empty(result.GroupAccessPolicies); Assert.Empty(result.UserAccessPolicies); @@ -135,7 +135,7 @@ public class AccessPoliciesControllerTests await Assert.ThrowsAsync(() => sutProvider.Sut.GetProjectAccessPoliciesAsync(id)); await sutProvider.GetDependency().DidNotReceiveWithAnyArgs() - .GetManyByGrantedProjectIdAsync(Arg.Any()); + .GetManyByGrantedProjectIdAsync(Arg.Any(), Arg.Any()); } [Theory] @@ -161,13 +161,13 @@ public class AccessPoliciesControllerTests break; } - sutProvider.GetDependency().GetManyByGrantedProjectIdAsync(default) + sutProvider.GetDependency().GetManyByGrantedProjectIdAsync(default, default) .ReturnsForAnyArgs(new List { resultAccessPolicy }); var result = await sutProvider.Sut.GetProjectAccessPoliciesAsync(id); await sutProvider.GetDependency().Received(1) - .GetManyByGrantedProjectIdAsync(Arg.Is(AssertHelper.AssertPropertyEqual(id))); + .GetManyByGrantedProjectIdAsync(Arg.Is(AssertHelper.AssertPropertyEqual(id)), Arg.Any()); Assert.Empty(result.GroupAccessPolicies); Assert.NotEmpty(result.UserAccessPolicies); @@ -187,13 +187,13 @@ public class AccessPoliciesControllerTests sutProvider.GetDependency().UserHasWriteAccessToProject(default, default) .ReturnsForAnyArgs(false); - sutProvider.GetDependency().GetManyByGrantedProjectIdAsync(default) + sutProvider.GetDependency().GetManyByGrantedProjectIdAsync(default, default) .ReturnsForAnyArgs(new List { resultAccessPolicy }); await Assert.ThrowsAsync(() => sutProvider.Sut.GetProjectAccessPoliciesAsync(id)); await sutProvider.GetDependency().DidNotReceiveWithAnyArgs() - .GetManyByGrantedProjectIdAsync(Arg.Any()); + .GetManyByGrantedProjectIdAsync(Arg.Any(), Arg.Any()); } [Theory] @@ -222,7 +222,7 @@ public class AccessPoliciesControllerTests var result = await sutProvider.Sut.GetServiceAccountAccessPoliciesAsync(id); await sutProvider.GetDependency().Received(1) - .GetManyByGrantedServiceAccountIdAsync(Arg.Is(AssertHelper.AssertPropertyEqual(id))); + .GetManyByGrantedServiceAccountIdAsync(Arg.Is(AssertHelper.AssertPropertyEqual(id)), Arg.Any()); Assert.Empty(result.UserAccessPolicies); Assert.Empty(result.GroupAccessPolicies); @@ -243,7 +243,7 @@ public class AccessPoliciesControllerTests await Assert.ThrowsAsync(() => sutProvider.Sut.GetServiceAccountAccessPoliciesAsync(id)); await sutProvider.GetDependency().DidNotReceiveWithAnyArgs() - .GetManyByGrantedServiceAccountIdAsync(Arg.Any()); + .GetManyByGrantedServiceAccountIdAsync(Arg.Any(), Arg.Any()); } [Theory] @@ -270,13 +270,13 @@ public class AccessPoliciesControllerTests break; } - sutProvider.GetDependency().GetManyByGrantedServiceAccountIdAsync(default) + sutProvider.GetDependency().GetManyByGrantedServiceAccountIdAsync(default, default) .ReturnsForAnyArgs(new List { resultAccessPolicy }); var result = await sutProvider.Sut.GetServiceAccountAccessPoliciesAsync(id); await sutProvider.GetDependency().Received(1) - .GetManyByGrantedServiceAccountIdAsync(Arg.Is(AssertHelper.AssertPropertyEqual(id))); + .GetManyByGrantedServiceAccountIdAsync(Arg.Is(AssertHelper.AssertPropertyEqual(id)), Arg.Any()); Assert.Empty(result.GroupAccessPolicies); Assert.NotEmpty(result.UserAccessPolicies); @@ -295,13 +295,13 @@ public class AccessPoliciesControllerTests sutProvider.GetDependency().UserHasWriteAccessToServiceAccount(default, default) .ReturnsForAnyArgs(false); - sutProvider.GetDependency().GetManyByGrantedServiceAccountIdAsync(default) + sutProvider.GetDependency().GetManyByGrantedServiceAccountIdAsync(default, default) .ReturnsForAnyArgs(new List { resultAccessPolicy }); await Assert.ThrowsAsync(() => sutProvider.Sut.GetServiceAccountAccessPoliciesAsync(id)); await sutProvider.GetDependency().DidNotReceiveWithAnyArgs() - .GetManyByGrantedServiceAccountIdAsync(Arg.Any()); + .GetManyByGrantedServiceAccountIdAsync(Arg.Any(), Arg.Any()); } [Theory]