mirror of
https://github.com/bitwarden/server.git
synced 2025-06-30 07:36:14 -05:00
[SM-1256] Add BulkSecretAuthorizationHandler (#4099)
* Add AccessToSecretsAsync to the repository * Add BulkSecretAuthorizationHandler * Update controller to use the new authz handler * Add integration test coverage
This commit is contained in:
@ -741,44 +741,83 @@ public class SecretsControllerTests : IClassFixture<ApiApplicationFactory>, IAsy
|
||||
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(PermissionType.RunAsAdmin)]
|
||||
[InlineData(PermissionType.RunAsUserWithPermission)]
|
||||
public async Task GetSecretsByIds_Success(PermissionType permissionType)
|
||||
[Fact]
|
||||
public async Task GetSecretsByIds_SecretsNotInTheSameOrganization_NotFound()
|
||||
{
|
||||
var (org, _) = await _organizationHelper.Initialize(true, true, true);
|
||||
await _loginHelper.LoginAsync(_email);
|
||||
|
||||
var (project, secretIds) = await CreateSecretsAsync(org.Id);
|
||||
|
||||
if (permissionType == PermissionType.RunAsUserWithPermission)
|
||||
{
|
||||
var (email, orgUser) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true);
|
||||
await _loginHelper.LoginAsync(email);
|
||||
|
||||
var accessPolicies = new List<BaseAccessPolicy>
|
||||
{
|
||||
new UserProjectAccessPolicy
|
||||
{
|
||||
GrantedProjectId = project.Id, OrganizationUserId = orgUser.Id, Read = true, Write = true,
|
||||
},
|
||||
};
|
||||
await _accessPolicyRepository.CreateManyAsync(accessPolicies);
|
||||
}
|
||||
else
|
||||
{
|
||||
var (email, _) = await _organizationHelper.CreateNewUser(OrganizationUserType.Admin, true);
|
||||
await _loginHelper.LoginAsync(email);
|
||||
}
|
||||
var otherOrg = await _organizationHelper.CreateSmOrganizationAsync();
|
||||
var (_, secretIds) = await CreateSecretsAsync(org.Id);
|
||||
var (_, diffOrgSecrets) = await CreateSecretsAsync(otherOrg.Id, 1);
|
||||
secretIds.AddRange(diffOrgSecrets);
|
||||
|
||||
var request = new GetSecretsRequestModel { Ids = secretIds };
|
||||
|
||||
var response = await _client.PostAsJsonAsync("/secrets/get-by-ids", request);
|
||||
|
||||
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(false)]
|
||||
[InlineData(true)]
|
||||
public async Task GetSecretsByIds_SecretsNonExistent_NotFound(bool partial)
|
||||
{
|
||||
var (org, _) = await _organizationHelper.Initialize(true, true, true);
|
||||
await _loginHelper.LoginAsync(_email);
|
||||
var ids = new List<Guid>();
|
||||
|
||||
if (partial)
|
||||
{
|
||||
var (_, secretIds) = await CreateSecretsAsync(org.Id);
|
||||
ids = secretIds;
|
||||
ids.Add(Guid.NewGuid());
|
||||
}
|
||||
|
||||
var request = new GetSecretsRequestModel { Ids = ids };
|
||||
|
||||
var response = await _client.PostAsJsonAsync("/secrets/get-by-ids", request);
|
||||
|
||||
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(true, false)]
|
||||
[InlineData(true, true)]
|
||||
[InlineData(false, false)]
|
||||
[InlineData(false, true)]
|
||||
public async Task GetSecretsByIds_NoAccess_NotFound(bool runAsServiceAccount, bool partialAccess)
|
||||
{
|
||||
var (org, _) = await _organizationHelper.Initialize(true, true, true);
|
||||
|
||||
var request = await SetupNoAccessRequestAsync(org.Id, runAsServiceAccount, partialAccess);
|
||||
|
||||
var response = await _client.PostAsJsonAsync("/secrets/get-by-ids", request);
|
||||
|
||||
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(PermissionType.RunAsAdmin)]
|
||||
[InlineData(PermissionType.RunAsUserWithPermission)]
|
||||
[InlineData(PermissionType.RunAsServiceAccountWithPermission)]
|
||||
public async Task GetSecretsByIds_Success(PermissionType permissionType)
|
||||
{
|
||||
var (org, _) = await _organizationHelper.Initialize(true, true, true);
|
||||
await _loginHelper.LoginAsync(_email);
|
||||
var request = await SetupGetSecretsByIdsRequestAsync(org.Id, permissionType);
|
||||
|
||||
var response = await _client.PostAsJsonAsync("/secrets/get-by-ids", request);
|
||||
response.EnsureSuccessStatusCode();
|
||||
var result = await response.Content.ReadFromJsonAsync<ListResponseModel<BaseSecretResponseModel>>();
|
||||
|
||||
Assert.NotNull(result);
|
||||
Assert.NotEmpty(result.Data);
|
||||
Assert.Equal(secretIds.Count, result.Data.Count());
|
||||
Assert.Equal(request.Ids.Count(), result.Data.Count());
|
||||
Assert.All(result.Data, data => Assert.Equal(_mockEncryptedString, data.Value));
|
||||
Assert.All(result.Data, data => Assert.Equal(_mockEncryptedString, data.Key));
|
||||
Assert.All(result.Data, data => Assert.Equal(_mockEncryptedString, data.Note));
|
||||
Assert.All(result.Data, data => Assert.Equal(org.Id, data.OrganizationId));
|
||||
}
|
||||
|
||||
|
||||
@ -1161,4 +1200,94 @@ public class SecretsControllerTests : IClassFixture<ApiApplicationFactory>, IAsy
|
||||
|
||||
return (secret, request);
|
||||
}
|
||||
|
||||
private async Task<GetSecretsRequestModel> SetupGetSecretsByIdsRequestAsync(Guid organizationId,
|
||||
PermissionType permissionType)
|
||||
{
|
||||
var (project, secretIds) = await CreateSecretsAsync(organizationId);
|
||||
|
||||
if (permissionType == PermissionType.RunAsUserWithPermission)
|
||||
{
|
||||
var (email, orgUser) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true);
|
||||
await _loginHelper.LoginAsync(email);
|
||||
|
||||
var accessPolicies = new List<BaseAccessPolicy>
|
||||
{
|
||||
new UserProjectAccessPolicy
|
||||
{
|
||||
GrantedProjectId = project.Id, OrganizationUserId = orgUser.Id, Read = true, Write = true
|
||||
}
|
||||
};
|
||||
await _accessPolicyRepository.CreateManyAsync(accessPolicies);
|
||||
}
|
||||
|
||||
if (permissionType == PermissionType.RunAsServiceAccountWithPermission)
|
||||
{
|
||||
var apiKeyDetails = await _organizationHelper.CreateNewServiceAccountApiKeyAsync();
|
||||
await _loginHelper.LoginWithApiKeyAsync(apiKeyDetails);
|
||||
|
||||
var accessPolicies = new List<BaseAccessPolicy>
|
||||
{
|
||||
new ServiceAccountProjectAccessPolicy
|
||||
{
|
||||
GrantedProjectId = project.Id,
|
||||
ServiceAccountId = apiKeyDetails.ApiKey.ServiceAccountId,
|
||||
Read = true,
|
||||
Write = true
|
||||
}
|
||||
};
|
||||
await _accessPolicyRepository.CreateManyAsync(accessPolicies);
|
||||
}
|
||||
|
||||
return new GetSecretsRequestModel { Ids = secretIds };
|
||||
}
|
||||
|
||||
private async Task<GetSecretsRequestModel> SetupNoAccessRequestAsync(Guid organizationId, bool runAsServiceAccount,
|
||||
bool partialAccess)
|
||||
{
|
||||
var (_, secretIds) = await CreateSecretsAsync(organizationId);
|
||||
|
||||
if (runAsServiceAccount)
|
||||
{
|
||||
var apiKeyDetails = await _organizationHelper.CreateNewServiceAccountApiKeyAsync();
|
||||
await _loginHelper.LoginWithApiKeyAsync(apiKeyDetails);
|
||||
|
||||
if (partialAccess)
|
||||
{
|
||||
var accessPolicies = new List<BaseAccessPolicy>
|
||||
{
|
||||
new ServiceAccountSecretAccessPolicy
|
||||
{
|
||||
GrantedSecretId = secretIds[0],
|
||||
ServiceAccountId = apiKeyDetails.ApiKey.ServiceAccountId,
|
||||
Read = true,
|
||||
Write = true
|
||||
}
|
||||
};
|
||||
await _accessPolicyRepository.CreateManyAsync(accessPolicies);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var (email, orgUser) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true);
|
||||
await _loginHelper.LoginAsync(email);
|
||||
|
||||
if (partialAccess)
|
||||
{
|
||||
var accessPolicies = new List<BaseAccessPolicy>
|
||||
{
|
||||
new UserSecretAccessPolicy
|
||||
{
|
||||
GrantedSecretId = secretIds[0],
|
||||
OrganizationUserId = orgUser.Id,
|
||||
Read = true,
|
||||
Write = true
|
||||
}
|
||||
};
|
||||
await _accessPolicyRepository.CreateManyAsync(accessPolicies);
|
||||
}
|
||||
}
|
||||
|
||||
return new GetSecretsRequestModel { Ids = secretIds };
|
||||
}
|
||||
}
|
||||
|
@ -412,30 +412,6 @@ public class SecretsControllerTests
|
||||
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.GetSecretsByIdsAsync(request));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task GetSecretsByIds_OrganizationMisMatch_ThrowsNotFound(SutProvider<SecretsController> sutProvider,
|
||||
List<Secret> data)
|
||||
{
|
||||
var (ids, request) = BuildGetSecretsRequestModel(data);
|
||||
sutProvider.GetDependency<ISecretRepository>().GetManyByIds(Arg.Is(ids)).ReturnsForAnyArgs(data);
|
||||
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.GetSecretsByIdsAsync(request));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task GetSecretsByIds_NoAccessToSecretsManager_ThrowsNotFound(
|
||||
SutProvider<SecretsController> sutProvider, List<Secret> data)
|
||||
{
|
||||
var (ids, request) = BuildGetSecretsRequestModel(data);
|
||||
var organizationId = SetOrganizations(ref data);
|
||||
|
||||
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(Arg.Is(organizationId))
|
||||
.ReturnsForAnyArgs(false);
|
||||
sutProvider.GetDependency<ISecretRepository>().GetManyByIds(Arg.Is(ids)).ReturnsForAnyArgs(data);
|
||||
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.GetSecretsByIdsAsync(request));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task GetSecretsByIds_AccessDenied_ThrowsNotFound(SutProvider<SecretsController> sutProvider,
|
||||
@ -445,10 +421,8 @@ public class SecretsControllerTests
|
||||
var organizationId = SetOrganizations(ref data);
|
||||
|
||||
sutProvider.GetDependency<ISecretRepository>().GetManyByIds(Arg.Is(ids)).ReturnsForAnyArgs(data);
|
||||
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(Arg.Is(organizationId))
|
||||
.ReturnsForAnyArgs(true);
|
||||
sutProvider.GetDependency<IAuthorizationService>()
|
||||
.AuthorizeAsync(Arg.Any<ClaimsPrincipal>(), data.First(),
|
||||
.AuthorizeAsync(Arg.Any<ClaimsPrincipal>(), data,
|
||||
Arg.Any<IEnumerable<IAuthorizationRequirement>>()).ReturnsForAnyArgs(AuthorizationResult.Failed());
|
||||
|
||||
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.GetSecretsByIdsAsync(request));
|
||||
@ -462,10 +436,8 @@ public class SecretsControllerTests
|
||||
var organizationId = SetOrganizations(ref data);
|
||||
|
||||
sutProvider.GetDependency<ISecretRepository>().GetManyByIds(Arg.Is(ids)).ReturnsForAnyArgs(data);
|
||||
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(Arg.Is(organizationId))
|
||||
.ReturnsForAnyArgs(true);
|
||||
sutProvider.GetDependency<IAuthorizationService>()
|
||||
.AuthorizeAsync(Arg.Any<ClaimsPrincipal>(), data.First(),
|
||||
.AuthorizeAsync(Arg.Any<ClaimsPrincipal>(), data,
|
||||
Arg.Any<IEnumerable<IAuthorizationRequirement>>()).ReturnsForAnyArgs(AuthorizationResult.Success());
|
||||
|
||||
var results = await sutProvider.Sut.GetSecretsByIdsAsync(request);
|
||||
|
Reference in New Issue
Block a user