1
0
mirror of https://github.com/bitwarden/server.git synced 2024-11-25 12:45:18 +01:00
bitwarden-server/test/IntegrationTestCommon/Factories/WebApplicationFactoryBase.cs

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

291 lines
12 KiB
C#
Raw Permalink Normal View History

using AspNetCoreRateLimit;
[PM-1188] Server owner auth migration (#2825) * [PM-1188] add sso project to auth * [PM-1188] move sso api models to auth * [PM-1188] fix sso api model namespace & imports * [PM-1188] move core files to auth * [PM-1188] fix core sso namespace & models * [PM-1188] move sso repository files to auth * [PM-1188] fix sso repo files namespace & imports * [PM-1188] move sso sql files to auth folder * [PM-1188] move sso test files to auth folders * [PM-1188] fix sso tests namespace & imports * [PM-1188] move auth api files to auth folder * [PM-1188] fix auth api files namespace & imports * [PM-1188] move auth core files to auth folder * [PM-1188] fix auth core files namespace & imports * [PM-1188] move auth email templates to auth folder * [PM-1188] move auth email folder back into shared directory * [PM-1188] fix auth email names * [PM-1188] move auth core models to auth folder * [PM-1188] fix auth model namespace & imports * [PM-1188] add entire Identity project to auth codeowners * [PM-1188] fix auth orm files namespace & imports * [PM-1188] move auth orm files to auth folder * [PM-1188] move auth sql files to auth folder * [PM-1188] move auth tests to auth folder * [PM-1188] fix auth test files namespace & imports * [PM-1188] move emergency access api files to auth folder * [PM-1188] fix emergencyaccess api files namespace & imports * [PM-1188] move emergency access core files to auth folder * [PM-1188] fix emergency access core files namespace & imports * [PM-1188] move emergency access orm files to auth folder * [PM-1188] fix emergency access orm files namespace & imports * [PM-1188] move emergency access sql files to auth folder * [PM-1188] move emergencyaccess test files to auth folder * [PM-1188] fix emergency access test files namespace & imports * [PM-1188] move captcha files to auth folder * [PM-1188] fix captcha files namespace & imports * [PM-1188] move auth admin files into auth folder * [PM-1188] fix admin auth files namespace & imports - configure mvc to look in auth folders for views * [PM-1188] remove extra imports and formatting * [PM-1188] fix ef auth model imports * [PM-1188] fix DatabaseContextModelSnapshot paths * [PM-1188] fix grant import in ef * [PM-1188] update sqlproj * [PM-1188] move missed sqlproj files * [PM-1188] move auth ef models out of auth folder * [PM-1188] fix auth ef models namespace * [PM-1188] remove auth ef models unused imports * [PM-1188] fix imports for auth ef models * [PM-1188] fix more ef model imports * [PM-1188] fix file encodings
2023-04-14 19:25:56 +02:00
using Bit.Core.Auth.Services;
using Bit.Core.Repositories;
using Bit.Core.Services;
using Bit.Core.Tools.Services;
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;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using NSubstitute;
2023-02-13 18:10:53 +01:00
using NoopRepos = Bit.Core.Repositories.Noop;
#nullable enable
namespace Bit.IntegrationTestCommon.Factories;
2022-08-29 22:06:55 +02:00
public static class FactoryConstants
{
public const string WhitelistedIp = "1.1.1.1";
}
public abstract class WebApplicationFactoryBase<T> : WebApplicationFactory<T>
where T : class
2022-08-29 20:53:16 +02:00
{
/// <summary>
/// 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 SqliteConnection? SqliteConnection { get; set; }
private readonly List<Action<IServiceCollection>> _configureTestServices = new();
private readonly List<Action<IConfigurationBuilder>> _configureAppConfiguration = new();
private bool _handleSqliteDisposal { get; set; }
Auth/PM-7322 - Registration with Email verification - Finish registration endpoint (#4182) * PM-7322 - AccountsController.cs - create empty method + empty req model to be able to create draft PR. * PM-7322 - Start on RegisterFinishRequestModel.cs * PM-7322 - WIP on Complete Registration endpoint * PM-7322 - UserService.cs - RegisterUserAsync - Tweak of token to be orgInviteToken as we are adding a new email verification token to the mix. * PM-7322 - UserService - Rename MP to MPHash * PM-7322 - More WIP progress on getting new finish registration process in place. * PM-7322 Create IRegisterUserCommand * PM-7322 - RegisterUserCommand.cs - first WIP draft * PM-7322 - Implement use of new command in Identity. * PM-7322 - Rename RegisterUserViaOrgInvite to just be RegisterUser as orgInvite is optional. * PM07322 - Test RegisterUserCommand.RegisterUser(...) happy paths and one bad request path. * PM-7322 - More WIP on RegisterUserCommand.cs and tests * PM-7322 - RegisterUserCommand.cs - refactor ValidateOrgInviteToken logic to always validate the token if we have one. * PM-7322 - RegisterUserCommand.cs - Refactor OrgInviteToken validation to be more clear + validate org invite token even in open registration scenarios + added tests. * PM-7322 - Add more test coverage to RegisterUserWithOptionalOrgInvite * PM-7322 - IRegisterUserCommand - DOCS * PM-7322 - Test RegisterUser * PM-7322 - IRegisterUserCommand - Add more docs. * PM-7322 - Finish updating all existing user service register calls to use the new command. * PM-7322 - RegistrationEmailVerificationTokenable.cs changes + tests * PM-7322 - RegistrationEmailVerificationTokenable.cs changed to only verify email as it's the only thing we need to verify + updated tests. * PM-7322 - Get RegisterUserViaEmailVerificationToken built and tested * PM-7322 - AccountsController.cs - get bones of PostRegisterFinish in place * PM-7322 - SendVerificationEmailForRegistrationCommand - Feature flag timing attack delays per architecture discussion with a default of keeping them around. * PM-7322 - RegisterFinishRequestModel.cs - EmailVerificationToken must be optional for org invite scenarios. * PM-7322 - HandlebarsMailService.cs - SendRegistrationVerificationEmailAsync - must URL encode email to avoid invalid email upon submission to server on complete registration step * PM-7322 - RegisterUserCommandTests.cs - add API key assertions * PM-7322 - Clean up RegisterUserCommand.cs * PM-7322 - Refactor AccountsController.cs existing org invite method and new process to consider new feature flag for delays. * PM-7322 - Add feature flag svc to AccountsControllerTests.cs + add TODO * PM-7322 - AccountsController.cs - Refactor shared IdentityResult logic into private helper. * PM-7322 - Work on getting PostRegisterFinish tests in place. * PM-7322 - AccountsControllerTests.cs - test new method. * PM-7322 - RegisterFinishRequestModel.cs - Update to use required keyword instead of required annotations as it is easier to catch mistakes. * PM-7322 - Fix misspelling * PM-7322 - Integration tests for RegistrationWithEmailVerification * PM-7322 - Fix leaky integration tests. * PM-7322 - Another leaky test fix. * PM-7322 - AccountsControllerTests.cs - fix RegistrationWithEmailVerification_WithOrgInviteToken_Succeeds * PM-7322 - AccountsControllerTests.cs - Finish out integration test suite!
2024-07-02 23:03:36 +02:00
public void SubstituteService<TService>(Action<TService> mockService)
where TService : class
{
_configureTestServices.Add(services =>
{
var foundServiceDescriptor = services.FirstOrDefault(sd => sd.ServiceType == typeof(TService))
?? throw new InvalidOperationException($"Could not find service of type {typeof(TService).FullName} to substitute");
services.Remove(foundServiceDescriptor);
var substitutedService = Substitute.For<TService>();
mockService(substitutedService);
services.Add(ServiceDescriptor.Singleton(typeof(TService), substitutedService));
});
}
/// <summary>
/// Allows you to add your own services to the application as required.
/// </summary>
/// <param name="configure">The service collection you want added to the test service collection.</param>
/// <remarks>This needs to be ran BEFORE making any calls through the factory to take effect.</remarks>
public void ConfigureServices(Action<IServiceCollection> configure)
{
_configureTestServices.Add(configure);
}
/// <summary>
/// Add your own configuration provider to the application.
/// </summary>
/// <param name="configure">The action adding your own providers.</param>
/// <remarks>This needs to be ran BEFORE making any calls through the factory to take effect.</remarks>
/// <example>
/// <code lang="C#">
/// factory.UpdateConfiguration(builder =&gt;
/// {
/// builder.AddInMemoryCollection(new Dictionary&lt;string, string?&gt;
/// {
/// { "globalSettings:attachment:connectionString", null},
/// { "globalSettings:events:connectionString", null},
/// })
/// })
/// </code>
/// </example>
public void UpdateConfiguration(Action<IConfigurationBuilder> configure)
{
_configureAppConfiguration.Add(configure);
}
/// <summary>
/// Updates a single configuration entry for multiple entries at once use <see cref="UpdateConfiguration(Action{IConfigurationBuilder})"/>.
/// </summary>
/// <param name="key">The fully qualified name of the setting, using <c>:</c> as delimiter between sections.</param>
/// <param name="value">The value of the setting.</param>
/// <remarks>This needs to be ran BEFORE making any calls through the factory to take effect.</remarks>
/// <example>
/// <code lang="C#">
/// factory.UpdateConfiguration("globalSettings:attachment:connectionString", null);
/// </code>
/// </example>
public void UpdateConfiguration(string key, string? value)
{
_configureAppConfiguration.Add(builder =>
{
builder.AddInMemoryCollection(new Dictionary<string, string?>
{
{ key, value },
});
});
}
/// <summary>
/// Configure the web host to use a SQLite in memory database
/// </summary>
protected override void ConfigureWebHost(IWebHostBuilder builder)
2022-08-29 22:06:55 +02:00
{
if (SqliteConnection == null)
{
SqliteConnection = new SqliteConnection("DataSource=:memory:");
SqliteConnection.Open();
_handleSqliteDisposal = true;
}
builder.ConfigureAppConfiguration(c =>
{
c.SetBasePath(AppContext.BaseDirectory)
.AddJsonFile("appsettings.json")
.AddJsonFile("appsettings.Development.json");
c.AddUserSecrets(typeof(Identity.Startup).Assembly, optional: true);
Auth/PM-5092 - Registration with Email verification - Send Email Verification Endpoint (#4173) * PM-5092 - Add new EnableEmailVerification global setting. * PM-5092 - WIP - AccountsController.cs - create stub for new PostRegisterSendEmailVerification * PM-5092 - RegisterSendEmailVerificationRequestModel * PM-5092 - Create EmailVerificationTokenable.cs and get started on tests (still WIP). * PM-5092 - EmailVerificationTokenable.cs finished + tests working. * PM-5092 - Add token data factory for new EmailVerificationTokenable factory. * PM-5092 - EmailVerificationTokenable.cs - set expiration to match existing verify email. * PM-5092 - Get SendVerificationEmailForRegistrationCommand command mostly written + register as scoped. * PM-5092 - Rename tokenable to be more clear and differentiate it from the existing email verification token. * PM-5092 - Add new registration verify email method on mail service. * PM-5092 - Refactor SendVerificationEmailForRegistrationCommand and add call to mail service to send email. * PM-5092 - NoopMailService.cs needs to implement all interface methods. * PM-5092 - AccountsController.cs - get PostRegisterSendEmailVerification logic in place. * PM-5092 - AccountsControllerTests.cs - Add some unit tests - WIP * PM-5092 - SendVerificationEmailForRegistrationCommandTests * PM-5092 - Add integration tests for new acct controller method * PM-5092 - Cleanup unit tests * PM-5092 - AccountsController.cs - PostRegisterSendEmailVerification - remove modelState invalid check as .NET literally executes this validation pre-method execution. * PM-5092 - Rename to read better - send verification email > send email verification * PM-5092 - Revert primary constructor approach so DI works. * PM-5092 - (1) Cleanup new but now not needed global setting (2) Add custom email for registration verify email. * PM-5092 - Fix email text * PM-5092 - (1) Modify ReferenceEvent.cs to allow nullable values for the 2 params which should have been nullable based on the constructor logic (2) Add new ReferenceEventType.cs for email verification register submit (3) Update AccountsController.cs to log new reference event (4) Update tests * PM-5092 - RegistrationEmailVerificationTokenable - update prefix, purpose, and token id to include registration to differentiate it from the existing email verification token. * PM-5092 - Per PR feedback, cleanup used dict. * PM-5092 - formatting pass (manual + dotnet format) * PM-5092 - Per PR feedback, log reference event after core business logic executes * PM-5092 - Per PR feedback, add validation + added nullable flag to name as it is optional. * PM-5092 - Per PR feedback, add constructor validation for required tokenable data * PM-5092 - RegisterVerifyEmail url now contains email as that is required in client side registration step to create a master key. * PM-5092 - Add fromEmail flag + some docs * PM-5092 - ReferenceEvent.cs - Per PR feedback, make SignupInitiationPath and PlanUpgradePath nullable * PM-5092 - ReferenceEvent.cs - remove nullability per PR feedback * PM-5092 - Per PR feedback, use default constructor and manually create reference event. * PM-5092 - Per PR feedback, add more docs!
2024-06-19 19:54:20 +02:00
c.AddInMemoryCollection(new Dictionary<string, string?>
{
// Manually insert a EF provider so that ConfigureServices will add EF repositories but we will override
// DbContextOptions to use an in memory database
{ "globalSettings:databaseProvider", "postgres" },
{ "globalSettings:postgreSql:connectionString", "Host=localhost;Username=test;Password=test;Database=test" },
[PS-93] Distributed Ip rate limiting (#2060) * Upgrade AspNetCoreRateLimiter and enable redis distributed cache for rate limiting. - Upgrades AspNetCoreRateLimiter to 4.0.2, which required updating NewtonSoft.Json to 13.0.1. - Replaces Microsoft.Extensions.Caching.Redis with Microsoft.Extensions.Caching.StackExchangeRedis as the original was deprecated and conflicted with the latest AspNetCoreRateLimiter - Adds startup task to Program.cs for Api/Identity projects to support AspNetCoreRateLimiters breaking changes for seeding its stores. - Adds a Redis connection string option to GlobalSettings Signed-off-by: Shane Melton <smelton@bitwarden.com> * Cleanup Redis distributed cache registration - Add new AddDistributedCache service collection extension to add either a Memory or Redis distributed cache. - Remove distributed cache registration from Identity service collection extension. - Add IpRateLimitSeedStartupService.cs to run at application startup to seed the Ip rate limiting policies. Signed-off-by: Shane Melton <smelton@bitwarden.com> * Add caching configuration to SSO Startup.cs Signed-off-by: Shane Melton <smelton@bitwarden.com> * Add ProjectName as an instance name for Redis options Signed-off-by: Shane Melton <smelton@bitwarden.com> * Use distributed cache in CustomIpRateLimitMiddleware.cs Signed-off-by: Shane Melton <smelton@bitwarden.com> * Undo changes to Program.cs and launchSettings.json * Move new service collection extensions to SharedWeb * Upgrade Caching.StackExchangeRedis package to v6 * Cleanup and fix leftover merge conflicts * Remove use of Newtonsoft.Json in distributed cache extensions * Cleanup more formatting * Fix formatting * Fix startup issue caused by merge and fix integration test Signed-off-by: Shane Melton <smelton@bitwarden.com> * Linting fix Signed-off-by: Shane Melton <smelton@bitwarden.com>
2022-07-19 20:58:32 +02:00
// Clear the redis connection string for distributed caching, forcing an in-memory implementation
2023-02-13 18:10:53 +01:00
{ "globalSettings:redis:connectionString", ""},
// Clear Storage
{ "globalSettings:attachment:connectionString", null},
{ "globalSettings:events:connectionString", null},
{ "globalSettings:send:connectionString", null},
{ "globalSettings:notifications:connectionString", null},
{ "globalSettings:storage:connectionString", null},
// This will force it to use an ephemeral key for IdentityServer
Auth/PM-5092 - Registration with Email verification - Send Email Verification Endpoint (#4173) * PM-5092 - Add new EnableEmailVerification global setting. * PM-5092 - WIP - AccountsController.cs - create stub for new PostRegisterSendEmailVerification * PM-5092 - RegisterSendEmailVerificationRequestModel * PM-5092 - Create EmailVerificationTokenable.cs and get started on tests (still WIP). * PM-5092 - EmailVerificationTokenable.cs finished + tests working. * PM-5092 - Add token data factory for new EmailVerificationTokenable factory. * PM-5092 - EmailVerificationTokenable.cs - set expiration to match existing verify email. * PM-5092 - Get SendVerificationEmailForRegistrationCommand command mostly written + register as scoped. * PM-5092 - Rename tokenable to be more clear and differentiate it from the existing email verification token. * PM-5092 - Add new registration verify email method on mail service. * PM-5092 - Refactor SendVerificationEmailForRegistrationCommand and add call to mail service to send email. * PM-5092 - NoopMailService.cs needs to implement all interface methods. * PM-5092 - AccountsController.cs - get PostRegisterSendEmailVerification logic in place. * PM-5092 - AccountsControllerTests.cs - Add some unit tests - WIP * PM-5092 - SendVerificationEmailForRegistrationCommandTests * PM-5092 - Add integration tests for new acct controller method * PM-5092 - Cleanup unit tests * PM-5092 - AccountsController.cs - PostRegisterSendEmailVerification - remove modelState invalid check as .NET literally executes this validation pre-method execution. * PM-5092 - Rename to read better - send verification email > send email verification * PM-5092 - Revert primary constructor approach so DI works. * PM-5092 - (1) Cleanup new but now not needed global setting (2) Add custom email for registration verify email. * PM-5092 - Fix email text * PM-5092 - (1) Modify ReferenceEvent.cs to allow nullable values for the 2 params which should have been nullable based on the constructor logic (2) Add new ReferenceEventType.cs for email verification register submit (3) Update AccountsController.cs to log new reference event (4) Update tests * PM-5092 - RegistrationEmailVerificationTokenable - update prefix, purpose, and token id to include registration to differentiate it from the existing email verification token. * PM-5092 - Per PR feedback, cleanup used dict. * PM-5092 - formatting pass (manual + dotnet format) * PM-5092 - Per PR feedback, log reference event after core business logic executes * PM-5092 - Per PR feedback, add validation + added nullable flag to name as it is optional. * PM-5092 - Per PR feedback, add constructor validation for required tokenable data * PM-5092 - RegisterVerifyEmail url now contains email as that is required in client side registration step to create a master key. * PM-5092 - Add fromEmail flag + some docs * PM-5092 - ReferenceEvent.cs - Per PR feedback, make SignupInitiationPath and PlanUpgradePath nullable * PM-5092 - ReferenceEvent.cs - remove nullability per PR feedback * PM-5092 - Per PR feedback, use default constructor and manually create reference event. * PM-5092 - Per PR feedback, add more docs!
2024-06-19 19:54:20 +02:00
{ "globalSettings:developmentDirectory", null },
// Email Verification
{ "globalSettings:enableEmailVerification", "true" },
{ "globalSettings:disableUserRegistration", "false" },
{ "globalSettings:launchDarkly:flagValues:email-verification", "true" },
// New Device Verification
{ "globalSettings:disableEmailNewDevice", "false" },
});
2022-08-29 22:06:55 +02:00
});
// Run configured actions after defaults to allow them to take precedence
foreach (var configureAppConfiguration in _configureAppConfiguration)
{
builder.ConfigureAppConfiguration(configureAppConfiguration);
}
builder.ConfigureTestServices(services =>
2022-08-29 22:06:55 +02:00
{
var dbContextOptions = services.First(sd => sd.ServiceType == typeof(DbContextOptions<DatabaseContext>));
services.Remove(dbContextOptions);
services.AddScoped(services =>
{
return new DbContextOptionsBuilder<DatabaseContext>()
.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
var licensingService = services.First(sd => sd.ServiceType == typeof(ILicensingService));
services.Remove(licensingService);
services.AddSingleton<ILicensingService, NoopLicensingService>();
// FUTURE CONSIDERATION: Add way to run this self hosted/cloud, for now it is cloud only
var pushRegistrationService = services.First(sd => sd.ServiceType == typeof(IPushRegistrationService));
services.Remove(pushRegistrationService);
services.AddSingleton<IPushRegistrationService, NoopPushRegistrationService>();
// Even though we are cloud we currently set this up as cloud, we can use the EF/selfhosted service
// instead of using Noop for this service
// TODO: Install and use azurite in CI pipeline
var eventWriteService = services.First(sd => sd.ServiceType == typeof(IEventWriteService));
services.Remove(eventWriteService);
services.AddSingleton<IEventWriteService, RepositoryEventWriteService>();
var eventRepositoryService = services.First(sd => sd.ServiceType == typeof(IEventRepository));
services.Remove(eventRepositoryService);
services.AddSingleton<IEventRepository, EventRepository>();
var mailDeliveryService = services.First(sd => sd.ServiceType == typeof(IMailDeliveryService));
services.Remove(mailDeliveryService);
services.AddSingleton<IMailDeliveryService, NoopMailDeliveryService>();
var captchaValidationService = services.First(sd => sd.ServiceType == typeof(ICaptchaValidationService));
services.Remove(captchaValidationService);
services.AddSingleton<ICaptchaValidationService, NoopCaptchaValidationService>();
2023-02-13 18:10:53 +01:00
// TODO: Install and use azurite in CI pipeline
var installationDeviceRepository =
services.First(sd => sd.ServiceType == typeof(IInstallationDeviceRepository));
services.Remove(installationDeviceRepository);
services.AddSingleton<IInstallationDeviceRepository, NoopRepos.InstallationDeviceRepository>();
// TODO: Install and use azurite in CI pipeline
var referenceEventService = services.First(sd => sd.ServiceType == typeof(IReferenceEventService));
services.Remove(referenceEventService);
services.AddSingleton<IReferenceEventService, NoopReferenceEventService>();
// Our Rate limiter works so well that it begins to fail tests unless we carve out
// one whitelisted ip. We should still test the rate limiter though and they should change the Ip
// to something that is NOT whitelisted
services.Configure<IpRateLimitOptions>(options =>
2022-08-29 22:06:55 +02:00
{
options.IpWhitelist = new List<string>
{
FactoryConstants.WhitelistedIp,
};
2022-08-29 20:53:16 +02:00
});
// Fix IP Rate Limiting
services.AddSingleton<IStartupFilter, CustomStartupFilter>();
// Disable logs
services.AddSingleton<ILoggerFactory, NullLoggerFactory>();
// Noop StripePaymentService - this could be changed to integrate with our Stripe test account
var stripePaymentService = services.First(sd => sd.ServiceType == typeof(IPaymentService));
services.Remove(stripePaymentService);
services.AddSingleton(Substitute.For<IPaymentService>());
2022-08-29 22:06:55 +02:00
});
foreach (var configureTestService in _configureTestServices)
{
builder.ConfigureTestServices(configureTestService);
}
2022-08-29 22:06:55 +02:00
}
public DatabaseContext GetDatabaseContext()
{
var scope = Services.CreateScope();
return scope.ServiceProvider.GetRequiredService<DatabaseContext>();
}
public TService GetService<TService>()
where TService : notnull
{
var scope = Services.CreateScope();
return scope.ServiceProvider.GetRequiredService<TService>();
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (_handleSqliteDisposal)
{
SqliteConnection!.Dispose();
}
}
private void MigrateDbContext<TContext>(IServiceCollection serviceCollection) where TContext : DbContext
{
var serviceProvider = serviceCollection.BuildServiceProvider();
using var scope = serviceProvider.CreateScope();
var services = scope.ServiceProvider;
var context = services.GetRequiredService<TContext>();
if (_handleSqliteDisposal)
{
context.Database.EnsureDeleted();
}
context.Database.EnsureCreated();
}
}