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

[SM-572] Modify project endpoint to return current user's permission (#2752)

* Add endpoints to check current user's permission

* Swap to adding current user permission onto GET

* Cleanup DI

* Add ProjectPermissionDetails DTO and query

* code review updates

* Remove assert recent for longer running creates
This commit is contained in:
Thomas Avery 2023-03-02 09:02:42 -06:00 committed by GitHub
parent 26c30f8854
commit 05f5d79938
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 94 additions and 23 deletions

View File

@ -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<Core.SecretsManager.Entities.Project
}
}
public async Task<ProjectPermissionDetails> 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<IEnumerable<Core.SecretsManager.Entities.Project>> GetManyByOrganizationIdAsync(Guid organizationId, Guid userId, AccessClientType accessType)
{
using var scope = ServiceScopeFactory.CreateScope();

View File

@ -80,9 +80,10 @@ public class ProjectsController : Controller
}
[HttpGet("projects/{id}")]
public async Task<ProjectResponseModel> GetAsync([FromRoute] Guid id)
public async Task<ProjectPermissionDetailsResponseModel> 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")]

View File

@ -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; }
}

View File

@ -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)
{

View File

@ -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; }
}

View File

@ -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<IEnumerable<Project>> GetManyByOrganizationIdAsync(Guid organizationId, Guid userId, AccessClientType accessType);
Task<IEnumerable<Project>> GetManyByOrganizationIdWriteAccessAsync(Guid organizationId, Guid userId, AccessClientType accessType);
Task<IEnumerable<Project>> GetManyByIds(IEnumerable<Guid> ids);
Task<ProjectPermissionDetails> GetPermissionDetailsByIdAsync(Guid id, Guid userId);
Task<Project> GetByIdAsync(Guid id);
Task<Project> CreateAsync(Project project);
Task ReplaceAsync(Project project);

View File

@ -703,7 +703,6 @@ public class AccessPoliciesControllerTest : IClassFixture<ApiApplicationFactory>
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<ApiApplicationFactory>
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);
}

View File

@ -302,10 +302,12 @@ public class ProjectsControllerTest : IClassFixture<ApiApplicationFactory>, IAsy
var response = await _client.GetAsync($"/projects/{project.Id}");
response.EnsureSuccessStatusCode();
var result = await response.Content.ReadFromJsonAsync<ProjectResponseModel>();
var result = await response.Content.ReadFromJsonAsync<ProjectPermissionDetailsResponseModel>();
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]

View File

@ -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<ProjectsController> sutProvider, Guid data)
public async void Get_SmNotEnabled_Throws(SutProvider<ProjectsController> sutProvider, Guid data, Guid orgId)
{
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(data).Returns(false);
SetupAdmin(sutProvider, orgId);
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(orgId).Returns(false);
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.GetAsync(data));
}
[Theory]
[BitAutoData]
public async void Get_ThrowsNotFound(SutProvider<ProjectsController> sutProvider, Guid data)
public async void Get_ThrowsNotFound(SutProvider<ProjectsController> sutProvider, Guid data, Guid orgId)
{
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(data).Returns(true);
SetupAdmin(sutProvider, orgId);
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.GetAsync(data));
}
@ -199,13 +199,13 @@ public class ProjectsControllerTests
break;
}
sutProvider.GetDependency<IProjectRepository>().GetByIdAsync(Arg.Is(data))
.ReturnsForAnyArgs(new Project { Id = data, OrganizationId = orgId });
sutProvider.GetDependency<IProjectRepository>().GetPermissionDetailsByIdAsync(Arg.Is(data), Arg.Any<Guid>())
.ReturnsForAnyArgs(new ProjectPermissionDetails() { Id = data, OrganizationId = orgId, Read = true, Write = true });
await sutProvider.Sut.GetAsync(data);
await sutProvider.GetDependency<IProjectRepository>().Received(1)
.GetByIdAsync(Arg.Is(data));
.GetPermissionDetailsByIdAsync(Arg.Is(data), Arg.Any<Guid>());
}
[Theory]