mirror of
https://github.com/bitwarden/server.git
synced 2026-01-31 14:13:18 +08:00
We want to reduce the amount of business critical test data in the company. One way of doing that is to generate test data on demand prior to client side testing. Clients will request a scene to be set up with a JSON body set of options, specific to a given scene. Successful seed requests will be responded to with a mangleMap which maps magic strings present in the request to the mangled, non-colliding versions inserted into the database. This way, the server is solely responsible for understanding uniqueness requirements in the database. scenes also are able to return custom data, depending on the scene. For example, user creation would benefit from a return value of the userId for further test setup on the client side. Clients will indicate they are running tests by including a unique header, x-play-id which specifies a unique testing context. The server uses this PlayId as the seed for any mangling that occurs. This allows the client to decide it will reuse a given PlayId if the test context builds on top of previously executed tests. When a given context is no longer needed, the API user will delete all test data associated with the PlayId by calling a delete endpoint. --------- Co-authored-by: Matt Gibson <mgibson@bitwarden.com>
244 lines
8.8 KiB
C#
244 lines
8.8 KiB
C#
using System.Globalization;
|
|
using System.IdentityModel.Tokens.Jwt;
|
|
using AspNetCoreRateLimit;
|
|
using Bit.Core;
|
|
using Bit.Core.Auth.Models.Business.Tokenables;
|
|
using Bit.Core.Billing.Extensions;
|
|
using Bit.Core.Context;
|
|
using Bit.Core.SecretsManager.Repositories;
|
|
using Bit.Core.SecretsManager.Repositories.Noop;
|
|
using Bit.Core.Settings;
|
|
using Bit.Core.Utilities;
|
|
using Bit.Identity.Utilities;
|
|
using Bit.SharedWeb.Swagger;
|
|
using Bit.SharedWeb.Utilities;
|
|
using Duende.IdentityServer.Services;
|
|
using Microsoft.Extensions.DependencyInjection.Extensions;
|
|
using Microsoft.IdentityModel.Logging;
|
|
using Microsoft.OpenApi.Models;
|
|
|
|
namespace Bit.Identity;
|
|
|
|
public class Startup
|
|
{
|
|
public Startup(IWebHostEnvironment env, IConfiguration configuration)
|
|
{
|
|
CultureInfo.DefaultThreadCurrentCulture = new CultureInfo("en-US");
|
|
Configuration = configuration;
|
|
Environment = env;
|
|
}
|
|
|
|
public IConfiguration Configuration { get; private set; }
|
|
public IWebHostEnvironment Environment { get; set; }
|
|
|
|
public void ConfigureServices(IServiceCollection services)
|
|
{
|
|
// Options
|
|
services.AddOptions();
|
|
|
|
// Settings
|
|
var globalSettings = services.AddGlobalSettingsServices(Configuration, Environment);
|
|
if (!globalSettings.SelfHosted)
|
|
{
|
|
services.Configure<IpRateLimitOptions>(Configuration.GetSection("IpRateLimitOptions"));
|
|
services.Configure<IpRateLimitPolicies>(Configuration.GetSection("IpRateLimitPolicies"));
|
|
}
|
|
|
|
// Data Protection
|
|
services.AddCustomDataProtectionServices(Environment, globalSettings);
|
|
|
|
// Repositories
|
|
services.AddDatabaseRepositories(globalSettings);
|
|
services.AddTestPlayIdTracking(globalSettings);
|
|
|
|
// Context
|
|
services.AddScoped<ICurrentContext, CurrentContext>();
|
|
services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
|
|
|
|
// Caching
|
|
services.AddMemoryCache();
|
|
services.AddDistributedCache(globalSettings);
|
|
|
|
// Mvc
|
|
services.AddMvc(config =>
|
|
{
|
|
config.Filters.Add(new ModelStateValidationFilterAttribute());
|
|
});
|
|
|
|
services.AddSwaggerGen(config =>
|
|
{
|
|
config.InitializeSwaggerFilters(Environment);
|
|
|
|
config.SwaggerDoc("v1", new OpenApiInfo { Title = "Bitwarden Identity", Version = "v1" });
|
|
});
|
|
|
|
if (!globalSettings.SelfHosted)
|
|
{
|
|
services.AddIpRateLimiting(globalSettings);
|
|
}
|
|
|
|
// Cookies
|
|
if (Environment.IsDevelopment())
|
|
{
|
|
services.Configure<CookiePolicyOptions>(options =>
|
|
{
|
|
options.MinimumSameSitePolicy = Microsoft.AspNetCore.Http.SameSiteMode.Unspecified;
|
|
options.OnAppendCookie = ctx =>
|
|
{
|
|
ctx.CookieOptions.SameSite = Microsoft.AspNetCore.Http.SameSiteMode.Unspecified;
|
|
};
|
|
});
|
|
}
|
|
|
|
JwtSecurityTokenHandler.DefaultMapInboundClaims = false;
|
|
|
|
// Authentication
|
|
services
|
|
.AddDistributedIdentityServices()
|
|
.AddAuthentication()
|
|
.AddCookie(AuthenticationSchemes.BitwardenExternalCookieAuthenticationScheme)
|
|
.AddOpenIdConnect("sso", "Single Sign On", options =>
|
|
{
|
|
options.Authority = globalSettings.BaseServiceUri.InternalSso;
|
|
options.RequireHttpsMetadata = !Environment.IsDevelopment() &&
|
|
globalSettings.BaseServiceUri.InternalIdentity.StartsWith("https");
|
|
options.ClientId = "oidc-identity";
|
|
options.ClientSecret = globalSettings.OidcIdentityClientKey;
|
|
options.ResponseMode = "form_post";
|
|
|
|
options.SignInScheme = AuthenticationSchemes.BitwardenExternalCookieAuthenticationScheme;
|
|
options.ResponseType = "code";
|
|
options.SaveTokens = false;
|
|
options.GetClaimsFromUserInfoEndpoint = true;
|
|
|
|
// Some browsers (safari) won't allow Secure cookies to be set on a http connection
|
|
options.CorrelationCookie.SecurePolicy = CookieSecurePolicy.SameAsRequest;
|
|
options.NonceCookie.SecurePolicy = CookieSecurePolicy.SameAsRequest;
|
|
|
|
options.Events = new Microsoft.AspNetCore.Authentication.OpenIdConnect.OpenIdConnectEvents
|
|
{
|
|
OnRedirectToIdentityProvider = context =>
|
|
{
|
|
// Pass domain_hint onto the sso idp
|
|
context.ProtocolMessage.DomainHint = context.Properties.Items["domain_hint"];
|
|
context.ProtocolMessage.Parameters.Add("organizationId", context.Properties.Items["organizationId"]);
|
|
if (context.Properties.Items.TryGetValue("user_identifier", out var userIdentifier))
|
|
{
|
|
context.ProtocolMessage.SessionState = userIdentifier;
|
|
}
|
|
|
|
if (context.Properties.Parameters.Count > 0 &&
|
|
context.Properties.Parameters.TryGetValue(SsoTokenable.TokenIdentifier, out var tokenValue))
|
|
{
|
|
var token = tokenValue?.ToString() ?? "";
|
|
context.ProtocolMessage.Parameters.Add(SsoTokenable.TokenIdentifier, token);
|
|
}
|
|
return Task.FromResult(0);
|
|
}
|
|
};
|
|
});
|
|
|
|
// IdentityServer
|
|
services.AddCustomIdentityServerServices(Environment, globalSettings);
|
|
|
|
// Identity
|
|
services.AddCustomIdentityServices(globalSettings);
|
|
|
|
// Services
|
|
services.AddBaseServices(globalSettings);
|
|
services.AddDefaultServices(globalSettings);
|
|
services.AddOptionality();
|
|
services.AddCoreLocalizationServices();
|
|
services.AddBillingOperations();
|
|
|
|
// TODO: Remove when OrganizationUser methods are moved out of OrganizationService, this noop dependency should
|
|
// TODO: no longer be required - see PM-1880
|
|
services.AddScoped<IServiceAccountRepository, NoopServiceAccountRepository>();
|
|
|
|
if (CoreHelpers.SettingHasValue(globalSettings.ServiceBus.ConnectionString) &&
|
|
CoreHelpers.SettingHasValue(globalSettings.ServiceBus.ApplicationCacheTopicName))
|
|
{
|
|
services.AddHostedService<Core.HostedServices.ApplicationCacheHostedService>();
|
|
}
|
|
|
|
// HttpClients
|
|
services.AddHttpClient("InternalSso", client =>
|
|
{
|
|
client.BaseAddress = new Uri(globalSettings.BaseServiceUri.InternalSso);
|
|
});
|
|
}
|
|
|
|
public void Configure(
|
|
IApplicationBuilder app,
|
|
IWebHostEnvironment env,
|
|
GlobalSettings globalSettings,
|
|
ILogger<Startup> logger)
|
|
{
|
|
IdentityModelEventSource.ShowPII = true;
|
|
|
|
// Add general security headers
|
|
app.UseMiddleware<SecurityHeadersMiddleware>();
|
|
|
|
if (!env.IsDevelopment())
|
|
{
|
|
var uri = new Uri(globalSettings.BaseServiceUri.Identity);
|
|
app.Use(async (ctx, next) =>
|
|
{
|
|
ctx.RequestServices.GetRequiredService<IServerUrls>().Origin = $"{uri.Scheme}://{uri.Host}";
|
|
await next();
|
|
});
|
|
}
|
|
|
|
if (globalSettings.SelfHosted)
|
|
{
|
|
app.UsePathBase("/identity");
|
|
app.UseForwardedHeaders(globalSettings);
|
|
}
|
|
|
|
// Default Middleware
|
|
app.UseDefaultMiddleware(env, globalSettings);
|
|
|
|
if (!globalSettings.SelfHosted)
|
|
{
|
|
// Rate limiting
|
|
app.UseMiddleware<CustomIpRateLimitMiddleware>();
|
|
}
|
|
|
|
if (env.IsDevelopment())
|
|
{
|
|
app.UseSwagger();
|
|
app.UseDeveloperExceptionPage();
|
|
app.UseCookiePolicy();
|
|
}
|
|
|
|
// Add localization
|
|
app.UseCoreLocalization();
|
|
|
|
// Add static files to the request pipeline.
|
|
app.UseStaticFiles();
|
|
|
|
// Add routing
|
|
app.UseRouting();
|
|
|
|
// Add Cors
|
|
app.UseCors(policy => policy.SetIsOriginAllowed(o =>
|
|
CoreHelpers.IsCorsOriginAllowed(o, globalSettings) ||
|
|
|
|
// If development - allow requests from the Swagger UI so it can authorize
|
|
(Environment.IsDevelopment() && o == globalSettings.BaseServiceUri.Api))
|
|
.AllowAnyMethod().AllowAnyHeader().AllowCredentials());
|
|
|
|
// Add current context
|
|
app.UseMiddleware<CurrentContextMiddleware>();
|
|
|
|
// Add IdentityServer to the request pipeline.
|
|
app.UseIdentityServer();
|
|
|
|
// Add Mvc stuff
|
|
app.UseEndpoints(endpoints => endpoints.MapDefaultControllerRoute());
|
|
|
|
// Log startup
|
|
logger.LogInformation(Constants.BypassFiltersEventId, "{Project} started.", globalSettings.ProjectName);
|
|
}
|
|
}
|