mirror of
https://github.com/bitwarden/server
synced 2025-12-28 14:13:48 +00:00
Merge branch 'master' into feature/flexible-collections
This commit is contained in:
@@ -2,17 +2,22 @@
|
||||
using Bit.IntegrationTestCommon.Factories;
|
||||
using IdentityServer4.AccessTokenValidation;
|
||||
using Microsoft.AspNetCore.TestHost;
|
||||
using Microsoft.Data.Sqlite;
|
||||
|
||||
namespace Bit.Api.IntegrationTest.Factories;
|
||||
|
||||
public class ApiApplicationFactory : WebApplicationFactoryBase<Startup>
|
||||
{
|
||||
private readonly IdentityApplicationFactory _identityApplicationFactory;
|
||||
private const string _connectionString = "DataSource=:memory:";
|
||||
|
||||
public ApiApplicationFactory()
|
||||
{
|
||||
SqliteConnection = new SqliteConnection(_connectionString);
|
||||
SqliteConnection.Open();
|
||||
|
||||
_identityApplicationFactory = new IdentityApplicationFactory();
|
||||
_identityApplicationFactory.DatabaseName = DatabaseName;
|
||||
_identityApplicationFactory.SqliteConnection = SqliteConnection;
|
||||
}
|
||||
|
||||
protected override void ConfigureWebHost(IWebHostBuilder builder)
|
||||
@@ -53,4 +58,10 @@ public class ApiApplicationFactory : WebApplicationFactoryBase<Startup>
|
||||
{
|
||||
return await _identityApplicationFactory.TokenFromPasswordAsync(email, masterPasswordHash);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
SqliteConnection.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ 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;
|
||||
@@ -661,16 +662,15 @@ public class AccessPoliciesControllerTests : IClassFixture<ApiApplicationFactory
|
||||
{
|
||||
var (org, orgUser) = await _organizationHelper.Initialize(true, true, true);
|
||||
await LoginAsync(_email);
|
||||
var ownerOrgUserId = orgUser.Id;
|
||||
var anotherOrg = await _organizationHelper.CreateSmOrganizationAsync();
|
||||
|
||||
var serviceAccount = await _serviceAccountRepository.CreateAsync(new ServiceAccount
|
||||
{
|
||||
OrganizationId = Guid.NewGuid(),
|
||||
OrganizationId = anotherOrg.Id,
|
||||
Name = _mockEncryptedString,
|
||||
});
|
||||
var request =
|
||||
await SetupUserServiceAccountAccessPolicyRequestAsync(permissionType, org.Id, orgUser.Id,
|
||||
serviceAccount.Id);
|
||||
await SetupUserServiceAccountAccessPolicyRequestAsync(permissionType, orgUser.Id, serviceAccount.Id);
|
||||
|
||||
var response =
|
||||
await _client.PostAsJsonAsync($"/service-accounts/{serviceAccount.Id}/access-policies", request);
|
||||
@@ -692,8 +692,7 @@ public class AccessPoliciesControllerTests : IClassFixture<ApiApplicationFactory
|
||||
Name = _mockEncryptedString,
|
||||
});
|
||||
var request =
|
||||
await SetupUserServiceAccountAccessPolicyRequestAsync(permissionType, org.Id, orgUser.Id,
|
||||
serviceAccount.Id);
|
||||
await SetupUserServiceAccountAccessPolicyRequestAsync(permissionType, orgUser.Id, serviceAccount.Id);
|
||||
|
||||
var response =
|
||||
await _client.PostAsJsonAsync($"/service-accounts/{serviceAccount.Id}/access-policies", request);
|
||||
@@ -1086,9 +1085,15 @@ public class AccessPoliciesControllerTests : IClassFixture<ApiApplicationFactory
|
||||
private async Task<(Guid ProjectId, Guid ServiceAccountId)> CreateProjectAndServiceAccountAsync(Guid organizationId,
|
||||
bool misMatchOrganization = false)
|
||||
{
|
||||
var newOrg = new Organization();
|
||||
if (misMatchOrganization)
|
||||
{
|
||||
newOrg = await _organizationHelper.CreateSmOrganizationAsync();
|
||||
}
|
||||
|
||||
var project = await _projectRepository.CreateAsync(new Project
|
||||
{
|
||||
OrganizationId = misMatchOrganization ? Guid.NewGuid() : organizationId,
|
||||
OrganizationId = misMatchOrganization ? newOrg.Id : organizationId,
|
||||
Name = _mockEncryptedString,
|
||||
});
|
||||
|
||||
@@ -1127,7 +1132,7 @@ public class AccessPoliciesControllerTests : IClassFixture<ApiApplicationFactory
|
||||
}
|
||||
|
||||
private async Task<AccessPoliciesCreateRequest> SetupUserServiceAccountAccessPolicyRequestAsync(
|
||||
PermissionType permissionType, Guid organizationId, Guid userId, Guid serviceAccountId)
|
||||
PermissionType permissionType, Guid userId, Guid serviceAccountId)
|
||||
{
|
||||
if (permissionType == PermissionType.RunAsUserWithPermission)
|
||||
{
|
||||
|
||||
@@ -189,15 +189,17 @@ public class SecretsControllerTests : IClassFixture<ApiApplicationFactory>, IAsy
|
||||
{
|
||||
var (org, _) = await _organizationHelper.Initialize(true, true, true);
|
||||
await LoginAsync(_email);
|
||||
var anotherOrg = await _organizationHelper.CreateSmOrganizationAsync();
|
||||
|
||||
var project = await _projectRepository.CreateAsync(new Project { Name = "123" });
|
||||
var project =
|
||||
await _projectRepository.CreateAsync(new Project { Name = "123", OrganizationId = anotherOrg.Id });
|
||||
|
||||
var request = new SecretCreateRequestModel
|
||||
{
|
||||
ProjectIds = new Guid[] { project.Id },
|
||||
ProjectIds = new[] { project.Id },
|
||||
Key = _mockEncryptedString,
|
||||
Value = _mockEncryptedString,
|
||||
Note = _mockEncryptedString,
|
||||
Note = _mockEncryptedString
|
||||
};
|
||||
|
||||
var response = await _client.PostAsJsonAsync($"/organizations/{org.Id}/secrets", request);
|
||||
@@ -594,8 +596,9 @@ public class SecretsControllerTests : IClassFixture<ApiApplicationFactory>, IAsy
|
||||
{
|
||||
var (org, _) = await _organizationHelper.Initialize(true, true, true);
|
||||
await LoginAsync(_email);
|
||||
var anotherOrg = await _organizationHelper.CreateSmOrganizationAsync();
|
||||
|
||||
var project = await _projectRepository.CreateAsync(new Project { Name = "123" });
|
||||
var project = await _projectRepository.CreateAsync(new Project { Name = "123", OrganizationId = anotherOrg.Id });
|
||||
|
||||
var secret = await _secretRepository.CreateAsync(new Secret
|
||||
{
|
||||
@@ -698,7 +701,7 @@ public class SecretsControllerTests : IClassFixture<ApiApplicationFactory>, IAsy
|
||||
var (org, _) = await _organizationHelper.Initialize(true, true, true);
|
||||
await LoginAsync(_email);
|
||||
|
||||
var (project, secretIds) = await CreateSecretsAsync(org.Id, 3);
|
||||
var (project, secretIds) = await CreateSecretsAsync(org.Id);
|
||||
|
||||
if (permissionType == PermissionType.RunAsUserWithPermission)
|
||||
{
|
||||
@@ -709,24 +712,22 @@ public class SecretsControllerTests : IClassFixture<ApiApplicationFactory>, IAsy
|
||||
{
|
||||
new UserProjectAccessPolicy
|
||||
{
|
||||
GrantedProjectId = project.Id, OrganizationUserId = orgUser.Id, Read = true, Write = true,
|
||||
},
|
||||
GrantedProjectId = project.Id, OrganizationUserId = orgUser.Id, Read = true, Write = true
|
||||
}
|
||||
};
|
||||
await _accessPolicyRepository.CreateManyAsync(accessPolicies);
|
||||
}
|
||||
|
||||
var response = await _client.PostAsJsonAsync($"/secrets/delete", secretIds);
|
||||
var response = await _client.PostAsJsonAsync("/secrets/delete", secretIds);
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
var results = await response.Content.ReadFromJsonAsync<ListResponseModel<BulkDeleteResponseModel>>();
|
||||
Assert.NotNull(results);
|
||||
|
||||
var index = 0;
|
||||
Assert.NotNull(results?.Data);
|
||||
Assert.Equal(secretIds.Count, results!.Data.Count());
|
||||
foreach (var result in results!.Data)
|
||||
{
|
||||
Assert.Equal(secretIds[index], result.Id);
|
||||
Assert.Contains(result.Id, secretIds);
|
||||
Assert.Null(result.Error);
|
||||
index++;
|
||||
}
|
||||
|
||||
var secrets = await _secretRepository.GetManyByIds(secretIds);
|
||||
|
||||
@@ -704,14 +704,14 @@ public class ServiceAccountsControllerTests : IClassFixture<ApiApplicationFactor
|
||||
var serviceAccount = await _serviceAccountRepository.CreateAsync(new ServiceAccount
|
||||
{
|
||||
OrganizationId = org.Id,
|
||||
Name = _mockEncryptedString,
|
||||
Name = _mockEncryptedString
|
||||
});
|
||||
|
||||
var accessToken = await _apiKeyRepository.CreateAsync(new ApiKey
|
||||
{
|
||||
ServiceAccountId = org.Id,
|
||||
ServiceAccountId = serviceAccount.Id,
|
||||
Name = _mockEncryptedString,
|
||||
ExpireAt = DateTime.UtcNow.AddDays(30),
|
||||
ExpireAt = DateTime.UtcNow.AddDays(30)
|
||||
});
|
||||
|
||||
var request = new RevokeAccessTokensRequest
|
||||
@@ -753,9 +753,9 @@ public class ServiceAccountsControllerTests : IClassFixture<ApiApplicationFactor
|
||||
|
||||
var accessToken = await _apiKeyRepository.CreateAsync(new ApiKey
|
||||
{
|
||||
ServiceAccountId = org.Id,
|
||||
ServiceAccountId = serviceAccount.Id,
|
||||
Name = _mockEncryptedString,
|
||||
ExpireAt = DateTime.UtcNow.AddDays(30),
|
||||
ExpireAt = DateTime.UtcNow.AddDays(30)
|
||||
});
|
||||
|
||||
var request = new RevokeAccessTokensRequest
|
||||
|
||||
@@ -53,6 +53,15 @@ public class SecretsManagerOrganizationHelper
|
||||
return (_organization, _owner);
|
||||
}
|
||||
|
||||
public async Task<Organization> CreateSmOrganizationAsync()
|
||||
{
|
||||
var email = $"integration-test{Guid.NewGuid()}@bitwarden.com";
|
||||
await _factory.LoginWithNewAccount(email);
|
||||
var (organization, owner) =
|
||||
await OrganizationTestHelpers.SignUpAsync(_factory, ownerEmail: email, billingEmail: email);
|
||||
return organization;
|
||||
}
|
||||
|
||||
public async Task<(string email, OrganizationUser orgUser)> CreateNewUser(OrganizationUserType userType, bool accessSecrets)
|
||||
{
|
||||
var email = $"integration-test{Guid.NewGuid()}@bitwarden.com";
|
||||
|
||||
@@ -747,14 +747,6 @@
|
||||
"resolved": "7.0.5",
|
||||
"contentHash": "yMLM/aK1MikVqpjxd7PJ1Pjgztd3VAd26ZHxyjxG3RPeM9cHjvS5tCg9kAAayR6eHmBg0ffZsHdT28WfA5tTlA=="
|
||||
},
|
||||
"Microsoft.EntityFrameworkCore.InMemory": {
|
||||
"type": "Transitive",
|
||||
"resolved": "7.0.5",
|
||||
"contentHash": "y3S/A/0uJX7KOhppC3xqyta6Z0PRz0qPLngH5GFu4GZ7/+Sw2u/amf7MavvR5GfZjGabGcohMpsRSahMmpF9gA==",
|
||||
"dependencies": {
|
||||
"Microsoft.EntityFrameworkCore": "7.0.5"
|
||||
}
|
||||
},
|
||||
"Microsoft.EntityFrameworkCore.Relational": {
|
||||
"type": "Transitive",
|
||||
"resolved": "7.0.5",
|
||||
@@ -3255,7 +3247,6 @@
|
||||
"Common": "[2023.9.0, )",
|
||||
"Identity": "[2023.9.0, )",
|
||||
"Microsoft.AspNetCore.Mvc.Testing": "[6.0.5, )",
|
||||
"Microsoft.EntityFrameworkCore.InMemory": "[7.0.5, )",
|
||||
"Microsoft.Extensions.Configuration": "[6.0.1, )"
|
||||
}
|
||||
},
|
||||
|
||||
@@ -26,4 +26,8 @@
|
||||
<ProjectReference Include="..\Core.Test\Core.Test.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Auth\" />
|
||||
<Folder Include="Auth\Controllers\" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
143
test/Api.Test/Auth/Controllers/WebAuthnControllerTests.cs
Normal file
143
test/Api.Test/Auth/Controllers/WebAuthnControllerTests.cs
Normal file
@@ -0,0 +1,143 @@
|
||||
using Bit.Api.Auth.Controllers;
|
||||
using Bit.Api.Auth.Models.Request.Accounts;
|
||||
using Bit.Api.Auth.Models.Request.Webauthn;
|
||||
using Bit.Core.Auth.Models.Business.Tokenables;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Tokens;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using Fido2NetLib;
|
||||
using NSubstitute;
|
||||
using NSubstitute.ReturnsExtensions;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Api.Test.Auth.Controllers;
|
||||
|
||||
[ControllerCustomize(typeof(WebAuthnController))]
|
||||
[SutProviderCustomize]
|
||||
public class WebAuthnControllerTests
|
||||
{
|
||||
[Theory, BitAutoData]
|
||||
public async Task Get_UserNotFound_ThrowsUnauthorizedAccessException(SutProvider<WebAuthnController> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
sutProvider.GetDependency<IUserService>().GetUserByPrincipalAsync(default).ReturnsNullForAnyArgs();
|
||||
|
||||
// Act
|
||||
var result = () => sutProvider.Sut.Get();
|
||||
|
||||
// Assert
|
||||
await Assert.ThrowsAsync<UnauthorizedAccessException>(result);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task PostOptions_UserNotFound_ThrowsUnauthorizedAccessException(SecretVerificationRequestModel requestModel, SutProvider<WebAuthnController> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
sutProvider.GetDependency<IUserService>().GetUserByPrincipalAsync(default).ReturnsNullForAnyArgs();
|
||||
|
||||
// Act
|
||||
var result = () => sutProvider.Sut.PostOptions(requestModel);
|
||||
|
||||
// Assert
|
||||
await Assert.ThrowsAsync<UnauthorizedAccessException>(result);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task PostOptions_UserVerificationFailed_ThrowsBadRequestException(SecretVerificationRequestModel requestModel, User user, SutProvider<WebAuthnController> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
sutProvider.GetDependency<IUserService>().GetUserByPrincipalAsync(default).ReturnsForAnyArgs(user);
|
||||
sutProvider.GetDependency<IUserService>().VerifySecretAsync(user, default).Returns(false);
|
||||
|
||||
// Act
|
||||
var result = () => sutProvider.Sut.PostOptions(requestModel);
|
||||
|
||||
// Assert
|
||||
await Assert.ThrowsAsync<BadRequestException>(result);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task Post_UserNotFound_ThrowsUnauthorizedAccessException(WebAuthnCredentialRequestModel requestModel, SutProvider<WebAuthnController> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
sutProvider.GetDependency<IUserService>().GetUserByPrincipalAsync(default).ReturnsNullForAnyArgs();
|
||||
|
||||
// Act
|
||||
var result = () => sutProvider.Sut.Post(requestModel);
|
||||
|
||||
// Assert
|
||||
await Assert.ThrowsAsync<UnauthorizedAccessException>(result);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task Post_ExpiredToken_ThrowsBadRequestException(WebAuthnCredentialRequestModel requestModel, CredentialCreateOptions createOptions, User user, SutProvider<WebAuthnController> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
var token = new WebAuthnCredentialCreateOptionsTokenable(user, createOptions);
|
||||
sutProvider.GetDependency<IUserService>()
|
||||
.GetUserByPrincipalAsync(default)
|
||||
.ReturnsForAnyArgs(user);
|
||||
sutProvider.GetDependency<IDataProtectorTokenFactory<WebAuthnCredentialCreateOptionsTokenable>>()
|
||||
.Unprotect(requestModel.Token)
|
||||
.Returns(token);
|
||||
|
||||
// Act
|
||||
var result = () => sutProvider.Sut.Post(requestModel);
|
||||
|
||||
// Assert
|
||||
await Assert.ThrowsAsync<BadRequestException>(result);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task Post_ValidInput_Returns(WebAuthnCredentialRequestModel requestModel, CredentialCreateOptions createOptions, User user, SutProvider<WebAuthnController> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
var token = new WebAuthnCredentialCreateOptionsTokenable(user, createOptions);
|
||||
sutProvider.GetDependency<IUserService>()
|
||||
.GetUserByPrincipalAsync(default)
|
||||
.ReturnsForAnyArgs(user);
|
||||
sutProvider.GetDependency<IUserService>()
|
||||
.CompleteWebAuthLoginRegistrationAsync(user, requestModel.Name, createOptions, Arg.Any<AuthenticatorAttestationRawResponse>())
|
||||
.Returns(true);
|
||||
sutProvider.GetDependency<IDataProtectorTokenFactory<WebAuthnCredentialCreateOptionsTokenable>>()
|
||||
.Unprotect(requestModel.Token)
|
||||
.Returns(token);
|
||||
|
||||
// Act
|
||||
await sutProvider.Sut.Post(requestModel);
|
||||
|
||||
// Assert
|
||||
// Nothing to assert since return is void
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task Delete_UserNotFound_ThrowsUnauthorizedAccessException(Guid credentialId, SecretVerificationRequestModel requestModel, SutProvider<WebAuthnController> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
sutProvider.GetDependency<IUserService>().GetUserByPrincipalAsync(default).ReturnsNullForAnyArgs();
|
||||
|
||||
// Act
|
||||
var result = () => sutProvider.Sut.Delete(credentialId, requestModel);
|
||||
|
||||
// Assert
|
||||
await Assert.ThrowsAsync<UnauthorizedAccessException>(result);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task Delete_UserVerificationFailed_ThrowsBadRequestException(Guid credentialId, SecretVerificationRequestModel requestModel, User user, SutProvider<WebAuthnController> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
sutProvider.GetDependency<IUserService>().GetUserByPrincipalAsync(default).ReturnsForAnyArgs(user);
|
||||
sutProvider.GetDependency<IUserService>().VerifySecretAsync(user, default).Returns(false);
|
||||
|
||||
// Act
|
||||
var result = () => sutProvider.Sut.Delete(credentialId, requestModel);
|
||||
|
||||
// Assert
|
||||
await Assert.ThrowsAsync<BadRequestException>(result);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
using Bit.Core.Auth.Models.Business.Tokenables;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using Fido2NetLib;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Core.Test.Auth.Models.Business.Tokenables;
|
||||
|
||||
public class WebAuthnCredentialCreateOptionsTokenableTests
|
||||
{
|
||||
[Theory, BitAutoData]
|
||||
public void Valid_TokenWithoutUser_ReturnsFalse(CredentialCreateOptions createOptions)
|
||||
{
|
||||
var token = new WebAuthnCredentialCreateOptionsTokenable(null, createOptions);
|
||||
|
||||
var isValid = token.Valid;
|
||||
|
||||
Assert.False(isValid);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public void Valid_TokenWithoutOptions_ReturnsFalse(User user)
|
||||
{
|
||||
var token = new WebAuthnCredentialCreateOptionsTokenable(user, null);
|
||||
|
||||
var isValid = token.Valid;
|
||||
|
||||
Assert.False(isValid);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public void Valid_NewlyCreatedToken_ReturnsTrue(User user, CredentialCreateOptions createOptions)
|
||||
{
|
||||
var token = new WebAuthnCredentialCreateOptionsTokenable(user, createOptions);
|
||||
|
||||
var isValid = token.Valid;
|
||||
|
||||
Assert.True(isValid);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public void ValidIsValid_TokenWithoutUser_ReturnsFalse(User user, CredentialCreateOptions createOptions)
|
||||
{
|
||||
var token = new WebAuthnCredentialCreateOptionsTokenable(null, createOptions);
|
||||
|
||||
var isValid = token.TokenIsValid(user);
|
||||
|
||||
Assert.False(isValid);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public void ValidIsValid_TokenWithoutOptions_ReturnsFalse(User user)
|
||||
{
|
||||
var token = new WebAuthnCredentialCreateOptionsTokenable(user, null);
|
||||
|
||||
var isValid = token.TokenIsValid(user);
|
||||
|
||||
Assert.False(isValid);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public void ValidIsValid_NonMatchingUsers_ReturnsFalse(User user1, User user2, CredentialCreateOptions createOptions)
|
||||
{
|
||||
var token = new WebAuthnCredentialCreateOptionsTokenable(user1, createOptions);
|
||||
|
||||
var isValid = token.TokenIsValid(user2);
|
||||
|
||||
Assert.False(isValid);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public void ValidIsValid_SameUser_ReturnsTrue(User user, CredentialCreateOptions createOptions)
|
||||
{
|
||||
var token = new WebAuthnCredentialCreateOptionsTokenable(user, createOptions);
|
||||
|
||||
var isValid = token.TokenIsValid(user);
|
||||
|
||||
Assert.True(isValid);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
using System.Text.Json;
|
||||
using AutoFixture;
|
||||
using Bit.Core.AdminConsole.Repositories;
|
||||
using Bit.Core.Auth.Entities;
|
||||
using Bit.Core.Auth.Enums;
|
||||
using Bit.Core.Auth.Models;
|
||||
using Bit.Core.Auth.Models.Business.Tokenables;
|
||||
using Bit.Core.Auth.Repositories;
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Models.Business;
|
||||
@@ -9,6 +13,7 @@ using Bit.Core.Models.Data.Organizations;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Settings;
|
||||
using Bit.Core.Tokens;
|
||||
using Bit.Core.Tools.Services;
|
||||
using Bit.Core.Vault.Repositories;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
@@ -180,6 +185,21 @@ public class UserServiceTests
|
||||
Assert.True(await sutProvider.Sut.HasPremiumFromOrganization(user));
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async void CompleteWebAuthLoginRegistrationAsync_ExceedsExistingCredentialsLimit_ReturnsFalse(SutProvider<UserService> sutProvider, User user, CredentialCreateOptions options, AuthenticatorAttestationRawResponse response, Generator<WebAuthnCredential> credentialGenerator)
|
||||
{
|
||||
// Arrange
|
||||
var existingCredentials = credentialGenerator.Take(5).ToList();
|
||||
sutProvider.GetDependency<IWebAuthnCredentialRepository>().GetManyByUserIdAsync(user.Id).Returns(existingCredentials);
|
||||
|
||||
// Act
|
||||
var result = await sutProvider.Sut.CompleteWebAuthLoginRegistrationAsync(user, "name", options, response);
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
sutProvider.GetDependency<IWebAuthnCredentialRepository>().DidNotReceive();
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum ShouldCheck
|
||||
{
|
||||
@@ -254,7 +274,10 @@ public class UserServiceTests
|
||||
sutProvider.GetDependency<IGlobalSettings>(),
|
||||
sutProvider.GetDependency<IOrganizationService>(),
|
||||
sutProvider.GetDependency<IProviderUserRepository>(),
|
||||
sutProvider.GetDependency<IStripeSyncService>());
|
||||
sutProvider.GetDependency<IStripeSyncService>(),
|
||||
sutProvider.GetDependency<IWebAuthnCredentialRepository>(),
|
||||
sutProvider.GetDependency<IDataProtectorTokenFactory<WebAuthnLoginTokenable>>()
|
||||
);
|
||||
|
||||
var actualIsVerified = await sut.VerifySecretAsync(user, secret);
|
||||
|
||||
|
||||
@@ -655,14 +655,6 @@
|
||||
"resolved": "7.0.5",
|
||||
"contentHash": "yMLM/aK1MikVqpjxd7PJ1Pjgztd3VAd26ZHxyjxG3RPeM9cHjvS5tCg9kAAayR6eHmBg0ffZsHdT28WfA5tTlA=="
|
||||
},
|
||||
"Microsoft.EntityFrameworkCore.InMemory": {
|
||||
"type": "Transitive",
|
||||
"resolved": "7.0.5",
|
||||
"contentHash": "y3S/A/0uJX7KOhppC3xqyta6Z0PRz0qPLngH5GFu4GZ7/+Sw2u/amf7MavvR5GfZjGabGcohMpsRSahMmpF9gA==",
|
||||
"dependencies": {
|
||||
"Microsoft.EntityFrameworkCore": "7.0.5"
|
||||
}
|
||||
},
|
||||
"Microsoft.EntityFrameworkCore.Relational": {
|
||||
"type": "Transitive",
|
||||
"resolved": "7.0.5",
|
||||
@@ -3072,7 +3064,6 @@
|
||||
"Common": "[2023.9.0, )",
|
||||
"Identity": "[2023.9.0, )",
|
||||
"Microsoft.AspNetCore.Mvc.Testing": "[6.0.5, )",
|
||||
"Microsoft.EntityFrameworkCore.InMemory": "[7.0.5, )",
|
||||
"Microsoft.Extensions.Configuration": "[6.0.1, )"
|
||||
}
|
||||
},
|
||||
|
||||
@@ -7,6 +7,7 @@ using Bit.Infrastructure.EntityFramework.Repositories;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Mvc.Testing;
|
||||
using Microsoft.AspNetCore.TestHost;
|
||||
using Microsoft.Data.Sqlite;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
@@ -19,7 +20,6 @@ namespace Bit.IntegrationTestCommon.Factories;
|
||||
|
||||
public static class FactoryConstants
|
||||
{
|
||||
public const string DefaultDatabaseName = "test_database";
|
||||
public const string WhitelistedIp = "1.1.1.1";
|
||||
}
|
||||
|
||||
@@ -27,14 +27,16 @@ public abstract class WebApplicationFactoryBase<T> : WebApplicationFactory<T>
|
||||
where T : class
|
||||
{
|
||||
/// <summary>
|
||||
/// The database name to use for this instance of the factory. By default it will use a shared database name so all instances will connect to the same database during it's lifetime.
|
||||
/// The database to use for this instance of the factory. By default it will use a shared database so all instances will connect to the same database during it's lifetime.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This will need to be set BEFORE using the <c>Server</c> property
|
||||
/// </remarks>
|
||||
public string DatabaseName { get; set; } = Guid.NewGuid().ToString();
|
||||
public SqliteConnection SqliteConnection { get; set; }
|
||||
|
||||
private readonly List<Action<IServiceCollection>> _configureTestServices = new();
|
||||
private bool _handleSqliteDisposal { get; set; }
|
||||
|
||||
|
||||
public void SubstitueService<TService>(Action<TService> mockService)
|
||||
where TService : class
|
||||
@@ -52,10 +54,17 @@ public abstract class WebApplicationFactoryBase<T> : WebApplicationFactory<T>
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Configure the web host to use an EF in memory database
|
||||
/// Configure the web host to use a SQLite in memory database
|
||||
/// </summary>
|
||||
protected override void ConfigureWebHost(IWebHostBuilder builder)
|
||||
{
|
||||
if (SqliteConnection == null)
|
||||
{
|
||||
SqliteConnection = new SqliteConnection("DataSource=:memory:");
|
||||
SqliteConnection.Open();
|
||||
_handleSqliteDisposal = true;
|
||||
}
|
||||
|
||||
builder.ConfigureAppConfiguration(c =>
|
||||
{
|
||||
c.SetBasePath(AppContext.BaseDirectory)
|
||||
@@ -89,11 +98,13 @@ public abstract class WebApplicationFactoryBase<T> : WebApplicationFactory<T>
|
||||
services.AddScoped(services =>
|
||||
{
|
||||
return new DbContextOptionsBuilder<DatabaseContext>()
|
||||
.UseInMemoryDatabase(DatabaseName)
|
||||
.UseSqlite(SqliteConnection)
|
||||
.UseApplicationServiceProvider(services)
|
||||
.Options;
|
||||
});
|
||||
|
||||
MigrateDbContext<DatabaseContext>(services);
|
||||
|
||||
// QUESTION: The normal licensing service should run fine on developer machines but not in CI
|
||||
// should we have a fork here to leave the normal service for developers?
|
||||
// TODO: Eventually add the license file to CI
|
||||
@@ -182,4 +193,23 @@ public abstract class WebApplicationFactoryBase<T> : WebApplicationFactory<T>
|
||||
var scope = Services.CreateScope();
|
||||
return scope.ServiceProvider.GetRequiredService<TS>();
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
if (_handleSqliteDisposal)
|
||||
{
|
||||
SqliteConnection.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private static void MigrateDbContext<TContext>(IServiceCollection serviceCollection) where TContext : DbContext
|
||||
{
|
||||
var serviceProvider = serviceCollection.BuildServiceProvider();
|
||||
using var scope = serviceProvider.CreateScope();
|
||||
var services = scope.ServiceProvider;
|
||||
var context = services.GetService<TContext>();
|
||||
context.Database.EnsureDeleted();
|
||||
context.Database.EnsureCreated();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="6.0.5" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="7.0.5" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration" Version="6.0.1" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -13,15 +13,6 @@
|
||||
"Microsoft.Extensions.Hosting": "6.0.1"
|
||||
}
|
||||
},
|
||||
"Microsoft.EntityFrameworkCore.InMemory": {
|
||||
"type": "Direct",
|
||||
"requested": "[7.0.5, )",
|
||||
"resolved": "7.0.5",
|
||||
"contentHash": "y3S/A/0uJX7KOhppC3xqyta6Z0PRz0qPLngH5GFu4GZ7/+Sw2u/amf7MavvR5GfZjGabGcohMpsRSahMmpF9gA==",
|
||||
"dependencies": {
|
||||
"Microsoft.EntityFrameworkCore": "7.0.5"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.Configuration": {
|
||||
"type": "Direct",
|
||||
"requested": "[6.0.1, )",
|
||||
|
||||
Reference in New Issue
Block a user