diff --git a/bitwarden_license/src/Commercial.Infrastructure.EntityFramework/SecretsManager/Repositories/ProjectRepository.cs b/bitwarden_license/src/Commercial.Infrastructure.EntityFramework/SecretsManager/Repositories/ProjectRepository.cs index 782486601..f9bb3740d 100644 --- a/bitwarden_license/src/Commercial.Infrastructure.EntityFramework/SecretsManager/Repositories/ProjectRepository.cs +++ b/bitwarden_license/src/Commercial.Infrastructure.EntityFramework/SecretsManager/Repositories/ProjectRepository.cs @@ -1,6 +1,7 @@ using System.Linq.Expressions; using AutoMapper; using Bit.Core.Enums; +using Bit.Core.SecretsManager.Models.Data; using Bit.Core.SecretsManager.Repositories; using Bit.Infrastructure.EntityFramework.Repositories; using Bit.Infrastructure.EntityFramework.SecretsManager.Models; @@ -27,6 +28,31 @@ public class ProjectRepository : Repository GetPermissionDetailsByIdAsync(Guid id, Guid userId) + { + using var scope = ServiceScopeFactory.CreateScope(); + var dbContext = GetDatabaseContext(scope); + + var project = await dbContext.Project + .Where(c => c.Id == id && c.DeletedDate == null) + .Select(p => new ProjectPermissionDetails + { + Id = p.Id, + OrganizationId = p.OrganizationId, + Name = p.Name, + CreationDate = p.CreationDate, + RevisionDate = p.RevisionDate, + DeletedDate = p.DeletedDate, + Read = p.UserAccessPolicies.Any(ap => ap.OrganizationUser.User.Id == userId && ap.Read) + || p.GroupAccessPolicies.Any(ap => + ap.Group.GroupUsers.Any(gu => gu.OrganizationUser.User.Id == userId && ap.Read)), + Write = p.UserAccessPolicies.Any(ap => ap.OrganizationUser.User.Id == userId && ap.Write) || + p.GroupAccessPolicies.Any(ap => + ap.Group.GroupUsers.Any(gu => gu.OrganizationUser.User.Id == userId && ap.Write)), + }).FirstOrDefaultAsync(); + return project; + } + public async Task> GetManyByOrganizationIdAsync(Guid organizationId, Guid userId, AccessClientType accessType) { using var scope = ServiceScopeFactory.CreateScope(); diff --git a/src/Api/SecretsManager/Controllers/ProjectsController.cs b/src/Api/SecretsManager/Controllers/ProjectsController.cs index 9dfb8f11c..75738b981 100644 --- a/src/Api/SecretsManager/Controllers/ProjectsController.cs +++ b/src/Api/SecretsManager/Controllers/ProjectsController.cs @@ -80,9 +80,10 @@ public class ProjectsController : Controller } [HttpGet("projects/{id}")] - public async Task GetAsync([FromRoute] Guid id) + public async Task GetAsync([FromRoute] Guid id) { - var project = await _projectRepository.GetByIdAsync(id); + var userId = _userService.GetProperUserId(User).Value; + var project = await _projectRepository.GetPermissionDetailsByIdAsync(id, userId); if (project == null) { throw new NotFoundException(); @@ -93,23 +94,34 @@ public class ProjectsController : Controller throw new NotFoundException(); } - var userId = _userService.GetProperUserId(User).Value; var orgAdmin = await _currentContext.OrganizationAdmin(project.OrganizationId); var accessClient = AccessClientHelper.ToAccessClient(_currentContext.ClientType, orgAdmin); - var hasAccess = accessClient switch + bool hasAccess; + var read = project.Read; + var write = project.Write; + + switch (accessClient) { - AccessClientType.NoAccessCheck => true, - AccessClientType.User => await _projectRepository.UserHasReadAccessToProject(id, userId), - _ => false, - }; + case AccessClientType.NoAccessCheck: + hasAccess = true; + write = true; + read = true; + break; + case AccessClientType.User: + hasAccess = project.Read; + break; + default: + hasAccess = false; + break; + } if (!hasAccess) { throw new NotFoundException(); } - return new ProjectResponseModel(project); + return new ProjectPermissionDetailsResponseModel(project, read, write); } [HttpPost("projects/delete")] diff --git a/src/Api/SecretsManager/Models/Response/ProjectPermissionDetailsResponseModel.cs b/src/Api/SecretsManager/Models/Response/ProjectPermissionDetailsResponseModel.cs new file mode 100644 index 000000000..162ce2205 --- /dev/null +++ b/src/Api/SecretsManager/Models/Response/ProjectPermissionDetailsResponseModel.cs @@ -0,0 +1,22 @@ +using Bit.Core.SecretsManager.Entities; + +namespace Bit.Api.SecretsManager.Models.Response; + +public class ProjectPermissionDetailsResponseModel : ProjectResponseModel +{ + private const string _objectName = "projectPermissionDetails"; + + public ProjectPermissionDetailsResponseModel(Project project, bool read, bool write, string obj = _objectName) : base(project, obj) + { + Read = read; + Write = write; + } + + public ProjectPermissionDetailsResponseModel() + { + } + + public bool Read { get; set; } + + public bool Write { get; set; } +} diff --git a/src/Api/SecretsManager/Models/Response/ProjectResponseModel.cs b/src/Api/SecretsManager/Models/Response/ProjectResponseModel.cs index 25934a7e3..bf7507915 100644 --- a/src/Api/SecretsManager/Models/Response/ProjectResponseModel.cs +++ b/src/Api/SecretsManager/Models/Response/ProjectResponseModel.cs @@ -7,8 +7,8 @@ public class ProjectResponseModel : ResponseModel { private const string _objectName = "project"; - public ProjectResponseModel(Project project) - : base(_objectName) + public ProjectResponseModel(Project project, string obj = _objectName) + : base(obj) { if (project == null) { diff --git a/src/Core/SecretsManager/Models/Data/ProjectPermissionDetails.cs b/src/Core/SecretsManager/Models/Data/ProjectPermissionDetails.cs new file mode 100644 index 000000000..435b95985 --- /dev/null +++ b/src/Core/SecretsManager/Models/Data/ProjectPermissionDetails.cs @@ -0,0 +1,9 @@ +using Bit.Core.SecretsManager.Entities; + +namespace Bit.Core.SecretsManager.Models.Data; + +public class ProjectPermissionDetails : Project +{ + public bool Read { get; set; } + public bool Write { get; set; } +} diff --git a/src/Core/SecretsManager/Repositories/IProjectRepository.cs b/src/Core/SecretsManager/Repositories/IProjectRepository.cs index dddd2e21d..1841fafec 100644 --- a/src/Core/SecretsManager/Repositories/IProjectRepository.cs +++ b/src/Core/SecretsManager/Repositories/IProjectRepository.cs @@ -1,5 +1,6 @@ using Bit.Core.Enums; using Bit.Core.SecretsManager.Entities; +using Bit.Core.SecretsManager.Models.Data; namespace Bit.Core.SecretsManager.Repositories; @@ -8,6 +9,7 @@ public interface IProjectRepository Task> GetManyByOrganizationIdAsync(Guid organizationId, Guid userId, AccessClientType accessType); Task> GetManyByOrganizationIdWriteAccessAsync(Guid organizationId, Guid userId, AccessClientType accessType); Task> GetManyByIds(IEnumerable ids); + Task GetPermissionDetailsByIdAsync(Guid id, Guid userId); Task GetByIdAsync(Guid id); Task CreateAsync(Project project); Task ReplaceAsync(Project project); diff --git a/test/Api.IntegrationTest/SecretsManager/Controllers/AccessPoliciesControllerTest.cs b/test/Api.IntegrationTest/SecretsManager/Controllers/AccessPoliciesControllerTest.cs index 305d24973..c6e481ebd 100644 --- a/test/Api.IntegrationTest/SecretsManager/Controllers/AccessPoliciesControllerTest.cs +++ b/test/Api.IntegrationTest/SecretsManager/Controllers/AccessPoliciesControllerTest.cs @@ -703,7 +703,6 @@ public class AccessPoliciesControllerTest : IClassFixture Assert.True(result.UserAccessPolicies.First().Read); Assert.True(result.UserAccessPolicies.First().Write); AssertHelper.AssertRecent(result.UserAccessPolicies.First().RevisionDate); - AssertHelper.AssertRecent(result.UserAccessPolicies.First().CreationDate); var createdAccessPolicy = await _accessPolicyRepository.GetByIdAsync(result.UserAccessPolicies.First().Id); @@ -711,7 +710,6 @@ public class AccessPoliciesControllerTest : IClassFixture Assert.Equal(result.UserAccessPolicies.First().Read, createdAccessPolicy!.Read); Assert.Equal(result.UserAccessPolicies.First().Write, createdAccessPolicy.Write); Assert.Equal(result.UserAccessPolicies.First().Id, createdAccessPolicy.Id); - AssertHelper.AssertRecent(createdAccessPolicy.CreationDate); AssertHelper.AssertRecent(createdAccessPolicy.RevisionDate); } diff --git a/test/Api.IntegrationTest/SecretsManager/Controllers/ProjectsControllerTest.cs b/test/Api.IntegrationTest/SecretsManager/Controllers/ProjectsControllerTest.cs index fa9dccc93..5665bee8a 100644 --- a/test/Api.IntegrationTest/SecretsManager/Controllers/ProjectsControllerTest.cs +++ b/test/Api.IntegrationTest/SecretsManager/Controllers/ProjectsControllerTest.cs @@ -302,10 +302,12 @@ public class ProjectsControllerTest : IClassFixture, IAsy var response = await _client.GetAsync($"/projects/{project.Id}"); response.EnsureSuccessStatusCode(); - var result = await response.Content.ReadFromJsonAsync(); + var result = await response.Content.ReadFromJsonAsync(); Assert.Equal(project.Name, result!.Name); Assert.Equal(project.RevisionDate, result.RevisionDate); Assert.Equal(project.CreationDate, result.CreationDate); + Assert.True(result.Read); + Assert.True(result.Write); } [Theory] diff --git a/test/Api.Test/SecretsManager/Controllers/ProjectsControllerTests.cs b/test/Api.Test/SecretsManager/Controllers/ProjectsControllerTests.cs index 39ba7bdba..e216c9b78 100644 --- a/test/Api.Test/SecretsManager/Controllers/ProjectsControllerTests.cs +++ b/test/Api.Test/SecretsManager/Controllers/ProjectsControllerTests.cs @@ -6,6 +6,7 @@ using Bit.Core.Enums; using Bit.Core.Exceptions; using Bit.Core.SecretsManager.Commands.Projects.Interfaces; using Bit.Core.SecretsManager.Entities; +using Bit.Core.SecretsManager.Models.Data; using Bit.Core.SecretsManager.Repositories; using Bit.Core.Services; using Bit.Core.Test.SecretsManager.AutoFixture.ProjectsFixture; @@ -165,19 +166,18 @@ public class ProjectsControllerTests [Theory] [BitAutoData] - public async void Get_SmNotEnabled_Throws(SutProvider sutProvider, Guid data) + public async void Get_SmNotEnabled_Throws(SutProvider sutProvider, Guid data, Guid orgId) { - sutProvider.GetDependency().AccessSecretsManager(data).Returns(false); - + SetupAdmin(sutProvider, orgId); + sutProvider.GetDependency().AccessSecretsManager(orgId).Returns(false); await Assert.ThrowsAsync(() => sutProvider.Sut.GetAsync(data)); } [Theory] [BitAutoData] - public async void Get_ThrowsNotFound(SutProvider sutProvider, Guid data) + public async void Get_ThrowsNotFound(SutProvider sutProvider, Guid data, Guid orgId) { - sutProvider.GetDependency().AccessSecretsManager(data).Returns(true); - + SetupAdmin(sutProvider, orgId); await Assert.ThrowsAsync(() => sutProvider.Sut.GetAsync(data)); } @@ -199,13 +199,13 @@ public class ProjectsControllerTests break; } - sutProvider.GetDependency().GetByIdAsync(Arg.Is(data)) - .ReturnsForAnyArgs(new Project { Id = data, OrganizationId = orgId }); + sutProvider.GetDependency().GetPermissionDetailsByIdAsync(Arg.Is(data), Arg.Any()) + .ReturnsForAnyArgs(new ProjectPermissionDetails() { Id = data, OrganizationId = orgId, Read = true, Write = true }); await sutProvider.Sut.GetAsync(data); await sutProvider.GetDependency().Received(1) - .GetByIdAsync(Arg.Is(data)); + .GetPermissionDetailsByIdAsync(Arg.Is(data), Arg.Any()); } [Theory]