1
0
mirror of https://github.com/bitwarden/server.git synced 2025-06-30 07:36:14 -05:00

[SM-429] Add permission checks to access policy endpoints (#2628)

* Add permission checks to access policy endpoints

* Fix unit tests

* Add service account grant permission checks

* Add service account grant tests

* Add new endpoint unit tests

* Cleanup unit tests add integration tests

* User permission enum in create tests

* Swap to NotFoundException for access checks

* Add filter for potential grantees

* Add in AccessSecretsManager check and test it

* Add code review updates

* Code review updates

* Refactor potential grantees endpoint

* Code review updates
This commit is contained in:
Thomas Avery
2023-02-06 11:26:06 -06:00
committed by GitHub
parent 9110efa44e
commit cf669286ed
20 changed files with 1710 additions and 146 deletions

View File

@ -1,9 +1,11 @@
using System.Net.Http.Headers;
using System.Net;
using System.Net.Http.Headers;
using Bit.Api.IntegrationTest.Factories;
using Bit.Api.IntegrationTest.Helpers;
using Bit.Api.IntegrationTest.SecretsManager.Enums;
using Bit.Api.Models.Response;
using Bit.Api.SecretsManager.Models.Request;
using Bit.Api.SecretsManager.Models.Response;
using Bit.Core.Entities;
using Bit.Core.Enums;
using Bit.Core.SecretsManager.Entities;
using Bit.Core.SecretsManager.Repositories;
using Bit.Test.Common.Helpers;
@ -13,16 +15,17 @@ namespace Bit.Api.IntegrationTest.SecretsManager.Controllers;
public class AccessPoliciesControllerTest : IClassFixture<ApiApplicationFactory>, IAsyncLifetime
{
private const string _mockEncryptedString =
"2.3Uk+WNBIoU5xzmVFNcoWzz==|1MsPIYuRfdOHfu/0uY6H2Q==|/98sp4wb6pHP1VTZ9JcNCYgQjEUMFPlqJgCwRk1YXKg=";
private readonly IAccessPolicyRepository _accessPolicyRepository;
private readonly HttpClient _client;
private readonly ApiApplicationFactory _factory;
private const string _mockEncryptedString = "2.3Uk+WNBIoU5xzmVFNcoWzz==|1MsPIYuRfdOHfu/0uY6H2Q==|/98sp4wb6pHP1VTZ9JcNCYgQjEUMFPlqJgCwRk1YXKg=";
private readonly IProjectRepository _projectRepository;
private readonly IServiceAccountRepository _serviceAccountRepository;
private Organization _organization = null!;
private string _email = null!;
private SecretsManagerOrganizationHelper _organizationHelper = null!;
public AccessPoliciesControllerTest(ApiApplicationFactory factory)
{
@ -35,46 +38,105 @@ public class AccessPoliciesControllerTest : IClassFixture<ApiApplicationFactory>
public async Task InitializeAsync()
{
var ownerEmail = $"integration-test{Guid.NewGuid()}@bitwarden.com";
var tokens = await _factory.LoginWithNewAccount(ownerEmail);
var (organization, _) =
await OrganizationTestHelpers.SignUpAsync(_factory, ownerEmail: ownerEmail, billingEmail: ownerEmail);
_client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", tokens.Token);
_organization = organization;
_email = $"integration-test{Guid.NewGuid()}@bitwarden.com";
await _factory.LoginWithNewAccount(_email);
_organizationHelper = new SecretsManagerOrganizationHelper(_factory, _email);
}
public Task DisposeAsync() => Task.CompletedTask;
[Fact]
public async Task CreateProjectAccessPolicies()
public Task DisposeAsync()
{
var initialProject = await _projectRepository.CreateAsync(new Project
_client.Dispose();
return Task.CompletedTask;
}
private async Task LoginAsync(string email)
{
var tokens = await _factory.LoginAsync(email);
_client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", tokens.Token);
}
[Theory]
[InlineData(false, false)]
[InlineData(true, false)]
[InlineData(false, true)]
public async Task CreateProjectAccessPolicies_SmNotEnabled_NotFound(bool useSecrets, bool accessSecrets)
{
var (org, _) = await _organizationHelper.Initialize(useSecrets, accessSecrets);
await LoginAsync(_email);
var project = await _projectRepository.CreateAsync(new Project
{
OrganizationId = _organization.Id,
Name = _mockEncryptedString
OrganizationId = org.Id,
Name = _mockEncryptedString,
});
var initialServiceAccount = await _serviceAccountRepository.CreateAsync(new ServiceAccount
var serviceAccount = await _serviceAccountRepository.CreateAsync(new ServiceAccount
{
OrganizationId = _organization.Id,
Name = _mockEncryptedString
OrganizationId = org.Id,
Name = _mockEncryptedString,
});
var request = new AccessPoliciesCreateRequest
{
ServiceAccountAccessPolicyRequests = new List<AccessPolicyRequest>
{
new() { GranteeId = initialServiceAccount.Id, Read = true, Write = true }
}
new() { GranteeId = serviceAccount.Id, Read = true, Write = true },
},
};
var response = await _client.PostAsJsonAsync($"/projects/{initialProject.Id}/access-policies", request);
var response = await _client.PostAsJsonAsync($"/projects/{project.Id}/access-policies", request);
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
[Theory]
[InlineData(PermissionType.RunAsAdmin)]
[InlineData(PermissionType.RunAsUserWithPermission)]
public async Task CreateProjectAccessPolicies(PermissionType permissionType)
{
var (org, _) = await _organizationHelper.Initialize(true, true);
await LoginAsync(_email);
var project = await _projectRepository.CreateAsync(new Project
{
OrganizationId = org.Id,
Name = _mockEncryptedString,
});
if (permissionType == PermissionType.RunAsUserWithPermission)
{
var (email, orgUser) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true);
await LoginAsync(email);
var accessPolicies = new List<BaseAccessPolicy>
{
new UserProjectAccessPolicy
{
GrantedProjectId = project.Id, OrganizationUserId = orgUser.Id, Read = true, Write = true,
},
};
await _accessPolicyRepository.CreateManyAsync(accessPolicies);
}
var serviceAccount = await _serviceAccountRepository.CreateAsync(new ServiceAccount
{
OrganizationId = org.Id,
Name = _mockEncryptedString,
});
var request = new AccessPoliciesCreateRequest
{
ServiceAccountAccessPolicyRequests = new List<AccessPolicyRequest>
{
new() { GranteeId = serviceAccount.Id, Read = true, Write = true },
},
};
var response = await _client.PostAsJsonAsync($"/projects/{project.Id}/access-policies", request);
response.EnsureSuccessStatusCode();
var result = await response.Content.ReadFromJsonAsync<ProjectAccessPoliciesResponseModel>();
Assert.NotNull(result);
Assert.Equal(initialServiceAccount.Id, result!.ServiceAccountAccessPolicies.First().ServiceAccountId);
Assert.Equal(serviceAccount.Id, result!.ServiceAccountAccessPolicies.First().ServiceAccountId);
Assert.True(result.ServiceAccountAccessPolicies.First().Read);
Assert.True(result.ServiceAccountAccessPolicies.First().Write);
AssertHelper.AssertRecent(result.ServiceAccountAccessPolicies.First().RevisionDate);
@ -91,15 +153,103 @@ public class AccessPoliciesControllerTest : IClassFixture<ApiApplicationFactory>
}
[Fact]
public async Task UpdateAccessPolicy()
public async Task CreateProjectAccessPolicies_NoPermission()
{
var initData = await SetupAccessPolicyRequest();
// Create a new account as a user
var (org, _) = await _organizationHelper.Initialize(true, true);
var (email, orgUser) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true);
await LoginAsync(email);
var project = await _projectRepository.CreateAsync(new Project
{
OrganizationId = org.Id,
Name = _mockEncryptedString,
});
var serviceAccount = await _serviceAccountRepository.CreateAsync(new ServiceAccount
{
OrganizationId = org.Id,
Name = _mockEncryptedString,
});
var request = new AccessPoliciesCreateRequest
{
ServiceAccountAccessPolicyRequests = new List<AccessPolicyRequest>
{
new() { GranteeId = serviceAccount.Id, Read = true, Write = true },
},
};
var response = await _client.PostAsJsonAsync($"/projects/{project.Id}/access-policies", request);
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
[Theory]
[InlineData(false, false)]
[InlineData(true, false)]
[InlineData(false, true)]
public async Task UpdateAccessPolicy_SmNotEnabled_NotFound(bool useSecrets, bool accessSecrets)
{
var (org, _) = await _organizationHelper.Initialize(useSecrets, accessSecrets);
await LoginAsync(_email);
var initData = await SetupAccessPolicyRequest(org.Id);
const bool expectedRead = true;
const bool expectedWrite = false;
var request = new AccessPolicyUpdateRequest { Read = expectedRead, Write = expectedWrite };
var response = await _client.PutAsJsonAsync($"/access-policies/{initData.InitialAccessPolicyId}", request);
var response = await _client.PutAsJsonAsync($"/access-policies/{initData.AccessPolicyId}", request);
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
[Fact]
public async Task UpdateAccessPolicy_NoPermission()
{
// Create a new account as a user
var (org, _) = await _organizationHelper.Initialize(true, true);
var (email, orgUser) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true);
await LoginAsync(email);
var initData = await SetupAccessPolicyRequest(orgUser.OrganizationId);
const bool expectedRead = true;
const bool expectedWrite = false;
var request = new AccessPolicyUpdateRequest { Read = expectedRead, Write = expectedWrite };
var response = await _client.PutAsJsonAsync($"/access-policies/{initData.AccessPolicyId}", request);
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
[Theory]
[InlineData(PermissionType.RunAsAdmin)]
[InlineData(PermissionType.RunAsUserWithPermission)]
public async Task UpdateAccessPolicy(PermissionType permissionType)
{
var (org, _) = await _organizationHelper.Initialize(true, true);
await LoginAsync(_email);
var initData = await SetupAccessPolicyRequest(org.Id);
if (permissionType == PermissionType.RunAsUserWithPermission)
{
var (email, orgUser) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true);
await LoginAsync(email);
var accessPolicies = new List<BaseAccessPolicy>
{
new UserProjectAccessPolicy
{
GrantedProjectId = initData.ProjectId, OrganizationUserId = orgUser.Id, Read = true, Write = true,
},
};
await _accessPolicyRepository.CreateManyAsync(accessPolicies);
}
const bool expectedRead = true;
const bool expectedWrite = false;
var request = new AccessPolicyUpdateRequest { Read = expectedRead, Write = expectedWrite };
var response = await _client.PutAsJsonAsync($"/access-policies/{initData.AccessPolicyId}", request);
response.EnsureSuccessStatusCode();
var result = await response.Content.ReadFromJsonAsync<ServiceAccountProjectAccessPolicyResponseModel>();
@ -116,29 +266,78 @@ public class AccessPoliciesControllerTest : IClassFixture<ApiApplicationFactory>
AssertHelper.AssertRecent(updatedAccessPolicy.RevisionDate);
}
[Theory]
[InlineData(false, false)]
[InlineData(true, false)]
[InlineData(false, true)]
public async Task DeleteAccessPolicy_SmNotEnabled_NotFound(bool useSecrets, bool accessSecrets)
{
var (org, _) = await _organizationHelper.Initialize(useSecrets, accessSecrets);
await LoginAsync(_email);
var initData = await SetupAccessPolicyRequest(org.Id);
var response = await _client.DeleteAsync($"/access-policies/{initData.AccessPolicyId}");
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
[Fact]
public async Task DeleteAccessPolicy()
public async Task DeleteAccessPolicy_NoPermission()
{
var initData = await SetupAccessPolicyRequest();
// Create a new account as a user
var (org, _) = await _organizationHelper.Initialize(true, true);
var (email, orgUser) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true);
await LoginAsync(email);
var response = await _client.DeleteAsync($"/access-policies/{initData.InitialAccessPolicyId}");
var initData = await SetupAccessPolicyRequest(orgUser.OrganizationId);
var response = await _client.DeleteAsync($"/access-policies/{initData.AccessPolicyId}");
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
[Theory]
[InlineData(PermissionType.RunAsAdmin)]
[InlineData(PermissionType.RunAsUserWithPermission)]
public async Task DeleteAccessPolicy(PermissionType permissionType)
{
var (org, _) = await _organizationHelper.Initialize(true, true);
await LoginAsync(_email);
var initData = await SetupAccessPolicyRequest(org.Id);
if (permissionType == PermissionType.RunAsUserWithPermission)
{
var (email, orgUser) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true);
await LoginAsync(email);
var accessPolicies = new List<BaseAccessPolicy>
{
new UserProjectAccessPolicy
{
GrantedProjectId = initData.ProjectId, OrganizationUserId = orgUser.Id, Read = true, Write = true,
},
};
await _accessPolicyRepository.CreateManyAsync(accessPolicies);
}
var response = await _client.DeleteAsync($"/access-policies/{initData.AccessPolicyId}");
response.EnsureSuccessStatusCode();
var test = await _accessPolicyRepository.GetByIdAsync(initData.InitialAccessPolicyId);
var test = await _accessPolicyRepository.GetByIdAsync(initData.AccessPolicyId);
Assert.Null(test);
}
[Fact]
public async Task GetProjectAccessPolicies_ReturnsEmpty()
{
var initialProject = await _projectRepository.CreateAsync(new Project
var (org, _) = await _organizationHelper.Initialize(true, true);
await LoginAsync(_email);
var project = await _projectRepository.CreateAsync(new Project
{
OrganizationId = _organization.Id,
OrganizationId = org.Id,
Name = _mockEncryptedString,
});
var response = await _client.GetAsync($"/projects/{initialProject.Id}/access-policies");
var response = await _client.GetAsync($"/projects/{project.Id}/access-policies");
response.EnsureSuccessStatusCode();
var result = await response.Content.ReadFromJsonAsync<ProjectAccessPoliciesResponseModel>();
@ -149,12 +348,59 @@ public class AccessPoliciesControllerTest : IClassFixture<ApiApplicationFactory>
Assert.Empty(result!.ServiceAccountAccessPolicies);
}
[Fact]
public async Task GetProjectAccessPolicies()
[Theory]
[InlineData(false, false)]
[InlineData(true, false)]
[InlineData(false, true)]
public async Task GetProjectAccessPolicies_SmNotEnabled_NotFound(bool useSecrets, bool accessSecrets)
{
var initData = await SetupAccessPolicyRequest();
var (org, _) = await _organizationHelper.Initialize(useSecrets, accessSecrets);
await LoginAsync(_email);
var initData = await SetupAccessPolicyRequest(org.Id);
var response = await _client.GetAsync($"/projects/{initData.InitialProjectId}/access-policies");
var response = await _client.GetAsync($"/projects/{initData.ProjectId}/access-policies");
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
[Fact]
public async Task GetProjectAccessPolicies_NoPermission()
{
// Create a new account as a user
var (org, _) = await _organizationHelper.Initialize(true, true);
var (email, orgUser) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true);
await LoginAsync(email);
var initData = await SetupAccessPolicyRequest(orgUser.OrganizationId);
var response = await _client.GetAsync($"/projects/{initData.ProjectId}/access-policies");
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
[Theory]
[InlineData(PermissionType.RunAsAdmin)]
[InlineData(PermissionType.RunAsUserWithPermission)]
public async Task GetProjectAccessPolicies(PermissionType permissionType)
{
var (org, _) = await _organizationHelper.Initialize(true, true);
await LoginAsync(_email);
var initData = await SetupAccessPolicyRequest(org.Id);
if (permissionType == PermissionType.RunAsUserWithPermission)
{
var (email, orgUser) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true);
await LoginAsync(email);
var accessPolicies = new List<BaseAccessPolicy>
{
new UserProjectAccessPolicy
{
GrantedProjectId = initData.ProjectId, OrganizationUserId = orgUser.Id, Read = true, Write = true,
},
};
await _accessPolicyRepository.CreateManyAsync(accessPolicies);
}
var response = await _client.GetAsync($"/projects/{initData.ProjectId}/access-policies");
response.EnsureSuccessStatusCode();
var result = await response.Content.ReadFromJsonAsync<ProjectAccessPoliciesResponseModel>();
@ -163,44 +409,166 @@ public class AccessPoliciesControllerTest : IClassFixture<ApiApplicationFactory>
Assert.Single(result!.ServiceAccountAccessPolicies);
}
private async Task<RequestSetupData> SetupAccessPolicyRequest()
[Theory]
[InlineData(false, false)]
[InlineData(true, false)]
[InlineData(false, true)]
public async Task GetPeoplePotentialGrantees_SmNotEnabled_NotFound(bool useSecrets, bool accessSecrets)
{
var initialProject = await _projectRepository.CreateAsync(new Project
var (org, _) = await _organizationHelper.Initialize(useSecrets, accessSecrets);
await LoginAsync(_email);
var response =
await _client.GetAsync(
$"/organizations/{org.Id}/access-policies/people/potential-grantees");
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
[Theory]
[InlineData(PermissionType.RunAsAdmin)]
[InlineData(PermissionType.RunAsUserWithPermission)]
public async Task GetPeoplePotentialGrantees_Success(PermissionType permissionType)
{
var (org, _) = await _organizationHelper.Initialize(true, true);
await LoginAsync(_email);
if (permissionType == PermissionType.RunAsUserWithPermission)
{
OrganizationId = _organization.Id,
var (email, orgUser) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true);
await LoginAsync(email);
}
var response =
await _client.GetAsync(
$"/organizations/{org.Id}/access-policies/people/potential-grantees");
response.EnsureSuccessStatusCode();
var result = await response.Content.ReadFromJsonAsync<ListResponseModel<PotentialGranteeResponseModel>>();
Assert.NotNull(result?.Data);
Assert.NotEmpty(result!.Data);
}
[Theory]
[InlineData(false, false)]
[InlineData(true, false)]
[InlineData(false, true)]
public async Task GetServiceAccountPotentialGrantees_SmNotEnabled_NotFound(bool useSecrets, bool accessSecrets)
{
var (org, _) = await _organizationHelper.Initialize(useSecrets, accessSecrets);
await LoginAsync(_email);
var response =
await _client.GetAsync(
$"/organizations/{org.Id}/access-policies/service-accounts/potential-grantees");
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
[Fact]
public async Task GetServiceAccountPotentialGrantees_OnlyReturnsServiceAccountsWithWriteAccess()
{
// Create a new account as a user
var (org, _) = await _organizationHelper.Initialize(true, true);
var (email, orgUser) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true);
await LoginAsync(email);
var serviceAccount = await _serviceAccountRepository.CreateAsync(new ServiceAccount
{
OrganizationId = org.Id,
Name = _mockEncryptedString,
});
var initialServiceAccount = await _serviceAccountRepository.CreateAsync(new ServiceAccount
var response =
await _client.GetAsync(
$"/organizations/{org.Id}/access-policies/service-accounts/potential-grantees");
response.EnsureSuccessStatusCode();
var result = await response.Content.ReadFromJsonAsync<ListResponseModel<PotentialGranteeResponseModel>>();
Assert.NotNull(result?.Data);
Assert.Empty(result!.Data);
}
[Theory]
[InlineData(PermissionType.RunAsAdmin)]
[InlineData(PermissionType.RunAsUserWithPermission)]
public async Task GetServiceAccountsPotentialGrantees_Success(PermissionType permissionType)
{
var (org, _) = await _organizationHelper.Initialize(true, true);
await LoginAsync(_email);
var serviceAccount = await _serviceAccountRepository.CreateAsync(new ServiceAccount
{
OrganizationId = _organization.Id,
OrganizationId = org.Id,
Name = _mockEncryptedString,
});
var initialAccessPolicy = await _accessPolicyRepository.CreateManyAsync(
if (permissionType == PermissionType.RunAsUserWithPermission)
{
var (email, orgUser) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true);
await LoginAsync(email);
await _accessPolicyRepository.CreateManyAsync(
new List<BaseAccessPolicy>
{
new UserServiceAccountAccessPolicy
{
GrantedServiceAccountId = serviceAccount.Id,
OrganizationUserId = orgUser.Id,
Read = true,
Write = true,
},
});
}
var response =
await _client.GetAsync(
$"/organizations/{org.Id}/access-policies/service-accounts/potential-grantees");
response.EnsureSuccessStatusCode();
var result = await response.Content.ReadFromJsonAsync<ListResponseModel<PotentialGranteeResponseModel>>();
Assert.NotNull(result?.Data);
Assert.NotEmpty(result!.Data);
Assert.Equal(serviceAccount.Id.ToString(), result!.Data.First(x => x.Id == serviceAccount.Id.ToString()).Id);
}
private async Task<RequestSetupData> SetupAccessPolicyRequest(Guid organizationId)
{
var project = await _projectRepository.CreateAsync(new Project
{
OrganizationId = organizationId,
Name = _mockEncryptedString,
});
var serviceAccount = await _serviceAccountRepository.CreateAsync(new ServiceAccount
{
OrganizationId = organizationId,
Name = _mockEncryptedString,
});
var accessPolicy = await _accessPolicyRepository.CreateManyAsync(
new List<BaseAccessPolicy>
{
new ServiceAccountProjectAccessPolicy
{
Read = true,
Write = true,
ServiceAccountId = initialServiceAccount.Id,
GrantedProjectId = initialProject.Id,
}
Read = true, Write = true, ServiceAccountId = serviceAccount.Id, GrantedProjectId = project.Id,
},
});
return new RequestSetupData
{
InitialProjectId = initialProject.Id,
InitialServiceAccountId = initialServiceAccount.Id,
InitialAccessPolicyId = initialAccessPolicy.First().Id,
ProjectId = project.Id,
ServiceAccountId = serviceAccount.Id,
AccessPolicyId = accessPolicy.First().Id,
};
}
private class RequestSetupData
{
public Guid InitialProjectId { get; set; }
public Guid InitialAccessPolicyId { get; set; }
public Guid InitialServiceAccountId { get; set; }
public Guid ProjectId { get; set; }
public Guid AccessPolicyId { get; set; }
public Guid ServiceAccountId { get; set; }
}
}