@ -1,10 +1,12 @@
@@ -1,10 +1,12 @@
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.Models.Response ;
using Bit.Api.SecretManagerFeatures.Models.Request ;
using Bit.Api.SecretManagerFeatures.Models.Response ;
using Bit.Core.Entities ;
using Bit.Core.Enums ;
using Bit.Core.Repositories ;
using Bit.Test.Common.Helpers ;
using Xunit ;
@ -13,49 +15,73 @@ namespace Bit.Api.IntegrationTest.Controllers;
@@ -13,49 +15,73 @@ namespace Bit.Api.IntegrationTest.Controllers;
public class ServiceAccountsControllerTest : IClassFixture < ApiApplicationFactory > , IAsyncLifetime
{
private readonly string _ mockEncryptedString =
private const string _ mockEncryptedString =
"2.3Uk+WNBIoU5xzmVFNcoWzz==|1MsPIYuRfdOHfu/0uY6H2Q==|/98sp4wb6pHP1VTZ9JcNCYgQjEUMFPlqJgCwRk1YXKg=" ;
private const string _ mockNewName =
"2.3AZ+WNBIoU5xzmVFNcoWzz==|1MsPIYuRfdOHfu/0uY6H2Q==|/98xy4wb6pHP1VTZ9JcNCYgQjEUMFPlqJgCwRk1YXKg=" ;
private readonly IAccessPolicyRepository _ accessPolicyRepository ;
private readonly HttpClient _ client ;
private readonly ApiApplicationFactory _f actory ;
private readonly IServiceAccountRepository _ serviceAccountRepository ;
private Organization _ organization = null ! ;
public ServiceAccountsControllerTest ( ApiApplicationFactory factory )
{
_f actory = factory ;
_ client = _f actory . CreateClient ( ) ;
_ serviceAccountRepository = _f actory . GetService < IServiceAccountRepository > ( ) ;
_ accessPolicyRepository = _f actory . GetService < IAccessPolicyRepository > ( ) ;
}
public async Task InitializeAsync ( )
{
var ownerEmail = $"integration-test{Guid.NewGuid()}@bitwarden.com" ;
var tokens = await _f actory . LoginWithNewAccount ( ownerEmail ) ;
var ( organization , _ ) = await OrganizationTestHelpers . SignUpAsync ( _f actory , ownerEmail : ownerEmail , billingEmail : ownerEmail ) ;
await _f actory . LoginWithNewAccount ( ownerEmail ) ;
( _ organization , _ ) =
await OrganizationTestHelpers . SignUpAsync ( _f actory , ownerEmail : ownerEmail , billingEmail : ownerEmail ) ;
var tokens = await _f actory . LoginAsync ( ownerEmail ) ;
_ client . DefaultRequestHeaders . Authorization = new AuthenticationHeaderValue ( "Bearer" , tokens . Token ) ;
_ organization = organization ;
}
public Task DisposeAsync ( )
public Task DisposeAsync ( ) = > Task . CompletedTask ;
[Fact]
public async Task GetServiceAccountsByOrganization_Admin ( )
{
return Task . CompletedTask ;
var serviceAccountIds = await SetupGetServiceAccountsByOrganizationAsync ( ) ;
var response = await _ client . GetAsync ( $"/organizations/{_organization.Id}/service-accounts" ) ;
response . EnsureSuccessStatusCode ( ) ;
var result = await response . Content . ReadFromJsonAsync < ListResponseModel < ServiceAccountResponseModel > > ( ) ;
Assert . NotNull ( result ) ;
Assert . NotEmpty ( result ! . Data ) ;
Assert . Equal ( serviceAccountIds . Count , result . Data . Count ( ) ) ;
}
[Fact]
public async Task GetServiceAccountsByOrganization ( )
public async Task GetServiceAccountsByOrganization_User_Success ( )
{
var serviceAccountsToCreate = 3 ;
var serviceAccountIds = new List < Guid > ( ) ;
for ( var i = 0 ; i < serviceAccountsToCreate ; i + + )
{
var serviceAccount = await _ serviceAccountRepository . CreateAsync ( new ServiceAccount
// Create a new account as a user
var user = await LoginAsNewOrgUserAsync ( ) ;
var serviceAccountIds = await SetupGetServiceAccountsByOrganizationAsync ( ) ;
var accessPolicies = serviceAccountIds . Select (
id = > new UserServiceAccountAccessPolicy
{
OrganizationId = _ organization . Id ,
Name = _ mockEncryptedString ,
} ) ;
serviceAccountIds . Add ( serviceAccount . Id ) ;
}
OrganizationUserId = user . Id ,
GrantedServiceAccountId = id ,
Read = true ,
Write = false ,
} ) . Cast < BaseAccessPolicy > ( ) . ToList ( ) ;
await _ accessPolicyRepository . CreateManyAsync ( accessPolicies ) ;
var response = await _ client . GetAsync ( $"/organizations/{_organization.Id}/service-accounts" ) ;
response . EnsureSuccessStatusCode ( ) ;
@ -67,12 +93,24 @@ public class ServiceAccountsControllerTest : IClassFixture<ApiApplicationFactory
@@ -67,12 +93,24 @@ public class ServiceAccountsControllerTest : IClassFixture<ApiApplicationFactory
}
[Fact]
public async Task CreateServiceAccount ( )
public async Task GetServiceAccountsByOrganization_User_NoPermission ( )
{
var request = new ServiceAccountCreateRequestModel ( )
{
Name = _ mockEncryptedString ,
} ;
// Create a new account as a user
await LoginAsNewOrgUserAsync ( ) ;
await SetupGetServiceAccountsByOrganizationAsync ( ) ;
var response = await _ client . GetAsync ( $"/organizations/{_organization.Id}/service-accounts" ) ;
response . EnsureSuccessStatusCode ( ) ;
var result = await response . Content . ReadFromJsonAsync < ListResponseModel < ServiceAccountResponseModel > > ( ) ;
Assert . NotNull ( result ) ;
Assert . Empty ( result ! . Data ) ;
}
[Fact]
public async Task CreateServiceAccount_Admin ( )
{
var request = new ServiceAccountCreateRequestModel { Name = _ mockEncryptedString } ;
var response = await _ client . PostAsJsonAsync ( $"/organizations/{_organization.Id}/service-accounts" , request ) ;
response . EnsureSuccessStatusCode ( ) ;
@ -91,7 +129,19 @@ public class ServiceAccountsControllerTest : IClassFixture<ApiApplicationFactory
@@ -91,7 +129,19 @@ public class ServiceAccountsControllerTest : IClassFixture<ApiApplicationFactory
}
[Fact]
public async Task UpdateServiceAccount ( )
public async Task CreateServiceAccount_User_NoPermissions ( )
{
// Create a new account as a user
await LoginAsNewOrgUserAsync ( ) ;
var request = new ServiceAccountCreateRequestModel { Name = _ mockEncryptedString } ;
var response = await _ client . PostAsJsonAsync ( $"/organizations/{_organization.Id}/service-accounts" , request ) ;
Assert . Equal ( HttpStatusCode . NotFound , response . StatusCode ) ;
}
[Fact]
public async Task UpdateServiceAccount_Admin ( )
{
var initialServiceAccount = await _ serviceAccountRepository . CreateAsync ( new ServiceAccount
{
@ -99,10 +149,41 @@ public class ServiceAccountsControllerTest : IClassFixture<ApiApplicationFactory
@@ -99,10 +149,41 @@ public class ServiceAccountsControllerTest : IClassFixture<ApiApplicationFactory
Name = _ mockEncryptedString ,
} ) ;
var request = new ServiceAccountUpdateRequestModel ( )
var request = new ServiceAccountUpdateRequestModel { Name = _ mockNewName } ;
var response = await _ client . PutAsJsonAsync ( $"/service-accounts/{initialServiceAccount.Id}" , request ) ;
response . EnsureSuccessStatusCode ( ) ;
var result = await response . Content . ReadFromJsonAsync < ServiceAccountResponseModel > ( ) ;
Assert . NotNull ( result ) ;
Assert . Equal ( request . Name , result ! . Name ) ;
Assert . NotEqual ( initialServiceAccount . Name , result . Name ) ;
AssertHelper . AssertRecent ( result . RevisionDate ) ;
Assert . NotEqual ( initialServiceAccount . RevisionDate , result . RevisionDate ) ;
var updatedServiceAccount = await _ serviceAccountRepository . GetByIdAsync ( initialServiceAccount . Id ) ;
Assert . NotNull ( result ) ;
Assert . Equal ( request . Name , updatedServiceAccount . Name ) ;
AssertHelper . AssertRecent ( updatedServiceAccount . RevisionDate ) ;
AssertHelper . AssertRecent ( updatedServiceAccount . CreationDate ) ;
Assert . NotEqual ( initialServiceAccount . Name , updatedServiceAccount . Name ) ;
Assert . NotEqual ( initialServiceAccount . RevisionDate , updatedServiceAccount . RevisionDate ) ;
}
[Fact]
public async Task UpdateServiceAccount_User_WithPermission ( )
{
// Create a new account as a user
var user = await LoginAsNewOrgUserAsync ( ) ;
var initialServiceAccount = await _ serviceAccountRepository . CreateAsync ( new ServiceAccount
{
Name = "2.3Uk+WNBIoU5xzmVFNcoWzz==|1MsPIYuRfdOHfu/0uY6H2Q==|/98xy4wb6pHP1VTZ9JcNCYgQjEUMFPlqJgCwRk1YXKg=" ,
} ;
OrganizationId = _ organization . Id ,
Name = _ mockEncryptedString ,
} ) ;
await CreateUserServiceAccountAccessPolicyAsync ( user . Id , initialServiceAccount . Id , true , true ) ;
var request = new ServiceAccountUpdateRequestModel { Name = _ mockNewName } ;
var response = await _ client . PutAsJsonAsync ( $"/service-accounts/{initialServiceAccount.Id}" , request ) ;
response . EnsureSuccessStatusCode ( ) ;
@ -123,21 +204,74 @@ public class ServiceAccountsControllerTest : IClassFixture<ApiApplicationFactory
@@ -123,21 +204,74 @@ public class ServiceAccountsControllerTest : IClassFixture<ApiApplicationFactory
}
[Fact]
public async Task CreateServiceAccountAccessToken ( )
public async Task UpdateServiceAccount_User_NoPermissions ( )
{
// Create a new account as a user
await LoginAsNewOrgUserAsync ( ) ;
var initialServiceAccount = await _ serviceAccountRepository . CreateAsync ( new ServiceAccount
{
OrganizationId = _ organization . Id ,
Name = _ mockEncryptedString ,
} ) ;
var request = new ServiceAccountUpdateRequestModel { Name = _ mockNewName } ;
var response = await _ client . PutAsJsonAsync ( $"/service-accounts/{initialServiceAccount.Id}" , request ) ;
Assert . Equal ( HttpStatusCode . Unauthorized , response . StatusCode ) ;
}
[Fact]
public async Task CreateServiceAccountAccessToken_Admin ( )
{
var serviceAccount = await _ serviceAccountRepository . CreateAsync ( new ServiceAccount
{
OrganizationId = _ organization . Id ,
Name = _ mockEncryptedString ,
} ) ;
var mockExpiresAt = DateTime . UtcNow . AddDays ( 3 0 ) ;
var request = new AccessTokenCreateRequestModel
{
Name = _ mockEncryptedString ,
EncryptedPayload = _ mockEncryptedString ,
Key = _ mockEncryptedString ,
ExpireAt = mockExpiresAt ,
} ;
var response = await _ client . PostAsJsonAsync ( $"/service-accounts/{serviceAccount.Id}/access-tokens" , request ) ;
response . EnsureSuccessStatusCode ( ) ;
var result = await response . Content . ReadFromJsonAsync < AccessTokenCreationResponseModel > ( ) ;
Assert . NotNull ( result ) ;
Assert . Equal ( request . Name , result ! . Name ) ;
Assert . NotNull ( result . ClientSecret ) ;
Assert . Equal ( mockExpiresAt , result . ExpireAt ) ;
AssertHelper . AssertRecent ( result . RevisionDate ) ;
AssertHelper . AssertRecent ( result . CreationDate ) ;
}
[Fact]
public async Task CreateServiceAccountAccessToken_User_WithPermission ( )
{
// Create a new account as a user
var user = await LoginAsNewOrgUserAsync ( ) ;
var serviceAccount = await _ serviceAccountRepository . CreateAsync ( new ServiceAccount
{
OrganizationId = _ organization . Id ,
Name = _ mockEncryptedString ,
} ) ;
await CreateUserServiceAccountAccessPolicyAsync ( user . Id , serviceAccount . Id , true , true ) ;
var mockExpiresAt = DateTime . UtcNow . AddDays ( 3 0 ) ;
var request = new AccessTokenCreateRequestModel ( )
var request = new AccessTokenCreateRequestModel
{
Name = _ mockEncryptedString ,
EncryptedPayload = _ mockEncryptedString ,
Key = _ mockEncryptedString ,
ExpireAt = mockExpiresAt
ExpireAt = mockExpiresAt ,
} ;
var response = await _ client . PostAsJsonAsync ( $"/service-accounts/{serviceAccount.Id}/access-tokens" , request ) ;
@ -153,7 +287,32 @@ public class ServiceAccountsControllerTest : IClassFixture<ApiApplicationFactory
@@ -153,7 +287,32 @@ public class ServiceAccountsControllerTest : IClassFixture<ApiApplicationFactory
}
[Fact]
public async Task CreateServiceAccountAccessTokenExpireAtNullAsync ( )
public async Task CreateServiceAccountAccessToken_User_NoPermission ( )
{
// Create a new account as a user
await LoginAsNewOrgUserAsync ( ) ;
var serviceAccount = await _ serviceAccountRepository . CreateAsync ( new ServiceAccount
{
OrganizationId = _ organization . Id ,
Name = _ mockEncryptedString ,
} ) ;
var mockExpiresAt = DateTime . UtcNow . AddDays ( 3 0 ) ;
var request = new AccessTokenCreateRequestModel
{
Name = _ mockEncryptedString ,
EncryptedPayload = _ mockEncryptedString ,
Key = _ mockEncryptedString ,
ExpireAt = mockExpiresAt ,
} ;
var response = await _ client . PostAsJsonAsync ( $"/service-accounts/{serviceAccount.Id}/access-tokens" , request ) ;
Assert . Equal ( HttpStatusCode . Unauthorized , response . StatusCode ) ;
}
[Fact]
public async Task CreateServiceAccountAccessTokenExpireAtNullAsync_Admin ( )
{
var serviceAccount = await _ serviceAccountRepository . CreateAsync ( new ServiceAccount
{
@ -161,12 +320,12 @@ public class ServiceAccountsControllerTest : IClassFixture<ApiApplicationFactory
@@ -161,12 +320,12 @@ public class ServiceAccountsControllerTest : IClassFixture<ApiApplicationFactory
Name = _ mockEncryptedString ,
} ) ;
var request = new AccessTokenCreateRequestModel ( )
var request = new AccessTokenCreateRequestModel
{
Name = _ mockEncryptedString ,
EncryptedPayload = _ mockEncryptedString ,
Key = _ mockEncryptedString ,
ExpireAt = null
ExpireAt = null ,
} ;
var response = await _ client . PostAsJsonAsync ( $"/service-accounts/{serviceAccount.Id}/access-tokens" , request ) ;
@ -180,4 +339,105 @@ public class ServiceAccountsControllerTest : IClassFixture<ApiApplicationFactory
@@ -180,4 +339,105 @@ public class ServiceAccountsControllerTest : IClassFixture<ApiApplicationFactory
AssertHelper . AssertRecent ( result . RevisionDate ) ;
AssertHelper . AssertRecent ( result . CreationDate ) ;
}
[Fact]
public async Task CreateServiceAccountAccessTokenExpireAtNullAsync_User_WithPermission ( )
{
// Create a new account as a user
var user = await LoginAsNewOrgUserAsync ( ) ;
var serviceAccount = await _ serviceAccountRepository . CreateAsync ( new ServiceAccount
{
OrganizationId = _ organization . Id ,
Name = _ mockEncryptedString ,
} ) ;
await CreateUserServiceAccountAccessPolicyAsync ( user . Id , serviceAccount . Id , true , true ) ;
var request = new AccessTokenCreateRequestModel
{
Name = _ mockEncryptedString ,
EncryptedPayload = _ mockEncryptedString ,
Key = _ mockEncryptedString ,
ExpireAt = null ,
} ;
var response = await _ client . PostAsJsonAsync ( $"/service-accounts/{serviceAccount.Id}/access-tokens" , request ) ;
response . EnsureSuccessStatusCode ( ) ;
var result = await response . Content . ReadFromJsonAsync < AccessTokenCreationResponseModel > ( ) ;
Assert . NotNull ( result ) ;
Assert . Equal ( request . Name , result ! . Name ) ;
Assert . NotNull ( result . ClientSecret ) ;
Assert . Null ( result . ExpireAt ) ;
AssertHelper . AssertRecent ( result . RevisionDate ) ;
AssertHelper . AssertRecent ( result . CreationDate ) ;
}
[Fact]
public async Task CreateServiceAccountAccessTokenExpireAtNullAsync_User_NoPermission ( )
{
// Create a new account as a user
await LoginAsNewOrgUserAsync ( ) ;
var serviceAccount = await _ serviceAccountRepository . CreateAsync ( new ServiceAccount
{
OrganizationId = _ organization . Id ,
Name = _ mockEncryptedString ,
} ) ;
var request = new AccessTokenCreateRequestModel
{
Name = _ mockEncryptedString ,
EncryptedPayload = _ mockEncryptedString ,
Key = _ mockEncryptedString ,
ExpireAt = null ,
} ;
var response = await _ client . PostAsJsonAsync ( $"/service-accounts/{serviceAccount.Id}/access-tokens" , request ) ;
Assert . Equal ( HttpStatusCode . Unauthorized , response . StatusCode ) ;
}
private async Task < List < Guid > > SetupGetServiceAccountsByOrganizationAsync ( )
{
const int serviceAccountsToCreate = 3 ;
var serviceAccountIds = new List < Guid > ( ) ;
for ( var i = 0 ; i < serviceAccountsToCreate ; i + + )
{
var serviceAccount = await _ serviceAccountRepository . CreateAsync ( new ServiceAccount
{
OrganizationId = _ organization . Id ,
Name = _ mockEncryptedString ,
} ) ;
serviceAccountIds . Add ( serviceAccount . Id ) ;
}
return serviceAccountIds ;
}
private async Task CreateUserServiceAccountAccessPolicyAsync ( Guid userId , Guid serviceAccountId , bool read ,
bool write )
{
var accessPolicies = new List < BaseAccessPolicy >
{
new UserServiceAccountAccessPolicy
{
OrganizationUserId = userId ,
GrantedServiceAccountId = serviceAccountId ,
Read = read ,
Write = write ,
} ,
} ;
await _ accessPolicyRepository . CreateManyAsync ( accessPolicies ) ;
}
private async Task < OrganizationUser > LoginAsNewOrgUserAsync ( OrganizationUserType type = OrganizationUserType . User )
{
var email = $"integration-test{Guid.NewGuid()}@bitwarden.com" ;
await _f actory . LoginWithNewAccount ( email ) ;
var orgUser = await OrganizationTestHelpers . CreateUserAsync ( _f actory , _ organization . Id , email , type ) ;
var tokens = await _f actory . LoginAsync ( email ) ;
_ client . DefaultRequestHeaders . Authorization = new AuthenticationHeaderValue ( "Bearer" , tokens . Token ) ;
return orgUser ;
}
}